Part of duplicate code analysis: #1882
Summary
A read-then-write double-check locking pattern (RLock → check → RUnlock → Lock → double-check → create) appears 3+ times across internal/launcher/ and internal/server/, representing ~45 lines of duplicated concurrent-map management code.
Duplication Details
Pattern: Double-Check Locking for Lazy Cache Initialisation
- Severity: Medium-High
- Occurrences: 3 distinct locations
- Locations:
internal/launcher/launcher.go — GetOrLaunch (lines ~70–90) and GetOrLaunchForSession (lines ~141–154)
internal/launcher/connection_pool.go — SessionConnectionPool.Get (lines ~208–237)
internal/server/routed.go — filteredServerCache.getOrCreate (lines ~46–74)
Code Sample (launcher.go GetOrLaunch):
l.mu.RLock()
if conn, ok := l.connections[serverID]; ok {
l.mu.RUnlock()
return conn, nil
}
l.mu.RUnlock()
l.mu.Lock()
defer l.mu.Unlock()
if conn, ok := l.connections[serverID]; ok {
return conn, nil
}
// ... create conn
l.connections[serverID] = conn
return conn, nil
Same skeleton in routed.go:
c.mu.RLock()
if server, exists := c.servers[key]; exists {
c.mu.RUnlock()
return server
}
c.mu.RUnlock()
c.mu.Lock()
defer c.mu.Unlock()
if server, exists := c.servers[key]; exists {
return server
}
// ... create server
c.servers[key] = server
return server
Impact Analysis
- Maintainability: Any correctness fix to the locking strategy (e.g., adding a
sync.Once per key) must be applied to 3+ independent locations
- Bug Risk: High — subtle differences between copies (e.g., one copy missing the second check inside the write lock) can introduce data races
- Code Bloat: ~45 lines of near-identical concurrent control flow
Refactoring Recommendations
-
Generic getOrCreate helper — introduce a type-parameterised helper (Go 1.18+):
// internal/syncutil/cache.go
func GetOrCreate[K comparable, V any](
mu *sync.RWMutex,
m map[K]V,
key K,
create func(K) (V, error),
) (V, error)
Each call site becomes a single syncutil.GetOrCreate(...) call.
-
sync.Map — evaluate whether sync.Map (stdlib) is sufficient for these use cases; it handles double-check locking internally and is idiomatic for append-mostly maps.
-
Document the pattern — if a custom implementation is retained, extract it to a named type (lockedMap[K, V]) with a single getOrCreate method, used by both launcher and server packages.
Implementation Checklist
Parent Issue
See parent analysis report: #1882
Related to #1882
Generated by Duplicate Code Detector · ◷
Part of duplicate code analysis: #1882
Summary
A read-then-write double-check locking pattern (RLock → check → RUnlock → Lock → double-check → create) appears 3+ times across
internal/launcher/andinternal/server/, representing ~45 lines of duplicated concurrent-map management code.Duplication Details
Pattern: Double-Check Locking for Lazy Cache Initialisation
internal/launcher/launcher.go—GetOrLaunch(lines ~70–90) andGetOrLaunchForSession(lines ~141–154)internal/launcher/connection_pool.go—SessionConnectionPool.Get(lines ~208–237)internal/server/routed.go—filteredServerCache.getOrCreate(lines ~46–74)Code Sample (launcher.go
GetOrLaunch):Same skeleton in
routed.go:Impact Analysis
sync.Onceper key) must be applied to 3+ independent locationsRefactoring Recommendations
Generic
getOrCreatehelper — introduce a type-parameterised helper (Go 1.18+):Each call site becomes a single
syncutil.GetOrCreate(...)call.sync.Map— evaluate whethersync.Map(stdlib) is sufficient for these use cases; it handles double-check locking internally and is idiomatic for append-mostly maps.Document the pattern — if a custom implementation is retained, extract it to a named type (
lockedMap[K, V]) with a singlegetOrCreatemethod, used by both launcher and server packages.Implementation Checklist
sync.Mapgo test -race ./internal/...make test-integrationParent Issue
See parent analysis report: #1882
Related to #1882