-
Notifications
You must be signed in to change notification settings - Fork 766
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
Object protocols without specialization #961
Conversation
I forgot to mention that this PR has a breaking change. |
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 so much for this - it's excellent to see that this code is shorter than what was there before and should unblock us for compiling pyo3 on stable! 🎉
There's a couple things I'd like to see changed:
- Typo
registory
->registry
in several places - Let's fix the
Send
+Sync
bounds in compile_error test for PyClass: Send #948 rather than adding in this PR.
I forgot to mention that this PR has a breaking change. nb_bool is now moved under PyNumberProtocol from PyObjectProtocol.
I understand why you moved this, but I am not sure if it is the right change from a user design.
The related C API is listed in the Object Protocol: https://docs.python.org/3/c-api/object.html?highlight=pyobject_istrue
Does it make the implementation much more complicated if we can keep this in the PyObjectProtocol
?
src/class/proto_methods.rs
Outdated
self.async_methods | ||
.store(Box::into_raw(Box::new(methods)), Ordering::SeqCst) |
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.
Technically all of these could leak memory if called for a second time.
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.
Yes, but the current code does the same. See https://github.com/PyO3/pyo3/blob/v0.10.1/src/pyclass.rs#L151.
The difference is now we cause memory leak for all protocols(e.g., iter
and basic
).
We can avoid this by changing the pub struct PyProtoRegistry
like the below:
pub struct PyProtoRegistry {
/// Protocols that needs specific table types
async_methods: AtomicPtr<ffi::PyAsyncMethods>,
buffer_methods: AtomicPtr<ffi::PyBufferProcs>,
mapping_methods: AtomicPtr<ffi::PyMappingMethods>,
number_methods: AtomicPtr<ffi::PyNumberMethods>,
sequence_methods: AtomicPtr<ffi::PySequenceMethods>,
/// Other methods.
/// AtomicUsize is used instead of `Atomic<Option<ffi::func>>` or so because
/// we can't use function pointer in const function.
tp_str: AtomicUsize,
tp_repr: AtomicUsize.
....
}
.
I chose this design first but abandoned it due to its ugliness.
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.
Cool, I think your design choice is fine. Can we maybe just add a docstring for now to warn calling these twice will leak?
Thank you for the review!
I accidentally pushed these changes when I was playing with them. I'll revert them.
I don't have any good solution now, unfortunately. |
👍 ok. I have a half-baked idea but it's hard to write down as I haven't properly thought about it yet. I'll try to find time tomorrow evening to experiment with it whether it can help and explain it better then. |
Thank you but wait for a bit... EDITED: // nb_bool is a part of PyObjectProtocol, but should be placed under tp_as_number
let mut nb_bool = None;
// basic methods
if let Some(basic) = T::basic_methods() {
unsafe { basic.as_ref() }.update_typeobj(type_object);
nb_bool = unsafe { basic.as_ref() }.nb_bool;
}
// number methods
type_object.tp_as_number = T::number_methods()
.map(|mut p| {
unsafe { p.as_mut() }.nb_bool = nb_bool;
p.as_ptr()
})
.unwrap_or_else(|| {
if nb_bool.is_some() {
let mut number_methods = ffi::PyNumberMethods::default();
number_methods.nb_bool = nb_bool;
Box::into_raw(Box::new(number_methods))
} else {
ptr::null_mut()
}
}); Still not sure we should do this... |
At last, I decided to move |
pyo3-derive-backend/src/pyproto.rs
Outdated
// E.g., if we have `__add__`, we need to skip `set_radd`. | ||
let mut skipped_setters = HashSet::new(); |
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.
Do you think that we still need skipped_setters
once we implement a solution for #844?
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.
At last, I decided to move nb_bool under PyObjectProtocol again.
I don't think the code is very ugly but please check it.
I think what you've done is nice. It's good to avoid the unnecessary breaking change 😄
This branch is looking good, I'm very excited by it! As well as your TODO for docs and tests I would be interested to hear what you think about how we could solve #844 with this new implementation.
It would also be good to add a test for #940 in this PR - if I read your implementation correctly I think that should already be solved by what you've done! |
1d6295c
to
ed31c70
Compare
Added a test for
But for now, I think this is sufficient for testing proc-macros. |
🍿🤓 |
I think we can reuse the same trick as |
I'm happy with that - this PR is already quite large 😄 |
Now I think this PR is ready. |
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.
This is looking fantastic to me! +1,373 / -2,245
- nice job 🚀
@@ -76,7 +82,7 @@ fn impl_proto_impl( | |||
} else { | |||
quote!(0) | |||
}; | |||
// TODO(kngwyu): doc | |||
// TODO(kngwyu): Set ml_doc |
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.
Let's create an issue for this so that we remember to do it?
The
diff --git a/tests/test_dunder.rs b/tests/test_dunder.rs
index 67a66fb49..47f22fc91 100644
--- a/tests/test_dunder.rs
+++ b/tests/test_dunder.rs
@@ -612,7 +612,11 @@ async def main():
# but we see still errors on Github actions...
if sys.platform == "win32" and sys.version_info >= (3, 8, 0):
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
-loop = asyncio.get_event_loop()
+try:
+ loop = asyncio.get_event_loop()
+except RuntimeError: # RuntimeError: There is no current event loop in thread 'Dummy-1'.
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
assert loop.run_until_complete(main()) is None
loop.close()
"# |
@konstin |
🎉 |
For #210.
Now all pyclass has a method registory for protocols:
, where
PyProtoRegistory
is a set of method tables:.
Using the
ctor
crate, we insert some initialization codes for this registry.For example, when we write this code:
, (roughly) the following code is generated:
One disadvantage of this solution is it needs redundant
Box::new
for some protocols(basic, iter, and so on). However, to keepPyProtoRegistry
simple and small, I chose this design.Unfortunately, some protocols are not yet tested.
TODO