Skip to content

Conversation

benbrandt
Copy link
Member

Updates to the latest version of py03. They have had quite a few breaking changes, but I attempted to do the minimum amount of changes to get to the latest version

@benbrandt
Copy link
Member Author

@dicej I don't think I fully grasped that the runtime crate was getting embedded. I'll take another closer look at what made the tests fail before you have to review to save you some time

Copy link
Member Author

@benbrandt benbrandt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dicej I have gotten it close, but there are still a few failing tests around resources.
Overall, I think I am likely missing some context on how best to represent lifetimes for some of these pointers, but hopefully this is still a helpful starting place?

anyhow = "1.0.87"
once_cell = "1.19.0"
pyo3 = { version = "0.20.0", features = ["abi3-py311", "num-bigint"] }
pyo3 = { version = "0.22.2", features = ["abi3-py312", "num-bigint"] }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like since this is embedded in the WIT world, we can tie this to the version of cpython we are using?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thought: If I understand the resolver correctly, this will likely actually be targeting abi for 3.9? Since pyo3 is shared across the crates and it goes to the lowest enabled feature?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Features are additive, so presumably both the abi3-py312 and abi3-py39 features will be enabled. Not sure what that means in practice.

@dicej
Copy link
Collaborator

dicej commented Sep 13, 2024

Looks like the resource tests are failing due to reference count issues; finalizers are either being run too soon or not at all. Formerly, we relied on pyo3 v0.20's Py::into_ref, which kept the returned reference alive until the GIL was released, which worked perfectly for our case, but it looks like there's no equivalent in v0.22, so we'll either need to implement our own equivalent or come up with a different strategy. I've got to sign off for the day, but I'll take another look tomorrow.

@benbrandt
Copy link
Member Author

Thanks for looking. If there isn't an immediately obvious solution, I'm also fine putting this on pause for a bit depending on your availability

@benbrandt
Copy link
Member Author

Hmm rereading the migration docs (yet again) I think I missed a few things in there as well. I can take another look as well to try and make some of this closer to the previous implementation

dicej and others added 4 commits September 13, 2024 09:52
Signed-off-by: Joel Dice <[email protected]>
Py03 0.22.x no longer has `Py::into_ref`, which returned a reference that was
guaranteed to survive until the GIL was released.  We were relying on that to
ensure borrow handles were released before returning to the caller.  Since
there's no equivalent feature in 0.22.x AFAICT, we need to clean up borrow
handles ourselves.

This also fixes `componentize_py_to_canon_handle`, which was causing reference
counts to be decremented too early.

Note that, although the tests are now passing, there may still be leaks due to
reference counts not being decremented where they should.  Not sure what the
best way to test that might be; we might need to research Python tools for heap
profiling and leak detection.

Finally, I haven't tested the examples yet; we'll want to do that before we
merge any of this into main.

Signed-off-by: Joel Dice <[email protected]>
It felt strange that the C apis were all returning objects with lifetimes.
This updates everything that returned a Bound to always return a Py, which seems safer, and is also closer to what the WASM side expects (even though currently they are laid out in memory the same).
ty: usize,
field: usize,
) -> &'a PyAny {
) -> Py<PyAny> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dicej I did another pass and changed all of these C apis to return a Py. On second thought, it felt really strange to be returning something with a lifetime created in the scope of this function over this boundary. Maybe it was technically ok, but this also seems to be closer to what the WASM side expects (a single pointer).

#[export_name = "componentize-py#ToCanonBool"]
pub extern "C" fn componentize_py_to_canon_bool(_py: &Python, value: &PyAny) -> u32 {
if value.is_true().unwrap() {
pub unsafe extern "C" fn componentize_py_to_canon_bool(py: &Python, value: &PyAny) -> u32 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit concerned that we've gone from a function that is safe and has no unsafe code, to a safe-to-call function with an unsafe block inside it, to an unsafe-to-call function whose entire body is unsafe.

Granted, the only caller currently is generated Wasm code, which doesn't have any notion of Rust's rules, but now we have to document the requirements to call these functions beyond what's indicated by the types. Speaking of which, what makes this function unsafe to call (versus being safe to call but containing unsafe code in the body)?

Also, more (and larger) unsafe code blocks greatly increase the maintenance burden, so I want to confirm that there's no way to avoid them when upgrading PyO3. In principle, this function has pretty simple requirements: it needs proof that the caller holds the GIL (as indicated by the &Python parameter) and it needs a valid reference to a Python value which lives at least as long as the call (the &PyAny parameter). With both of those things satisfied, it's not clear to me why we'd need unsafe code to examine the value. Was the old PyO3 API unsound (i.e. providing only the illusion of safety), or have we just not hit upon the correct way to use the new API while avoiding unsafe?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's another option that doesn't require any unsafe:

#[export_name = "componentize-py#ToCanonBool"]
pub extern "C" fn componentize_py_to_canon_bool(
    py: &Python,
    value: ManuallyDrop<Py<PyAny>>,
) -> u32 {
    if value.is_truthy(*py).unwrap() {
        1
    } else {
        0
    }
}

It's not terribly pretty, and I suspect there are better options, but it works. I'll need to study the docs more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be better:

#[export_name = "componentize-py#ToCanonBool"]
pub extern "C" fn componentize_py_to_canon_bool(_py: &Python, value: Borrowed<PyAny>) -> u32 {
    if value.is_truthy().unwrap() {
        1
    } else {
        0
    }
}

I feel like there's a type missing in PyO3. We have Bound (owned, with proof of GIL), Borrowed (borrowed, with proof of GIL), and Py (owned, no proof of GIL), but there's no BorrowedPy (borrowed, no proof of GIL) -- that's the one we really want here.

@benbrandt
Copy link
Member Author

@dicej fair points. I think the ultimate issue is that we don't currently have a good way to test the changes. And while pyo3 claims to fix some soundness issues, I am worried I've potentially just created a lot more.

I think I'll put a pause on this to give us both a break as there isn't currently a pressing need to upgrade. And I might try and come at this with fresh eyes later

@benbrandt benbrandt closed this Sep 14, 2024
@dicej
Copy link
Collaborator

dicej commented Sep 16, 2024

SGTM. For what it's worth, I think that if we make appropriate use of the Py, Bound, and Borrowed types, we'll be able to avoid any new unsafe code and end up with something we have at least the same level of confidence in as we did with v0.20 (i.e. not perfect confidence -- there could still be subtle leaks lurking -- but good enough to move forward).

benbrandt added a commit to benbrandt/componentize-py that referenced this pull request Oct 26, 2024
Second attempt at solving the issues in bytecodealliance#104

This time I didn't need to introduce any extra unsafe, and I think the signatures match the intention of the previous code as best as I can understand.
@benbrandt benbrandt mentioned this pull request Oct 26, 2024
dicej pushed a commit that referenced this pull request Oct 28, 2024
* Update to pyo3 v0.22.5

Second attempt at solving the issues in #104

This time I didn't need to introduce any extra unsafe, and I think the signatures match the intention of the previous code as best as I can understand.

* bump minor version deps

* fix python versions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants