go: sync: sync.Map keys will never be garbage collected
What version of Go are you using (go version
)?
$ go version go1.15
Does this issue reproduce with the latest release?
yes
What operating system and processor architecture are you using (go env
)?
Can reproduce on any os and arch.
What did you do?
We are using some big struct as keys for sync.Map
, struct{} as value, and we observed a memory leak after upgrading to go 1.15. I confirmed that this is because sync.Map
will never delete keys. Instead, it only set the value to nil.
It worked well before 1.15 because we happened to have no read operation during the lifetime, and we were only reading it during shutdown so we didn’t discover the memory leak, as some code like this: https://play.golang.org/p/YdY4gOcXVMO. So we happened to only have no key prompted to sync.Map.read
, and thus Delete could delete the key in sync.Map.dirty
.
In go1.15, this behaviour was changed by https://go-review.googlesource.com/c/go/+/205899, and causes a memory leak in our code.
This isn’t the same as the behaviour of the native map. I admit I have misused sync.Map
, but I think this behaviour should either be documented clearly or be changed.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 26
- Comments: 39 (34 by maintainers)
Commits related to this issue
- doc: keys of sync.Map will never be deleted Keys of sync.Map will never be deleted, this behaviour isn't the same as the native map. We should document this clearly. Fixes #40999 . — committed to PureWhiteWu/go by PureWhiteWu 4 years ago
- doc: keys of sync.Map will never be deleted Keys of sync.Map will never be deleted, this behaviour isn't the same as the native map. We should document this clearly. Fixes #40999 . — committed to PureWhiteWu/go by PureWhiteWu 4 years ago
- [release-branch.go1.15] sync: delete dirty keys inside Map.LoadAndDelete Updates #40999 Fixes #41011 Change-Id: Ie32427e5cb5ed512b976b554850f50be156ce9f2 Reviewed-on: https://go-review.googlesource.... — committed to golang/go by changkun 4 years ago
- remove unused type; overdue fixing: https://github.com/golang/go/issues/40999 — committed to AZ-X/pique by AZ-X 4 years ago
One bug at a time mate
Here is a simpler reproduction
Keys in the
read
map cannot be purged immediately, but they are only retained until the cost to rebuild the map has been amortized away. That is an intentional tradeoff in the design.But perhaps we are missing some sort of cache-miss tracking for deleted keys. If you have a real-world program for which that causes a problem, please open a new issue and we can at least see if there is a straightforward fix for it.
(But also bear in mind that
sync.Map
is intended to solve a specific pattern in the Go standard library. It is surely not optimal for all “concurrent map” use-cases, and if it’s not a good fit for your use-case it is totally fine to use something else instead.sync.Map
should almost never appear in exported APIs, so in most cases it is easy to swap out for something else.)@davecheney I’ve added a Debug method in sync.Map:
And the example code to call Debug: https://play.golang.org/p/H3fkSdAw-JJ Output:
As you can see, the keys are not deleted.
@gopherbot, please backport to 1.15. This is a regression from Go 1.14.7, and can cause difficult-to-predict memory leaks in existing code.
If you add a sm.Range(…) between sm.Store and sm.Delete, the memory leaks on go 1.14, too.
@iand Sure, example code: https://play.golang.org/p/lHKYvukGbpZ Also I added the Debug method above in both go 1.15 and 1.14. On go 1.15, output:
On go 1.14, output:
You can see that, if there’s no read operation on map, the keys in
dirty
part can be deleted.