Skip to content

Commit 38e5fea

Browse files
committed
Avoid data race in Xxo_setattro
1 parent d680b17 commit 38e5fea

1 file changed

Lines changed: 36 additions & 4 deletions

File tree

Modules/xxlimited.c

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ typedef struct {
142142
*/
143143
char x_buffer[BUFSIZE]; /* buffer for Py_buffer */
144144
Py_ssize_t x_exports; /* how many buffer are exported */
145+
PyThread_type_lock x_lock; /* Lock for thread safety */
145146
} XxoObject_Data;
146147

147148
// Get the `XxoObject_Data` structure for a given instance of our type.
@@ -204,6 +205,15 @@ Xxo_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
204205
Py_DECREF(self);
205206
return NULL;
206207
}
208+
209+
// Set up a per-instance lock for thread safety.
210+
xxo_data->x_lock = PyThread_allocate_lock();
211+
if (xxo_data->x_lock == NULL) {
212+
PyErr_SetString(PyExc_SystemError, "could not allocate lock");
213+
Py_DECREF(self);
214+
return NULL;
215+
}
216+
207217
xxo_data->x_attr = NULL;
208218
memset(xxo_data->x_buffer, 0, BUFSIZE);
209219
xxo_data->x_exports = 0;
@@ -257,13 +267,23 @@ Xxo_finalize(PyObject *self)
257267
}
258268

259269
// dealloc: drop all remaining references and free memory
260-
// This function must preserve currently raised exception, if any.
261270
static void
262271
Xxo_dealloc(PyObject *self)
263272
{
273+
// This function must preserve currently raised exception, if any.
264274
PyObject *exc = PyErr_GetRaisedException();
275+
265276
PyObject_GC_UnTrack(self);
266277
Xxo_finalize(self);
278+
279+
// Free x_lock. This is not a Python object so it cannot
280+
// form reference cycles, so it's only handled here, not in
281+
// the traverse, clear and finalize handlers.
282+
XxoObject_Data *data = Xxo_get_data(self);
283+
if (data && data->x_lock) {
284+
PyThread_free_lock(data->x_lock);
285+
}
286+
267287
PyTypeObject *tp = Py_TYPE(self);
268288
freefunc free = PyType_GetSlot(tp, Py_tp_free);
269289
free(self);
@@ -314,13 +334,23 @@ Xxo_setattro(PyObject *self, PyObject *name, PyObject *v)
314334
if (data == NULL) {
315335
return -1;
316336
}
337+
338+
// If the attribute dict is not created yet, make one.
339+
// This needs to be protected by a lock to avoid another thread
340+
// creating a duplicate dict.
341+
PyThread_acquire_lock(data->x_lock, 1);
317342
if (data->x_attr == NULL) {
318343
// prepare the attribute dict
319344
data->x_attr = PyDict_New();
345+
PyThread_release_lock(data->x_lock);
320346
if (data->x_attr == NULL) {
321347
return -1;
322348
}
323349
}
350+
else {
351+
PyThread_release_lock(data->x_lock);
352+
}
353+
324354
if (v == NULL) {
325355
// delete an attribute
326356
int rv = PyDict_DelItem(data->x_attr, name);
@@ -589,6 +619,11 @@ xx_free(void *module)
589619
{
590620
// allow xx_modexec to omit calling xx_clear on error
591621
(void)xx_clear((PyObject *)module);
622+
623+
xx_state *state = PyModule_GetState(module);
624+
if (state == NULL) {
625+
return;
626+
}
592627
}
593628

594629
// Information that CPython uses to prevent loading incompatible extenstions
@@ -625,9 +660,6 @@ static PyModuleDef_Slot xx_slots[] = {
625660
* Without this slot, free-threaded builds of CPython will enable
626661
* the GIL when this module is loaded.
627662
*/
628-
// TODO: This is not quite true yet: there is a race in Xxo_setattro,
629-
// for example.
630-
// We include this for CPython's internal testing.
631663
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
632664

633665
{0, NULL}

0 commit comments

Comments
 (0)