diff --git a/locksordering.txt b/locksordering.txt index 4a2c9e71721..7d750ac4a58 100644 --- a/locksordering.txt +++ b/locksordering.txt @@ -28,5 +28,9 @@ clearObserverState so that they cannot interleave which would leave Raft nodes i inconsistent observer states. jscmMu -> Account -> jsAccount - jscmMu -> stream.clsMu - jscmMu -> RaftNode \ No newline at end of file + jscmMu -> stream.clsMu + jscmMu -> RaftNode + +The "clsMu" lock protects the consumer list on a stream, used for signalling consumer activity. + + stream -> clsMu diff --git a/server/stream.go b/server/stream.go index 8e8029e4ace..d8f3f6e4f0e 100644 --- a/server/stream.go +++ b/server/stream.go @@ -25,6 +25,7 @@ import ( "os" "path/filepath" "reflect" + "slices" "strconv" "strings" "sync" @@ -2267,8 +2268,12 @@ func (mset *stream) purge(preq *JSApiStreamPurgeRequest) (purged uint64, err err fseq = ss.First } + // Take a copy of cList to avoid o.purge() potentially taking the stream lock and + // violating the lock ordering. mset.clsMu.RLock() - for _, o := range mset.cList { + cList := slices.Clone(mset.cList) + mset.clsMu.RUnlock() + for _, o := range cList { start := fseq o.mu.RLock() // we update consumer sequences if: @@ -2290,7 +2295,6 @@ func (mset *stream) purge(preq *JSApiStreamPurgeRequest) (purged uint64, err err o.purge(start, lseq, isWider) } } - mset.clsMu.RUnlock() return purged, nil }