Thanks to visit codestin.com
Credit goes to github.com

Skip to content

GH-118095: Make sure that progress is made if there are pending calls being handled. #118484

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Include/internal/pycore_ceval_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ struct _pending_call {
#define MAXPENDINGCALLSLOOP_MAIN 0

struct _pending_calls {
int busy;
PyThreadState *handling_thread;
PyMutex mutex;
/* Request for running pending calls. */
int32_t npending;
Expand Down
21 changes: 10 additions & 11 deletions Python/ceval_gil.c
Original file line number Diff line number Diff line change
Expand Up @@ -877,21 +877,20 @@ make_pending_calls(PyThreadState *tstate)
/* Only one thread (per interpreter) may run the pending calls
at once. In the same way, we don't do recursive pending calls. */
PyMutex_Lock(&pending->mutex);
if (pending->busy) {
if (pending->handling_thread != NULL) {
/* A pending call was added after another thread was already
handling the pending calls (and had already "unsignaled").
Once that thread is done, it may have taken care of all the
pending calls, or there might be some still waiting.
Regardless, this interpreter's pending calls will stay
"signaled" until that first thread has finished. At that
point the next thread to trip the eval breaker will take
care of any remaining pending calls. Until then, though,
all the interpreter's threads will be tripping the eval
breaker every time it's checked. */
To avoid all threads constantly stopping on the eval breaker,
we clear the bit for this thread and make sure it is set
for the thread currently handling the pending call. */
_Py_set_eval_breaker_bit(pending->handling_thread, _PY_CALLS_TO_DO_BIT);
_Py_unset_eval_breaker_bit(tstate, _PY_CALLS_TO_DO_BIT);
Comment on lines +885 to +889
Copy link
Member

Choose a reason for hiding this comment

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

Does this mean that only the thread that was already handling pending calls will handle any remaining ones (until the queue is empty)? Currently the next available thread handles the remaining ones, so no single thread is blocked too long handling pending calls.

Copy link
Member Author

Choose a reason for hiding this comment

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

Currently, the next available thread keeps tripping the eval breaker until the current call is handled, so the _PY_CALLS_TO_DO_BIT is in effect blocking.

A non-blocking and fair mechanism would be good, but the current mechanism is neither.
This makes it non-blocking, although maybe less fair.

Copy link
Member

Choose a reason for hiding this comment

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

That's fine. We can circle back to a fair mechanism later, if someone cares enough.

PyMutex_Unlock(&pending->mutex);
return 0;
}
pending->busy = 1;
pending->handling_thread = tstate;
PyMutex_Unlock(&pending->mutex);

/* unsignal before starting to call callbacks, so that any callback
Expand All @@ -900,7 +899,7 @@ make_pending_calls(PyThreadState *tstate)

int32_t npending;
if (_make_pending_calls(pending, &npending) != 0) {
pending->busy = 0;
pending->handling_thread = NULL;
/* There might not be more calls to make, but we play it safe. */
signal_pending_calls(tstate, interp);
return -1;
Expand All @@ -912,7 +911,7 @@ make_pending_calls(PyThreadState *tstate)

if (_Py_IsMainThread() && _Py_IsMainInterpreter(interp)) {
if (_make_pending_calls(pending_main, &npending) != 0) {
pending->busy = 0;
pending->handling_thread = NULL;
/* There might not be more calls to make, but we play it safe. */
signal_pending_calls(tstate, interp);
return -1;
Expand All @@ -923,7 +922,7 @@ make_pending_calls(PyThreadState *tstate)
}
}

pending->busy = 0;
pending->handling_thread = NULL;
return 0;
}

Expand Down
Loading