-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
Move exc state to generator. Fixes bpo-25612 #1773
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
Move exc state to generator. Fixes bpo-25612 #1773
Conversation
Hello, and thanks for your contribution! I'm a bot set up to make sure that the project can legally accept your contribution by verifying you have signed the PSF contributor agreement (CLA). Unfortunately we couldn't find an account corresponding to your GitHub username on bugs.python.org (b.p.o) to verify you have signed the CLA (this might be simply due to a missing "GitHub Name" entry in your b.p.o account settings). This is necessary for legal reasons before we can look at your contribution. Please follow the steps outlined in the CPython devguide to rectify this issue. Thanks again to your contribution and we look forward to looking at it! |
@markshannon, thanks for your PR! By analyzing the history of the files in this pull request, we identified @benjaminp, @serhiy-storchaka and @Yhg1s to be potential reviewers. |
I've just linked by bpo user to my github user. Which should satisfy @the-knights-who-say-ni, hopefully. |
You might also want to check how this affects this strange behavior that @arigo found: https://bugs.python.org/issue28884#msg282532 |
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 like this refactoring a lot. I'd go a step further and refactor tstate->*curexc_
too as well as add a couple of helper macros (not defined in limited API).
Objects/genobject.c
Outdated
@@ -115,6 +118,9 @@ gen_dealloc(PyGenObject *gen) | |||
Py_CLEAR(gen->gi_code); | |||
Py_CLEAR(gen->gi_name); | |||
Py_CLEAR(gen->gi_qualname); | |||
Py_CLEAR(gen->gi_exc_state.exc_type); |
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.
Maybe we should add a macro PyExcState_CLEAR
(and _VISIT
)?
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.
Done (although I used inline functions)
Include/pystate.h
Outdated
PyObject *exc_type; | ||
PyObject *exc_value; | ||
PyObject *exc_traceback; | ||
PyExcState exc_state; |
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.
Given that this is already quite an invasive change, maybe replace curexc_*
fields with curexc_state
?
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.
PyExcState structs form a stack (hence the "previous" field), but there is only one current exception.
Include/pystate.h
Outdated
@@ -150,6 +162,8 @@ typedef struct _ts { | |||
PyObject *async_gen_firstiter; | |||
PyObject *async_gen_finalizer; | |||
|
|||
PyExcState *exc_info; |
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.
Can we have exc_info
, exc_state
and curexc_*
defined next to each other with comments explaining how we use each one of them? Since you've already spent time to figure this stuff out anyways.
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.
Done
Objects/genobject.c
Outdated
f->f_exc_type = NULL; | ||
f->f_exc_value = NULL; | ||
f->f_exc_traceback = NULL; | ||
t = gen->gi_exc_state.exc_type; |
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.
If we had a PyExcState_CLEAR
macro we could use it here.
e0b55d4
to
e19057d
Compare
@njsmith It does indeed fix https://bugs.python.org/issue28884#msg282532. I've added it as a test case. |
Include/pystate.h
Outdated
PyObject *exc_type; | ||
PyObject *exc_value; | ||
PyObject *exc_traceback; | ||
/* The exception currently being handled, if no coroutines/generators are present. |
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.
There are many lines that are longer than 79 (see PEP 7). Could you please fix this? </nitpicking>
Python/errors.c
Outdated
PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate) { | ||
PyExcState *exc_info = tstate->exc_info; | ||
while ((exc_info->exc_type == NULL || exc_info->exc_type == Py_None) && | ||
exc_info->exc_previous != NULL) |
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.
Another nit, per PEP 7 we now want to use {
even for blocks with one statement.
Aside from a few code formatting nits the patch looks good. I just want to test it on my machine before giving it a green light; will do that tomorrow or the day after. |
Objects/genobject.c
Outdated
@@ -86,6 +93,19 @@ _PyGen_Finalize(PyObject *self) | |||
PyErr_Restore(error_type, error_value, error_traceback); | |||
} | |||
|
|||
static inline void PyExcState_clear(PyExcState *exc_state) { |
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.
Another minor nit: please rename to PyExcState_Clear
.
0cc764b
to
800150c
Compare
Include/pystate.h
Outdated
state inside the generator and the exception state of the calling | ||
frame (which shouldn't be impacted when the generator "yields" | ||
from an except handler). | ||
These three fields exist exactly for that. */ |
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.
Should this comment be left verbatim like this? The comment applied when these fields were part of the frame object, but they've been removed from the frame object completely. I don't claim to understand the patch completely but I assume the use of these fields have changed somewhat--otherwise what's the point of shuffling the data around?
Include/pystate.h
Outdated
|
||
struct _excstate *exc_previous; | ||
|
||
} PyExcState; |
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.
Would you consider a longer name here? I always remember MvL saying "avoid abbreviations in identifiers, they are mystifying to non-English speakers". "PyExceptionState" isn't that awful is it?
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.
If this name is not public, it should be underscored.
A short name would LGTM, but the PyExc
prefix is used only for exception classes. The PyException
prefix is used for manipulating individual exception objects (PyExceptionClass_Check, PyException_GetTraceback). All other names related to exceptions and error handling have prefix PyErr
.
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.
PyExcState
sounds ok to me.
@pitrou Antoine, could you please take a look? I'm inclined to merge this. @markshannon Could you please rebase and add a NEWS entry? Would be cool if you could address the few review nits, but if you don't have time I can merge this as is and fix them myself. |
Include/pyerrors.h
Outdated
@@ -78,6 +78,7 @@ PyAPI_FUNC(void) PyErr_SetNone(PyObject *); | |||
PyAPI_FUNC(void) PyErr_SetObject(PyObject *, PyObject *); | |||
#ifndef Py_LIMITED_API | |||
PyAPI_FUNC(void) _PyErr_SetKeyError(PyObject *); | |||
PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate); |
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.
PyAPI_FUNC()
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 isn't intended to be an API function. It is only used in errors.c
and sysmodule.c
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.
Even for private functions we still use PyAPI_FUNC
by convention (such as _PyErr_SetKeyError
just above).
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.
But it isn't meant to be part of an API. What does PyAPI_FUNC
mean?
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.
Even for private functions we still use PyAPI_FUNC by convention (such as _PyErr_SetKeyError just above).
See _PyCoro_GetAwaitableIter
and _PyGen_yf
etc. I agree with @markshannon here, we don't really need PyAPI_FUNC
.
Include/pystate.h
Outdated
|
||
struct _excstate *exc_previous; | ||
|
||
} PyExcState; |
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.
If this name is not public, it should be underscored.
A short name would LGTM, but the PyExc
prefix is used only for exception classes. The PyException
prefix is used for manipulating individual exception objects (PyExceptionClass_Check, PyException_GetTraceback). All other names related to exceptions and error handling have prefix PyErr
.
@@ -15,14 +15,22 @@ static char *NON_INIT_CORO_MSG = "can't send non-None value to a " | |||
static char *ASYNC_GEN_IGNORED_EXIT_MSG = | |||
"async generator ignored GeneratorExit"; | |||
|
|||
static inline int |
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.
Py_LOCAL_INLINE(int)
?
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.
Now that we support C99, I think we ought to use standard C where possible.
Objects/genobject.c
Outdated
@@ -15,14 +15,22 @@ static char *NON_INIT_CORO_MSG = "can't send non-None value to a " | |||
static char *ASYNC_GEN_IGNORED_EXIT_MSG = | |||
"async generator ignored GeneratorExit"; | |||
|
|||
static inline int | |||
PyExcState_traverse(PyExcState *exc_state, visitproc visit, void *arg) { |
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.
Move {
on a separate line.
If PyExcState_traverse
is private and local use the all-lowercase-letters style.
Objects/genobject.c
Outdated
@@ -86,6 +94,19 @@ _PyGen_Finalize(PyObject *self) | |||
PyErr_Restore(error_type, error_value, error_traceback); | |||
} | |||
|
|||
static inline void PyExcState_Clear(PyExcState *exc_state) { |
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.
Move static inline void
(or Py_LOCAL_INLINE(int)
?) and {
on separate lines.
If PyExcState_Clear
is private and local use the all-lowercase-letters style.
Python/errors.c
Outdated
@@ -52,6 +52,15 @@ PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback) | |||
Py_XDECREF(oldtraceback); | |||
} | |||
|
|||
PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate) { |
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.
Move PyExcState *
and {
on separate lines.
Python/errors.c
Outdated
PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate) { | ||
PyExcState *exc_info = tstate->exc_info; | ||
while ((exc_info->exc_type == NULL || exc_info->exc_type == Py_None) && | ||
exc_info->exc_previous != NULL) { |
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.
Move {
on a separate line (new PEP 7 rule for multiline conditions).
Python/pystate.c
Outdated
Py_CLEAR(tstate->exc_state.exc_value); | ||
Py_CLEAR(tstate->exc_state.exc_traceback); | ||
assert(tstate->exc_info->exc_previous == NULL); | ||
if (Py_VerboseFlag && tstate->exc_info != &tstate->exc_state) { |
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.
Py_VerboseFlag
only is used for import related tracing.
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 consistent with https://github.com/python/cpython/blob/master/Python/pystate.c#L434. I'm not sure what is the ideal behaviour, but it ought to be consistent.
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.
Then that code should be fixed too.
Python/pystate.c
Outdated
Py_CLEAR(tstate->exc_state.exc_traceback); | ||
assert(tstate->exc_info->exc_previous == NULL); | ||
if (Py_VerboseFlag && tstate->exc_info != &tstate->exc_state) { | ||
fprintf(stderr, |
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.
Can PyErr_WarnEx()
or PySys_WriteStderr()
be used here?
Python/errors.c
Outdated
@@ -52,6 +52,15 @@ PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback) | |||
Py_XDECREF(oldtraceback); | |||
} | |||
|
|||
PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate) { |
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.
The result of this function is read-only, and exc_previous is not used. Wouldn't be better to return just three objects like in PyErr_GetExcInfo()
? This would make the PyExcState
structure more private.
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.
If I made it return three objects, it would be the same as PyErr_GetExcInfo()
.
_PyErr_GetExcInfo()
is provided as a way to handle the type, value, traceback
trio in a reasonably modular fashion, without to much overhead.
As it is only used in errors.c
and sysmodule.c
, it is effectively private.
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.
If it would be the same as PyErr_GetExcInfo()
, we should just use PyErr_GetExcInfo()
.
Python/pystate.c
Outdated
Py_CLEAR(tstate->exc_state.exc_type); | ||
Py_CLEAR(tstate->exc_state.exc_value); | ||
Py_CLEAR(tstate->exc_state.exc_traceback); | ||
assert(tstate->exc_info->exc_previous == NULL); |
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.
Add a comment here?
This looks good to me on the principle. It needs adressing @serhiy-storchaka 's comments, and also fixing the merge conflicts. |
@markshannon Mark, will you be able to work on this in the following couple of weeks? If not, I can help resolve code review comments. |
Not right now, but in the next week or so, yes. |
…erator/thread) object where it belongs.
800150c
to
415121e
Compare
…t exception 'state' forms a stack.
7929841
to
e5ffc67
Compare
@markshannon, do you have push rights or would you like someone else to merge this PR? |
Include/pystate.h
Outdated
|
||
struct _exc_stackitem *exc_previous; | ||
|
||
} _PyErr_StackItem; |
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.
Inconsistent names _exc_stackitem
and _PyErr_StackItem
.
Objects/genobject.c
Outdated
@@ -87,6 +96,20 @@ _PyGen_Finalize(PyObject *self) | |||
PyErr_Restore(error_type, error_value, error_traceback); | |||
} | |||
|
|||
static inline void exc_state_clear(_PyErr_StackItem *exc_state) |
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.
Move static inline void
on a separate line.
Python/errors.c
Outdated
@@ -53,6 +53,17 @@ PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback) | |||
Py_XDECREF(oldtraceback); | |||
} | |||
|
|||
_PyErr_StackItem *_PyErr_GetTopmostException(PyThreadState *tstate) |
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.
Move _PyErr_StackItem *
on a separate line.
Python/errors.c
Outdated
@@ -52,6 +52,15 @@ PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback) | |||
Py_XDECREF(oldtraceback); | |||
} | |||
|
|||
PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate) { |
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.
If it would be the same as PyErr_GetExcInfo()
, we should just use PyErr_GetExcInfo()
.
Is the problem I described on the tracker resolved? |
@serhiy-storchaka I've just fixed it. |
Thanks a lot, Mark, for looking into this. |
CPython change how the exception information is stored internally in 3.7a3. This simply adds fences based on python version around how to access the objects. Xref: python/cpython@ae3087c https://bugs.python.org/issue25612 python/cpython#1773
CPython change how the exception information is stored internally in 3.7a3. This simply adds fences based on python version around how to access the objects. Xref: python/cpython@ae3087c https://bugs.python.org/issue25612 python/cpython#1773
bpo-25612 (python#1773) moves the exception state information from frame object to coroutine (generator/thread) object where it belongs. As a consequence Stackless moves the exception state information for non-current tasklets from thread-state to the tasklet. This changes the pickle format of frame, tasklet and generator objects. The commit adds three test cases.
Moves the exception state from the frame to the coroutine. (Coroutine in the CS sense, which includes generators, coroutines and thread). This cleans up the code, removing about 60 lines, and fixes https://bugs.python.org/issue25612.
https://bugs.python.org/issue25612