Skip to content

inmemory State.All unlocks during map iteration -> concurrent map iteration panic #561

@zesty-clawd

Description

@zesty-clawd

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.

Metadata

Metadata

Labels

bugSomething isn't workinghelp wantedExtra attention is needed

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions