-
Notifications
You must be signed in to change notification settings - Fork 13k
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
Introduce RefCell::try_borrow_unguarded #59211
Conversation
r? @KodrAus (rust_highfive has picked a reviewer for you, use r? to override) |
I might be misunderstanding, but is there a reason you can't have the function call |
We call |
Ah, I see. On the other hand, I think the only reason the cc @RalfJung as well, might have relevance for stacked borrows and UCG in general |
To send a value over a channel to a different thread, that channel already must do some synchronisation to ensure that memory accesses aren't observed in the wrong order etc, that's independent from what |
Let me try to explain the use case in depth: We can't get
|
I think my confusion lies not in the need for I'm envisioning that the layout thread calls |
That's the responsibility of the script/layout interface that ensures that script doesn't run while layout is going on. It indeed can't happen. |
Sounds good. I think the concern/part of the reason we deprecated and removed this originally is that getting it right is hard; and this API is actually not useful for anyone who's not doing correct but hard to get right (i.e., using RefCell from multiple threads) things. Maybe we should add to the docs on borrow_state that "you really shouldn't use this unless you really know what you are doing." |
Yeah I can sympathise with that. I'm not even entirely sure that we 100% got it correctly in Servo, given there is too much unsafe code all over the place related to the script/layout invariant I described earlier, and nothing to try to avoid the specific scenario I told of. On a side note that's why I'm working on
Sounds good to me, though if someone is calling it in multiple threads at once they already should be aware of being careful because they must have written an unsafe |
Can we think of any other use-case for this that doesn't involve pretending |
As an alternative approach re. #59211 (comment), you may consider two `methods: fn will_borrow_succeed(&self) -> bool { ... }
fn will_borrow_mut_succeed(&self) -> bool { ... } These would correspond to the actual checks done for |
The method itself seems fine, just from a "is this a safe extension of However, I'm afraid I don't understand enough about how Servo uses this to evaluate whether Servo's use of this is correct. Is it possible to produce a small amount of code demonstrating this?
In the last step, which thread will this call happen in? You mentioned layout is in a different thread, so it seems to me that there's a race here between DOM code releasing the mutable borrow and layout calling |
There is no race condition because we prevent them by doing the necessary synchronisation. That method prevents UB in the case we had a bug where a refcell was mutably borrowed before layout started. This check does avoid UB, because we wouldn't look into a refcell that was been mutably borrowed. |
I see, so the "doing the synchronization" part is separate from the "prevent reentrant accesses" part, and |
@RalfJung Yes. |
This method has existed before, and was removed because:
But now there is a new use case that was not known at the time and that is not covered by the @rfcbot fcp merge Having two methods that return booleans instead of one that returns an enum sounds fine in terms of functionality. (Or maybe just one? If there is no use case without UB for the other one…) Naming them might be awkward, but Imagine that some code has access to The Servo use case is similar: in that we want to do multi-threaded computation on Additionally there isn’t just one refcell, but a whole tree of nodes that each contain multiple refcells. Tree traversal is where we have parallelism, so we can’t do the runtime check on each refcell on the original thread. (What we send to computation threads is |
Team member @SimonSapin has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
I'd be against the method names that @Centril proposed. The primary reason is that There seems to be no reason for the mut variant ( Perhaps the API should instead be I'm not a fan of the "unchecked" since it does check that it is currently safe; but because it doesn't increment the ref-count it would potentially be unsafe in the future. To be clear, I'm opposed to re-adding |
This borrow is not completely unchecked, the point is to check that the refcell is in an appropriate state when the borrow starts. But then we don’t track this new borrow or when it ends. Maybe |
Can you say more about what bindgen did with the borrow sate? Could it have used |
This is the commit that removed the RefCell: rust-lang/rust-bindgen@cfdf15f Looking at the BorrowState usage in the deleted files (rust-lang/rust-bindgen@cfdf15f#diff-2c09afcdc3c420ab0678ba9b5e83959cL421) looks like it wanted |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
I'm not sure what that "something" is, but it sounds like a recipe for disaster in itself, because that JS code can in turn call into DOM trying to change the very same thing, causing a RefCell panic? |
Something here is indeed any DOM call, and that is indeed a bug if we call JS while a RefCell from within the DOM is mutably borrowed, because that's a case that will eventually cause a panic as you describe. This PR is about being able to panic from layout too instead of causing straight UB if the JS code calls a DOM method that calls layout. |
Err, misread your comment, "something" here is anything that needs to be mutated sometimes in DOM, be it element's attributes, ids, urls, whatever. Indeed, if we keep that mutably borrowed while we reenter into JS code, that will ultimately lead to a panic. This is by design and not going to change any time soon. |
Okay, so when Layout threads call Fwiw, if my two cents are worth anything, it seems to me like what you want to do (putting RefCell into some parallel-sync-mode when script is not running) is so niche that maybe you need something else than RefCell to suit your use case. (Maybe something where the borrow flag can be in "Sync" mode as well as "Reading" and "Writing" modes? Not sure.) And if you like to implement this on top of RefCell like you do today with |
I'm 100% fine with your two cents, and I would be ok with exposing |
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. The RFC will be merged soon. |
📌 Commit 38811a1 has been approved by |
Introduce RefCell::try_borrow_unguarded *Come sit next to the fireplace with me, this is going to be a long story.* So, you may already be aware that Servo has weird design constraints that forces us developers working on it to do weird things. The thing that interests us today is that we do layout on a separate thread with its own thread pool to do some things in parallel, whereas the data it uses comes from the script thread, which implements the entire DOM and related pieces, with `!Sync` data types such as `RefCell<T>`. The invariant we maintain is that script does not do anything ever with the DOM data as long as layout is doing its job. That's all nice and all, but one thing we don't ensure is that we don't actually know if script was currently mutably borrowing some `RefCell<T>` prior to starting layout, which may lead to aliasing mutable memory and obviously undefined behaviour. This PR reinstates `RefCell::borrow_state` so that [this method](https://github.com/servo/servo/blob/master/components/script/dom/bindings/cell.rs#L23-L30) can make use of it and return `None` if the cell was mutably borrowed. Cc @SimonSapin
☀️ Test successful - checks-travis, status-appveyor |
Just wanted to say that |
Servo has been using this since servo/servo#23196 to add a runtime check to some unsafe code, as discussed in PR rust-lang#59211. Stabilizing would help do more of the same in libraries that also have users on Stable.
Stabilize RefCell::try_borrow_unguarded Servo has been using this since servo/servo#23196 to add a runtime check to some unsafe code, as discussed in PR rust-lang#59211. Stabilizing would help do more of the same in libraries that also have users on Stable.
Stabilize RefCell::try_borrow_unguarded Servo has been using this since servo/servo#23196 to add a runtime check to some unsafe code, as discussed in PR rust-lang#59211. Stabilizing would help do more of the same in libraries that also have users on Stable.
Apologies for pinging an old issue, but I saw
If I'm reading the thread above correctly, there's some soundness problem with this arrangement that |
Come sit next to the fireplace with me, this is going to be a long story.
So, you may already be aware that Servo has weird design constraints that forces us developers working on it to do weird things. The thing that interests us today is that we do layout on a separate thread with its own thread pool to do some things in parallel, whereas the data it uses comes from the script thread, which implements the entire DOM and related pieces, with
!Sync
data types such asRefCell<T>
.The invariant we maintain is that script does not do anything ever with the DOM data as long as layout is doing its job. That's all nice and all, but one thing we don't ensure is that we don't actually know if script was currently mutably borrowing some
RefCell<T>
prior to starting layout, which may lead to aliasing mutable memory and obviously undefined behaviour.This PR reinstates
RefCell::borrow_state
so that this method can make use of it and returnNone
if the cell was mutably borrowed.Cc @SimonSapin