-
Notifications
You must be signed in to change notification settings - Fork 120
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
Give PyArray<PyObject> another try. #216
Conversation
Ah, it is not deterministic! Executed often enough, it is triggered... |
And it also seems to trigger only if the other tests in the that binary are executed. |
Yeah, I'm sorry but I also don't have much intuition about this bug. I just confirmed SIGSEGV is triggered in PyFinalizeEx using gdb. |
It seems to be a double-free, i.e. if I add |
Yeah, |
Oh, I think |
I pushed a fix for @kngwyu One thing unrelated I wonder is why |
I found one more place where However, I do not think ensuring a call to I also wrapped those partially initialized arrays into |
Great
Ah, that's an awesome finding, thanks.
Yeah, your understanding is correct and it should be unsafe.
I do think that throwing out copy_nonoverlapping is OK for now. |
Opened #217 to discuss how to solve this as it seems unrelated to this change and should probably result in new API. |
I added another commit which takes care of properly leaking uninitialized PyObject arrays to avoid heap corruption which has the nice side effect of restoring that optimization since the other NumPy types are assumed to be |
src/array.rs
Outdated
// all other data types are assumed to be `Copy` by NumPy | ||
if T::DATA_TYPE == DataType::Object { | ||
// keep array referenced as long as its contents is not initialized | ||
let ref_ = mem::ManuallyDrop::new(array.to_object(py)); |
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.
To avoid leaking memory if clone panics, you could use a guard pattern similar to https://github.com/PyO3/pyo3/blob/00c84eb0baec6f41623e83737a291d3e0d30cc5b/src/conversions/array.rs#L93
You would need to drop the initialized elements by hand and then zero the array so that numpy can deallocate safely I guess.
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.
Certainly, however I prefer to postpone this to separate PR after we have reached soundness w.r.t PyArray
creation.
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.
👍 makes sense, let's just remember to open an issue for this when this PR merges.
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.
I took a different approach for now:
// Use zero-initialized pointers for object arrays
// so that partially initialized arrays can be dropped safely
// in case the iterator implementation panics.
let array = if T::DATA_TYPE == DataType::Object {
Self::zeros(py, [iter.len()], false)
} else {
Self::new(py, [iter.len()], false)
};
This way we start out with null pointers in the object case and the array is always safe to drop. I think the overhead is warranted due to the cost that arrays of pointers imply in any case. (Of course, using a guard that just zeros out uninitialized array elements if there actually is a panic can still be implemented as a follow-up optimization.)
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.
Oh very nice!
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.
Yeah, it looks a good temporal solution 👍🏼
…ed memory if the iterator panics.
Sorry for the notification spam, but I wanted to add that I rebooted this patch series again to make the contract of |
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.
Looks great to me, nice work! Could perhaps add extra tests for from_slice
for PyObject vec etc
I suppose you mean extra test for Will add a test case for starting from an |
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.
Thanks!
This is a huge step.
@adamreichold
Just let me confirm: we can merge this PR as is and you want to refactor Element
in a separate PR, right?
Yes, this PR is good to go in my opinion. I don't think we will need to touch |
👍🏼 |
I am not sure what changed since #143 (comment) or whether the segmentation fault was only triggered by a more involved test but this seems to work using Python 3.8.2 on Linux. (Similarly to how #138 (comment) worked in this environment.)
Fixes #175