Skip to content

Commit b8e4ae8

Browse files
committed
Initial implementation.
1 parent d4c72fe commit b8e4ae8

File tree

2 files changed

+40
-3
lines changed

2 files changed

+40
-3
lines changed

Include/internal/pycore_atexit.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ struct atexit_state {
5252
atexit_py_callback **callbacks;
5353
int ncallbacks;
5454
int callback_len;
55+
56+
PyMutex lock;
5557
};
5658

5759
// Export for '_interpchannels' shared extension

Modules/atexitmodule.c

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
#include "pycore_interp.h" // PyInterpreterState.atexit
1313
#include "pycore_pystate.h" // _PyInterpreterState_GET
1414

15+
/* Note: anything declared as static in this file assumes the lock is held
16+
* (except for the Python-level functions) */
17+
#define _PyAtExit_LOCK(state) PyMutex_Lock(&state->lock);
18+
#define _PyAtExit_UNLOCK(state) PyMutex_Unlock(&state->lock);
19+
1520
/* ===================================================================== */
1621
/* Callback machinery. */
1722

@@ -38,13 +43,15 @@ PyUnstable_AtExit(PyInterpreterState *interp,
3843
callback->next = NULL;
3944

4045
struct atexit_state *state = &interp->atexit;
46+
_PyAtExit_LOCK(state);
4147
if (state->ll_callbacks == NULL) {
4248
state->ll_callbacks = callback;
4349
state->last_ll_callback = callback;
4450
}
4551
else {
4652
state->last_ll_callback->next = callback;
4753
}
54+
_PyAtExit_UNLOCK(state);
4855
return 0;
4956
}
5057

@@ -99,6 +106,8 @@ void
99106
_PyAtExit_Fini(PyInterpreterState *interp)
100107
{
101108
struct atexit_state *state = &interp->atexit;
109+
// XXX Can this be unlocked?
110+
_PyAtExit_LOCK(state);
102111
atexit_cleanup(state);
103112
PyMem_Free(state->callbacks);
104113
state->callbacks = NULL;
@@ -114,6 +123,7 @@ _PyAtExit_Fini(PyInterpreterState *interp)
114123
PyMem_Free(callback);
115124
exitfunc(data);
116125
}
126+
_PyAtExit_UNLOCK(state);
117127
}
118128

119129

@@ -155,7 +165,9 @@ void
155165
_PyAtExit_Call(PyInterpreterState *interp)
156166
{
157167
struct atexit_state *state = &interp->atexit;
168+
_PyAtExit_LOCK(state);
158169
atexit_callfuncs(state);
170+
_PyAtExit_UNLOCK(state);
159171
}
160172

161173

@@ -192,31 +204,39 @@ atexit_register(PyObject *module, PyObject *args, PyObject *kwargs)
192204
}
193205

194206
struct atexit_state *state = get_atexit_state();
207+
/* In theory, we could hold the lock for a shorter amount of time
208+
* using some fancy compare-exchanges with the length. However, I'm lazy.
209+
*/
210+
_PyAtExit_LOCK(state);
195211
if (state->ncallbacks >= state->callback_len) {
196212
atexit_py_callback **r;
197213
state->callback_len += 16;
198214
size_t size = sizeof(atexit_py_callback*) * (size_t)state->callback_len;
199215
r = (atexit_py_callback**)PyMem_Realloc(state->callbacks, size);
200216
if (r == NULL) {
217+
_PyAtExit_UNLOCK(state);
201218
return PyErr_NoMemory();
202219
}
203220
state->callbacks = r;
204221
}
205222

206223
atexit_py_callback *callback = PyMem_Malloc(sizeof(atexit_py_callback));
207224
if (callback == NULL) {
225+
_PyAtExit_UNLOCK(state);
208226
return PyErr_NoMemory();
209227
}
210228

211229
callback->args = PyTuple_GetSlice(args, 1, PyTuple_GET_SIZE(args));
212230
if (callback->args == NULL) {
213231
PyMem_Free(callback);
232+
_PyAtExit_UNLOCK(state);
214233
return NULL;
215234
}
216235
callback->func = Py_NewRef(func);
217236
callback->kwargs = Py_XNewRef(kwargs);
218237

219238
state->callbacks[state->ncallbacks++] = callback;
239+
_PyAtExit_UNLOCK(state);
220240

221241
return Py_NewRef(func);
222242
}
@@ -233,7 +253,9 @@ static PyObject *
233253
atexit_run_exitfuncs(PyObject *module, PyObject *unused)
234254
{
235255
struct atexit_state *state = get_atexit_state();
256+
_PyAtExit_LOCK(state);
236257
atexit_callfuncs(state);
258+
_PyAtExit_UNLOCK(state);
237259
Py_RETURN_NONE;
238260
}
239261

@@ -246,7 +268,10 @@ Clear the list of previously registered exit functions.");
246268
static PyObject *
247269
atexit_clear(PyObject *module, PyObject *unused)
248270
{
249-
atexit_cleanup(get_atexit_state());
271+
struct atexit_state *state = get_atexit_state();
272+
_PyAtExit_LOCK(state);
273+
atexit_cleanup(state);
274+
_PyAtExit_UNLOCK(state);
250275
Py_RETURN_NONE;
251276
}
252277

@@ -260,7 +285,10 @@ static PyObject *
260285
atexit_ncallbacks(PyObject *module, PyObject *unused)
261286
{
262287
struct atexit_state *state = get_atexit_state();
263-
return PyLong_FromSsize_t(state->ncallbacks);
288+
_PyAtExit_LOCK(state);
289+
PyObject *res = PyLong_FromSsize_t(state->ncallbacks);
290+
_PyAtExit_UNLOCK(state);
291+
return res;
264292
}
265293

266294
PyDoc_STRVAR(atexit_unregister__doc__,
@@ -276,21 +304,28 @@ static PyObject *
276304
atexit_unregister(PyObject *module, PyObject *func)
277305
{
278306
struct atexit_state *state = get_atexit_state();
307+
_PyAtExit_LOCK(state);
279308
for (int i = 0; i < state->ncallbacks; i++)
280309
{
281310
atexit_py_callback *cb = state->callbacks[i];
282311
if (cb == NULL) {
283312
continue;
284313
}
285314

286-
int eq = PyObject_RichCompareBool(cb->func, func, Py_EQ);
315+
// We need to hold our own reference to this
316+
// in case another thread is trying to unregister as well.
317+
PyObject *to_compare = cb->func;
318+
_PyAtExit_UNLOCK(state);
319+
int eq = PyObject_RichCompareBool(to_compare, func, Py_EQ);
287320
if (eq < 0) {
288321
return NULL;
289322
}
323+
_PyAtExit_LOCK(state);
290324
if (eq) {
291325
atexit_delete_cb(state, i);
292326
}
293327
}
328+
_PyAtExit_UNLOCK(state);
294329
Py_RETURN_NONE;
295330
}
296331

0 commit comments

Comments
 (0)