From e3d8fe4854a7ef7ff0637688cda9e65feb680122 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Thu, 15 Oct 2026 12:37:10 +0300 Subject: Replace the writer spin lock with a blocking mutex term_write_begin spun on an atomic sequence word. A waiter burned CPU, and with no priority inheritance a SCHED_FIFO waiter could starve a preempted holder on the same CPU. The writer section is now a pthread mutex with PTHREAD_PRIO_INHERIT where the platform has it, and an SRWLOCK on Windows. Re-entry on the owning thread still only counts the depth. The platform wrapper (init, lock, unlock, destroy) is in compat.h. term_new initializes the mutex, and term_free destroys it. Meson links the thread library into the core, term_dep, and term_internal_dep. The README, term.h, and the amalgamation note say that POSIX needs -pthread. Tests: the feed and snapshot thread test now checks the final screen and history against a serial reference. A new test checks that term_lock nests and that another thread waits for the last term_unlock. Co-Authored-By: Claude Haiku 5.5 --- src/compat.h | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) (limited to 'src/compat.h') diff --git a/src/compat.h b/src/compat.h index cb62679..6c3302f 100644 --- a/src/compat.h +++ b/src/compat.h @@ -14,6 +14,19 @@ #include #include +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#include +#endif + #if defined(__GNUC__) || defined(__clang__) #define TERM_COMPILER_GNU 1 #elif defined(_MSC_VER) @@ -184,6 +197,68 @@ static inline void term_cpu_relax(void) #endif } +/* The writer lock. POSIX uses a mutex with priority inheritance where the + * platform has it, so a waiter lifts the thread that holds the lock. Windows + * uses an SRWLOCK. Neither nests, so term_write_begin() counts re-entry. */ +#if defined(_WIN32) +typedef SRWLOCK term_mutex; +#else +typedef pthread_mutex_t term_mutex; +/* Strict C11 can hide PTHREAD_PRIO_INHERIT in pthread.h, as zig's aarch64 + * glibc headers do. Every POSIX system that has the option uses 1 for it. */ +#if !defined(PTHREAD_PRIO_INHERIT) +#define PTHREAD_PRIO_INHERIT 1 +#endif +#endif + +/* Returns false when the lock cannot be made. */ +static inline bool term_mutex_init(term_mutex *m) +{ +#if defined(_WIN32) + InitializeSRWLock(m); + return true; +#elif defined(_POSIX_THREAD_PRIO_INHERIT) && _POSIX_THREAD_PRIO_INHERIT > 0 + pthread_mutexattr_t attr; + if (pthread_mutexattr_init(&attr) != 0) + return false; + bool ok = pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT) == 0 && + pthread_mutex_init(m, &attr) == 0; + pthread_mutexattr_destroy(&attr); + return ok; +#else + return pthread_mutex_init(m, NULL) == 0; +#endif +} + +/* A normal mutex fails only when it is broken, so the result is ignored. */ +static inline void term_mutex_lock(term_mutex *m) +{ +#if defined(_WIN32) + AcquireSRWLockExclusive(m); +#else + (void)pthread_mutex_lock(m); +#endif +} + +static inline void term_mutex_unlock(term_mutex *m) +{ +#if defined(_WIN32) + ReleaseSRWLockExclusive(m); +#else + (void)pthread_mutex_unlock(m); +#endif +} + +/* An SRWLOCK has no destroy. */ +static inline void term_mutex_destroy(term_mutex *m) +{ +#if defined(_WIN32) + (void)m; +#else + (void)pthread_mutex_destroy(m); +#endif +} + /* strdup is POSIX, so the core copies strings itself. */ static inline char *term_strdup(const char *s) { -- cgit v1.2.3