Frontend Asset Optimization
grafana/grafana#90939
TypeScript / Go
Golden recall: 50%
Extra findings: 1
Golden Comments (1/2 found)
| # | Comment | Severity | Status | FriendlyReviewer Detail |
|---|---|---|---|---|
| #1 | Missing double-check in locking pattern — the write lock is acquired without re-verifying whether the cache was populated in the meantime | MEDIUM | ✓ Found | FriendlyReviewer: "Missing double-check inside write-locked section. All goroutines that pass the initial nil check will re-read the manifest serially, defeating caching." (webassets.go L40) — severity upgraded from Medium to High. |
| #2 | Nil cache overwrite on error — entryPointAssetsCache = result assigns unconditionally (even nil), overwriting valid cached data permanently |
HIGH | ✗ Missed | FriendlyReviewer did not detect this issue. The unconditional assignment entryPointAssetsCache = result skips the error check from GetWebAssets. If the function fails, result is nil and the cache is permanently corrupted — no subsequent request will retrieve assets until the server restarts. This is the most critical bug in the PR. |
Supplementary Findings (1 finding)
| Finding | File | Severity | Legitimate? |
|---|---|---|---|
Mutable pointer returned from cache — return entryPointAssetsCache returns a *dtos.EntryPointAssets that callers can mutate (e.g., append to JSFiles), corrupting the cache for all subsequent requests |
webassets.go:52 | MEDIUM | ✓ True bug — callers can inadvertently corrupt cached data without synchronization |
Honest Analysis
FriendlyReviewer's full review
As posted on the PR — the complete output from FriendlyReviewer.
Hi there 👋
| 🌥️ Tech |
|---|
| 1 high, 1 medium |
The changes introduce a sync.RWMutex to protect the global entryPointAssetsCache, replacing unsynchronized access. However, the double-checked locking implementation is broken – the re-check inside the write-locked section is missing, causing redundant manifest reads under concurrent load. This is a critical correctness issue. Additionally, returning a mutable pointer to the cached data risks cache corruption if callers mutate the returned struct.
These might need a close look
- 🔴 pkg/api/webassets/webassets.go (L40)
Critical: Missing double-check inside write-locked section. The classic double-checked locking pattern requires a second check of the cache after acquiring the exclusive write lock to prevent redundant manifest reads. Currently, all goroutines that pass the initial nil check will re-read the manifest serially, defeating caching.
Worth checking
- 🟡 pkg/api/webassets/webassets.go (L52)
Medium: Returning a mutable pointer (*dtos.EntryPointAssets) from the cache. Callers could mutate the cached data (e.g., append toJSFiles), corrupting the cache for all subsequent requests. Consider returning a deep copy or storing an immutable value type.
FriendlyReviewer correctly identified golden #1 (missing double-check) with a severity upgrade from Medium to High. The description is precise: all goroutines passing the initial nil check enter the write-locked section and re-read the manifest serially, defeating the purpose of caching entirely. This is a textbook double-checked locking bug, and FriendlyReviewer caught it exactly.
However, FriendlyReviewer missed golden #2, which is the most critical issue in the PR. The unconditional assignment
entryPointAssetsCache = resulthappens before checking whetherGetWebAssetsreturned an error. If the fetch fails,resultis nil, and the cache is permanently overwritten with nil — no subsequent request will ever retrieve valid assets until the server restarts. This is a silent data-loss bug with no recovery path. FriendlyReviewer's analysis of the locking pattern was thorough, but it did not trace the error-return path through the assignment, which is arguably the more dangerous defect.The one supplementary finding (mutable pointer returned from cache) is legitimate and valuable. FriendlyReviewer identified that callers receiving
*dtos.EntryPointAssetscan mutate the slices (JSFiles,CSSFiles) directly, corrupting the shared cache without any synchronization. This is a different race condition than the one the PR was trying to fix, and the golden comments did not account for it.For a 16-line diff in a single file, FriendlyReviewer's analysis is focused but incomplete. It found the locking bug well but missed the error-handling bug that is functionally more severe. The supplementary finding adds genuine value beyond the golden set. The combination of the two golden bugs and the supplementary finding together provide a complete picture of what is wrong with this code, but no single review source — human or automated — caught everything.