Skip to content

Commit

Permalink
pythongh-112075: Add critical sections for most dict APIs (python#114508
Browse files Browse the repository at this point in the history
)

Starts adding thread safety to dict objects.


Use @critical_section for APIs which are exposed via argument clinic and don't directly correlate with a public C API which needs to acquire the lock
Use a _lock_held suffix for keeping changes to complicated functions simple and just wrapping them with a critical section
Acquire and release the lock in an existing function where it won't be overly disruptive to the existing logic
  • Loading branch information
DinoV authored Feb 6, 2024
1 parent b6228b5 commit 92abb01
Show file tree
Hide file tree
Showing 6 changed files with 782 additions and 284 deletions.
46 changes: 46 additions & 0 deletions Include/internal/pycore_critical_section.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,37 @@ extern "C" {
# define Py_END_CRITICAL_SECTION2() \
_PyCriticalSection2_End(&_cs2); \
}

// Asserts that the mutex is locked. The mutex must be held by the
// top-most critical section otherwise there's the possibility
// that the mutex would be swalled out in some code paths.
#define _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(mutex) \
_PyCriticalSection_AssertHeld(mutex)

// Asserts that the mutex for the given object is locked. The mutex must
// be held by the top-most critical section otherwise there's the
// possibility that the mutex would be swalled out in some code paths.
#ifdef Py_DEBUG

#define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op) \
if (Py_REFCNT(op) != 1) { \
_Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(&_PyObject_CAST(op)->ob_mutex); \
}

#else /* Py_DEBUG */

#define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op)

#endif /* Py_DEBUG */

#else /* !Py_GIL_DISABLED */
// The critical section APIs are no-ops with the GIL.
# define Py_BEGIN_CRITICAL_SECTION(op)
# define Py_END_CRITICAL_SECTION()
# define Py_BEGIN_CRITICAL_SECTION2(a, b)
# define Py_END_CRITICAL_SECTION2()
# define _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(mutex)
# define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op)
#endif /* !Py_GIL_DISABLED */

typedef struct {
Expand Down Expand Up @@ -236,6 +261,27 @@ _PyCriticalSection2_End(_PyCriticalSection2 *c)
PyAPI_FUNC(void)
_PyCriticalSection_SuspendAll(PyThreadState *tstate);

#ifdef Py_GIL_DISABLED

static inline void
_PyCriticalSection_AssertHeld(PyMutex *mutex) {
#ifdef Py_DEBUG
PyThreadState *tstate = _PyThreadState_GET();
uintptr_t prev = tstate->critical_section;
if (prev & _Py_CRITICAL_SECTION_TWO_MUTEXES) {
_PyCriticalSection2 *cs = (_PyCriticalSection2 *)(prev & ~_Py_CRITICAL_SECTION_MASK);
assert(cs != NULL && (cs->base.mutex == mutex || cs->mutex2 == mutex));
}
else {
_PyCriticalSection *cs = (_PyCriticalSection *)(tstate->critical_section & ~_Py_CRITICAL_SECTION_MASK);
assert(cs != NULL && cs->mutex == mutex);
}

#endif
}

#endif

#ifdef __cplusplus
}
#endif
Expand Down
27 changes: 15 additions & 12 deletions Modules/_sre/sre.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@ static const char copyright[] =
" SRE 2.2.2 Copyright (c) 1997-2002 by Secret Labs AB ";

#include "Python.h"
#include "pycore_dict.h" // _PyDict_Next()
#include "pycore_long.h" // _PyLong_GetZero()
#include "pycore_moduleobject.h" // _PyModule_GetState()
#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION
#include "pycore_dict.h" // _PyDict_Next()
#include "pycore_long.h" // _PyLong_GetZero()
#include "pycore_moduleobject.h" // _PyModule_GetState()

#include "sre.h" // SRE_CODE
#include "sre.h" // SRE_CODE

#include <ctype.h> // tolower(), toupper(), isalnum()
#include <ctype.h> // tolower(), toupper(), isalnum()

#define SRE_CODE_BITS (8 * sizeof(SRE_CODE))

Expand Down Expand Up @@ -2349,26 +2350,28 @@ _sre_SRE_Match_groupdict_impl(MatchObject *self, PyObject *default_value)
if (!result || !self->pattern->groupindex)
return result;

Py_BEGIN_CRITICAL_SECTION(self->pattern->groupindex);
while (_PyDict_Next(self->pattern->groupindex, &pos, &key, &value, &hash)) {
int status;
Py_INCREF(key);
value = match_getslice(self, key, default_value);
if (!value) {
Py_DECREF(key);
goto failed;
Py_CLEAR(result);
goto exit;
}
status = _PyDict_SetItem_KnownHash(result, key, value, hash);
Py_DECREF(value);
Py_DECREF(key);
if (status < 0)
goto failed;
if (status < 0) {
Py_CLEAR(result);
goto exit;
}
}
exit:
Py_END_CRITICAL_SECTION();

return result;

failed:
Py_DECREF(result);
return NULL;
}

/*[clinic input]
Expand Down
30 changes: 28 additions & 2 deletions Objects/clinic/dictobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 92abb01

Please sign in to comment.