libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
os_thread.h
1/*-
2 * Copyright (c) 2026, The XTC Project
3 * Use of this source code is governed by the ISC License.
4 *
5 * src/inc/os_thread.h
6 * Thread, TLS, mutex, rwlock, cond, sem abstractions.
7 * The pthreads implementation lives in src/os/os_*.c; a Win32
8 * implementation lands later behind the same surface.
9 * See M1_CLAIMS.md, T1-T7, L1-L5, Mu1-Mu6.
10 */
11
12#ifndef XTC_OS_THREAD_H
13#define XTC_OS_THREAD_H
14
15#include "xtc_export.h"
16
17#include <stddef.h>
18#include <stdint.h>
19
20/*
21 * Thread-local storage-class specifier. GCC and clang spell it
22 * __thread; MSVC spells it __declspec(thread); C11 has _Thread_local.
23 * XTC_THREAD_LOCAL is the portable spelling used throughout the
24 * source. (Defined here because both xtc_int.h and loop_int.h
25 * include this header.)
26 */
27#if defined(_MSC_VER)
28# define XTC_THREAD_LOCAL __declspec(thread)
29#elif defined(__GNUC__) || defined(__clang__)
30# define XTC_THREAD_LOCAL __thread
31#else
32# define XTC_THREAD_LOCAL _Thread_local
33#endif
34
35/*
36 * Opaque handles. The full struct lives in the implementation file;
37 * callers see only an opaque pointer plus a sentinel-zero state.
38 *
39 * We reserve a small in-line "state" struct rather than allocating
40 * via __os_malloc -- initialisation must work before the allocator
41 * is initialised on some platforms.
42 */
43struct __os_thread { void *opaque; };
44/*
45 * Opaque storage with explicit alignment. pthread_mutex_t / cond_t /
46 * rwlock_t / sem_t carry stricter alignment requirements than `char`
47 * on some platforms (notably illumos / Solaris where uninitialized or
48 * misaligned storage causes pthread_mutex_init to return EINVAL).
49 * `_Alignas(long long)` gives us 8-byte alignment which covers every
50 * known pthread implementation. The rwlock buffer is 256 bytes:
51 * macOS's pthread_rwlock_t is ~200 (vs ~56 on glibc), so 128 was too
52 * small there.
53 */
54struct __os_mutex { _Alignas(long long) unsigned char storage[64]; };
55struct __os_rwlock { _Alignas(long long) unsigned char storage[256]; };
56struct __os_cond { _Alignas(long long) unsigned char storage[64]; };
57struct __os_sem { _Alignas(long long) unsigned char storage[64]; };
58
59/*
60 * Static initializers for file-scope-static locks (no _init() call).
61 * POSIX has PTHREAD_MUTEX_INITIALIZER / PTHREAD_RWLOCK_INITIALIZER; on
62 * the coming Win32 backing the mutex is an SRWLock (which HAS a static
63 * initializer, SRWLOCK_INIT -- unlike CRITICAL_SECTION), so this shape
64 * stays portable. Storage is zero-padded after the pthread type; both
65 * POSIX initializers are all-zero on the platforms libxtc targets, so
66 * a braced-zero initializer is correct and also matches SRWLOCK_INIT
67 * (a zeroed pointer). A run-time __os_mutex_init remains available and
68 * is required where a mutex is heap-allocated or needs attributes.
69 */
70#define XTC_OS_MUTEX_INIT { { 0 } }
71#define XTC_OS_RWLOCK_INIT { { 0 } }
72
73typedef struct __os_thread __os_thread_t;
74typedef struct __os_mutex __os_mutex_t;
75typedef struct __os_rwlock __os_rwlock_t;
76typedef struct __os_cond __os_cond_t;
77typedef struct __os_sem __os_sem_t;
78typedef unsigned long __os_tls_key_t;
79
80typedef void *(*__os_thread_fn)(void *);
81typedef void (*__os_tls_dtor)(void *);
82
83/*
84 * One-time initialization. __os_once_t is a flag with a static
85 * initializer (XTC_OS_ONCE_INIT); __os_call_once runs fn exactly once
86 * across all threads racing on the same flag. POSIX maps to
87 * pthread_once; Windows to InitOnceExecuteOnce. Replaces hand-rolled
88 * set-once guards (sig_atomic_t + double-checked stores) with one
89 * portable, race-free primitive.
90 */
91#if defined(_WIN32)
92/* INIT_ONCE is a single pointer-sized slot; keep the header pthread-free. */
93typedef void *__os_once_t;
94#define XTC_OS_ONCE_INIT NULL
95#else
96#include <pthread.h>
97typedef pthread_once_t __os_once_t;
98#define XTC_OS_ONCE_INIT PTHREAD_ONCE_INIT
99#endif
100
101/*
102 * --- Threads ---
103 *
104 * PUBLIC: int __os_thread_create __P((__os_thread_t *, __os_thread_fn, void *));
105 * PUBLIC: int __os_thread_join __P((__os_thread_t *, void **));
106 * PUBLIC: int __os_thread_detach __P((__os_thread_t *));
107 * PUBLIC: int __os_thread_self __P((__os_thread_t *));
108 * PUBLIC: void __os_thread_yield __P((void));
109 * PUBLIC: int __os_thread_setname __P((const char *));
110 * PUBLIC: void __os_thread_apply_default_qos __P((void));
111 * PUBLIC: int __os_thread_set_affinity __P((int));
112 */
113XTC_API int __os_thread_create(__os_thread_t *thr, __os_thread_fn fn, void *arg);
114
115/* Create a raw pthread with all signals blocked (mask restored after),
116 * for the few call sites that hold a raw pthread_t rather than an
117 * __os_thread_t (the PSI slab thread, the deadlock detector). Keeps
118 * every runtime thread from inheriting a permissive signal mask.
119 * Declared with a pthread_t; callers already include <pthread.h>. */
120#include <pthread.h>
121int __os_pthread_create_masked(pthread_t *out, void *(*fn)(void *),
122 void *arg);
123XTC_API int __os_thread_join(__os_thread_t *thr, void **retval);
124XTC_API int __os_thread_detach(__os_thread_t *thr);
125XTC_API int __os_thread_self(__os_thread_t *out);
126XTC_API void __os_thread_yield(void);
127XTC_API int __os_thread_setname(const char *name);
128XTC_API void __os_thread_apply_default_qos(void);
129XTC_API int __os_thread_set_affinity(int cpu);
130
131/*
132 * --- TLS ---
133 *
134 * PUBLIC: int __os_tls_create __P((__os_tls_key_t *, __os_tls_dtor));
135 * PUBLIC: int __os_tls_destroy __P((__os_tls_key_t));
136 * PUBLIC: int __os_tls_set __P((__os_tls_key_t, void *));
137 * PUBLIC: void *__os_tls_get __P((__os_tls_key_t));
138 */
139XTC_API int __os_tls_create(__os_tls_key_t *key, __os_tls_dtor dtor);
140XTC_API int __os_tls_destroy(__os_tls_key_t key);
141XTC_API int __os_tls_set(__os_tls_key_t key, void *value);
142XTC_API void *__os_tls_get(__os_tls_key_t key);
143
144/*
145 * Register fn(arg) to run when the CALLING thread exits -- including
146 * threads libxtc did NOT create (an embedding host's threads, or
147 * PostgreSQL's pg_threads.h carrier threads). This is the narrow
148 * "clean up my thread_local resources on the way out" contract, built
149 * on the same pthread_key_t / FlsAlloc destructor mechanism as TLS but
150 * without exposing a full key. Multiple registrations on one thread
151 * are all run, in reverse order of registration (last-registered
152 * first), the way C atexit composes. Returns XTC_OK, or XTC_E_NOMEM
153 * if the per-thread record could not be allocated.
154 *
155 * PUBLIC: int __os_thread_atexit __P((void (*)(void *), void *));
156 */
157XTC_API int __os_thread_atexit(void (*fn)(void *), void *arg);
158
159/*
160 * --- One-time init ---
161 *
162 * PUBLIC: int __os_call_once __P((__os_once_t *, void (*)(void)));
163 */
164XTC_API int __os_call_once(__os_once_t *once, void (*fn)(void));
165
166/*
167 * --- Mutex ---
168 *
169 * PUBLIC: int __os_mutex_init __P((__os_mutex_t *));
170 * PUBLIC: int __os_mutex_destroy __P((__os_mutex_t *));
171 * PUBLIC: int __os_mutex_lock __P((__os_mutex_t *));
172 * PUBLIC: int __os_mutex_trylock __P((__os_mutex_t *));
173 * PUBLIC: int __os_mutex_unlock __P((__os_mutex_t *));
174 */
175XTC_API int __os_mutex_init(__os_mutex_t *m);
176XTC_API int __os_mutex_destroy(__os_mutex_t *m);
177XTC_API int __os_mutex_lock(__os_mutex_t *m);
178XTC_API int __os_mutex_trylock(__os_mutex_t *m);
179XTC_API int __os_mutex_unlock(__os_mutex_t *m);
180
181/*
182 * --- RWLock ---
183 *
184 * unlock is SPLIT into read/write forms: POSIX pthread_rwlock_unlock is
185 * mode-agnostic, but Windows SRWLock has NO mode-agnostic release --
186 * ReleaseSRWLockShared vs ReleaseSRWLockExclusive are distinct calls
187 * chosen by how the lock was taken. Callers already know which lock
188 * they hold, so the split keeps the surface portable to the coming
189 * Win32 backing without per-lock held-mode tracking.
190 *
191 * PUBLIC: int __os_rwlock_init __P((__os_rwlock_t *));
192 * PUBLIC: int __os_rwlock_destroy __P((__os_rwlock_t *));
193 * PUBLIC: int __os_rwlock_rdlock __P((__os_rwlock_t *));
194 * PUBLIC: int __os_rwlock_wrlock __P((__os_rwlock_t *));
195 * PUBLIC: int __os_rwlock_rdunlock __P((__os_rwlock_t *));
196 * PUBLIC: int __os_rwlock_wrunlock __P((__os_rwlock_t *));
197 */
198XTC_API int __os_rwlock_init(__os_rwlock_t *r);
199XTC_API int __os_rwlock_destroy(__os_rwlock_t *r);
200XTC_API int __os_rwlock_rdlock(__os_rwlock_t *r);
201XTC_API int __os_rwlock_wrlock(__os_rwlock_t *r);
202XTC_API int __os_rwlock_rdunlock(__os_rwlock_t *r);
203XTC_API int __os_rwlock_wrunlock(__os_rwlock_t *r);
204
205/*
206 * --- Condition variable ---
207 *
208 * PUBLIC: int __os_cond_init __P((__os_cond_t *));
209 * PUBLIC: int __os_cond_destroy __P((__os_cond_t *));
210 * PUBLIC: int __os_cond_wait __P((__os_cond_t *, __os_mutex_t *));
211 * PUBLIC: int __os_cond_signal __P((__os_cond_t *));
212 * PUBLIC: int __os_cond_broadcast __P((__os_cond_t *));
213 */
214XTC_API int __os_cond_init(__os_cond_t *c);
215XTC_API int __os_cond_destroy(__os_cond_t *c);
216XTC_API int __os_cond_wait(__os_cond_t *c, __os_mutex_t *m);
217XTC_API int __os_cond_signal(__os_cond_t *c);
218XTC_API int __os_cond_broadcast(__os_cond_t *c);
219
220/*
221 * --- Semaphore (counting; unnamed, in-process) ---
222 *
223 * PUBLIC: int __os_sem_init __P((__os_sem_t *, unsigned));
224 * PUBLIC: int __os_sem_destroy __P((__os_sem_t *));
225 * PUBLIC: int __os_sem_post __P((__os_sem_t *));
226 * PUBLIC: int __os_sem_wait __P((__os_sem_t *));
227 * PUBLIC: int __os_sem_trywait __P((__os_sem_t *));
228 */
229XTC_API int __os_sem_init(__os_sem_t *s, unsigned initial);
230XTC_API int __os_sem_destroy(__os_sem_t *s);
231XTC_API int __os_sem_post(__os_sem_t *s);
232XTC_API int __os_sem_wait(__os_sem_t *s);
233XTC_API int __os_sem_trywait(__os_sem_t *s);
234
235#endif /* XTC_OS_THREAD_H */