| #include "pythoncapi_compat.h" | |
| #define PY_SSIZE_T_CLEAN | |
| #include <Python.h> | |
| #include "librt_threading.h" | |
| #include "mypyc_util.h" | |
| #if !defined(_WIN32) | |
| #include <unistd.h> | |
| #include <errno.h> | |
| #endif | |
| #if CPY_3_14_FEATURES | |
| // Python 3.14+ (all platforms, with or without the GIL): Use PyMutex (1-byte | |
| // atomic lock with parking lot). PyMutex gives better interruptibility than the | |
| // pthread fallback, and its fast release path is competitive with sem_t. | |
| // PyMutex_LockFast, _PyMutex_LockTimed, and _PY_LOCK_DETACH are internal | |
| // CPython APIs that might change across minor releases. | |
| #define LOCK_BACKEND_PYMUTEX | |
| #ifndef Py_BUILD_CORE | |
| #define Py_BUILD_CORE | |
| #endif | |
| #include "internal/pycore_lock.h" | |
| #elif defined(_WIN32) | |
| // Python <3.14 on Windows: Use Slim Reader/Writer Lock | |
| #define LOCK_BACKEND_SRWLOCK | |
| #include <windows.h> | |
| #else | |
| // Python <3.14 on POSIX. | |
| // | |
| // Prefer a POSIX unnamed semaphore when the platform supports it well and the | |
| // GIL is enabled, and fall back to a pthread mutex + condition variable | |
| // otherwise. We use the same test CPython uses to pick its semaphore-based lock | |
| // (see Python/thread_pthread.h): an unnamed semaphore is only usable when | |
| // sem_init() actually works AND a timed wait is available. Notably this is true | |
| // on Linux but false on macOS (whose sem_init() is a non-functional stub), so | |
| // macOS uses the mutex+condvar fallback. Free-threaded builds also use the | |
| // mutex+condvar fallback because the semaphore backend's `locked` bookkeeping | |
| // relies on GIL serialization. | |
| #if !defined(Py_GIL_DISABLED) && \ | |
| defined(_POSIX_SEMAPHORES) && (_POSIX_SEMAPHORES + 0) != -1 && \ | |
| (defined(HAVE_SEM_TIMEDWAIT) || defined(HAVE_SEM_CLOCKWAIT)) | |
| #define LOCK_BACKEND_SEM | |
| #include <semaphore.h> | |
| #else | |
| #define LOCK_BACKEND_PTHREAD | |
| #include <pthread.h> | |
| #endif | |
| #endif | |
| // | |
| // Lock | |
| // | |
| // A fast mutex lock for use from mypyc-compiled code. | |
| // | |
| // On Python 3.14+ (all platforms), this uses CPython's PyMutex, a 1-byte atomic | |
| // lock backed by a parking lot for contended waits. PyMutex automatically | |
| // releases the GIL when blocking. | |
| // | |
| // On Python 3.13 and earlier with Windows, this uses an SRWLOCK (Slim | |
| // Reader/Writer Lock) plus a CONDITION_VARIABLE guarding a `locked` flag. The SRWLOCK only | |
| // protects the flag and is never held across the user's critical section, so | |
| // release() may be called from a thread other than the acquirer (matching | |
| // threading.Lock semantics). This mirrors CPython's Windows lock (NRMUTEX in | |
| // Python/thread_nt.h) and is the Windows twin of the POSIX pthread+condvar | |
| // backend below. | |
| // | |
| // On Python 3.13 and earlier with POSIX systems, there are two backends, both | |
| // of which allow release() from a thread other than the one that acquired the | |
| // lock (matching threading.Lock semantics): | |
| // | |
| // - Where unnamed POSIX semaphores work well (e.g. Linux) and the GIL is | |
| // enabled, this uses a sem_t initialized to 1: acquire is sem_wait, release | |
| // is sem_post. Semaphores have no ownership concept, so cross-thread release | |
| // is directly well-defined. | |
| // | |
| // - Otherwise (e.g. macOS, whose sem_init() is a non-functional stub, and | |
| // free-threaded builds), this uses a pthread mutex + condition variable | |
| // guarding a `locked` flag. The mutex only protects the flag and is never | |
| // held across the user's critical section, so the OS mutex is always | |
| // unlocked on the same thread that locked it. | |
| // | |
| // ---------- Platform-specific lock state ---------- | |
| #if defined(LOCK_BACKEND_PYMUTEX) | |
| typedef struct { | |
| PyObject_HEAD | |
| PyMutex mutex; | |
| } LockObject; | |
| #elif defined(LOCK_BACKEND_SRWLOCK) | |
| typedef struct { | |
| PyObject_HEAD | |
| // The SRWLOCK below does NOT represent the Python lock; like the pthread | |
| // fallback's `mut`, it only guards `locked` and the condition variable, | |
| // and is held just long enough to inspect/flip the flag -- never across | |
| // the user's critical section. That is what allows release() from a | |
| // thread other than the acquirer: SRWLOCK's same-thread-release rule is | |
| // never violated, and clearing `locked` is just a guarded store. This | |
| // mirrors CPython's Windows lock (NRMUTEX in Python/thread_nt.h). | |
| SRWLOCK srw; | |
| CONDITION_VARIABLE lock_released; | |
| int locked; // 0=unlocked, 1=locked; protected by `srw` | |
| } LockObject; | |
| #elif defined(LOCK_BACKEND_SEM) | |
| typedef struct { | |
| PyObject_HEAD | |
| sem_t sem; // counting semaphore, initialized to 1 | |
| // Tracks the locked state for locked() and release-unlocked detection. | |
| // The semaphore itself is the source of truth for mutual exclusion; this | |
| // flag is advisory bookkeeping. It is set after a successful acquire and | |
| // cleared before sem_post in release. | |
| // | |
| // This backend is only selected when the GIL is enabled, so this flag is a | |
| // plain int relying on the GIL for serialization: it is only ever touched | |
| // with the GIL held (the blocking sem_wait drops the GIL, but the flag | |
| // store happens after the GIL is reacquired). This mirrors the old | |
| // PyThread_type_lock wrapper bookkeeping used by CPython 3.12 and earlier, | |
| // where _thread lock kept a plain `char locked` for sanity checks and | |
| // locked(). | |
| int locked; | |
| } LockObject; | |
| #else // pthread mutex + condvar fallback | |
| typedef struct { | |
| PyObject_HEAD | |
| // The pthread mutex below does NOT represent the Python lock; it only | |
| // guards the `locked` flag and the condition variable. The Python lock | |
| // state is `locked` itself. This indirection (matching CPython's POSIX | |
| // lock) is what allows release() from a thread other than the acquirer: | |
| // `mut` is always locked and unlocked within a single call on a single | |
| // thread, so pthread's same-thread-unlock rule is never violated. | |
| pthread_mutex_t mut; | |
| pthread_cond_t lock_released; | |
| // Always accessed while holding `mut` (including the locked() reader), | |
| // so a plain int is sufficient -- the mutex provides the ordering. | |
| // Matches CPython's pthread_lock.locked (a plain char). | |
| int locked; // 0=unlocked, 1=locked; protected by `mut` | |
| } LockObject; | |
| #endif | |
| // ---------- Platform-specific init/acquire/release ---------- | |
| static inline int | |
| Lock_init_internal(LockObject *self) | |
| { | |
| #if defined(LOCK_BACKEND_PYMUTEX) | |
| self->mutex = (PyMutex){0}; | |
| #elif defined(LOCK_BACKEND_SRWLOCK) | |
| InitializeSRWLock(&self->srw); | |
| InitializeConditionVariable(&self->lock_released); | |
| self->locked = 0; | |
| #elif defined(LOCK_BACKEND_SEM) | |
| if (sem_init(&self->sem, 0, 1) != 0) { | |
| PyErr_SetFromErrno(PyExc_OSError); | |
| return -1; | |
| } | |
| self->locked = 0; | |
| #else | |
| int status = pthread_mutex_init(&self->mut, NULL); | |
| if (status != 0) { | |
| errno = status; | |
| PyErr_SetFromErrno(PyExc_OSError); | |
| return -1; | |
| } | |
| status = pthread_cond_init(&self->lock_released, NULL); | |
| if (status != 0) { | |
| pthread_mutex_destroy(&self->mut); | |
| errno = status; | |
| PyErr_SetFromErrno(PyExc_OSError); | |
| return -1; | |
| } | |
| self->locked = 0; | |
| #endif | |
| return 0; | |
| } | |
| // Try to acquire the lock. Returns 1 (true) on success, 0 (false) if | |
| // non-blocking and the lock is held, or -1 if interrupted by an error-raising | |
| // signal handler. | |
| static int | |
| Lock_acquire_impl(LockObject *self, int blocking) | |
| { | |
| #if defined(LOCK_BACKEND_PYMUTEX) | |
| if (!blocking) { | |
| PyLockStatus r = _PyMutex_LockTimed(&self->mutex, 0, _Py_LOCK_DONT_DETACH); | |
| return r == PY_LOCK_ACQUIRED; | |
| } | |
| if (PyMutex_LockFast(&self->mutex)) { | |
| return 1; | |
| } | |
| PyLockStatus r = _PyMutex_LockTimed(&self->mutex, -1, | |
| _PY_LOCK_DETACH | _PY_LOCK_HANDLE_SIGNALS); | |
| if (r == PY_LOCK_INTR) { | |
| return -1; | |
| } | |
| return 1; | |
| #elif defined(LOCK_BACKEND_SRWLOCK) | |
| // `srw` only guards `locked` and the condition variable; it is held just | |
| // long enough to inspect/flip the flag, never across the user's critical | |
| // section. This is what lets a different thread call release(). | |
| // | |
| // Fast path: grab the lock without releasing the GIL if it is free. This is | |
| // also the whole story in the non-blocking case. | |
| AcquireSRWLockExclusive(&self->srw); | |
| if (!self->locked) { | |
| self->locked = 1; | |
| ReleaseSRWLockExclusive(&self->srw); | |
| return 1; | |
| } | |
| ReleaseSRWLockExclusive(&self->srw); | |
| if (!blocking) { | |
| return 0; | |
| } | |
| // Slow path: wait for the lock to be released, with the GIL dropped so | |
| // other Python threads can run (and release the lock). | |
| // SleepConditionVariableSRW atomically releases the SRWLOCK while sleeping | |
| // and reacquires it on wake, exactly like pthread_cond_wait. | |
| Py_BEGIN_ALLOW_THREADS | |
| AcquireSRWLockExclusive(&self->srw); | |
| while (self->locked) { | |
| SleepConditionVariableSRW(&self->lock_released, &self->srw, INFINITE, 0); | |
| } | |
| self->locked = 1; | |
| ReleaseSRWLockExclusive(&self->srw); | |
| Py_END_ALLOW_THREADS | |
| return 1; | |
| #elif defined(LOCK_BACKEND_SEM) | |
| // A semaphore has no ownership: any thread may sem_post a token that | |
| // another thread consumed via sem_wait, so cross-thread release is | |
| // directly well-defined. `locked` is advisory bookkeeping for locked() | |
| // and for guarding against releasing an unheld lock. | |
| // | |
| // Fast path: try a non-blocking acquire first to avoid GIL release/reacquire | |
| // overhead in the common uncontended case. This is also the whole story in | |
| // the non-blocking case. | |
| { | |
| int status; | |
| do { | |
| status = sem_trywait(&self->sem); | |
| } while (status == -1 && errno == EINTR); | |
| if (status == 0) { | |
| self->locked = 1; | |
| return 1; | |
| } | |
| } | |
| if (!blocking) { | |
| return 0; // EAGAIN: already held | |
| } | |
| // Slow path: block with the GIL dropped so other Python threads can run | |
| // (and release the lock). If a signal interrupts sem_wait(), run pending | |
| // Python signal handlers with the GIL held; retry unless a handler raises. | |
| for (;;) { | |
| int status; | |
| int err = 0; | |
| Py_BEGIN_ALLOW_THREADS | |
| status = sem_wait(&self->sem); | |
| if (status == -1) { | |
| err = errno; | |
| } | |
| Py_END_ALLOW_THREADS | |
| if (status == 0) { | |
| self->locked = 1; | |
| return 1; | |
| } | |
| if (err != EINTR) { | |
| PyErr_SetFromErrno(PyExc_OSError); | |
| return -1; | |
| } | |
| if (Py_MakePendingCalls() < 0) { | |
| return -1; | |
| } | |
| } | |
| #else // pthread mutex + condvar fallback | |
| // `mut` only guards `locked` and the condition variable; it is held just | |
| // long enough to inspect/flip the flag, never across the user's critical | |
| // section. This is what lets a different thread call release(). | |
| // | |
| // Fast path: grab the lock without releasing the GIL if it is free. This is | |
| // also the whole story in the non-blocking case. | |
| pthread_mutex_lock(&self->mut); | |
| if (!self->locked) { | |
| self->locked = 1; | |
| pthread_mutex_unlock(&self->mut); | |
| return 1; | |
| } | |
| pthread_mutex_unlock(&self->mut); | |
| if (!blocking) { | |
| return 0; | |
| } | |
| // Slow path: wait for the lock to be released, with the GIL dropped so | |
| // other Python threads can run (and release the lock). If we wake but do | |
| // not get the lock, give pending Python signal handlers a chance to run, | |
| // matching CPython's pthread fallback. | |
| for (;;) { | |
| int acquired = 0; | |
| int interrupted = 0; | |
| int status; | |
| Py_BEGIN_ALLOW_THREADS | |
| status = pthread_mutex_lock(&self->mut); | |
| if (status == 0) { | |
| while (self->locked) { | |
| status = pthread_cond_wait(&self->lock_released, &self->mut); | |
| if (status != 0) { | |
| break; | |
| } | |
| if (self->locked) { | |
| interrupted = 1; | |
| break; | |
| } | |
| } | |
| if (status == 0 && !interrupted) { | |
| self->locked = 1; | |
| acquired = 1; | |
| } | |
| int unlock_status = pthread_mutex_unlock(&self->mut); | |
| if (status == 0) { | |
| status = unlock_status; | |
| } | |
| } | |
| Py_END_ALLOW_THREADS | |
| if (status != 0) { | |
| errno = status; | |
| PyErr_SetFromErrno(PyExc_OSError); | |
| return -1; | |
| } | |
| if (acquired) { | |
| return 1; | |
| } | |
| if (interrupted && Py_MakePendingCalls() < 0) { | |
| return -1; | |
| } | |
| } | |
| #endif | |
| } | |
| // Release the lock. Returns 0 on success, -1 if the lock was not held. | |
| static int | |
| Lock_release_impl(LockObject *self) | |
| { | |
| #if defined(LOCK_BACKEND_PYMUTEX) | |
| // threading.Lock is unowned, so release() may be called from a different | |
| // thread than acquire(). CPython's atomic _PyMutex_TryUnlock() is not part | |
| // of the public API and is not exported by all CPython builds. This fast | |
| // partial-replacement path deliberately avoids a second guard mutex: | |
| // ordinary release() of an unlocked lock raises RuntimeError, but racy | |
| // erroneous release() calls are undefined behavior and may make | |
| // PyMutex_Unlock() abort. | |
| if (!PyMutex_IsLocked(&self->mutex)) { | |
| return -1; | |
| } | |
| PyMutex_Unlock(&self->mutex); | |
| return 0; | |
| #elif defined(LOCK_BACKEND_SRWLOCK) | |
| AcquireSRWLockExclusive(&self->srw); | |
| if (!self->locked) { | |
| ReleaseSRWLockExclusive(&self->srw); | |
| return -1; | |
| } | |
| self->locked = 0; | |
| // Wake one waiter (if any). Signalling under `srw` is fine and avoids a | |
| // lost-wakeup race. | |
| WakeConditionVariable(&self->lock_released); | |
| ReleaseSRWLockExclusive(&self->srw); | |
| return 0; | |
| #elif defined(LOCK_BACKEND_SEM) | |
| // Check-then-clear the flag, then post. This backend is only selected when | |
| // the GIL is enabled, so the check and clear are serialized without an | |
| // atomic. | |
| if (!self->locked) { | |
| return -1; | |
| } | |
| self->locked = 0; | |
| sem_post(&self->sem); | |
| return 0; | |
| #else // pthread mutex + condvar fallback | |
| pthread_mutex_lock(&self->mut); | |
| if (!self->locked) { | |
| pthread_mutex_unlock(&self->mut); | |
| return -1; | |
| } | |
| self->locked = 0; | |
| // Wake one waiter (if any). Signalling under `mut` is fine and avoids a | |
| // lost-wakeup race. | |
| pthread_cond_signal(&self->lock_released); | |
| pthread_mutex_unlock(&self->mut); | |
| return 0; | |
| #endif | |
| } | |
| static inline int | |
| Lock_is_locked(LockObject *self) | |
| { | |
| #if defined(LOCK_BACKEND_PYMUTEX) | |
| return PyMutex_IsLocked(&self->mutex); | |
| #elif defined(LOCK_BACKEND_SRWLOCK) | |
| // locked() is not expected to be on a perf-critical path, so take the | |
| // SRWLOCK (shared) for a clean read of the guarded flag rather than | |
| // relying on an atomic/volatile field. | |
| AcquireSRWLockShared(&self->srw); | |
| int result = self->locked; | |
| ReleaseSRWLockShared(&self->srw); | |
| return result; | |
| #elif defined(LOCK_BACKEND_SEM) | |
| // The flag is GIL-serialized (see the struct comment); locked() is a | |
| // plain read, matching the old CPython _thread lock bookkeeping described | |
| // above. | |
| return self->locked != 0; | |
| #else // pthread mutex + condvar fallback | |
| // `locked` is only ever accessed under `mut`; take it for a clean read. | |
| // locked() is not expected to be on a perf-critical path. | |
| pthread_mutex_lock(&self->mut); | |
| int result = self->locked; | |
| pthread_mutex_unlock(&self->mut); | |
| return result; | |
| #endif | |
| } | |
| // ---------- Python type methods (shared across platforms) ---------- | |
| static PyTypeObject LockType; | |
| static PyObject * | |
| Lock_new(PyTypeObject *type, PyObject *args, PyObject *kwds) | |
| { | |
| if (type != &LockType) { | |
| PyErr_SetString(PyExc_TypeError, "Lock cannot be subclassed"); | |
| return NULL; | |
| } | |
| LockObject *self = (LockObject *)type->tp_alloc(type, 0); | |
| if (self != NULL && Lock_init_internal(self) < 0) { | |
| type->tp_free((PyObject *)self); | |
| return NULL; | |
| } | |
| return (PyObject *)self; | |
| } | |
| static int | |
| Lock_init(LockObject *self, PyObject *args, PyObject *kwds) | |
| { | |
| if (!PyArg_ParseTuple(args, "")) { | |
| return -1; | |
| } | |
| if (kwds != NULL && PyDict_Size(kwds) > 0) { | |
| PyErr_SetString(PyExc_TypeError, | |
| "Lock() takes no keyword arguments"); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static void | |
| Lock_dealloc(LockObject *self) | |
| { | |
| #if defined(LOCK_BACKEND_SEM) | |
| sem_destroy(&self->sem); | |
| #elif defined(LOCK_BACKEND_PTHREAD) | |
| // `mut` is only ever held transiently within a single call, so it is | |
| // always unlocked here even if the Python lock is still "locked". | |
| // Some pthread implementations require the cond to be destroyed first. | |
| pthread_cond_destroy(&self->lock_released); | |
| pthread_mutex_destroy(&self->mut); | |
| #endif | |
| Py_TYPE(self)->tp_free((PyObject *)self); | |
| } | |
| static PyObject * | |
| Lock_acquire(LockObject *self, PyObject *const *args, Py_ssize_t nargs, | |
| PyObject *kwnames) | |
| { | |
| int blocking = 1; | |
| Py_ssize_t nkw = kwnames ? PyTuple_GET_SIZE(kwnames) : 0; | |
| if (nargs + nkw > 1) { | |
| PyErr_SetString(PyExc_TypeError, "acquire() takes at most 1 argument"); | |
| return NULL; | |
| } | |
| if (nargs == 1) { | |
| blocking = PyObject_IsTrue(args[0]); | |
| if (blocking < 0) | |
| return NULL; | |
| } else if (nkw == 1) { | |
| PyObject *key = PyTuple_GET_ITEM(kwnames, 0); | |
| if (PyUnicode_CompareWithASCIIString(key, "blocking") != 0) { | |
| PyErr_Format(PyExc_TypeError, | |
| "acquire() got an unexpected keyword argument '%U'", | |
| key); | |
| return NULL; | |
| } | |
| blocking = PyObject_IsTrue(args[0]); | |
| if (blocking < 0) | |
| return NULL; | |
| } | |
| int result = Lock_acquire_impl(self, blocking); | |
| if (result < 0) { | |
| return NULL; | |
| } | |
| return PyBool_FromLong(result); | |
| } | |
| static PyObject * | |
| Lock_release(LockObject *self, PyObject *Py_UNUSED(ignored)) | |
| { | |
| if (Lock_release_impl(self) < 0) { | |
| PyErr_SetString(PyExc_RuntimeError, "cannot release an unlocked lock"); | |
| return NULL; | |
| } | |
| Py_RETURN_NONE; | |
| } | |
| static PyObject * | |
| Lock_locked(LockObject *self, PyObject *Py_UNUSED(ignored)) | |
| { | |
| return PyBool_FromLong(Lock_is_locked(self)); | |
| } | |
| static PyObject * | |
| Lock_enter(LockObject *self, PyObject *Py_UNUSED(ignored)) | |
| { | |
| int result = Lock_acquire_impl(self, 1); | |
| if (result < 0) | |
| return NULL; | |
| return PyBool_FromLong(result); | |
| } | |
| static PyObject * | |
| Lock_exit(LockObject *self, PyObject *const *args, Py_ssize_t nargs) | |
| { | |
| return Lock_release(self, NULL); | |
| } | |
| static PyMethodDef Lock_methods[] = { | |
| {"acquire", (PyCFunction)(void(*)(void))Lock_acquire, METH_FASTCALL | METH_KEYWORDS, | |
| PyDoc_STR("Acquire the lock, blocking or non-blocking.\n" | |
| "Returns True if the lock was acquired, False otherwise.")}, | |
| {"release", (PyCFunction)Lock_release, METH_NOARGS, | |
| PyDoc_STR("Release the lock.")}, | |
| {"locked", (PyCFunction)Lock_locked, METH_NOARGS, | |
| PyDoc_STR("Return True if the lock is currently held.")}, | |
| {"__enter__", (PyCFunction)Lock_enter, METH_NOARGS, | |
| PyDoc_STR("Acquire the lock.")}, | |
| {"__exit__", (PyCFunction)Lock_exit, METH_FASTCALL, | |
| PyDoc_STR("Release the lock.")}, | |
| {NULL} | |
| }; | |
| static PyTypeObject LockType = { | |
| .ob_base = PyVarObject_HEAD_INIT(NULL, 0) | |
| .tp_name = "Lock", | |
| .tp_doc = PyDoc_STR("A fast mutual exclusion lock"), | |
| .tp_basicsize = sizeof(LockObject), | |
| .tp_itemsize = 0, | |
| .tp_flags = Py_TPFLAGS_DEFAULT, | |
| .tp_new = Lock_new, | |
| .tp_init = (initproc)Lock_init, | |
| .tp_dealloc = (destructor)Lock_dealloc, | |
| .tp_methods = Lock_methods, | |
| }; | |
| static PyTypeObject * | |
| Lock_type_internal(void) { | |
| return &LockType; | |
| } | |
| // Create a new Lock object (for use from compiled code) | |
| static PyObject * | |
| Lock_new_internal(void) { | |
| LockObject *self = (LockObject *)LockType.tp_alloc(&LockType, 0); | |
| if (self != NULL && Lock_init_internal(self) < 0) { | |
| LockType.tp_free((PyObject *)self); | |
| return NULL; | |
| } | |
| return (PyObject *)self; | |
| } | |
| // Acquire the lock (blocking), for use from compiled code. | |
| // Returns true on success, sets error and returns 2 (ERR_MAGIC) on failure. | |
| static char | |
| Lock_acquire_internal(PyObject *self) { | |
| int result = Lock_acquire_impl((LockObject *)self, 1); | |
| if (result < 0) { | |
| return 2; | |
| } | |
| return (char)result; | |
| } | |
| // Acquire the lock with explicit blocking arg, for use from compiled code. | |
| // Returns true if acquired, false otherwise. Sets error and returns 2 | |
| // (ERR_MAGIC) on failure. | |
| static char | |
| Lock_acquire_blocking_internal(PyObject *self, char blocking) { | |
| int result = Lock_acquire_impl((LockObject *)self, blocking); | |
| if (result < 0) { | |
| return 2; | |
| } | |
| return (char)result; | |
| } | |
| // Release the lock, for use from compiled code. | |
| // Returns 0 (None) on success, sets error and returns 2 (ERR_MAGIC) on failure. | |
| static char | |
| Lock_release_internal(PyObject *self) { | |
| if (Lock_release_impl((LockObject *)self) < 0) { | |
| PyErr_SetString(PyExc_RuntimeError, "cannot release an unlocked lock"); | |
| return 2; | |
| } | |
| return 0; | |
| } | |
| // Check if the lock is held, for use from compiled code. | |
| static char | |
| Lock_locked_internal(PyObject *self) { | |
| return (char)Lock_is_locked((LockObject *)self); | |
| } | |
| static PyMethodDef librt_threading_module_methods[] = { | |
| {NULL, NULL, 0, NULL} | |
| }; | |
| static int | |
| threading_abi_version(void) { | |
| return LIBRT_THREADING_ABI_VERSION; | |
| } | |
| static int | |
| threading_api_version(void) { | |
| return LIBRT_THREADING_API_VERSION; | |
| } | |
| static int | |
| librt_threading_module_exec(PyObject *m) | |
| { | |
| if (PyType_Ready(&LockType) < 0) { | |
| return -1; | |
| } | |
| if (PyModule_AddObjectRef(m, "Lock", (PyObject *)&LockType) < 0) { | |
| return -1; | |
| } | |
| // Export mypyc internal C API via capsule | |
| static void *threading_api[LIBRT_THREADING_API_LEN] = { | |
| (void *)threading_abi_version, | |
| (void *)threading_api_version, | |
| (void *)Lock_type_internal, | |
| (void *)Lock_new_internal, | |
| (void *)Lock_acquire_internal, | |
| (void *)Lock_release_internal, | |
| (void *)Lock_locked_internal, | |
| (void *)Lock_acquire_blocking_internal, | |
| }; | |
| PyObject *c_api_object = PyCapsule_New((void *)threading_api, "librt.threading._C_API", NULL); | |
| if (PyModule_Add(m, "_C_API", c_api_object) < 0) { | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static PyModuleDef_Slot librt_threading_module_slots[] = { | |
| {Py_mod_exec, librt_threading_module_exec}, | |
| #ifdef Py_MOD_GIL_NOT_USED | |
| {Py_mod_gil, Py_MOD_GIL_NOT_USED}, | |
| #endif | |
| {0, NULL} | |
| }; | |
| static PyModuleDef librt_threading_module = { | |
| .m_base = PyModuleDef_HEAD_INIT, | |
| .m_name = "threading", | |
| .m_doc = "Fast threading primitives optimized for mypyc", | |
| .m_size = 0, | |
| .m_methods = librt_threading_module_methods, | |
| .m_slots = librt_threading_module_slots, | |
| }; | |
| PyMODINIT_FUNC | |
| PyInit_threading(void) | |
| { | |
| return PyModuleDef_Init(&librt_threading_module); | |
| } |