Conversation
f2a7a17 to
654255a
Compare
f27fda1 to
623b119
Compare
The stats logger stalled when a config update held _databasesLock for write across an unbounded index-readiness wait. - rest: updateCalculatedStats now reads databasesSnapshot, an atomic copy of _databases refreshed under the write lock on every mutation, so stats collection never blocks on _databasesLock. - base: SgwStats.String() marshals a shallow copy of the DbStats map, and NewDBStats/ClearDBStats do their Prometheus (un)registration outside dbStatsMapMutex. The serialized fields moved into an embedded sgwStatsFields struct so String() copies them wholesale rather than naming each one, and a new stat cannot be dropped from the output. - base: ClearDBStats holds dbReplicatorStatsMutex while iterating DbReplicatorStats. A replication registering during teardown could otherwise panic with "concurrent map read and map write". - base: add PopMapEntry map helper. DBReplicatorStats holds dbReplicatorStatsMutex for the whole function again, so an entry is published only once every stat is built. An earlier version inserted the empty entry first, which let a concurrent caller receive a struct of nil stats, and cached a half-built entry forever when registration failed partway. DatabaseContext.Close() now marks the database offline before tearing it down. Close() never changed State, so the DBOnline guard in updateCalculatedStats could not skip a closing database once the reader stopped taking _databasesLock. _unloadDatabase also drops the database from the snapshot before closing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
torcolvin
left a comment
There was a problem hiding this comment.
Pretty much just readability comments and hoping to screw this up in the future.
| // Mark offline before teardown so the lock-free stats reader skips this database (CBG-5472). | ||
| atomic.StoreUint32(&context.State, DBOffline) |
There was a problem hiding this comment.
I think conceptually Stopping makes more sense here?
| _collectionRegistry map[string]string // _collectionRegistry is a map of fully qualified collection name to db name, used for local uniqueness checks | ||
| _dbConfigs map[string]*RuntimeDatabaseConfig // _dbConfigs is a map of db name to the RuntimeDatabaseConfig | ||
| _databases map[string]*db.DatabaseContext // _databases is a map of dbname to db.DatabaseContext | ||
| _databases map[string]*db.DatabaseContext // _databases is a map of dbname to db.DatabaseContext. Mutations must call _updateDatabasesSnapshot |
There was a problem hiding this comment.
Rather than this comment, I'd be inclined to make this a type with Insert / Delete / Clear for modification and a Snapshot function?
| dbStats.unregisterCBLReplicationPushStats() | ||
| // DBReplicatorStats() writes this map lazily, and can run while the database is torn down. Hold | ||
| // the mutex so iterating here cannot hit "concurrent map read and map write". | ||
| dbStats.dbReplicatorStatsMutex.Lock() |
There was a problem hiding this comment.
I'd feel better if this was in a function with a defer in the rare case that there is something that panics when unloading the database - if there is something that goes wrong when unloading a database, locking the stats would make it impossible to debug
| if err != nil { | ||
| return // reporting the failure to the caller is the correct behaviour | ||
| } |
There was a problem hiding this comment.
This doesn't seem right here?
| } | ||
| close(start) | ||
| } | ||
| wg.Wait() |
There was a problem hiding this comment.
use WaitWithTimeout for sync group waiting.
|
|
||
| // Iterates DbReplicatorStats concurrently with the registrations above. | ||
| stats.ClearDBStats(dbName) | ||
| wg.Wait() |
There was a problem hiding this comment.
Should you do wait in a defer? and use WaitWithTimeout for sync group waiting.
|
I hit a separate race condition and I've developed a test for it in stats_test.go - this PR fixes test but not the other. Before I realised this PR was touching the same code, this is my proposed fix https://github.com/couchbase/sync_gateway/pull/new/dbstats-race - see the message for a longer description. I hit this bug here https://jenkins.sgwdev.com/job/Pipeline/job/PR-8608/1/testReport/junit/(root)/EE-rest_replicatortest/TestReplicationHeartbeatRemovalPushWithConfigReload_versionVector/ on #8608. |
Actually, I know what the problem is here: to test ISGR rebalancing, I create two databases on the same "node" in the same config group. In this case, they have to share the same database name, and this behavior is expected. However, this would never occur in the real world because a sync gateway production node can't have two databases with the same name on the same node. This means this problem probably still could be fixed with locking in this code, however it is test only. |
CBG-5472
Avoid blocking stats logger from database updates and initialisations. Access lock-free version of databases on ServerContext to build stats and minimise the locking required inside DbStats.
Pre-review checklist
Dependencies (if applicable)
Integration Tests