Skip to content
Closed
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
23 changes: 16 additions & 7 deletions Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@ extern "C" {
#include "pycore_atomic.h"
#include "pythread.h"

struct _pending_call;

struct _pending_call {
int (*func)(void *);
void *arg;
struct _pending_call *next;
};

// We technically do not need this limit around any longer since we
// moved from a circular queue to a linked list. However, having a
// size limit is still a good idea so we keep the one we already had.
#define NPENDINGCALLS 32

struct _pending_calls {
unsigned long main_thread;
PyThread_type_lock lock;
Expand All @@ -20,13 +33,9 @@ struct _pending_calls {
thread state.
Guarded by the GIL. */
int async_exc;
#define NPENDINGCALLS 32
struct {
int (*func)(void *);
void *arg;
} calls[NPENDINGCALLS];
int first;
int last;
int ncalls;
struct _pending_call *head;
struct _pending_call *last;
};

#include "pycore_gil.h"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Simply the ceval pending calls list by using a linked list.
73 changes: 51 additions & 22 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,48 @@ _PyEval_SignalReceived(void)
SIGNAL_PENDING_SIGNALS();
}

static int
_add_pending_call(int (*func)(void *), void *arg)
{
// TODO: Drop the limit?
if (_PyRuntime.ceval.pending.ncalls == NPENDINGCALLS) {
return -1; /* Queue full */
}

struct _pending_call *call = PyMem_RawMalloc(sizeof(struct _pending_call));
if (call == NULL) {
return -1;
}
call->func = func;
call->arg = arg;
call->next = NULL;

if (_PyRuntime.ceval.pending.head == NULL) {
_PyRuntime.ceval.pending.head = call;
}
else {
_PyRuntime.ceval.pending.last->next = call;
}
_PyRuntime.ceval.pending.last = call;
_PyRuntime.ceval.pending.ncalls++;
return 0;
}

static void
_pop_pending_call(int (**func)(void *), void **arg)
{
struct _pending_call *call = _PyRuntime.ceval.pending.head;
if (call == NULL) {
return; /* Queue empty */
}
_PyRuntime.ceval.pending.head = call->next;
_PyRuntime.ceval.pending.ncalls--;

*func = call->func;
*arg = call->arg;
PyMem_RawFree(call);
}

/* This implementation is thread-safe. It allows
scheduling to be made from any thread, and even from an executing
callback.
Expand All @@ -330,7 +372,6 @@ _PyEval_SignalReceived(void)
int
Py_AddPendingCall(int (*func)(void *), void *arg)
{
int i, j, result=0;
PyThread_type_lock lock = _PyRuntime.ceval.pending.lock;

/* try a few times for the lock. Since this mechanism is used
Expand All @@ -345,6 +386,7 @@ Py_AddPendingCall(int (*func)(void *), void *arg)
* this function is called before any bytecode evaluation takes place.
*/
if (lock != NULL) {
int i;
for (i = 0; i<100; i++) {
if (PyThread_acquire_lock(lock, NOWAIT_LOCK))
break;
Expand All @@ -353,20 +395,13 @@ Py_AddPendingCall(int (*func)(void *), void *arg)
return -1;
}

i = _PyRuntime.ceval.pending.last;
j = (i + 1) % NPENDINGCALLS;
if (j == _PyRuntime.ceval.pending.first) {
result = -1; /* Queue full */
} else {
_PyRuntime.ceval.pending.calls[i].func = func;
_PyRuntime.ceval.pending.calls[i].arg = arg;
_PyRuntime.ceval.pending.last = j;
}
int res = _add_pending_call(func, arg);

/* signal main loop */
SIGNAL_PENDING_CALLS();
if (lock != NULL)
PyThread_release_lock(lock);
return result;
return res;
}

static int
Expand Down Expand Up @@ -420,24 +455,18 @@ make_pending_calls(void)

/* perform a bounded number of calls, in case of recursion */
for (int i=0; i<NPENDINGCALLS; i++) {
int j;
int (*func)(void *);
int (*func)(void *) = NULL;
void *arg = NULL;

/* pop one item off the queue while holding the lock */
PyThread_acquire_lock(_PyRuntime.ceval.pending.lock, WAIT_LOCK);
j = _PyRuntime.ceval.pending.first;
if (j == _PyRuntime.ceval.pending.last) {
func = NULL; /* Queue empty */
} else {
func = _PyRuntime.ceval.pending.calls[j].func;
arg = _PyRuntime.ceval.pending.calls[j].arg;
_PyRuntime.ceval.pending.first = (j + 1) % NPENDINGCALLS;
}
_pop_pending_call(&func, &arg);
PyThread_release_lock(_PyRuntime.ceval.pending.lock);

/* having released the lock, perform the callback */
if (func == NULL)
if (func == NULL) {
break;
}
res = func(arg);
if (res) {
goto error;
Expand Down