new.next is set afternew has been inserted into the registry.
Every unsafe { prev.as_ref() } on another thread possibly returns a &Node<T> to the &mut Node<T> we're modifying, which is Undefined Behavior.
Even if it weren't UB, this would temporarilly truncate the registry to a single entry.
The first commit at least moves the new.next assignment before the CAS makes it visible in the registry. I'm not 100% sure if this gets rid of the undefined behavior however - we technically still have a &mut Node<T> around. (I've also made Registry::submit take the Box instead of the reference so it's at least easier to locally reason about soundness...)
As such, the second commit switches to keeping a ptr::NonNull<T> around instead of a &'static Node<T>.
Okay, so undefined behavior would be pretty hard to trigger, but it is possible (I think):
This would be pretty boneheaded thing to do, and won't return the full list of plugins, but I've seen worse in shipped code.
https://github.com/dtolnay/inventory/blob/31b0974e6ab749967ee3506d166302c5a138221c/src/lib.rs#L154-L165
new.next
is set afternew
has been inserted into the registry.unsafe { prev.as_ref() }
on another thread possibly returns a&Node<T>
to the&mut Node<T>
we're modifying, which is Undefined Behavior.The first commit at least moves the
new.next
assignment before the CAS makes it visible in the registry. I'm not 100% sure if this gets rid of the undefined behavior however - we technically still have a&mut Node<T>
around. (I've also made Registry::submit take the Box instead of the reference so it's at least easier to locally reason about soundness...)As such, the second commit switches to keeping a
ptr::NonNull<T>
around instead of a&'static Node<T>
.