diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/seobeo/s_worker.c	Tue Aug 04 06:23:37 2026 -0700
@@ -0,0 +1,443 @@
+#include "seobeo/seobeo_worker.h"
+
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct {
+  Seobeo_Work_Function function;
+  void *p_context;
+  Seobeo_Work_Cleanup cleanup;
+} Seobeo_Work_Item;
+
+struct Seobeo_Thread {
+  pthread_t thread;
+  Seobeo_Work_Item item;
+};
+
+struct Seobeo_Worker_Pool {
+  pthread_t *p_threads;
+  Seobeo_Work_Item *p_queue;
+  uint32 worker_count;
+  uint32 queue_capacity;
+  uint32 queue_head;
+  uint32 queue_count;
+  uint32 active_count;
+  uint32 cleanup_count;
+  boolean accepting;
+  boolean stopping;
+  boolean drain;
+  boolean shutdown_started;
+  boolean joined;
+  pthread_mutex_t mutex;
+  pthread_cond_t work_available;
+  pthread_cond_t idle;
+};
+
+static _Thread_local Seobeo_Worker_Pool *g_current_worker_pool = NULL;
+
+static void Seobeo_Work_Item_Run(Seobeo_Work_Item *p_item)
+{
+  p_item->function(p_item->p_context);
+  if (p_item->cleanup)
+    p_item->cleanup(p_item->p_context);
+}
+
+static void *Seobeo_Thread_Run(void *p_argument)
+{
+  Seobeo_Thread *p_thread = p_argument;
+  Seobeo_Work_Item_Run(&p_thread->item);
+  return NULL;
+}
+
+Seobeo_Thread *Seobeo_Thread_Start(
+    Seobeo_Work_Function function,
+    void *p_context,
+    Seobeo_Work_Cleanup cleanup)
+{
+  if (!function)
+    return NULL;
+
+  Seobeo_Thread *p_thread = calloc(1, sizeof(*p_thread));
+  if (!p_thread)
+    return NULL;
+  p_thread->item = (Seobeo_Work_Item){
+    .function = function,
+    .p_context = p_context,
+    .cleanup = cleanup,
+  };
+  if (pthread_create(
+          &p_thread->thread,
+          NULL,
+          Seobeo_Thread_Run,
+          p_thread) != 0)
+  {
+    free(p_thread);
+    return NULL;
+  }
+  return p_thread;
+}
+
+Seobeo_Worker_Result Seobeo_Thread_Join(Seobeo_Thread *p_thread)
+{
+  if (!p_thread)
+    return SEOBEO_WORKER_INVALID_ARGUMENT;
+  if (pthread_equal(pthread_self(), p_thread->thread))
+    return SEOBEO_WORKER_INVALID_ARGUMENT;
+
+  int result = pthread_join(p_thread->thread, NULL);
+  free(p_thread);
+  return result == 0
+      ? SEOBEO_WORKER_OK
+      : SEOBEO_WORKER_THREAD_ERROR;
+}
+
+static void *Seobeo_Thread_Run_Detached(void *p_argument)
+{
+  Seobeo_Work_Item *p_item = p_argument;
+  Seobeo_Work_Item_Run(p_item);
+  free(p_item);
+  return NULL;
+}
+
+Seobeo_Worker_Result Seobeo_Thread_Start_Detached(
+    Seobeo_Work_Function function,
+    void *p_context,
+    Seobeo_Work_Cleanup cleanup)
+{
+  if (!function)
+    return SEOBEO_WORKER_INVALID_ARGUMENT;
+
+  Seobeo_Work_Item *p_item = malloc(sizeof(*p_item));
+  if (!p_item)
+    return SEOBEO_WORKER_OUT_OF_MEMORY;
+  *p_item = (Seobeo_Work_Item){
+    .function = function,
+    .p_context = p_context,
+    .cleanup = cleanup,
+  };
+
+  pthread_attr_t attributes;
+  if (pthread_attr_init(&attributes) != 0)
+  {
+    free(p_item);
+    return SEOBEO_WORKER_THREAD_ERROR;
+  }
+  int result = pthread_attr_setdetachstate(
+      &attributes,
+      PTHREAD_CREATE_DETACHED);
+  pthread_t thread;
+  if (result == 0)
+  {
+    result = pthread_create(
+        &thread,
+        &attributes,
+        Seobeo_Thread_Run_Detached,
+        p_item);
+  }
+  pthread_attr_destroy(&attributes);
+  if (result != 0)
+  {
+    free(p_item);
+    return SEOBEO_WORKER_THREAD_ERROR;
+  }
+  return SEOBEO_WORKER_OK;
+}
+
+uint64 Seobeo_Thread_Current_Id(void)
+{
+  pthread_t thread = pthread_self();
+  const uint8 *p_bytes = (const uint8 *)&thread;
+  uint64 hash = 1469598103934665603ULL;
+  for (size_t i = 0; i < sizeof(thread); i++)
+  {
+    hash ^= p_bytes[i];
+    hash *= 1099511628211ULL;
+  }
+  return hash;
+}
+
+static boolean Seobeo_Worker_Pool_Is_Current_Thread(
+    Seobeo_Worker_Pool *p_pool)
+{
+  pthread_t current = pthread_self();
+  for (uint32 i = 0; i < p_pool->worker_count; i++)
+  {
+    if (pthread_equal(current, p_pool->p_threads[i]))
+      return TRUE;
+  }
+  return FALSE;
+}
+
+static void *Seobeo_Worker_Pool_Run(void *p_argument)
+{
+  Seobeo_Worker_Pool *p_pool = p_argument;
+  while (TRUE)
+  {
+    pthread_mutex_lock(&p_pool->mutex);
+    while (p_pool->queue_count == 0 && !p_pool->stopping)
+      pthread_cond_wait(&p_pool->work_available, &p_pool->mutex);
+
+    if (p_pool->stopping &&
+        (!p_pool->drain || p_pool->queue_count == 0))
+    {
+      pthread_mutex_unlock(&p_pool->mutex);
+      break;
+    }
+
+    Seobeo_Work_Item item = p_pool->p_queue[p_pool->queue_head];
+    p_pool->queue_head =
+        (p_pool->queue_head + 1) % p_pool->queue_capacity;
+    p_pool->queue_count--;
+    p_pool->active_count++;
+    pthread_mutex_unlock(&p_pool->mutex);
+
+    Seobeo_Worker_Pool *p_previous_pool = g_current_worker_pool;
+    g_current_worker_pool = p_pool;
+    Seobeo_Work_Item_Run(&item);
+    g_current_worker_pool = p_previous_pool;
+
+    pthread_mutex_lock(&p_pool->mutex);
+    p_pool->active_count--;
+    if (p_pool->queue_count == 0 &&
+        p_pool->active_count == 0 &&
+        p_pool->cleanup_count == 0)
+      pthread_cond_broadcast(&p_pool->idle);
+    pthread_mutex_unlock(&p_pool->mutex);
+  }
+  return NULL;
+}
+
+Seobeo_Worker_Pool *Seobeo_Worker_Pool_Create(
+    uint32 worker_count,
+    uint32 queue_capacity)
+{
+  if (worker_count == 0 || queue_capacity == 0)
+    return NULL;
+
+  Seobeo_Worker_Pool *p_pool = calloc(1, sizeof(*p_pool));
+  if (!p_pool)
+    return NULL;
+  p_pool->p_threads = calloc(worker_count, sizeof(*p_pool->p_threads));
+  p_pool->p_queue = calloc(queue_capacity, sizeof(*p_pool->p_queue));
+  if (!p_pool->p_threads || !p_pool->p_queue)
+  {
+    free(p_pool->p_threads);
+    free(p_pool->p_queue);
+    free(p_pool);
+    return NULL;
+  }
+
+  p_pool->worker_count = worker_count;
+  p_pool->queue_capacity = queue_capacity;
+  p_pool->accepting = TRUE;
+  boolean mutex_initialized = FALSE;
+  boolean work_condition_initialized = FALSE;
+  boolean idle_condition_initialized = FALSE;
+  if (pthread_mutex_init(&p_pool->mutex, NULL) == 0)
+    mutex_initialized = TRUE;
+  if (mutex_initialized &&
+      pthread_cond_init(&p_pool->work_available, NULL) == 0)
+    work_condition_initialized = TRUE;
+  if (work_condition_initialized &&
+      pthread_cond_init(&p_pool->idle, NULL) == 0)
+    idle_condition_initialized = TRUE;
+  if (!idle_condition_initialized)
+  {
+    if (work_condition_initialized)
+      pthread_cond_destroy(&p_pool->work_available);
+    if (mutex_initialized)
+      pthread_mutex_destroy(&p_pool->mutex);
+    free(p_pool->p_threads);
+    free(p_pool->p_queue);
+    free(p_pool);
+    return NULL;
+  }
+
+  uint32 created = 0;
+  for (; created < worker_count; created++)
+  {
+    if (pthread_create(
+            &p_pool->p_threads[created],
+            NULL,
+            Seobeo_Worker_Pool_Run,
+            p_pool) != 0)
+      break;
+  }
+  if (created != worker_count)
+  {
+    pthread_mutex_lock(&p_pool->mutex);
+    p_pool->stopping = TRUE;
+    pthread_cond_broadcast(&p_pool->work_available);
+    pthread_mutex_unlock(&p_pool->mutex);
+    for (uint32 i = 0; i < created; i++)
+      pthread_join(p_pool->p_threads[i], NULL);
+    pthread_cond_destroy(&p_pool->idle);
+    pthread_cond_destroy(&p_pool->work_available);
+    pthread_mutex_destroy(&p_pool->mutex);
+    free(p_pool->p_threads);
+    free(p_pool->p_queue);
+    free(p_pool);
+    return NULL;
+  }
+  return p_pool;
+}
+
+Seobeo_Worker_Result Seobeo_Worker_Pool_Submit(
+    Seobeo_Worker_Pool *p_pool,
+    Seobeo_Work_Function function,
+    void *p_context,
+    Seobeo_Work_Cleanup cleanup)
+{
+  if (!p_pool || !function)
+    return SEOBEO_WORKER_INVALID_ARGUMENT;
+
+  pthread_mutex_lock(&p_pool->mutex);
+  if (!p_pool->accepting)
+  {
+    pthread_mutex_unlock(&p_pool->mutex);
+    return SEOBEO_WORKER_STOPPED;
+  }
+  if (p_pool->queue_count == p_pool->queue_capacity)
+  {
+    pthread_mutex_unlock(&p_pool->mutex);
+    return SEOBEO_WORKER_QUEUE_FULL;
+  }
+
+  uint32 tail =
+      (p_pool->queue_head + p_pool->queue_count) %
+      p_pool->queue_capacity;
+  p_pool->p_queue[tail] = (Seobeo_Work_Item){
+    .function = function,
+    .p_context = p_context,
+    .cleanup = cleanup,
+  };
+  p_pool->queue_count++;
+  pthread_cond_signal(&p_pool->work_available);
+  pthread_mutex_unlock(&p_pool->mutex);
+  return SEOBEO_WORKER_OK;
+}
+
+Seobeo_Worker_Result Seobeo_Worker_Pool_Wait(
+    Seobeo_Worker_Pool *p_pool)
+{
+  if (!p_pool ||
+      Seobeo_Worker_Pool_Is_Current_Thread(p_pool) ||
+      g_current_worker_pool == p_pool)
+    return SEOBEO_WORKER_INVALID_ARGUMENT;
+
+  pthread_mutex_lock(&p_pool->mutex);
+  while (p_pool->queue_count > 0 ||
+         p_pool->active_count > 0 ||
+         p_pool->cleanup_count > 0)
+    pthread_cond_wait(&p_pool->idle, &p_pool->mutex);
+  pthread_mutex_unlock(&p_pool->mutex);
+  return SEOBEO_WORKER_OK;
+}
+
+Seobeo_Worker_Result Seobeo_Worker_Pool_Shutdown(
+    Seobeo_Worker_Pool *p_pool,
+    boolean drain)
+{
+  if (!p_pool ||
+      Seobeo_Worker_Pool_Is_Current_Thread(p_pool) ||
+      g_current_worker_pool == p_pool)
+    return SEOBEO_WORKER_INVALID_ARGUMENT;
+
+  pthread_mutex_lock(&p_pool->mutex);
+  if (p_pool->joined)
+  {
+    pthread_mutex_unlock(&p_pool->mutex);
+    return SEOBEO_WORKER_OK;
+  }
+  if (p_pool->shutdown_started)
+  {
+    while (!p_pool->joined)
+      pthread_cond_wait(&p_pool->idle, &p_pool->mutex);
+    pthread_mutex_unlock(&p_pool->mutex);
+    return SEOBEO_WORKER_OK;
+  }
+  p_pool->shutdown_started = TRUE;
+  p_pool->accepting = FALSE;
+  p_pool->stopping = TRUE;
+  p_pool->drain = drain;
+
+  if (!drain)
+  {
+    while (p_pool->queue_count > 0)
+    {
+      Seobeo_Work_Item item = p_pool->p_queue[p_pool->queue_head];
+      p_pool->queue_head =
+          (p_pool->queue_head + 1) % p_pool->queue_capacity;
+      p_pool->queue_count--;
+      p_pool->cleanup_count++;
+      pthread_mutex_unlock(&p_pool->mutex);
+      if (item.cleanup)
+      {
+        Seobeo_Worker_Pool *p_previous_pool = g_current_worker_pool;
+        g_current_worker_pool = p_pool;
+        item.cleanup(item.p_context);
+        g_current_worker_pool = p_previous_pool;
+      }
+      pthread_mutex_lock(&p_pool->mutex);
+      p_pool->cleanup_count--;
+      if (p_pool->queue_count == 0 &&
+          p_pool->active_count == 0 &&
+          p_pool->cleanup_count == 0)
+        pthread_cond_broadcast(&p_pool->idle);
+    }
+  }
+  pthread_cond_broadcast(&p_pool->work_available);
+  pthread_mutex_unlock(&p_pool->mutex);
+
+  for (uint32 i = 0; i < p_pool->worker_count; i++)
+  {
+    if (pthread_join(p_pool->p_threads[i], NULL) != 0)
+      return SEOBEO_WORKER_THREAD_ERROR;
+  }
+
+  pthread_mutex_lock(&p_pool->mutex);
+  p_pool->joined = TRUE;
+  if (p_pool->active_count == 0 &&
+      p_pool->cleanup_count == 0)
+    pthread_cond_broadcast(&p_pool->idle);
+  pthread_mutex_unlock(&p_pool->mutex);
+  return SEOBEO_WORKER_OK;
+}
+
+void Seobeo_Worker_Pool_Destroy(Seobeo_Worker_Pool *p_pool)
+{
+  if (!p_pool)
+    return;
+  if (Seobeo_Worker_Pool_Is_Current_Thread(p_pool) ||
+      g_current_worker_pool == p_pool)
+    return;
+  if (!p_pool->joined)
+    (void)Seobeo_Worker_Pool_Shutdown(p_pool, TRUE);
+  pthread_cond_destroy(&p_pool->idle);
+  pthread_cond_destroy(&p_pool->work_available);
+  pthread_mutex_destroy(&p_pool->mutex);
+  free(p_pool->p_threads);
+  free(p_pool->p_queue);
+  free(p_pool);
+}
+
+uint32 Seobeo_Worker_Pool_Pending(Seobeo_Worker_Pool *p_pool)
+{
+  if (!p_pool)
+    return 0;
+  pthread_mutex_lock(&p_pool->mutex);
+  uint32 count = p_pool->queue_count;
+  pthread_mutex_unlock(&p_pool->mutex);
+  return count;
+}
+
+uint32 Seobeo_Worker_Pool_Active(Seobeo_Worker_Pool *p_pool)
+{
+  if (!p_pool)
+    return 0;
+  pthread_mutex_lock(&p_pool->mutex);
+  uint32 count = p_pool->active_count;
+  pthread_mutex_unlock(&p_pool->mutex);
+  return count;
+}