Skip to content

Commit 78c6ee2

Browse files
use linked list
1 parent 37e533a commit 78c6ee2

File tree

7 files changed

+114
-185
lines changed

7 files changed

+114
-185
lines changed

Include/internal/pycore_ceval.h

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,8 @@ extern void _PyEval_SignalReceived(void);
4848
#define _Py_PENDING_MAINTHREADONLY 1
4949
#define _Py_PENDING_RAWFREE 2
5050

51-
typedef int _Py_add_pending_call_result;
52-
#define _Py_ADD_PENDING_SUCCESS 0
53-
#define _Py_ADD_PENDING_FULL -1
54-
5551
// Export for '_testinternalcapi' shared extension
56-
PyAPI_FUNC(_Py_add_pending_call_result) _PyEval_AddPendingCall(
52+
PyAPI_FUNC(int) _PyEval_AddPendingCall(
5753
PyInterpreterState *interp,
5854
_Py_pending_call_func func,
5955
void *arg,

Include/internal/pycore_ceval_state.h

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,49 +14,38 @@ extern "C" {
1414

1515
typedef int (*_Py_pending_call_func)(void *);
1616

17-
struct _pending_call {
17+
typedef struct _pending_call {
1818
_Py_pending_call_func func;
1919
void *arg;
2020
int flags;
21-
};
22-
23-
#define PENDINGCALLSARRAYSIZE 300
21+
struct _pending_call *next;
22+
} _pending_call;
2423

25-
#define MAXPENDINGCALLS PENDINGCALLSARRAYSIZE
2624
/* For interpreter-level pending calls, we want to avoid spending too
2725
much time on pending calls in any one thread, so we apply a limit. */
28-
#if MAXPENDINGCALLS > 100
29-
# define MAXPENDINGCALLSLOOP 100
30-
#else
31-
# define MAXPENDINGCALLSLOOP MAXPENDINGCALLS
32-
#endif
26+
#define MAXPENDINGCALLSLOOP 100
3327

34-
/* We keep the number small to preserve as much compatibility
35-
as possible with earlier versions. */
36-
#define MAXPENDINGCALLS_MAIN 32
3728
/* For the main thread, we want to make sure all pending calls are
3829
run at once, for the sake of prompt signal handling. This is
3930
unlikely to cause any problems since there should be very few
4031
pending calls for the main thread. */
41-
#define MAXPENDINGCALLSLOOP_MAIN 0
32+
#define MAXPENDINGCALLSLOOP_MAIN INT32_MAX
4233

4334
struct _pending_calls {
4435
PyThreadState *handling_thread;
4536
PyMutex mutex;
46-
/* Request for running pending calls. */
37+
/* The number of pending calls. */
4738
int32_t npending;
48-
/* The maximum allowed number of pending calls.
49-
If the queue fills up to this point then _PyEval_AddPendingCall()
50-
will return _Py_ADD_PENDING_FULL. */
51-
int32_t max;
5239
/* We don't want a flood of pending calls to interrupt any one thread
5340
for too long, so we keep a limit on the number handled per pass.
5441
A value of 0 means there is no limit (other than the maximum
5542
size of the list of pending calls). */
5643
int32_t maxloop;
57-
struct _pending_call calls[PENDINGCALLSARRAYSIZE];
58-
int first;
59-
int next;
44+
_pending_call *head;
45+
46+
#define _Py_PENDING_CALLS_FREELIST_SIZE 100
47+
_pending_call *freelist;
48+
size_t freelist_num;
6049
};
6150

6251

@@ -97,8 +86,6 @@ struct _ceval_runtime_state {
9786
/* Pending calls to be made only on the main thread. */
9887
// The signal machinery falls back on this
9988
// so it must be especially stable and efficient.
100-
// For example, we use a preallocated array
101-
// for the list of pending calls.
10289
struct _pending_calls pending_mainthread;
10390
PyMutex sys_trace_profile_mutex;
10491
};

Include/internal/pycore_runtime_init.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,6 @@ extern PyTypeObject _PyExc_MemoryError;
163163
.parser = _parser_runtime_state_INIT, \
164164
.ceval = { \
165165
.pending_mainthread = { \
166-
.max = MAXPENDINGCALLS_MAIN, \
167166
.maxloop = MAXPENDINGCALLSLOOP_MAIN, \
168167
}, \
169168
.perf = _PyEval_RUNTIME_PERF_INIT, \
@@ -223,7 +222,6 @@ extern PyTypeObject _PyExc_MemoryError;
223222
.ceval = { \
224223
.recursion_limit = Py_DEFAULT_RECURSION_LIMIT, \
225224
.pending = { \
226-
.max = MAXPENDINGCALLS, \
227225
.maxloop = MAXPENDINGCALLSLOOP, \
228226
}, \
229227
}, \

Lib/test/test_capi/test_misc.py

Lines changed: 6 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,27 +1557,20 @@ def callback():
15571557

15581558
for i in range(n):
15591559
time.sleep(random.random()*0.02) #0.01 secs on average
1560-
#try submitting callback until successful.
1561-
#rely on regular interrupt to flush queue if we are
1562-
#unsuccessful.
1563-
while True:
1564-
if _testcapi._pending_threadfunc(callback):
1565-
break
1560+
self.assertTrue(_testcapi._pending_threadfunc(callback))
15661561

1567-
def pendingcalls_submit(self, l, n, *, main=True, ensure=False):
1562+
def pendingcalls_submit(self, l, n, *, main=True):
15681563
def callback():
15691564
#this function can be interrupted by thread switching so let's
15701565
#use an atomic operation
15711566
l.append(None)
15721567

15731568
if main:
15741569
return _testcapi._pending_threadfunc(callback, n,
1575-
blocking=False,
1576-
ensure_added=ensure)
1570+
blocking=False)
15771571
else:
15781572
return _testinternalcapi.pending_threadfunc(callback, n,
1579-
blocking=False,
1580-
ensure_added=ensure)
1573+
blocking=False)
15811574

15821575
def pendingcalls_wait(self, l, numadded, context = None):
15831576
#now, stick around until l[0] has grown to 10
@@ -1635,51 +1628,11 @@ def test_main_pendingcalls_non_threaded(self):
16351628
#again, just using the main thread, likely they will all be dispatched at
16361629
#once. It is ok to ask for too many, because we loop until we find a slot.
16371630
#the loop can be interrupted to dispatch.
1638-
#there are only 32 dispatch slots, so we go for twice that!
16391631
l = []
16401632
n = 64
16411633
self.main_pendingcalls_submit(l, n)
16421634
self.pendingcalls_wait(l, n)
16431635

1644-
def test_max_pending(self):
1645-
with self.subTest('main-only'):
1646-
maxpending = 32
1647-
1648-
l = []
1649-
added = self.pendingcalls_submit(l, 1, main=True)
1650-
self.pendingcalls_wait(l, added)
1651-
self.assertEqual(added, 1)
1652-
1653-
l = []
1654-
added = self.pendingcalls_submit(l, maxpending, main=True)
1655-
self.pendingcalls_wait(l, added)
1656-
self.assertEqual(added, maxpending)
1657-
1658-
l = []
1659-
added = self.pendingcalls_submit(l, maxpending+1, main=True)
1660-
self.pendingcalls_wait(l, added)
1661-
self.assertEqual(added, maxpending)
1662-
1663-
with self.subTest('not main-only'):
1664-
# Per-interpreter pending calls has a much higher limit
1665-
# on how many may be pending at a time.
1666-
maxpending = 300
1667-
1668-
l = []
1669-
added = self.pendingcalls_submit(l, 1, main=False)
1670-
self.pendingcalls_wait(l, added)
1671-
self.assertEqual(added, 1)
1672-
1673-
l = []
1674-
added = self.pendingcalls_submit(l, maxpending, main=False)
1675-
self.pendingcalls_wait(l, added)
1676-
self.assertEqual(added, maxpending)
1677-
1678-
l = []
1679-
added = self.pendingcalls_submit(l, maxpending+1, main=False)
1680-
self.pendingcalls_wait(l, added)
1681-
self.assertEqual(added, maxpending)
1682-
16831636
class PendingTask(types.SimpleNamespace):
16841637

16851638
_add_pending = _testinternalcapi.pending_threadfunc
@@ -1713,10 +1666,10 @@ def callback():
17131666
# the eval breaker, so we take a naive approach to
17141667
# make sure.
17151668
if threading.get_ident() not in worker_tids:
1716-
self._add_pending(callback, ensure_added=True)
1669+
self._add_pending(callback)
17171670
return
17181671
self.run()
1719-
self._add_pending(callback, ensure_added=True)
1672+
self._add_pending(callback)
17201673

17211674
def create_thread(self, worker_tids):
17221675
return threading.Thread(

Modules/_testcapimodule.c

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -837,14 +837,13 @@ static PyObject *
837837
pending_threadfunc(PyObject *self, PyObject *arg, PyObject *kwargs)
838838
{
839839
static char *kwlist[] = {"callback", "num",
840-
"blocking", "ensure_added", NULL};
840+
"blocking", NULL};
841841
PyObject *callable;
842842
unsigned int num = 1;
843843
int blocking = 0;
844-
int ensure_added = 0;
845844
if (!PyArg_ParseTupleAndKeywords(arg, kwargs,
846-
"O|I$pp:_pending_threadfunc", kwlist,
847-
&callable, &num, &blocking, &ensure_added))
845+
"O|I$p:_pending_threadfunc", kwlist,
846+
&callable, &num, &blocking))
848847
{
849848
return NULL;
850849
}
@@ -861,16 +860,9 @@ pending_threadfunc(PyObject *self, PyObject *arg, PyObject *kwargs)
861860

862861
unsigned int num_added = 0;
863862
for (; num_added < num; num_added++) {
864-
if (ensure_added) {
865-
int r;
866-
do {
867-
r = Py_AddPendingCall(&_pending_callback, callable);
868-
} while (r < 0);
869-
}
870-
else {
871-
if (Py_AddPendingCall(&_pending_callback, callable) < 0) {
872-
break;
873-
}
863+
if (Py_AddPendingCall(&_pending_callback, callable) < 0) {
864+
// out of memory and freelist is empty
865+
break;
874866
}
875867
}
876868

Modules/_testinternalcapi.c

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,18 +1072,17 @@ pending_threadfunc(PyObject *self, PyObject *args, PyObject *kwargs)
10721072
PyObject *callable;
10731073
unsigned int num = 1;
10741074
int blocking = 0;
1075-
int ensure_added = 0;
10761075
static char *kwlist[] = {"callback", "num",
1077-
"blocking", "ensure_added", NULL};
1076+
"blocking", NULL};
10781077
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
1079-
"O|I$pp:pending_threadfunc", kwlist,
1080-
&callable, &num, &blocking, &ensure_added))
1078+
"O|I$p:pending_threadfunc", kwlist,
1079+
&callable, &num, &blocking))
10811080
{
10821081
return NULL;
10831082
}
10841083
PyInterpreterState *interp = _PyInterpreterState_GET();
10851084

1086-
/* create the reference for the callbackwhile we hold the lock */
1085+
/* create the reference for the callback while we hold the lock */
10871086
for (unsigned int i = 0; i < num; i++) {
10881087
Py_INCREF(callable);
10891088
}
@@ -1095,18 +1094,9 @@ pending_threadfunc(PyObject *self, PyObject *args, PyObject *kwargs)
10951094

10961095
unsigned int num_added = 0;
10971096
for (; num_added < num; num_added++) {
1098-
if (ensure_added) {
1099-
_Py_add_pending_call_result r;
1100-
do {
1101-
r = _PyEval_AddPendingCall(interp, &_pending_callback, callable, 0);
1102-
assert(r == _Py_ADD_PENDING_SUCCESS
1103-
|| r == _Py_ADD_PENDING_FULL);
1104-
} while (r == _Py_ADD_PENDING_FULL);
1105-
}
1106-
else {
1107-
if (_PyEval_AddPendingCall(interp, &_pending_callback, callable, 0) < 0) {
1108-
break;
1109-
}
1097+
if (_PyEval_AddPendingCall(interp, &_pending_callback, callable, 0) < 0) {
1098+
// out of memory and freelist is empty
1099+
break;
11101100
}
11111101
}
11121102

@@ -1162,16 +1152,14 @@ pending_identify(PyObject *self, PyObject *args)
11621152
PyThread_acquire_lock(mutex, WAIT_LOCK);
11631153
/* It gets released in _pending_identify_callback(). */
11641154

1165-
_Py_add_pending_call_result r;
1166-
do {
1167-
Py_BEGIN_ALLOW_THREADS
1168-
r = _PyEval_AddPendingCall(interp,
1169-
&_pending_identify_callback, (void *)mutex,
1170-
0);
1171-
Py_END_ALLOW_THREADS
1172-
assert(r == _Py_ADD_PENDING_SUCCESS
1173-
|| r == _Py_ADD_PENDING_FULL);
1174-
} while (r == _Py_ADD_PENDING_FULL);
1155+
1156+
Py_BEGIN_ALLOW_THREADS
1157+
int r = _PyEval_AddPendingCall(interp,
1158+
&_pending_identify_callback, (void *)mutex,
1159+
0);
1160+
(void)r;
1161+
assert(r == 0);
1162+
Py_END_ALLOW_THREADS
11751163

11761164
/* Wait for the pending call to complete. */
11771165
PyThread_acquire_lock(mutex, WAIT_LOCK);

0 commit comments

Comments
 (0)