Skip to content

Commit

Permalink
gh-121368: Fix seq lock memory ordering in _PyType_Lookup
Browse files Browse the repository at this point in the history
  • Loading branch information
colesbury committed Jul 4, 2024
1 parent 5f660e8 commit 9a68c4c
Show file tree
Hide file tree
Showing 9 changed files with 47 additions and 16 deletions.
3 changes: 3 additions & 0 deletions Include/cpython/pyatomic.h
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,9 @@ _Py_atomic_load_ssize_acquire(const Py_ssize_t *obj);
// See https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence
static inline void _Py_atomic_fence_seq_cst(void);

// Acquire fence
static inline void _Py_atomic_fence_acquire(void);

// Release fence
static inline void _Py_atomic_fence_release(void);

Expand Down
4 changes: 4 additions & 0 deletions Include/cpython/pyatomic_gcc.h
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,10 @@ static inline void
_Py_atomic_fence_seq_cst(void)
{ __atomic_thread_fence(__ATOMIC_SEQ_CST); }

static inline void
_Py_atomic_fence_acquire(void)
{ __atomic_thread_fence(__ATOMIC_ACQUIRE); }

static inline void
_Py_atomic_fence_release(void)
{ __atomic_thread_fence(__ATOMIC_RELEASE); }
12 changes: 12 additions & 0 deletions Include/cpython/pyatomic_msc.h
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,18 @@ _Py_atomic_fence_seq_cst(void)
#else
# error "no implementation of _Py_atomic_fence_seq_cst"
#endif
}

static inline void
_Py_atomic_fence_acquire(void)
{
#if defined(_M_ARM64)
__dmb(_ARM64_BARRIER_ISHLD);
#elif defined(_M_X64) || defined(_M_IX86)
_ReadBarrier();
#else
# error "no implementation of _Py_atomic_fence_acquire"
#endif
}

static inline void
Expand Down
7 changes: 7 additions & 0 deletions Include/cpython/pyatomic_std.h
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,13 @@ _Py_atomic_fence_seq_cst(void)
atomic_thread_fence(memory_order_seq_cst);
}

static inline void
_Py_atomic_fence_acquire(void)
{
_Py_USING_STD;
atomic_thread_fence(memory_order_acquire);
}

static inline void
_Py_atomic_fence_release(void)
{
Expand Down
8 changes: 4 additions & 4 deletions Include/internal/pycore_lock.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,12 @@ PyAPI_FUNC(void) _PySeqLock_AbandonWrite(_PySeqLock *seqlock);
PyAPI_FUNC(uint32_t) _PySeqLock_BeginRead(_PySeqLock *seqlock);

// End the read operation and confirm that the sequence number has not changed.
// Returns 1 if the read was successful or 0 if the read should be re-tried.
PyAPI_FUNC(uint32_t) _PySeqLock_EndRead(_PySeqLock *seqlock, uint32_t previous);
// Returns 1 if the read was successful or 0 if the read should be retried.
PyAPI_FUNC(int) _PySeqLock_EndRead(_PySeqLock *seqlock, uint32_t previous);

// Check if the lock was held during a fork and clear the lock. Returns 1
// if the lock was held and any associated datat should be cleared.
PyAPI_FUNC(uint32_t) _PySeqLock_AfterFork(_PySeqLock *seqlock);
// if the lock was held and any associated data should be cleared.
PyAPI_FUNC(int) _PySeqLock_AfterFork(_PySeqLock *seqlock);

#ifdef __cplusplus
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TODO
1 change: 1 addition & 0 deletions Modules/_testcapi/pyatomic.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ test_atomic_fences(PyObject *self, PyObject *obj) {
// Just make sure that the fences compile. We are not
// testing any synchronizing ordering.
_Py_atomic_fence_seq_cst();
_Py_atomic_fence_acquire();
_Py_atomic_fence_release();
Py_RETURN_NONE;
}
Expand Down
2 changes: 1 addition & 1 deletion Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -5387,7 +5387,7 @@ _PyType_LookupRef(PyTypeObject *type, PyObject *name)
#ifdef Py_GIL_DISABLED
// synchronize-with other writing threads by doing an acquire load on the sequence
while (1) {
int sequence = _PySeqLock_BeginRead(&entry->sequence);
uint32_t sequence = _PySeqLock_BeginRead(&entry->sequence);
uint32_t entry_version = _Py_atomic_load_uint32_relaxed(&entry->version);
uint32_t type_version = _Py_atomic_load_uint32_acquire(&type->tp_version_tag);
if (entry_version == type_version &&
Expand Down
25 changes: 14 additions & 11 deletions Python/lock.c
Original file line number Diff line number Diff line change
Expand Up @@ -547,28 +547,31 @@ uint32_t _PySeqLock_BeginRead(_PySeqLock *seqlock)
return sequence;
}

uint32_t _PySeqLock_EndRead(_PySeqLock *seqlock, uint32_t previous)
int _PySeqLock_EndRead(_PySeqLock *seqlock, uint32_t previous)
{
// Synchronize again and validate that the entry hasn't been updated
// while we were readying the values.
if (_Py_atomic_load_uint32_acquire(&seqlock->sequence) == previous) {
// gh-121368: We need an explicit acquire fence here to ensure that
// this load of the sequence number is not reordered before any loads
// withing the read lock.
_Py_atomic_fence_acquire();

if (_Py_atomic_load_uint32_relaxed(&seqlock->sequence) == previous) {
return 1;
}
}

_Py_yield();
return 0;
_Py_yield();
return 0;
}

uint32_t _PySeqLock_AfterFork(_PySeqLock *seqlock)
int _PySeqLock_AfterFork(_PySeqLock *seqlock)
{
// Synchronize again and validate that the entry hasn't been updated
// while we were readying the values.
if (SEQLOCK_IS_UPDATING(seqlock->sequence)) {
if (SEQLOCK_IS_UPDATING(seqlock->sequence)) {
seqlock->sequence = 0;
return 1;
}
}

return 0;
return 0;
}

#undef PyMutex_Lock
Expand Down

0 comments on commit 9a68c4c

Please sign in to comment.