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