Fix the concurrency semantics for Gc<T>
.
#54
Merged
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.
This makes two major changes to the API:
T: Sync
forGc<T>
.Gc<T> : Send + Sync
ifT: Send + Sync
, fixing the ergonomic problems raised in Gc type does not implement Send or Sync #49.Sync
's purpose is to ensure that two threads can access the data inT
in a thread-safe way. In other words, it implies thatT
has synchronisation guarantees. Originally, this was added as a constraint onT
because any finalizer forT
would run on a separate thread. However, it's now safe to remove this as a constraint because softdevteam/alloy#30 guarantees that a finalizer won't run early. This means that even without synchronized access, a race can't happen, because it's impossible for the finalizer to accessT
's data while it's still in use by the mutator.However, even though
Gc<T>
can now implementSend
-- thanks to multi-threaded collection support --Gc<T>
still requires thatT: Send
, becauseT
could be a type with shared ownership which aliases. This is necessary because off-thread finalization could mutate shared memory without synchronisation. An example usingRc
makes this clear:Since finalizing any non-
Send
value can cause UB, we still disallow the construction ofGc<T: !Send>
completely. This is certainly the most conservative approach. There are others:Send
values. This is valid, since finalizers are not guaranteed to run. However, it's not exactly practical, it would mean that anyGc<Rc<...>>
type structure would always leak.!Send
values on their mutator thread. This is really dangerous in the general case, because if any locks are held on the shared data by the mutator, this will deadlock (it's basically a variant of the async-signal-safe problem). However, ifT
is!Send
, deadlocking is unlikely [although not impossible!], and could be an acceptable compromise. It's out of the scope of this PR, but it's something I've been playing around with.