view seobeo/s_worker.c @ 267:a78f5986011f

discover bundled production runtimes Locate Bazel-packaged Copilot and Python binaries by stable suffix instead of hardcoding an external repository prefix. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Fri, 07 Aug 2026 12:58:55 -0700
parents 745fd127b2a1
children
line wrap: on
line source

#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;
}