Skip to content

cache fmsgid requests#16

Merged
markmnl merged 3 commits intomainfrom
cache-fmsgid
Apr 29, 2026
Merged

cache fmsgid requests#16
markmnl merged 3 commits intomainfrom
cache-fmsgid

Conversation

@markmnl
Copy link
Copy Markdown
Owner

@markmnl markmnl commented Apr 29, 2026

No description provided.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a small in-memory cache around fmsgid lookups (plus a dedicated HTTP client with a timeout) to reduce repeated upstream calls during JWT auth.

Changes:

  • Introduces a dedicated http.Client with a 5s timeout for fmsgid requests.
  • Adds a TTL-based in-memory cache for successful (200) fmsgid responses.
  • Updates checkFmsgID to consult/store cache entries around the network call.

Comment thread src/middleware/jwt.go
Comment on lines +242 to +256
var fmsgIDCache sync.Map // map[string]fmsgIDEntry, key = addr

// checkFmsgID queries the fmsgid service for a user address.
// Returns (statusCode, acceptingNew, error).
// Returns (statusCode, acceptingNew, error). Successful 200 responses are
// cached for fmsgIDCacheTTL to avoid hammering fmsgid when a browser fires
// many concurrent requests with the same JWT.
func checkFmsgID(idURL, addr string) (int, bool, error) {
cacheKey := idURL + "|" + addr
if v, ok := fmsgIDCache.Load(cacheKey); ok {
entry := v.(fmsgIDEntry)
if time.Now().Before(entry.expires) {
return entry.code, entry.acceptingNew, nil
}
fmsgIDCache.Delete(cacheKey)
}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fmsgIDCache entries are only deleted on cache hits after expiry. If an address is looked up once and never again, the expired entry will remain in the sync.Map indefinitely, so the cache can grow without bound over the process lifetime. Consider adding periodic cleanup (e.g., a ticker that Ranges and deletes expired entries, similar to the cleanup loop in middleware/ratelimit.go) or switching to a bounded LRU/TTL cache implementation.

Copilot uses AI. Check for mistakes.
Comment thread src/middleware/jwt.go
Comment on lines 248 to +251
func checkFmsgID(idURL, addr string) (int, bool, error) {
cacheKey := idURL + "|" + addr
if v, ok := fmsgIDCache.Load(cacheKey); ok {
entry := v.(fmsgIDEntry)
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cacheKey := idURL + "|" + addr relies on a string delimiter, which is easy to get wrong and can theoretically collide. Using a comparable struct key (e.g., {idURL, addr}) avoids delimiter issues and also makes it clearer what dimensions are being cached.

Copilot uses AI. Check for mistakes.
Comment thread src/middleware/jwt.go
Comment on lines 258 to 260
url := strings.TrimRight(idURL, "/") + "/fmsgid/" + addr
resp, err := http.Get(url) //nolint:gosec // URL constructed from trusted config + validated addr
resp, err := fmsgIDClient.Get(url) //nolint:gosec // URL constructed from trusted config + validated addr
if err != nil {
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addr is concatenated directly into the URL path. IsValidAddr currently allows characters like '/', '?', or '#' (it only checks leading '@' and another '@'), so a crafted sub could change the requested path/query on the fmsgid service. Please build the URL using url.JoinPath (or escape the path segment) and/or tighten IsValidAddr to the exact allowed character set so the gosec suppression is justified.

Copilot uses AI. Check for mistakes.
Comment thread src/middleware/jwt.go
Comment on lines +245 to +256
// Returns (statusCode, acceptingNew, error). Successful 200 responses are
// cached for fmsgIDCacheTTL to avoid hammering fmsgid when a browser fires
// many concurrent requests with the same JWT.
func checkFmsgID(idURL, addr string) (int, bool, error) {
cacheKey := idURL + "|" + addr
if v, ok := fmsgIDCache.Load(cacheKey); ok {
entry := v.(fmsgIDEntry)
if time.Now().Before(entry.expires) {
return entry.code, entry.acceptingNew, nil
}
fmsgIDCache.Delete(cacheKey)
}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says caching avoids hammering fmsgid "when a browser fires many concurrent requests", but this implementation does not deduplicate concurrent cache misses—multiple goroutines can still call fmsgid at once before the first response is stored. If reducing burst load is a goal, consider adding per-key request coalescing (e.g., singleflight.Group) so concurrent lookups share one upstream request.

Copilot uses AI. Check for mistakes.
Comment thread src/middleware/jwt.go Outdated
Comment on lines 279 to 285
fmsgIDCache.Store(cacheKey, fmsgIDEntry{
expires: time.Now().Add(fmsgIDCacheTTL),
code: http.StatusOK,
acceptingNew: result.AcceptingNew,
})
return http.StatusOK, result.AcceptingNew, nil
}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces new caching behavior for fmsgid lookups, but there isn't a test asserting the cache is actually used (e.g., that a second request with the same addr/idURL does not trigger a second upstream HTTP call, and that non-200 responses are not cached). Since src/middleware/jwt_test.go already covers fmsgid interactions, consider adding a test that counts requests or makes the second upstream call fail to verify caching.

Copilot uses AI. Check for mistakes.
Comment thread src/middleware/jwt.go
acceptingNew bool
}

var fmsgIDCache sync.Map // map[string]fmsgIDEntry, key = addr
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline comment on fmsgIDCache says the key is addr, but the implementation keys by idURL + "|" + addr. Please update the comment to match reality (and ideally clarify the key format) to avoid misleading future maintainers.

Suggested change
var fmsgIDCache sync.Map // map[string]fmsgIDEntry, key = addr
var fmsgIDCache sync.Map // map[string]fmsgIDEntry, key = idURL + "|" + addr

Copilot uses AI. Check for mistakes.
@markmnl markmnl merged commit 18c303c into main Apr 29, 2026
1 check passed
@markmnl markmnl deleted the cache-fmsgid branch April 29, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants