-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Guards can be sound #19
Comments
Would the guards model help you in implementing QSBR-style reclamation, as you described in a previous comment? |
If a guard can exist after the corresponding handle is dropped, it'll be usable in helping! In order to make sure that I understood your solution correctly, I'd like to ask few questions. I think when a handle is dropped, the underlying local bag is also dropped. So each guard should have its own bag. That means all the guards derived from a single handle share only the local epoch in the registry. Am I understanding correctly? Also, we need to somehow track the number of (guards + pins) for each handle. We can store it in the registry or we can manage it in an EDIT: maybe a handle and guards can share a |
I implemented I changed A caveat is that in order to access Please have a look! |
If we don't need to get a guard from a handle, then the implementation can be much simpler: https://github.com/jeehoonkang/crossbeam-epoch/tree/guard-from-collector Note that it's enough for helping. In this branch, a guard can be created directly from a collector. Each guard or handle created from a collector is registered in the collector. |
It's probably possible to provide EDIT: Oh right, I missed that they allow returning references from concurrent structures, but I'd say that it's not necessarily a good idea, since such a |
@Vtec234 I described one of guard's use case on wait-free construction (helping): #18 (comment) I agree that |
Pin guard and scope can both be used incorrectly. The reason I proposed to keep the guard-based pinning API is that it is more expressive. At the most general level, the evidence is that scope can be implemented in terms of guard, but not vice versa. But what is really relevant is whether the difference affects practical usage. The pin-with-guard approach allows the guard to be moved into a structure and returned from the function that did the pinning. This could come useful to data structures that want to provide efficient data access interfaces while hiding the implementation. Consider for example a lock-free container that provides temporary read-only access to the contained value. With the guard API, it is possible to provide a method like:
(Full implementation in the playground.)
With scope-based pinning, achieving the same performance would require
With a container that provides Even if we made A, B, and C methods on the same object, it would still not work. Unless the constructor itself receives a scope (which requires the caller to be aware of Crossbeam/epoch reclamation), there is nothing it can do to provide the object's methods access to the scope. I can understand that this can be construed as a feature, since it makes it harder to keep the thread pinned by passing the guard to potentially foreign code, or by simply dropping it. But there is nothing that stops a carelessly written thunk passed to (and invoked by) by (Edit: this comment was written before I saw Vtec234's edit, which basically describes the same use case more briefly. Leaving the comment, as the longer description might still be useful.) |
Hmm, not quite. All guards derived from a single handle share the
Yeah, we must track the number of pins, like you did with
This looks decent, but we can avoid reference counting. :)
I think pinning would be too slow here because each guard contains an I have a branch that completely replaces scopes with guards. While I haven't benchmarked anything, performance should be comparable to what it was before. Also, the code is not super clean, but you'll get the idea.
To be clear, I don't think we should have both scopes and guards, but only one of those. Note that guards are strictly more powerful than scopes since you can implement scopes using guards: struct Scope(Guard);
fn pin_scope<R, F: FnOnce(&Scope) -> R>(&self, f: F) -> R {
let guard = pin_guard();
let scope = Scope(guard);
f(&scope)
} You can't implement guards using scopes in a similar manner.
The benefits of guards are twofold. First, ergonomics. Compare the following: epoch::pin(|scope| {
// ...
});
unsafe {
epoch::unprotected(|scope| {
// ...
})
}
let guard = &epoch::pin();
// ...
let guard = unsafe { &epoch::unprotected() };
// ... Second, flexibility. @jeehoonkang and @hniksic have expressed a desire for returning references/iterators/whatever that keep the current thread pinned for their entire lifetime. This is simply not possible with scopes.
You could make the exact same argument about // Why have guards...
let guard = mutex.lock();
let val = refcell.borrow_mut();
// ...instead of scopes? Scopes are more explicit.
mutex.lock(|guard| {
// ...
});
refcell.borrow_mut(|val| {
// ...
}); But I actually agree with you here. The explicit But, in the end, whether a data structure API is modeled using |
One more thing. If we switch to guards, this will affect collectors and handles a little bit: let c = Collector::new();
let h = c.handle();
let guard = h.pin();
thread::spawn(move || {
// This is wrong. Now the same handle has guards in two different threads.
let guard = h.pin();
}) We'll have to make |
Just as a watcher-from-the-shadows - |
Exactly. Memory gets leaked, but it's not a safety issue. |
@stjepang I like your branch, and want it to be merged. I have a minor comment on |
The reason is that I need to perform a specific action whenever a node is unlinked: unsafe {
global.push_bag(&mut *c.local.bag.get(), guard);
global.collect(guard);
guard.defer(move || curr.into_owned())
} We can probably support that in the |
Thanks all for the detailed explanations, I no longer object to this change. It's slightly amusing that we're essentially going back to the original design of I wonder whether with this API |
Nice, when we accepted the first rfc I had the felling the guard would eventually come up again to allow stuff like this https://github.com/arthurprs/burst-trie/blob/thread_safe/src/map.rs#L42 (code looks like crap, it was an experiment on top of my first rust project)
That is probably ok. |
In the Atomic API RFC, I argued that we should model pinning using scopes rather than guards, for three reasons:
This time I'd just like to show that the first point is invalid - it's actually pretty easy to make guards sound.
Here's a quick refresher. The problem was the following: if you pin the current thread and obtain a guard, you can keep having the guard for a very long time, even after the thread-local
Handle
gets destructed. Using it after that would be unsafe.And here's the solution... Currently, when traversing the linked list of registered threads, we remove a node if its next pointer is tagged with 1. We should just have an additional check: remove it if the tag is 1 and if the thread is unpinned. That's all we need to change.
cc @hniksic Guards would make the following possible (mentioned here):
The text was updated successfully, but these errors were encountered: