Skip to content

use PyErr_SetRaisedException in raise_lazy#5876

Open
bschoenmaeckers wants to merge 3 commits intoPyO3:mainfrom
bschoenmaeckers:update_raise_lazy
Open

use PyErr_SetRaisedException in raise_lazy#5876
bschoenmaeckers wants to merge 3 commits intoPyO3:mainfrom
bschoenmaeckers:update_raise_lazy

Conversation

@bschoenmaeckers
Copy link
Member

This fixes a TODO in raise_lazy

@bschoenmaeckers bschoenmaeckers added the CI-skip-changelog Skip checking changelog entry label Mar 13, 2026
@bschoenmaeckers bschoenmaeckers force-pushed the update_raise_lazy branch 3 times, most recently from c155ece to b727b3d Compare March 13, 2026 13:42
Copy link
Member

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

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

Thanks for working on this! I've wanted to move forward with this for some time, it will allow us to (hopefully) improve the exception story piece by piece.

I think last time I looked at this I thought it was hard. See #4669 where I once pushed something similar, I forget why exactly I never got back to finishing it off...

@bschoenmaeckers
Copy link
Member Author

Thanks for working on this! I've wanted to move forward with this for some time, it will allow us to (hopefully) improve the exception story piece by piece.

I think last time I looked at this I thought it was hard. See #4669 where I once pushed something similar, I forget why exactly I never got back to finishing it off...

Reading through #4669 makes me remember we have safe api's to do things like this. 😅

Copy link
Member

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

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

I think as long as we're confident that the subtle difference with exception chaining will not cause us problems, I'm happy to proceed with this.

Comment on lines +396 to +397
#[cfg(not(Py_3_12))]
ffi::PyErr_SetObject(ptype.as_ptr(), pvalue.as_ptr());
Copy link
Member

Choose a reason for hiding this comment

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

To be more sure that the difference in exception chaining on the legacy path can't lead to diverging behavior, we could change the legacy path to call PyErr_Restore after creating a fully normalized exception object?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it is valuable to compare the PyErr_SetObject behaviour in our test suite so can verify our code path with PyErr_SetRaisedException is correct.

@bschoenmaeckers bschoenmaeckers force-pushed the update_raise_lazy branch 4 times, most recently from 5c6319e to 0648d71 Compare March 16, 2026 11:01
@bschoenmaeckers bschoenmaeckers removed the CI-skip-changelog Skip checking changelog entry label Mar 16, 2026
@bschoenmaeckers
Copy link
Member Author

Tests are passing, so could you please take another look.

Copy link
Member

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

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

Thanks, good to test all cases here and be sure we're not accidentally regressing something. Looks like this is shaping up well, just a few smaller thoughts...

let context = err.context(py).unwrap();
assert!(context.is_instance_of::<PyRuntimeError>(py))
})
}
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should have a test which directly compares the result against against ffi::PyErr_SetObject so that we can keep up to date with any future changes too?

Copy link
Member Author

Choose a reason for hiding this comment

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

I've added a test that compare's all the different variants I'm aware of.

Add context test

fix context test & use safe api's

Changelog

Remove `compat::PyObject_CallOneArg`

fix clippy

Add test for 'PyErr::set_context'

Return `Bound<'py, PyBaseException>` from `create_normalized_exception`
@bschoenmaeckers bschoenmaeckers added the CI-skip-changelog Skip checking changelog entry label Mar 17, 2026
Copy link
Member

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

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

Thanks, I would like to see a couple more tests around the context case when comparing to PyErr_SetObject, and then hopefully we're good to merge 👍

let err2 = PyErr::from_value(exc2.into_any());

assert!(err1.get_type(py).is(err2.get_type(py)));
assert!(err1.context(py).xor(err2.context(py)).is_none());
Copy link
Member

Choose a reason for hiding this comment

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

I guess there is another test where if we call PyErr_SetHandledException first, we expect that value to be placed in context? Looking at PyErr_SetObject implementation, there is some logic to avoid creating cycles in the context too, I feel like we should probably be careful to behave similarly?

Copy link
Member

Choose a reason for hiding this comment

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

I wonder, thinking about the context cycles, does that imply this approach is getting too complex? Maybe there is an alternative where we take control of the exception object creation as this PR already does (as that'll be helpful for improving PyErr::new as well as eventually removing the "lazy" state as per #4859), but we continue to use PyErr_SetObject to populate the interpreter state? Not as efficient as PyErr_SetRaisedException but would guarantee that we always use the standard behavior.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe we should just be calling the new create_normalized_exception directly from PyErrStateInner::normalize, as a first step towards #4859? (And avoid touching the interpreter's exception state at all.)

I think that would be slightly breaking in that creating a PyErr would no longer automatically pick up any __context__ (you'd have to actually call PyErr::restore to do that). But maybe that's actually more correct?

Copy link
Member

Choose a reason for hiding this comment

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

(Sorry to keep having slightly new / different ideas here.)

Copy link
Member Author

Choose a reason for hiding this comment

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

but we continue to use PyErr_SetObject to populate the interpreter state? Not as efficient as PyErr_SetRaisedException but would guarantee that we always use the standard behavior.

Could be an option, I will play around with cycle detection first to see if it's that horrible to implement. Maybe I will find a better solution on the way.

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

Labels

CI-skip-changelog Skip checking changelog entry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants