#if defined(__linux__)
#define USE_EPOLL
#include <sys/epoll.h>
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \
defined(__NetBSD__)
#define USE_KQUEUE
#include <sys/event.h>
#include <sys/time.h>
#else
#error \
"Unsupported platform. Requires kqueue (BSD/macOS) or epoll (Linux) support."
#endif
#include "scheduler.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern int close(int);
#ifdef USE_KQUEUE
extern int kqueue(void);
#endif
#define MAX_IO_EVENTS 32
static Scheduler global_scheduler;
static bool scheduler_initialized = false;
static Strand *strand_alloc(uint64_t id, StackCell *initial_stack) {
Strand *strand = (Strand *)malloc(sizeof(Strand));
if (!strand) {
runtime_error("strand_alloc: out of memory");
}
strand->stack_meta = stack_alloc(CEM_INITIAL_STACK_SIZE);
if (!strand->stack_meta) {
free(strand);
runtime_error("strand_alloc: failed to allocate dynamic stack");
}
strand->id = id;
strand->state = STRAND_READY;
strand->stack = initial_stack;
strand->cleanup_handlers = NULL; strand->blocked_fd = -1; strand->next = NULL;
memset(&strand->context, 0, sizeof(cem_context_t));
return strand;
}
static void strand_run_cleanup_handlers(Strand *strand) {
if (!strand)
return;
CleanupHandler *handler = strand->cleanup_handlers;
while (handler) {
CleanupHandler *next = handler->next;
if (handler->func) {
handler->func(handler->arg);
} else {
fprintf(stderr, "WARNING: cleanup handler with NULL function pointer\n");
}
free(handler);
handler = next;
}
strand->cleanup_handlers = NULL;
}
static void strand_free(Strand *strand) {
if (!strand)
return;
strand_run_cleanup_handlers(strand);
free_stack(strand->stack);
if (strand->stack_meta) {
stack_free(strand->stack_meta);
}
free(strand);
}
void ready_queue_push(Strand *strand) {
if (!strand)
return;
strand->next = NULL;
if (global_scheduler.ready_queue_tail) {
global_scheduler.ready_queue_tail->next = strand;
global_scheduler.ready_queue_tail = strand;
} else {
global_scheduler.ready_queue_head = strand;
global_scheduler.ready_queue_tail = strand;
}
}
Strand *ready_queue_pop(void) {
if (!global_scheduler.ready_queue_head) {
return NULL; }
Strand *strand = global_scheduler.ready_queue_head;
global_scheduler.ready_queue_head = strand->next;
if (!global_scheduler.ready_queue_head) {
global_scheduler.ready_queue_tail = NULL;
}
strand->next = NULL;
return strand;
}
bool ready_queue_is_empty(void) {
return global_scheduler.ready_queue_head == NULL;
}
void scheduler_init(void) {
if (scheduler_initialized) {
runtime_error("scheduler_init: scheduler already initialized");
}
memset(&global_scheduler, 0, sizeof(Scheduler));
global_scheduler.next_strand_id = 1;
#ifdef USE_KQUEUE
global_scheduler.kqueue_fd = kqueue();
if (global_scheduler.kqueue_fd == -1) {
runtime_error("scheduler_init: kqueue() failed");
}
#elif defined(USE_EPOLL)
global_scheduler.epoll_fd = epoll_create1(0);
if (global_scheduler.epoll_fd == -1) {
perror("scheduler_init: epoll_create1() failed");
runtime_error("scheduler_init: Failed to create epoll instance");
}
#endif
stack_guard_init_signal_handler();
stack_guard_set_scheduler(&global_scheduler);
scheduler_initialized = true;
}
void scheduler_shutdown(void) {
if (!scheduler_initialized) {
return; }
while (!ready_queue_is_empty()) {
Strand *strand = ready_queue_pop();
strand_free(strand);
}
while (global_scheduler.blocked_list) {
Strand *strand = global_scheduler.blocked_list;
global_scheduler.blocked_list = strand->next;
strand_free(strand);
}
if (global_scheduler.current_strand) {
strand_free(global_scheduler.current_strand);
global_scheduler.current_strand = NULL;
}
#ifdef USE_KQUEUE
if (global_scheduler.kqueue_fd != -1) {
close(global_scheduler.kqueue_fd);
global_scheduler.kqueue_fd = -1;
}
#elif defined(USE_EPOLL)
if (global_scheduler.epoll_fd != -1) {
close(global_scheduler.epoll_fd);
global_scheduler.epoll_fd = -1;
}
#endif
scheduler_initialized = false;
}
static void strand_entry_trampoline(void) {
Strand *strand = global_scheduler.current_strand;
if (!strand) {
runtime_error("strand_entry_trampoline: no current strand");
}
StackCell *(*entry_func)(StackCell *) = strand->entry_func;
StackCell *initial_stack = strand->stack;
StackCell *final_stack = entry_func(initial_stack);
strand->state = STRAND_COMPLETED;
strand->stack = final_stack;
cem_swapcontext(&strand->context, &global_scheduler.scheduler_context);
}
uint64_t strand_spawn(StackCell *(*entry_func)(StackCell *),
StackCell *initial_stack) {
if (!scheduler_initialized) {
runtime_error("strand_spawn: scheduler not initialized");
}
if (!entry_func) {
runtime_error("strand_spawn: entry_func is NULL");
}
uint64_t id = global_scheduler.next_strand_id++;
Strand *strand = strand_alloc(id, initial_stack);
strand->entry_func = entry_func;
cem_makecontext(
&strand->context, strand->stack_meta->usable_base,
strand->stack_meta->usable_size, strand_entry_trampoline,
NULL);
ready_queue_push(strand);
return id;
}
void strand_push_cleanup(CleanupFunc func, void *arg) {
if (!scheduler_initialized) {
runtime_error("strand_push_cleanup: scheduler not initialized");
}
Strand *strand = global_scheduler.current_strand;
if (!strand) {
runtime_error("strand_push_cleanup: no current strand");
}
if (!func) {
runtime_error("strand_push_cleanup: cleanup function cannot be NULL");
}
CleanupHandler *handler = (CleanupHandler *)malloc(sizeof(CleanupHandler));
if (!handler) {
runtime_error("strand_push_cleanup: out of memory");
}
handler->func = func;
handler->arg = arg;
handler->next = strand->cleanup_handlers;
strand->cleanup_handlers = handler;
}
void strand_pop_cleanup(void) {
if (!scheduler_initialized) {
runtime_error("strand_pop_cleanup: scheduler not initialized");
}
Strand *strand = global_scheduler.current_strand;
if (!strand) {
runtime_error("strand_pop_cleanup: no current strand");
}
CleanupHandler *handler = strand->cleanup_handlers;
if (!handler) {
runtime_error("strand_pop_cleanup: no cleanup handlers to pop");
}
strand->cleanup_handlers = handler->next;
free(handler);
}
void strand_update_cleanup_arg(void *new_arg) {
if (!scheduler_initialized) {
runtime_error("strand_update_cleanup_arg: scheduler not initialized");
}
Strand *strand = global_scheduler.current_strand;
if (!strand) {
runtime_error("strand_update_cleanup_arg: no current strand");
}
CleanupHandler *handler = strand->cleanup_handlers;
if (!handler) {
runtime_error("strand_update_cleanup_arg: no cleanup handlers to update");
}
handler->arg = new_arg;
}
void strand_yield(void) {
if (!scheduler_initialized) {
runtime_error("strand_yield: scheduler not initialized");
}
if (!global_scheduler.current_strand) {
runtime_error("strand_yield: no current strand (must be called from within "
"a strand)");
}
Strand *strand = global_scheduler.current_strand;
strand->state = STRAND_YIELDED;
ready_queue_push(strand);
global_scheduler.current_strand = NULL;
cem_swapcontext(&strand->context, &global_scheduler.scheduler_context);
}
static void blocked_list_add(Strand *strand) {
if (!strand)
return;
strand->next = global_scheduler.blocked_list;
global_scheduler.blocked_list = strand;
}
static bool blocked_list_remove(Strand *strand) {
if (!strand || !global_scheduler.blocked_list)
return false;
if (global_scheduler.blocked_list == strand) {
global_scheduler.blocked_list = strand->next;
strand->next = NULL;
return true;
}
Strand *prev = global_scheduler.blocked_list;
Strand *curr = prev->next;
while (curr) {
if (curr == strand) {
prev->next = curr->next;
curr->next = NULL;
return true;
}
prev = curr;
curr = curr->next;
}
return false;
}
void strand_block_on_read(int fd) {
if (!scheduler_initialized) {
runtime_error("strand_block_on_read: scheduler not initialized");
}
if (!global_scheduler.current_strand) {
runtime_error("strand_block_on_read: no current strand");
}
if (fd < 0) {
runtime_error("strand_block_on_read: invalid file descriptor");
}
Strand *strand = global_scheduler.current_strand;
strand->state = STRAND_BLOCKED_READ;
strand->blocked_fd = fd;
#ifdef USE_KQUEUE
struct kevent ev;
EV_SET(&ev, fd, EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0, strand);
if (kevent(global_scheduler.kqueue_fd, &ev, 1, NULL, 0, NULL) == -1) {
runtime_error("strand_block_on_read: kevent registration failed");
}
#elif defined(USE_EPOLL)
struct epoll_event ev;
ev.events =
EPOLLIN | EPOLLET | EPOLLONESHOT; ev.data.ptr = strand;
if (epoll_ctl(global_scheduler.epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
perror("strand_block_on_read: epoll_ctl failed");
runtime_error(
"strand_block_on_read: Failed to register fd for read events");
}
#endif
blocked_list_add(strand);
global_scheduler.current_strand = NULL;
cem_swapcontext(&strand->context, &global_scheduler.scheduler_context);
strand->blocked_fd = -1;
}
void strand_block_on_write(int fd) {
if (!scheduler_initialized) {
runtime_error("strand_block_on_write: scheduler not initialized");
}
if (!global_scheduler.current_strand) {
runtime_error("strand_block_on_write: no current strand");
}
if (fd < 0) {
runtime_error("strand_block_on_write: invalid file descriptor");
}
Strand *strand = global_scheduler.current_strand;
strand->state = STRAND_BLOCKED_WRITE;
strand->blocked_fd = fd;
#ifdef USE_KQUEUE
struct kevent ev;
EV_SET(&ev, fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, strand);
if (kevent(global_scheduler.kqueue_fd, &ev, 1, NULL, 0, NULL) == -1) {
runtime_error("strand_block_on_write: kevent registration failed");
}
#elif defined(USE_EPOLL)
struct epoll_event ev;
ev.events =
EPOLLOUT | EPOLLET | EPOLLONESHOT; ev.data.ptr = strand;
if (epoll_ctl(global_scheduler.epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
perror("strand_block_on_write: epoll_ctl failed");
runtime_error(
"strand_block_on_write: Failed to register fd for write events");
}
#endif
blocked_list_add(strand);
global_scheduler.current_strand = NULL;
cem_swapcontext(&strand->context, &global_scheduler.scheduler_context);
strand->blocked_fd = -1;
}
StackCell *scheduler_run(void) {
if (!scheduler_initialized) {
runtime_error("scheduler_run: scheduler not initialized");
}
while (true) {
if (!ready_queue_is_empty()) {
Strand *strand = ready_queue_pop();
if (!strand) {
break; }
strand->state = STRAND_RUNNING;
global_scheduler.current_strand = strand;
stack_check_and_grow(strand, CEM_CONTEXT_GET_SP(&strand->context));
cem_swapcontext(&global_scheduler.scheduler_context, &strand->context);
if (strand->state == STRAND_COMPLETED) {
StackCell *final_stack = strand->stack;
strand->stack = NULL;
if (ready_queue_is_empty() && !global_scheduler.blocked_list &&
strand->id == 1) {
StackCell *result = final_stack;
strand_free(strand);
global_scheduler.current_strand = NULL;
return result;
}
free_stack(final_stack);
strand_free(strand);
global_scheduler.current_strand = NULL;
} else if (strand->state == STRAND_YIELDED) {
} else if (strand->state == STRAND_BLOCKED_READ ||
strand->state == STRAND_BLOCKED_WRITE) {
} else {
runtime_error(
"scheduler_run: strand in unexpected state after context switch");
}
} else if (global_scheduler.blocked_list) {
#ifdef USE_KQUEUE
struct kevent events[MAX_IO_EVENTS];
int nevents = kevent(global_scheduler.kqueue_fd, NULL, 0, events,
MAX_IO_EVENTS, NULL);
if (nevents == -1) {
perror("scheduler_run: kevent wait failed");
runtime_error("scheduler_run: I/O event wait failed");
}
for (int i = 0; i < nevents; i++) {
Strand *strand = (Strand *)events[i].udata;
if (!strand)
continue;
blocked_list_remove(strand);
strand->state = STRAND_READY;
ready_queue_push(strand);
}
#elif defined(USE_EPOLL)
struct epoll_event events[MAX_IO_EVENTS];
int nevents = epoll_wait(global_scheduler.epoll_fd, events, MAX_IO_EVENTS,
-1);
if (nevents == -1) {
perror("scheduler_run: epoll_wait failed");
runtime_error("scheduler_run: I/O event wait failed");
}
for (int i = 0; i < nevents; i++) {
Strand *strand = (Strand *)events[i].data.ptr;
if (!strand)
continue;
blocked_list_remove(strand);
strand->state = STRAND_READY;
ready_queue_push(strand);
}
#endif
} else {
break;
}
}
global_scheduler.current_strand = NULL;
return NULL;
}
StackCell *test_yield(StackCell *stack) {
if (global_scheduler.current_strand) {
strand_yield();
}
return stack;
}
void scheduler_debug_print(void) {
printf("Scheduler state:\n");
printf(" Initialized: %s\n", scheduler_initialized ? "true" : "false");
printf(" Current strand: %llu\n",
global_scheduler.current_strand
? (unsigned long long)global_scheduler.current_strand->id
: 0);
printf(" Next strand ID: %llu\n",
(unsigned long long)global_scheduler.next_strand_id);
printf(" Ready queue: ");
if (ready_queue_is_empty()) {
printf("(empty)\n");
} else {
Strand *s = global_scheduler.ready_queue_head;
while (s) {
printf("%llu ", (unsigned long long)s->id);
s = s->next;
}
printf("\n");
}
}