-
Notifications
You must be signed in to change notification settings - Fork 553
Description
Summary
session/inmemory.go's state.All() ranges over the internal map[string]any while temporarily releasing the read lock on each element. If any goroutine calls State.Set concurrently, Go can panic with "concurrent map iteration and map write" because the map is mutated during an active iteration.
Where
session/inmemory.go, function state.All()
func (s *state) All() iter.Seq2[string, any] {
return func(yield func(key string, val any) bool) {
s.mu.RLock()
for k, v := range s.state {
s.mu.RUnlock()
if !yield(k, v) {
return
}
s.mu.RLock()
}
s.mu.RUnlock()
}
}Repro (panic)
s := session.NewInMemorySession("id").State()
_ = s.Set("k0", 0)
// iterate in one goroutine (slow)
go func() {
for k, v := range s.All() {
_ = k; _ = v
time.Sleep(1 * time.Millisecond)
}
}()
// mutate concurrently
for i := 0; i < 1000; i++ {
_ = s.Set(fmt.Sprintf("k%d", i), i)
}This can trigger: fatal error: concurrent map iteration and map write.
Expected
State.All() should be safe to call concurrently with Set, or at least avoid panicking.
Suggested fix
Snapshot the state map under the lock, then iterate over the snapshot after unlocking:
s.mu.RLock()
copy := maps.Clone(s.state)
s.mu.RUnlock()
for k, v := range copy { ... }Alternatively, keep the lock for the whole iteration (but that can block writers).
Impact
Any streaming/long-running consumer of State.All() can crash if another goroutine updates state during iteration.