view seobeo/s_worker.c @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -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;
}