What happened?
Deadlock occurs when a sync.Map's Range callback calls any method (e.g. Load, Store, Delete) on that same map.
Go's sync.Map states that Range callback doesn't block when calling other methods on m: "Range does not block other methods on the receiver; even f itself may call any method on m."
Minimal reproducer
package main
import "sync"
func main() {
var m sync.Map
m.Store(1, 1)
m.Range(func(k, v any) bool {
m.Delete(k) // hangs forever here
return true
})
}
go run main.go exits immediately, as expected.
tinygo run main.go hangs indefinitely and never returns.
Root cause
src/sync/map.go is a plain map guarded by a single non-reentrant task.PMutex, and every method holds that lock for its entire duration.
Range likewise holds the lock across the user callback, so any sync.Map method called from inside the callback deadlocks.
Reproduction environment
- TinyGo 0.41.1, linux/amd64 (go1.26.4, LLVM 20.1.1)
- TinyGo 0.42.0 dev, commit 801bd48, linux/amd64 (go1.26.4, LLVM 20.1.8)
What happened?
Deadlock occurs when a sync.Map's
Rangecallback calls any method (e.g.Load,Store,Delete) on that same map.Go's
sync.Mapstates thatRangecallback doesn't block when calling other methods onm: "Range does not block other methods on the receiver; even f itself may call any method on m."Minimal reproducer
go run main.goexits immediately, as expected.tinygo run main.gohangs indefinitely and never returns.Root cause
src/sync/map.go is a plain map guarded by a single non-reentrant
task.PMutex, and every method holds that lock for its entire duration.Rangelikewise holds the lock across the user callback, so anysync.Mapmethod called from inside the callback deadlocks.Reproduction environment