Object protocols without specialization#961
Conversation
|
I forgot to mention that this PR has a breaking change. |
davidhewitt
left a comment
There was a problem hiding this comment.
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->registryin several places - Let's fix the
Send+Syncbounds in #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.
Technically all of these could leak memory if called for a second time.
There was a problem hiding this comment.
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.
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.
Do you think that we still need skipped_setters once we implement a solution for #844?
davidhewitt
left a comment
There was a problem hiding this comment.
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. |
davidhewitt
left a comment
There was a problem hiding this comment.
This is looking fantastic to me! +1,373 / -2,245 - nice job 🚀
| quote!(0) | ||
| }; | ||
| // TODO(kngwyu): doc | ||
| // TODO(kngwyu): Set ml_doc |
There was a problem hiding this comment.
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
PyProtoRegistoryis a set of method tables:.
Using the
ctorcrate, 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::newfor some protocols(basic, iter, and so on). However, to keepPyProtoRegistrysimple and small, I chose this design.Unfortunately, some protocols are not yet tested.
TODO