comparison seobeo/s_worker.c @ 250:745fd127b2a1

[seobeo] Add bounded worker interface Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Tue, 04 Aug 2026 06:23:37 -0700
parents
children
comparison
equal deleted inserted replaced
249:c5129452493e 250:745fd127b2a1
1 #include "seobeo/seobeo_worker.h"
2
3 #include <pthread.h>
4 #include <stdlib.h>
5 #include <string.h>
6
7 typedef struct {
8 Seobeo_Work_Function function;
9 void *p_context;
10 Seobeo_Work_Cleanup cleanup;
11 } Seobeo_Work_Item;
12
13 struct Seobeo_Thread {
14 pthread_t thread;
15 Seobeo_Work_Item item;
16 };
17
18 struct Seobeo_Worker_Pool {
19 pthread_t *p_threads;
20 Seobeo_Work_Item *p_queue;
21 uint32 worker_count;
22 uint32 queue_capacity;
23 uint32 queue_head;
24 uint32 queue_count;
25 uint32 active_count;
26 uint32 cleanup_count;
27 boolean accepting;
28 boolean stopping;
29 boolean drain;
30 boolean shutdown_started;
31 boolean joined;
32 pthread_mutex_t mutex;
33 pthread_cond_t work_available;
34 pthread_cond_t idle;
35 };
36
37 static _Thread_local Seobeo_Worker_Pool *g_current_worker_pool = NULL;
38
39 static void Seobeo_Work_Item_Run(Seobeo_Work_Item *p_item)
40 {
41 p_item->function(p_item->p_context);
42 if (p_item->cleanup)
43 p_item->cleanup(p_item->p_context);
44 }
45
46 static void *Seobeo_Thread_Run(void *p_argument)
47 {
48 Seobeo_Thread *p_thread = p_argument;
49 Seobeo_Work_Item_Run(&p_thread->item);
50 return NULL;
51 }
52
53 Seobeo_Thread *Seobeo_Thread_Start(
54 Seobeo_Work_Function function,
55 void *p_context,
56 Seobeo_Work_Cleanup cleanup)
57 {
58 if (!function)
59 return NULL;
60
61 Seobeo_Thread *p_thread = calloc(1, sizeof(*p_thread));
62 if (!p_thread)
63 return NULL;
64 p_thread->item = (Seobeo_Work_Item){
65 .function = function,
66 .p_context = p_context,
67 .cleanup = cleanup,
68 };
69 if (pthread_create(
70 &p_thread->thread,
71 NULL,
72 Seobeo_Thread_Run,
73 p_thread) != 0)
74 {
75 free(p_thread);
76 return NULL;
77 }
78 return p_thread;
79 }
80
81 Seobeo_Worker_Result Seobeo_Thread_Join(Seobeo_Thread *p_thread)
82 {
83 if (!p_thread)
84 return SEOBEO_WORKER_INVALID_ARGUMENT;
85 if (pthread_equal(pthread_self(), p_thread->thread))
86 return SEOBEO_WORKER_INVALID_ARGUMENT;
87
88 int result = pthread_join(p_thread->thread, NULL);
89 free(p_thread);
90 return result == 0
91 ? SEOBEO_WORKER_OK
92 : SEOBEO_WORKER_THREAD_ERROR;
93 }
94
95 static void *Seobeo_Thread_Run_Detached(void *p_argument)
96 {
97 Seobeo_Work_Item *p_item = p_argument;
98 Seobeo_Work_Item_Run(p_item);
99 free(p_item);
100 return NULL;
101 }
102
103 Seobeo_Worker_Result Seobeo_Thread_Start_Detached(
104 Seobeo_Work_Function function,
105 void *p_context,
106 Seobeo_Work_Cleanup cleanup)
107 {
108 if (!function)
109 return SEOBEO_WORKER_INVALID_ARGUMENT;
110
111 Seobeo_Work_Item *p_item = malloc(sizeof(*p_item));
112 if (!p_item)
113 return SEOBEO_WORKER_OUT_OF_MEMORY;
114 *p_item = (Seobeo_Work_Item){
115 .function = function,
116 .p_context = p_context,
117 .cleanup = cleanup,
118 };
119
120 pthread_attr_t attributes;
121 if (pthread_attr_init(&attributes) != 0)
122 {
123 free(p_item);
124 return SEOBEO_WORKER_THREAD_ERROR;
125 }
126 int result = pthread_attr_setdetachstate(
127 &attributes,
128 PTHREAD_CREATE_DETACHED);
129 pthread_t thread;
130 if (result == 0)
131 {
132 result = pthread_create(
133 &thread,
134 &attributes,
135 Seobeo_Thread_Run_Detached,
136 p_item);
137 }
138 pthread_attr_destroy(&attributes);
139 if (result != 0)
140 {
141 free(p_item);
142 return SEOBEO_WORKER_THREAD_ERROR;
143 }
144 return SEOBEO_WORKER_OK;
145 }
146
147 uint64 Seobeo_Thread_Current_Id(void)
148 {
149 pthread_t thread = pthread_self();
150 const uint8 *p_bytes = (const uint8 *)&thread;
151 uint64 hash = 1469598103934665603ULL;
152 for (size_t i = 0; i < sizeof(thread); i++)
153 {
154 hash ^= p_bytes[i];
155 hash *= 1099511628211ULL;
156 }
157 return hash;
158 }
159
160 static boolean Seobeo_Worker_Pool_Is_Current_Thread(
161 Seobeo_Worker_Pool *p_pool)
162 {
163 pthread_t current = pthread_self();
164 for (uint32 i = 0; i < p_pool->worker_count; i++)
165 {
166 if (pthread_equal(current, p_pool->p_threads[i]))
167 return TRUE;
168 }
169 return FALSE;
170 }
171
172 static void *Seobeo_Worker_Pool_Run(void *p_argument)
173 {
174 Seobeo_Worker_Pool *p_pool = p_argument;
175 while (TRUE)
176 {
177 pthread_mutex_lock(&p_pool->mutex);
178 while (p_pool->queue_count == 0 && !p_pool->stopping)
179 pthread_cond_wait(&p_pool->work_available, &p_pool->mutex);
180
181 if (p_pool->stopping &&
182 (!p_pool->drain || p_pool->queue_count == 0))
183 {
184 pthread_mutex_unlock(&p_pool->mutex);
185 break;
186 }
187
188 Seobeo_Work_Item item = p_pool->p_queue[p_pool->queue_head];
189 p_pool->queue_head =
190 (p_pool->queue_head + 1) % p_pool->queue_capacity;
191 p_pool->queue_count--;
192 p_pool->active_count++;
193 pthread_mutex_unlock(&p_pool->mutex);
194
195 Seobeo_Worker_Pool *p_previous_pool = g_current_worker_pool;
196 g_current_worker_pool = p_pool;
197 Seobeo_Work_Item_Run(&item);
198 g_current_worker_pool = p_previous_pool;
199
200 pthread_mutex_lock(&p_pool->mutex);
201 p_pool->active_count--;
202 if (p_pool->queue_count == 0 &&
203 p_pool->active_count == 0 &&
204 p_pool->cleanup_count == 0)
205 pthread_cond_broadcast(&p_pool->idle);
206 pthread_mutex_unlock(&p_pool->mutex);
207 }
208 return NULL;
209 }
210
211 Seobeo_Worker_Pool *Seobeo_Worker_Pool_Create(
212 uint32 worker_count,
213 uint32 queue_capacity)
214 {
215 if (worker_count == 0 || queue_capacity == 0)
216 return NULL;
217
218 Seobeo_Worker_Pool *p_pool = calloc(1, sizeof(*p_pool));
219 if (!p_pool)
220 return NULL;
221 p_pool->p_threads = calloc(worker_count, sizeof(*p_pool->p_threads));
222 p_pool->p_queue = calloc(queue_capacity, sizeof(*p_pool->p_queue));
223 if (!p_pool->p_threads || !p_pool->p_queue)
224 {
225 free(p_pool->p_threads);
226 free(p_pool->p_queue);
227 free(p_pool);
228 return NULL;
229 }
230
231 p_pool->worker_count = worker_count;
232 p_pool->queue_capacity = queue_capacity;
233 p_pool->accepting = TRUE;
234 boolean mutex_initialized = FALSE;
235 boolean work_condition_initialized = FALSE;
236 boolean idle_condition_initialized = FALSE;
237 if (pthread_mutex_init(&p_pool->mutex, NULL) == 0)
238 mutex_initialized = TRUE;
239 if (mutex_initialized &&
240 pthread_cond_init(&p_pool->work_available, NULL) == 0)
241 work_condition_initialized = TRUE;
242 if (work_condition_initialized &&
243 pthread_cond_init(&p_pool->idle, NULL) == 0)
244 idle_condition_initialized = TRUE;
245 if (!idle_condition_initialized)
246 {
247 if (work_condition_initialized)
248 pthread_cond_destroy(&p_pool->work_available);
249 if (mutex_initialized)
250 pthread_mutex_destroy(&p_pool->mutex);
251 free(p_pool->p_threads);
252 free(p_pool->p_queue);
253 free(p_pool);
254 return NULL;
255 }
256
257 uint32 created = 0;
258 for (; created < worker_count; created++)
259 {
260 if (pthread_create(
261 &p_pool->p_threads[created],
262 NULL,
263 Seobeo_Worker_Pool_Run,
264 p_pool) != 0)
265 break;
266 }
267 if (created != worker_count)
268 {
269 pthread_mutex_lock(&p_pool->mutex);
270 p_pool->stopping = TRUE;
271 pthread_cond_broadcast(&p_pool->work_available);
272 pthread_mutex_unlock(&p_pool->mutex);
273 for (uint32 i = 0; i < created; i++)
274 pthread_join(p_pool->p_threads[i], NULL);
275 pthread_cond_destroy(&p_pool->idle);
276 pthread_cond_destroy(&p_pool->work_available);
277 pthread_mutex_destroy(&p_pool->mutex);
278 free(p_pool->p_threads);
279 free(p_pool->p_queue);
280 free(p_pool);
281 return NULL;
282 }
283 return p_pool;
284 }
285
286 Seobeo_Worker_Result Seobeo_Worker_Pool_Submit(
287 Seobeo_Worker_Pool *p_pool,
288 Seobeo_Work_Function function,
289 void *p_context,
290 Seobeo_Work_Cleanup cleanup)
291 {
292 if (!p_pool || !function)
293 return SEOBEO_WORKER_INVALID_ARGUMENT;
294
295 pthread_mutex_lock(&p_pool->mutex);
296 if (!p_pool->accepting)
297 {
298 pthread_mutex_unlock(&p_pool->mutex);
299 return SEOBEO_WORKER_STOPPED;
300 }
301 if (p_pool->queue_count == p_pool->queue_capacity)
302 {
303 pthread_mutex_unlock(&p_pool->mutex);
304 return SEOBEO_WORKER_QUEUE_FULL;
305 }
306
307 uint32 tail =
308 (p_pool->queue_head + p_pool->queue_count) %
309 p_pool->queue_capacity;
310 p_pool->p_queue[tail] = (Seobeo_Work_Item){
311 .function = function,
312 .p_context = p_context,
313 .cleanup = cleanup,
314 };
315 p_pool->queue_count++;
316 pthread_cond_signal(&p_pool->work_available);
317 pthread_mutex_unlock(&p_pool->mutex);
318 return SEOBEO_WORKER_OK;
319 }
320
321 Seobeo_Worker_Result Seobeo_Worker_Pool_Wait(
322 Seobeo_Worker_Pool *p_pool)
323 {
324 if (!p_pool ||
325 Seobeo_Worker_Pool_Is_Current_Thread(p_pool) ||
326 g_current_worker_pool == p_pool)
327 return SEOBEO_WORKER_INVALID_ARGUMENT;
328
329 pthread_mutex_lock(&p_pool->mutex);
330 while (p_pool->queue_count > 0 ||
331 p_pool->active_count > 0 ||
332 p_pool->cleanup_count > 0)
333 pthread_cond_wait(&p_pool->idle, &p_pool->mutex);
334 pthread_mutex_unlock(&p_pool->mutex);
335 return SEOBEO_WORKER_OK;
336 }
337
338 Seobeo_Worker_Result Seobeo_Worker_Pool_Shutdown(
339 Seobeo_Worker_Pool *p_pool,
340 boolean drain)
341 {
342 if (!p_pool ||
343 Seobeo_Worker_Pool_Is_Current_Thread(p_pool) ||
344 g_current_worker_pool == p_pool)
345 return SEOBEO_WORKER_INVALID_ARGUMENT;
346
347 pthread_mutex_lock(&p_pool->mutex);
348 if (p_pool->joined)
349 {
350 pthread_mutex_unlock(&p_pool->mutex);
351 return SEOBEO_WORKER_OK;
352 }
353 if (p_pool->shutdown_started)
354 {
355 while (!p_pool->joined)
356 pthread_cond_wait(&p_pool->idle, &p_pool->mutex);
357 pthread_mutex_unlock(&p_pool->mutex);
358 return SEOBEO_WORKER_OK;
359 }
360 p_pool->shutdown_started = TRUE;
361 p_pool->accepting = FALSE;
362 p_pool->stopping = TRUE;
363 p_pool->drain = drain;
364
365 if (!drain)
366 {
367 while (p_pool->queue_count > 0)
368 {
369 Seobeo_Work_Item item = p_pool->p_queue[p_pool->queue_head];
370 p_pool->queue_head =
371 (p_pool->queue_head + 1) % p_pool->queue_capacity;
372 p_pool->queue_count--;
373 p_pool->cleanup_count++;
374 pthread_mutex_unlock(&p_pool->mutex);
375 if (item.cleanup)
376 {
377 Seobeo_Worker_Pool *p_previous_pool = g_current_worker_pool;
378 g_current_worker_pool = p_pool;
379 item.cleanup(item.p_context);
380 g_current_worker_pool = p_previous_pool;
381 }
382 pthread_mutex_lock(&p_pool->mutex);
383 p_pool->cleanup_count--;
384 if (p_pool->queue_count == 0 &&
385 p_pool->active_count == 0 &&
386 p_pool->cleanup_count == 0)
387 pthread_cond_broadcast(&p_pool->idle);
388 }
389 }
390 pthread_cond_broadcast(&p_pool->work_available);
391 pthread_mutex_unlock(&p_pool->mutex);
392
393 for (uint32 i = 0; i < p_pool->worker_count; i++)
394 {
395 if (pthread_join(p_pool->p_threads[i], NULL) != 0)
396 return SEOBEO_WORKER_THREAD_ERROR;
397 }
398
399 pthread_mutex_lock(&p_pool->mutex);
400 p_pool->joined = TRUE;
401 if (p_pool->active_count == 0 &&
402 p_pool->cleanup_count == 0)
403 pthread_cond_broadcast(&p_pool->idle);
404 pthread_mutex_unlock(&p_pool->mutex);
405 return SEOBEO_WORKER_OK;
406 }
407
408 void Seobeo_Worker_Pool_Destroy(Seobeo_Worker_Pool *p_pool)
409 {
410 if (!p_pool)
411 return;
412 if (Seobeo_Worker_Pool_Is_Current_Thread(p_pool) ||
413 g_current_worker_pool == p_pool)
414 return;
415 if (!p_pool->joined)
416 (void)Seobeo_Worker_Pool_Shutdown(p_pool, TRUE);
417 pthread_cond_destroy(&p_pool->idle);
418 pthread_cond_destroy(&p_pool->work_available);
419 pthread_mutex_destroy(&p_pool->mutex);
420 free(p_pool->p_threads);
421 free(p_pool->p_queue);
422 free(p_pool);
423 }
424
425 uint32 Seobeo_Worker_Pool_Pending(Seobeo_Worker_Pool *p_pool)
426 {
427 if (!p_pool)
428 return 0;
429 pthread_mutex_lock(&p_pool->mutex);
430 uint32 count = p_pool->queue_count;
431 pthread_mutex_unlock(&p_pool->mutex);
432 return count;
433 }
434
435 uint32 Seobeo_Worker_Pool_Active(Seobeo_Worker_Pool *p_pool)
436 {
437 if (!p_pool)
438 return 0;
439 pthread_mutex_lock(&p_pool->mutex);
440 uint32 count = p_pool->active_count;
441 pthread_mutex_unlock(&p_pool->mutex);
442 return count;
443 }