@@ -41,6 +41,7 @@ PyUnstable_AtExit(PyInterpreterState *interp,
41
41
callback -> next = NULL ;
42
42
43
43
struct atexit_state * state = & interp -> atexit ;
44
+ _PyAtExit_LockCallbacks (state );
44
45
atexit_callback * top = state -> ll_callbacks ;
45
46
if (top == NULL ) {
46
47
state -> ll_callbacks = callback ;
@@ -49,36 +50,16 @@ PyUnstable_AtExit(PyInterpreterState *interp,
49
50
callback -> next = top ;
50
51
state -> ll_callbacks = callback ;
51
52
}
53
+ _PyAtExit_UnlockCallbacks (state );
52
54
return 0 ;
53
55
}
54
56
55
57
56
- static void
57
- atexit_delete_cb (struct atexit_state * state , int i )
58
- {
59
- atexit_py_callback * cb = state -> callbacks [i ];
60
- state -> callbacks [i ] = NULL ;
61
-
62
- Py_DECREF (cb -> func );
63
- Py_DECREF (cb -> args );
64
- Py_XDECREF (cb -> kwargs );
65
- PyMem_Free (cb );
66
- }
67
-
68
-
69
58
/* Clear all callbacks without calling them */
70
59
static void
71
60
atexit_cleanup (struct atexit_state * state )
72
61
{
73
- atexit_py_callback * cb ;
74
- for (int i = 0 ; i < state -> ncallbacks ; i ++ ) {
75
- cb = state -> callbacks [i ];
76
- if (cb == NULL )
77
- continue ;
78
-
79
- atexit_delete_cb (state , i );
80
- }
81
- state -> ncallbacks = 0 ;
62
+ PyList_Clear (state -> callbacks );
82
63
}
83
64
84
65
@@ -89,23 +70,21 @@ _PyAtExit_Init(PyInterpreterState *interp)
89
70
// _PyAtExit_Init() must only be called once
90
71
assert (state -> callbacks == NULL );
91
72
92
- state -> callback_len = 32 ;
93
- state -> ncallbacks = 0 ;
94
- state -> callbacks = PyMem_New (atexit_py_callback * , state -> callback_len );
73
+ state -> callbacks = PyList_New (0 );
95
74
if (state -> callbacks == NULL ) {
96
75
return _PyStatus_NO_MEMORY ();
97
76
}
98
77
return _PyStatus_OK ();
99
78
}
100
79
101
-
102
80
void
103
81
_PyAtExit_Fini (PyInterpreterState * interp )
104
82
{
83
+ // In theory, there shouldn't be any threads left by now, so we
84
+ // won't lock this.
105
85
struct atexit_state * state = & interp -> atexit ;
106
86
atexit_cleanup (state );
107
- PyMem_Free (state -> callbacks );
108
- state -> callbacks = NULL ;
87
+ Py_CLEAR (state -> callbacks );
109
88
110
89
atexit_callback * next = state -> ll_callbacks ;
111
90
state -> ll_callbacks = NULL ;
@@ -120,35 +99,44 @@ _PyAtExit_Fini(PyInterpreterState *interp)
120
99
}
121
100
}
122
101
123
-
124
102
static void
125
103
atexit_callfuncs (struct atexit_state * state )
126
104
{
127
105
assert (!PyErr_Occurred ());
106
+ assert (state -> callbacks != NULL );
107
+ assert (PyList_CheckExact (state -> callbacks ));
128
108
129
- if (state -> ncallbacks == 0 ) {
109
+ // Create a copy of the list for thread safety
110
+ PyObject * copy = PyList_GetSlice (state -> callbacks , 0 , PyList_GET_SIZE (state -> callbacks ));
111
+ if (copy == NULL )
112
+ {
113
+ PyErr_WriteUnraisable (NULL );
130
114
return ;
131
115
}
132
116
133
- for (int i = state -> ncallbacks - 1 ; i >= 0 ; i -- ) {
134
- atexit_py_callback * cb = state -> callbacks [i ];
135
- if (cb == NULL ) {
136
- continue ;
137
- }
117
+ for (Py_ssize_t i = 0 ; i < PyList_GET_SIZE (copy ); ++ i ) {
118
+ // We don't have to worry about evil borrowed references, because
119
+ // no other threads can access this list.
120
+ PyObject * tuple = PyList_GET_ITEM (copy , i );
121
+ assert (PyTuple_CheckExact (tuple ));
122
+
123
+ PyObject * func = PyTuple_GET_ITEM (tuple , 0 );
124
+ PyObject * args = PyTuple_GET_ITEM (tuple , 1 );
125
+ PyObject * kwargs = PyTuple_GET_ITEM (tuple , 2 );
138
126
139
- // bpo-46025: Increment the refcount of cb-> func as the call itself may unregister it
140
- PyObject * the_func = Py_NewRef ( cb -> func );
141
- PyObject * res = PyObject_Call ( cb -> func , cb -> args , cb -> kwargs );
127
+ PyObject * res = PyObject_Call ( func ,
128
+ args ,
129
+ kwargs == Py_None ? NULL : kwargs );
142
130
if (res == NULL ) {
143
131
PyErr_FormatUnraisable (
144
- "Exception ignored in atexit callback %R" , the_func );
132
+ "Exception ignored in atexit callback %R" , func );
145
133
}
146
134
else {
147
135
Py_DECREF (res );
148
136
}
149
- Py_DECREF (the_func );
150
137
}
151
138
139
+ Py_DECREF (copy );
152
140
atexit_cleanup (state );
153
141
154
142
assert (!PyErr_Occurred ());
@@ -194,33 +182,27 @@ atexit_register(PyObject *module, PyObject *args, PyObject *kwargs)
194
182
"the first argument must be callable" );
195
183
return NULL ;
196
184
}
185
+ PyObject * func_args = PyTuple_GetSlice (args , 1 , PyTuple_GET_SIZE (args ));
186
+ PyObject * func_kwargs = kwargs ;
197
187
198
- struct atexit_state * state = get_atexit_state ();
199
- if (state -> ncallbacks >= state -> callback_len ) {
200
- atexit_py_callback * * r ;
201
- state -> callback_len += 16 ;
202
- size_t size = sizeof (atexit_py_callback * ) * (size_t )state -> callback_len ;
203
- r = (atexit_py_callback * * )PyMem_Realloc (state -> callbacks , size );
204
- if (r == NULL ) {
205
- return PyErr_NoMemory ();
206
- }
207
- state -> callbacks = r ;
188
+ if (func_kwargs == NULL )
189
+ {
190
+ func_kwargs = Py_None ;
208
191
}
209
-
210
- atexit_py_callback * callback = PyMem_Malloc ( sizeof ( atexit_py_callback ));
211
- if ( callback == NULL ) {
212
- return PyErr_NoMemory () ;
192
+ PyObject * callback = PyTuple_Pack ( 3 , func , func_args , func_kwargs );
193
+ if ( callback == NULL )
194
+ {
195
+ return NULL ;
213
196
}
214
197
215
- callback -> args = PyTuple_GetSlice (args , 1 , PyTuple_GET_SIZE (args ));
216
- if (callback -> args == NULL ) {
217
- PyMem_Free (callback );
198
+ struct atexit_state * state = get_atexit_state ();
199
+ // atexit callbacks go in a LIFO order
200
+ if (PyList_Insert (state -> callbacks , 0 , callback ) < 0 )
201
+ {
202
+ Py_DECREF (callback );
218
203
return NULL ;
219
204
}
220
- callback -> func = Py_NewRef (func );
221
- callback -> kwargs = Py_XNewRef (kwargs );
222
-
223
- state -> callbacks [state -> ncallbacks ++ ] = callback ;
205
+ Py_DECREF (callback );
224
206
225
207
return Py_NewRef (func );
226
208
}
@@ -264,7 +246,33 @@ static PyObject *
264
246
atexit_ncallbacks (PyObject * module , PyObject * unused )
265
247
{
266
248
struct atexit_state * state = get_atexit_state ();
267
- return PyLong_FromSsize_t (state -> ncallbacks );
249
+ assert (state -> callbacks != NULL );
250
+ assert (PyList_CheckExact (state -> callbacks ));
251
+ return PyLong_FromSsize_t (PyList_GET_SIZE (state -> callbacks ));
252
+ }
253
+
254
+ static int
255
+ atexit_unregister_locked (PyObject * callbacks , PyObject * func )
256
+ {
257
+ for (Py_ssize_t i = 0 ; i < PyList_GET_SIZE (callbacks ); ++ i ) {
258
+ PyObject * tuple = PyList_GET_ITEM (callbacks , i );
259
+ assert (PyTuple_CheckExact (tuple ));
260
+ PyObject * to_compare = PyTuple_GET_ITEM (tuple , 0 );
261
+ int cmp = PyObject_RichCompareBool (func , to_compare , Py_EQ );
262
+ if (cmp < 0 )
263
+ {
264
+ return -1 ;
265
+ }
266
+ if (cmp == 1 ) {
267
+ // We found a callback!
268
+ if (PyList_SetSlice (callbacks , i , i + 1 , NULL ) < 0 ) {
269
+ return -1 ;
270
+ }
271
+ -- i ;
272
+ }
273
+ }
274
+
275
+ return 0 ;
268
276
}
269
277
270
278
PyDoc_STRVAR (atexit_unregister__doc__ ,
@@ -280,22 +288,11 @@ static PyObject *
280
288
atexit_unregister (PyObject * module , PyObject * func )
281
289
{
282
290
struct atexit_state * state = get_atexit_state ();
283
- for (int i = 0 ; i < state -> ncallbacks ; i ++ )
284
- {
285
- atexit_py_callback * cb = state -> callbacks [i ];
286
- if (cb == NULL ) {
287
- continue ;
288
- }
289
-
290
- int eq = PyObject_RichCompareBool (cb -> func , func , Py_EQ );
291
- if (eq < 0 ) {
292
- return NULL ;
293
- }
294
- if (eq ) {
295
- atexit_delete_cb (state , i );
296
- }
297
- }
298
- Py_RETURN_NONE ;
291
+ int result ;
292
+ Py_BEGIN_CRITICAL_SECTION (state -> callbacks );
293
+ result = atexit_unregister_locked (state -> callbacks , func );
294
+ Py_END_CRITICAL_SECTION ();
295
+ return result < 0 ? NULL : Py_None ;
299
296
}
300
297
301
298
0 commit comments