Skip to content

Commit

Permalink
gh-xxxxxx: Add PyMutex and _PyParkingLot APIs
Browse files Browse the repository at this point in the history
`PyMutex` is a one byte lock with fast, inlineable lock and unlock
functions for the common uncontended case. The design is based on
WebKit's `WTF::Lock`.

PyMutex is built using the `_PyParkingLot` APIs, which provides a
cross-platform futex-like API (based on WebKit's `WTF::ParkingLot`).
This internal API will be used for building other synchronization
primitives used to implement PEP 703, such as one-time initialization
and events.
  • Loading branch information
colesbury committed Aug 31, 2023
1 parent 433319f commit e864815
Show file tree
Hide file tree
Showing 16 changed files with 1,149 additions and 2 deletions.
2 changes: 2 additions & 0 deletions Include/Python.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@
#include "pymem.h"
#include "pytypedefs.h"
#include "pybuffer.h"
#include "cpython/pyatomic.h"
#include "object.h"
#include "objimpl.h"
#include "typeslots.h"
#include "pyhash.h"
#include "cpython/pydebug.h"
#include "cpython/lock.h"
#include "bytearrayobject.h"
#include "bytesobject.h"
#include "unicodeobject.h"
Expand Down
84 changes: 84 additions & 0 deletions Include/cpython/lock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Lightweight locks and other synchronization mechanisms.
//
// These implementations are based on WebKit's WTF::Lock. See
// https://webkit.org/blog/6161/locking-in-webkit/ for a description of the
// design.

#ifndef Py_LIMITED_API
#ifndef Py_LOCK_H
#define Py_LOCK_H
#ifdef __cplusplus
extern "C" {
#endif

#include "cpython/pyatomic.h"

// A mutex that occupies one byte. The lock can be zero initialized.
//
// Only the two least significant bits are used. The remaining bits should be
// zero:
// 0b00: unlocked
// 0b01: locked
// 0b10: unlocked and has parked threads
// 0b11: locked and has parked threads
//
// Typical initialization:
// PyMutex m;
// memset(&m, 0, sizeof(m));
//
// Typical usage:
// PyMutex_Lock(&m);
// ...
// PyMutex_Unlock(&m);
typedef struct _PyMutex {
uint8_t v;
} PyMutex;

typedef enum {
_Py_UNLOCKED = 0,
_Py_LOCKED = 1,
_Py_HAS_PARKED = 2,
} _PyMutex_State;

// (private) slow path for locking the mutex
PyAPI_FUNC(void) _PyMutex_LockSlow(PyMutex *m);

// (private) slow path for unlocking the mutex
PyAPI_FUNC(void) _PyMutex_UnlockSlow(PyMutex *m);

// Locks the mutex.
//
// If the mutex is currently locked, the calling thread will be parked until
// the mutex is unlocked. If the current thread holds the GIL, then the GIL
// will be released while the thread is parked.
static inline void
PyMutex_Lock(PyMutex *m)
{
uint8_t expected = _Py_UNLOCKED;
if (!_Py_atomic_compare_exchange_uint8(&m->v, &expected, _Py_LOCKED)) {
_PyMutex_LockSlow(m);
}
}

// Unlocks the mutex.
static inline void
PyMutex_Unlock(PyMutex *m)
{
uint8_t expected = _Py_LOCKED;
if (!_Py_atomic_compare_exchange_uint8(&m->v, &expected, _Py_UNLOCKED)) {
_PyMutex_UnlockSlow(m);
}
}

// Checks if the mutex is currently locked.
static inline int
_PyMutex_IsLocked(PyMutex *m)
{
return (_Py_atomic_load_uint8(&m->v) & _Py_LOCKED) != 0;
}

#ifdef __cplusplus
}
#endif
#endif /* !Py_LOCK_H */
#endif /* Py_LIMITED_API */
3 changes: 2 additions & 1 deletion Include/cpython/pyatomic.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
// "A Relaxed Guide to memory_order_relaxed" for discussion of and common usage
// or relaxed atomics.

#ifndef Py_LIMITED_API
#ifndef Py_ATOMIC_H
#define Py_ATOMIC_H

Expand Down Expand Up @@ -403,4 +404,4 @@ static inline void _Py_atomic_fence_release(void);
#endif

#endif /* Py_ATOMIC_H */

#endif /* Py_LIMITED_API */
107 changes: 107 additions & 0 deletions Include/internal/pycore_llist.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// A doubly-linked list that can be embedded in a struct.
//
// Usage:
// struct llist_node head = LLIST_INIT(head);
// typedef struct {
// ...
// struct llist_node node;
// } MyObj;
//
// llist_insert_tail(&head, &obj->node);
// llist_remove(&obj->node);
//
// struct llist_node *node;
// llist_for_each(node, &head) {
// MyObj *obj = llist_data(node, MyObj, node);
// ...
// }
//

#ifndef Py_INTERNAL_LLIST_H
#define Py_INTERNAL_LLIST_H

#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

#ifndef Py_BUILD_CORE
# error "Py_BUILD_CORE must be defined to include this header"
#endif

struct llist_node {
struct llist_node *next;
struct llist_node *prev;
};

// Get the struct containing a node.
#define llist_data(node, type, member) \
(type*)((char*)node - offsetof(type, member))

// Iterate over a list.
#define llist_for_each(node, head) \
for (node = (head)->next; node != (head); node = node->next)

// Iterate over a list, but allow removal of the current node.
#define llist_for_each_safe(node, head) \
struct llist_node *_next; \
for (node = (head)->next, _next = node->next; \
node != (head); node = _next, _next = node->next)

#define LLIST_INIT(head) { &head, &head }

static inline void
llist_init(struct llist_node *head)
{
head->next = head;
head->prev = head;
}

// Returns 1 if the list is empty, 0 otherwise.
static inline int
llist_empty(struct llist_node *head)
{
return head->next == head;
}

// Appends to the tail of the list.
static inline void
llist_insert_tail(struct llist_node *head, struct llist_node *node)
{
node->prev = head->prev;
node->next = head;
head->prev->next = node;
head->prev = node;
}

// Remove a node from the list.
static inline void
llist_remove(struct llist_node *node)
{
struct llist_node *prev = node->prev;
struct llist_node *next = node->next;
prev->next = next;
next->prev = prev;
node->prev = NULL;
node->next = NULL;
}

// Append all nodes from head2 onto head1. head2 is left empty.
static inline void
llist_concat(struct llist_node *head1, struct llist_node *head2)
{
if (!llist_empty(head2)) {
head1->prev->next = head2->next;
head2->next->prev = head1->prev;

head1->prev = head2->prev;
head2->prev->next = head1;
llist_init(head2);
}
}

#ifdef __cplusplus
}
#endif
#endif /* !Py_INTERNAL_LLIST_H */
72 changes: 72 additions & 0 deletions Include/internal/pycore_lock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#ifndef Py_INTERNAL_LOCK_H
#define Py_INTERNAL_LOCK_H
#ifdef __cplusplus
extern "C" {
#endif

#ifndef Py_BUILD_CORE
# error "this header requires Py_BUILD_CORE define"
#endif

#include "pycore_time.h" // _PyTime_t


typedef enum _PyLockFlags {
// Do not detach/release the GIL when waiting on the lock.
_Py_LOCK_DONT_DETACH = 0,

// Detach/release the GIL when waiting on the lock.
_PY_LOCK_DETACH = 1,

// Handle signals if interrupted while waiting on the lock.
_PY_LOCK_MAKE_PENDING_CALLS = 2,
} _PyLockFlags;

// Lock a mutex with an optional timeout and additional options. See
// _PyLockFlags for details.
extern PyLockStatus
_PyMutex_TimedLock(PyMutex *m, _PyTime_t timeout_ns, _PyLockFlags flags);

// Unlock a mutex, returns 0 if the mutex is not locked (used for improved
// error messages).
extern int _PyMutex_TryUnlock(PyMutex *m);

// _PyRawMutex implements a word-sized mutex that that does not depend on the
// parking lot API, and therefore can be used in the parking lot
// implementation.
//
// The mutex uses a packed representation: the least significant bit is used to
// indicate whether the mutex is locked or not. The remaining bits are either
// zero or a pointer to a `struct raw_mutex_entry` (see lock.c).
typedef struct {
uintptr_t v;
} _PyRawMutex;

// Slow paths for lock/unlock
extern void _PyRawMutex_LockSlow(_PyRawMutex *m);
extern void _PyRawMutex_UnlockSlow(_PyRawMutex *m);

static inline void
_PyRawMutex_Lock(_PyRawMutex *m)
{
uintptr_t expected = _Py_UNLOCKED;
if (_Py_atomic_compare_exchange_uintptr(&m->v, &expected, _Py_LOCKED)) {
return;
}
_PyRawMutex_LockSlow(m);
}

static inline void
_PyRawMutex_Unlock(_PyRawMutex *m)
{
uintptr_t expected = _Py_LOCKED;
if (_Py_atomic_compare_exchange_uintptr(&m->v, &expected, _Py_UNLOCKED)) {
return;
}
_PyRawMutex_UnlockSlow(m);
}

#ifdef __cplusplus
}
#endif
#endif /* !Py_INTERNAL_LOCK_H */
Loading

0 comments on commit e864815

Please sign in to comment.