Repository navigation
Conversation
With copy-on-write enabled, rb.Clone() marks every container of rb as shared, and x.Or(rb) / x.Xor(rb) do the same for the containers they share when x also uses copy-on-write. These operations leave rb's values unchanged but write its needCopyOnWrite slice, so goroutines that only clone a shared bitmap race with each other (#528). Flags only go from zero to one on these read-only paths, and only the owner clears them when it modifies the bitmap. Atomic loads and stores are therefore enough. needCopyOnWrite becomes []uint32 (sync/atomic has no byte operations). needsCopyOnWrite uses an atomic load, and setNeedsCopyOnWrite stores only if the flag is still zero, so once a container is shared, later clones only read the flag. Paths that only the owner reaches keep plain accesses. Applied to both the 32-bit and 64-bit packages. Concurrent read-only use, including Clone, is now documented as safe, and new -race tests cover Clone, in-place and static set operations, iteration and serialization running together on one copy-on-write bitmap. Fixes #528
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes #528. Supersedes #595 (docs only) and #574 (mutex around
Clone).With copy-on-write enabled, some operations leave a bitmap's values unchanged but still write its
needCopyOnWriteflags:rb.Clone()marks every container ofrbas shared.x.Or(rb)andx.Xor(rb)mark the containers they share, whenxalso uses copy-on-write.So goroutines that only clone one shared copy-on-write bitmap race with each other (#528). Static operations (
Or(x1, x2),Flip,FastOr, …) read those flags, so they race with a concurrentClonetoo. A lock aroundClonealone (#574) leaves those races in place.On these read-only paths, a flag only ever goes from 0 to 1. Only the bitmap's owner sets a flag back to 0, when it copies a container before modifying it, and modifying the bitmap concurrently is already forbidden. So atomic loads and stores are enough: no compare-and-swap, no mutex.
Changes Made
needCopyOnWriteis now[]uint32instead of[]bool, in bothroaringArrayandroaringArray64, becausesync/atomichas no byte operations.needsCopyOnWrite(i)usesatomic.LoadUint32.setNeedsCopyOnWrite(i)loads first and stores only if the flag is still 0, so once a container is shared, later clones only read the flag.markAllAsNeedingCopyOnWritegoes throughsetNeedsCopyOnWrite.mergeBulk, insert/remove,resize, deserialization) keep plain accesses.clone(), the new bitmap's flags are filled with plain stores, since no other goroutine can see it yet.frozenViewputs the flags in its arena asuint32. The preceding arena sections all have sizes that are multiples of 8, so the flags stay aligned.SetCopyOnWrite,Clone(32- and 64-bit) and the README "Goroutine safety" section now say that read-only operations, includingClone, may run concurrently on a copy-on-write bitmap as long as nobody modifies it, and that each goroutine may modify its own clone.Testing
New tests
cow_concurrency_test.goandroaring64/cow_concurrency_test.gobuild a shared copy-on-write bitmap with array, bitmap and run containers. They run these operations on it together from many goroutines:Clone, and cloning then modifying the clonex.Or,x.Xor,x.Andandx.AndNotwith the shared bitmap as argumentOr,Xor,And,AndNot,Flip,FastOr,FastAnd,HeapOr,ParOrandParAndContains,Rank,Select,ToArray, iteration,Intersects, the cardinality functions,EqualsandWriteToAfterwards they check that the source is unchanged and that modifying the source does not affect an earlier clone.
-race: 39 data races reported in the root package and 33 inroaring64.go test ./...andgo test -race ./...pass.go vetreports nothing new. Cross-builds forlinux/s390x(big-endian) andlinux/386succeed.Performance Impact
A/B on Apple arm64, with a synthetic bitmap of 2048 containers (1024 bitmap, 1024 array):
Cloneof a copy-on-write bitmapx.Or(src), both copy-on-writeOr, copy-on-write sourceOr, no copy-on-writeAdd/Remove, no copy-on-writeThe atomic loads themselves cost little: replacing them with plain loads saves only about 170 ns on the
Clonebenchmark. Most of the extra time and memory in a copy-on-writeClonecomes from the 4× larger flag slice (4 bytes per container instead of 1). Packing the flags into a[]uint64bitset withatomic.OrUint64would remove that cost. But inserting and removing at an index would then need bit shifting, so I kept[]uint32for simplicity. That could be a follow-up.Breaking Changes
None.
needCopyOnWriteis unexported, and the serialization formats are unchanged.Related Issues
Fixes #528. Supersedes #574 and #595.