Move exc state to generator. Fixes bpo-25612 by markshannon · Pull Request #1773 · python/cpython (original) (raw)

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 andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Conversation51 Commits10 Checks0 Files changed

Conversation

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters

[ Show hidden characters]({{ revealButtonHref }})

markshannon

@the-knights-who-say-ni

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!

@mention-bot

@1st1 1st1 self-requested a review

May 23, 2017 22:12

@markshannon

@njsmith

1st1

Member

@1st1 1st1 left a 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).

@@ -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);

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)?

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)

PyObject *exc_type;
PyObject *exc_value;
PyObject *exc_traceback;
PyExcState exc_state;

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?

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.

@@ -150,6 +162,8 @@ typedef struct _ts {
PyObject *async_gen_firstiter;
PyObject *async_gen_finalizer;
PyExcState *exc_info;

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.

Choose a reason for hiding this comment

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

Done

f->f_exc_type = NULL;
f->f_exc_value = NULL;
f->f_exc_traceback = NULL;
t = gen->gi_exc_state.exc_type;

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.

@markshannon

1st1

PyObject *exc_type;
PyObject *exc_value;
PyObject *exc_traceback;
/* The exception currently being handled, if no coroutines/generators are present.

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>

1st1

PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate) {
PyExcState *exc_info = tstate->exc_info;
while ((exc_info->exc_type == NULL |
exc_info->exc_previous != NULL)

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.

@1st1

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.

1st1

@@ -86,6 +93,19 @@ _PyGen_Finalize(PyObject *self)
PyErr_Restore(error_type, error_value, error_traceback);
}
static inline void PyExcState_clear(PyExcState *exc_state) {

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.

larryhastings

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. */

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?

larryhastings

struct _excstate *exc_previous;
} PyExcState;

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?

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.

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.

@1st1

@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.

serhiy-storchaka

@@ -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);

Choose a reason for hiding this comment

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

PyAPI_FUNC()

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

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).

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?

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.

struct _excstate *exc_previous;
} PyExcState;

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

Choose a reason for hiding this comment

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

Py_LOCAL_INLINE(int)?

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.

@@ -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) {

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.

@@ -86,6 +94,19 @@ _PyGen_Finalize(PyObject *self)
PyErr_Restore(error_type, error_value, error_traceback);
}
static inline void PyExcState_Clear(PyExcState *exc_state) {

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.

@@ -52,6 +52,15 @@ PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback)
Py_XDECREF(oldtraceback);
}
PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate) {

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.

PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate) {
PyExcState *exc_info = tstate->exc_info;
while ((exc_info->exc_type == NULL |
exc_info->exc_previous != NULL) {

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).

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) {

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.

Choose a reason for hiding this comment

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

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.

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,

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?

@@ -52,6 +52,15 @@ PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback)
Py_XDECREF(oldtraceback);
}
PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate) {

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.

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.

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().

pitrou

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);

Choose a reason for hiding this comment

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

Add a comment here?

@pitrou

This looks good to me on the principle. It needs adressing @serhiy-storchaka 's comments, and also fixing the merge conflicts.

@1st1

@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.

@markshannon

Not right now, but in the next week or so, yes.

@markshannon

…erator/thread) object where it belongs.

@markshannon

…t exception 'state' forms a stack.

@markshannon

1st1

1st1 approved these changes Oct 22, 2017

@pitrou

pitrou

@pitrou

@markshannon, do you have push rights or would you like someone else to merge this PR?

serhiy-storchaka

struct _exc_stackitem *exc_previous;
} _PyErr_StackItem;

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.

@@ -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)

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.

@@ -53,6 +53,17 @@ PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback)
Py_XDECREF(oldtraceback);
}
_PyErr_StackItem *_PyErr_GetTopmostException(PyThreadState *tstate)

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.

@@ -52,6 +52,15 @@ PyErr_Restore(PyObject *type, PyObject *value, PyObject *traceback)
Py_XDECREF(oldtraceback);
}
PyExcState *_PyErr_GetExcInfo(PyThreadState *tstate) {

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().

@serhiy-storchaka

Is the problem I described on the tracker resolved?

@markshannon

@markshannon

@markshannon

@1st1

Thanks a lot, Mark, for looking into this.

tacaswell added a commit to tacaswell/cython that referenced this pull request

Oct 28, 2017

@tacaswell

This was referenced

Oct 28, 2017

scoder pushed a commit to cython/cython that referenced this pull request

Oct 28, 2017

@tacaswell @scoder

akruis pushed a commit to stackless-dev/stackless that referenced this pull request

Oct 13, 2018

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.

@ghost ghost mentioned this pull request

Mar 29, 2019