-
Notifications
You must be signed in to change notification settings - Fork 778
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
Require Send in addition to Sync on Py::get and PyCell::get #3169
Conversation
bors try |
tryBuild failed: |
4fa8bb3
to
172407e
Compare
bors try |
I noticed that our versioning of the compile error tests does not really meaning that the tests do not really pass on 1.63 even though we have "since-1.63" test cases. I wonder if we should simplify this (in a separate PR) to have "testcases limited to nightly" and "testcases limited to not our MSRV (1.48)" as everything else effectively tracks stable anyway due to the brittleness of the stderr files. |
tryBuild succeeded! The publicly hosted instance of bors-ng is deprecated and will go away soon. If you want to self-host your own instance, instructions are here. If you want to switch to GitHub's built-in merge queue, visit their help page. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds reasonable.
While the unsendable parameter and the thread checker help us detect accesses to unsendable types at runtime, they do not prevent sending such types to other threads in the first place where Py::get and PyCell::get provide direct access without checking thread affinity. So we could either call the thread checker for both methods or we add the additional Send bound. I opted for the latter to keep the zero overhead approach of the methods intact.
172407e
to
0cb18dd
Compare
I'm not yet convinced we need the There is a lot of prior discussion when we originally debated what bounds I'm currently relying on the definition that " On the other hand, we decided on I'm open to being convinced that I suppose we should focus on this: what examples of |
I think we only recently had a similar discussion with a comment from birkenfeld on URLO resolving this one way or the other in an issue or PR here but I currently cannot find it... ATM, I am even more confused. Let's say I even have a |
Indeed, the test case #[test]
fn drop_unsendable_elsewhere() {
use std::cell::RefCell;
use std::rc::Rc;
use std::thread::{current, spawn, ThreadId};
#[pyclass(unsendable)]
struct Unsendable {
thread_id: Rc<RefCell<ThreadId>>,
}
impl Drop for Unsendable {
fn drop(&mut self) {
let created = *self.thread_id.borrow();
let dropped = current().id();
assert_eq!(
created, dropped,
"I was created on {created:?}, but dropped on {dropped:?}."
);
}
}
let unsendable = Python::with_gil(|py| {
Py::new(
py,
Unsendable {
thread_id: Rc::new(RefCell::new(current().id())),
},
)
.unwrap()
});
spawn(move || {
Python::with_gil(move |_py| {
drop(unsendable);
});
});
} prints
|
What is worse: I think in the above case, we can only abort the process. While leaking is safe, this would be not-running-drop-but-freeing-the-memory-anyway. |
I think @birkenfeld's comment from URLO still applies: I think this is the same underlying issue why ( For the sake of argument, we could also turn this around: Better safe than sorry if we do not have any example of useful types which would be ruled out by the additional trait bound? |
Hmm, that drop behavior is worrying, I confess I thought we did panic though clearly that is not the case. So I'm now warming to the idea we add this bound. I think there's a further question about what to do with I'm now feeling somewhat tempted to deprecate |
Will open a separate PR to discuss and handle the |
So while this appears to clash with the definition of So while |
Ok. Happy to revisit this at any time; I'm reasonably comfortable that this is sound however if we have further thoughts that can convince us either way it would be good to review them. |
Just to round this out, this argument on Stack Overflow further convinced me that we are sound here:
So I believe we will properly handle the traits:
|
3176: Prevent dropping unsendable classes on other threads. r=davidhewitt a=adamreichold Continuing the discussed from #3169 (comment) and #3169 (comment): We already have checks in place to avoid borrowing these classes on other threads but it was still possible to send them to another thread and drop them there (while holding the GIL). This change avoids running the `Drop` implementation in such a case even though Python will still free the underlying memory. This might leak resources owned by the object, but it avoids undefined behaviour due to access the unsendable type from another thread. This does assume that the object was not unsafely integrated into an intrusive data structures which still point to the now freed memory. In that case, the only recourse would be to abort the process as freeing the memory is unavoidable when the tp_dealloc slot is called. (And moving it elsewhere into a new allocation would still break any existing pointers.) Co-authored-by: Adam Reichold <adam.reichold@t-online.de>
3176: Prevent dropping unsendable classes on other threads. r=davidhewitt a=adamreichold Continuing the discussed from #3169 (comment) and #3169 (comment): We already have checks in place to avoid borrowing these classes on other threads but it was still possible to send them to another thread and drop them there (while holding the GIL). This change avoids running the `Drop` implementation in such a case even though Python will still free the underlying memory. This might leak resources owned by the object, but it avoids undefined behaviour due to access the unsendable type from another thread. This does assume that the object was not unsafely integrated into an intrusive data structures which still point to the now freed memory. In that case, the only recourse would be to abort the process as freeing the memory is unavoidable when the tp_dealloc slot is called. (And moving it elsewhere into a new allocation would still break any existing pointers.) Co-authored-by: Adam Reichold <adam.reichold@t-online.de>
3168: Do not apply deferred ref count updates and prevent the GIL from being acquired inside of __traverse__ implementations. r=davidhewitt a=adamreichold Closes #2301 Closes #3165 3176: Prevent dropping unsendable classes on other threads. r=davidhewitt a=adamreichold Continuing the discussed from #3169 (comment) and #3169 (comment): We already have checks in place to avoid borrowing these classes on other threads but it was still possible to send them to another thread and drop them there (while holding the GIL). This change avoids running the `Drop` implementation in such a case even though Python will still free the underlying memory. This might leak resources owned by the object, but it avoids undefined behaviour due to access the unsendable type from another thread. This does assume that the object was not unsafely integrated into an intrusive data structures which still point to the now freed memory. In that case, the only recourse would be to abort the process as freeing the memory is unavoidable when the tp_dealloc slot is called. (And moving it elsewhere into a new allocation would still break any existing pointers.) Co-authored-by: Adam Reichold <adam.reichold@t-online.de>
While the unsendable parameter and the thread checker help us detect accesses to unsendable types at runtime, they do not prevent sending such types to other threads in the first place where
Py::get
andPyCell::get
provide direct access without checking thread affinity.So we could either call the thread checker for both methods or we add the additional Send bound. I opted for the latter to keep the zero overhead approach of the methods intact.