scheduler.c

Unnamed repository; edit this file 'description' to name the repository.
git clone git://git.finwo.net/lib/scheduler.c
Log | Files | Refs | README | LICENSE

commit 84c5daa19aa76209ed633581abc1f3c591ee1d8f
Author: finwo <finwo@pm.me>
Date:   Wed, 18 Mar 2026 18:37:25 +0100

Project init

Diffstat:
A.dep.export | 1+
ACODE_OF_CONDUCT.md | 6++++++
ALICENSE.md | 34++++++++++++++++++++++++++++++++++
AREADME.md | 275+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfig.mk | 1+
Aexamples/.gitignore | 6++++++
Aexamples/Makefile | 27+++++++++++++++++++++++++++
Aexamples/README.md | 39+++++++++++++++++++++++++++++++++++++++
Aexamples/example_basic.c | 15+++++++++++++++
Aexamples/example_io.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aexamples/example_multi.c | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aexamples/example_periodic.c | 41+++++++++++++++++++++++++++++++++++++++++
Aexamples/example_removal.c | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/scheduler.c | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/scheduler.h | 28++++++++++++++++++++++++++++
15 files changed, 853 insertions(+), 0 deletions(-)

diff --git a/.dep.export b/.dep.export @@ -0,0 +1 @@ +include/finwo/scheduler.h src/scheduler.h diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md @@ -0,0 +1,6 @@ +<!-- 46b43825-f791-485e-9445-415ee7bbbf2d --> +# Contributor Code of Conduct + +This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters. + +For more information please visit the [No Code of Conduct](https://github.com/domgetter/NCoC) homepage. diff --git a/LICENSE.md b/LICENSE.md @@ -0,0 +1,34 @@ +Copyright (c) 2026 finwo + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to use, copy, +modify, and distribute the Software, subject to the following conditions: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions, and the following disclaimer. + + 2. Redistributions in binary form, or any public offering of the Software + (including hosted or managed services), must reproduce the above copyright + notice, this list of conditions, and the following disclaimer in the + documentation and/or other materials provided. + + 3. Any redistribution or public offering of the Software must clearly attribute + the Software to the original copyright holder, reference this License, and + include a link to the official project repository or website. + + 4. The Software may not be renamed, rebranded, or marketed in a manner that + implies it is an independent or proprietary product. Derivative works must + clearly state that they are based on the Software. + + 5. Modifications to copies of the Software must carry prominent notices stating + that changes were made, the nature of the modifications, and the date of the + modifications. + +Any violation of these conditions terminates the permissions granted herein. + +THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md @@ -0,0 +1,275 @@ +# scheduler — Minimal Event-Loop Scheduler in C + +A lightweight, single-file event-loop scheduler for Unix-like systems. Uses `select(2)` for I/O multiplexing with a linked list of tasks invoked on every tick. + +```c +#include "src/scheduler.h" +#include <stdio.h> +#include <stdlib.h> + +static int greet(int64_t ts, pt_task_t *task) { + (void)ts; (void)task; + printf("Hello, world!\n"); + return SCHED_DONE; +} + +int main(void) { + sched_create(greet, NULL); + sched_main(); + return 0; +} +``` + +Compile and run: +```sh +gcc -o greet examples/example_basic.c src/scheduler.c && ./greet +# Hello, world! +``` + +--- + +## Features + +- **Minimal** — ~120 lines of C, single header + single source file +- **No dependencies** — only `<stdint.h>` and `<sys/select.h>` +- **I/O multiplexing** — built-in `select(2)` integration via `sched_has_data()` +- **Cross-project friendly** — zero-configuration include via `config.mk` +- **Extensible** — task `udata` lets you attach arbitrary state + +--- + +## Installation + +### With dep (recommended) + +```sh +dep add finwo/scheduler +``` + +### As a git submodule + +```sh +git submodule add https://github.com/finwo/scheduler.c.git scheduler +``` + +Then in your `Makefile`, include `scheduler/config.mk`: +```make +include scheduler/config.mk +``` + +And add the include path: +```make +CFLAGS += -I$(shell pwd)/scheduler +``` + +### Manual + +Copy `src/scheduler.h` and `src/scheduler.c` into your project. + +--- + +## API Reference + +### Status Codes + +| Macro | Value | Meaning | +|-------|-------|---------| +| `SCHED_RUNNING` | 0 | Task wants to keep running; scheduler will call it again next tick | +| `SCHED_DONE` | 1 | Task has finished; scheduler removes it from the list | +| `SCHED_ERROR` | 2 | Task encountered an error; scheduler removes it | + +### Task Callback + +```c +typedef int (*pt_task_fn)(int64_t timestamp, pt_task_t *task); +``` + +Every task is a function of this type. It receives: + +- `timestamp` — current time in **milliseconds since epoch** +- `task` — the task handle; use `task->udata` to access your attached data + +Return one of `SCHED_RUNNING`, `SCHED_DONE`, or `SCHED_ERROR`. + +### Task Handle + +```c +typedef struct pt_task { + struct pt_task *next; + pt_task_fn func; + void *udata; + char is_active; + int maxfd; +} pt_task_t; +``` + +The struct is opaque — create tasks via `sched_create()` and manage them through the public API. + +### `sched_create` + +```c +pt_task_t *sched_create(pt_task_fn fn, void *udata); +``` + +Register a new task. The task is prepended to the internal linked list, so it runs **before** any existing tasks on every tick (LIFO order). + +- `fn` — your task callback; must not be `NULL` +- `udata` — arbitrary pointer passed to every callback invocation +- **Returns** — the new `pt_task_t*`, or `NULL` if `fn` is `NULL` + +> [!TIP] +> Storing the returned `pt_task_t*` lets you remove the task later from any context — including from a different task or signal handler. + +### `sched_remove` + +```c +int sched_remove(pt_task_t *task); +``` + +Synchronously remove a task from the scheduler and free its memory. + +- `task` — handle returned by `sched_create`; `NULL` is a no-op +- **Returns** — `0` on success, `1` if `task` was not found + +> [!WARNING] +> Removing a task from within its own callback is safe — the scheduler saves the `next` pointer before invoking the callback. + +### `sched_main` + +```c +int sched_main(void); +``` + +Start the main event loop. Blocks until all tasks have been removed (each returned `SCHED_DONE` or `SCHED_ERROR`). + +- **Returns** — `0` on normal exit (all tasks completed), or immediately `0` if the task list is empty on entry + +### `sched_has_data` + +```c +int sched_has_data(int *in_fds); +``` + +Check which file descriptors are ready for I/O. Call this from within a task callback. The array format is `[count, fd1, fd2, ..., fdN]`, where negative file descriptors are silently skipped. + +**Phase 1 — register interest:** before `select()` fires, pass your FDs to register read interest. + +```c +int fds[3] = { 2, sock_fd, pipe_fd }; +int ready = sched_has_data(fds); // registers sock_fd and pipe_fd +``` + +**Phase 2 — check result:** after `select()` returns, call again to get the first ready FD. + +```c +int ready = sched_has_data(fds); // returns first ready FD, or -1 if none +``` + +- **Returns** — the first ready FD from the array, or `-1` if none are ready + +--- + +## Usage Examples + +Each example is a complete, compilable program. + +| Example | Demonstrates | +|---------|-------------| +| [example_basic.c](examples/example_basic.c) | Minimal one-shot task | +| [example_periodic.c](examples/example_periodic.c) | Timer-based periodic task | +| [example_io.c](examples/example_io.c) | I/O monitoring with `sched_has_data()` | +| [example_removal.c](examples/example_removal.c) | Parent removes child task via returned `pt_task_t*` | +| [example_multi.c](examples/example_multi.c) | Multiple coordinated tasks sharing state | + +See [examples/README.md](examples/README.md) for build instructions. + +--- + +## Architecture + +### Event Loop + +`sched_main()` runs an infinite `for(;;)` loop: + +1. Scan `g_want_fds` to find the highest file descriptor +2. Call `select()` with a **100 ms timeout** on that fd set +3. Record the `select()` result in `g_select_result`; clear `g_want_fds` +4. Call `gettimeofday()` and compute a millisecond timestamp +5. Iterate the task list; invoke each task callback with the timestamp +6. If a callback returns anything other than `SCHED_RUNNING`, remove the task +7. Exit when the task list is empty + +### Task List + +Tasks are stored in a singly-linked list. New tasks are **prepended** to the front (LIFO), meaning the most recently registered task runs first on each tick. This is a deliberate trade-off: O(1) insertion at the cost of insertion-order unpredictability. + +### I/O Model + +The scheduler uses `select(2)` rather than `poll(2)` or `epoll(7)`. This keeps the library dependency-free and portable across all Unix systems, but carries the standard `select(2)` limitations (see [Limitations](#limitations)). + +--- + +## Memory Management + +| Who allocates | Who frees | Notes | +|---|---|---| +| `sched_create()` → `udata` pointer | **Caller** | The scheduler never touches your `udata`. You must `free()` it yourself, typically in a cleanup task or after `sched_main()` returns. | +| `sched_create()` → `pt_task_t` struct | **`sched_remove()`** | Freed automatically when the task returns `SCHED_DONE` or `SCHED_ERROR`, or when explicitly removed. | + +**Pattern for owned `udata`:** + +```c +static int cleanup_task(int64_t ts, pt_task_t *task) { + MyCtx *ctx = task->udata; + free(ctx->buf); + free(ctx); + return SCHED_DONE; +} +``` + +--- + +## Signal Handling + +`sed_main()` calls `select(2)`, which is **not async-signal-safe** by itself. A few considerations: + +### `select()` interruption + +If a signal arrives while `select()` is blocked, it returns `-1` with `errno == EINTR`. The scheduler currently **ignores** this error — it loops and retries. This means signals cause a ~100 ms delay in worst case (the timeout), which is usually fine. + +### Calling `sched_remove()` from a signal handler + +This is **unsafe** — `sched_remove()` calls `free()` and modifies linked-list pointers, which are not async-signal-safe operations. If you need to stop tasks from signal handlers, use a volatile flag: + +```c +static volatile sig_atomic_t keep_running = 1; + +static int worker(int64_t ts, pt_task_t *task) { + if (!keep_running) return SCHED_DONE; + // ... do work + return SCHED_RUNNING; +} +``` + +Then set `keep_running = 0` from the signal handler (this is safe because `sig_atomic_t` guarantees atomic read/write). + +### `SA_RESTART` + +If you install signal handlers with `sigaction()`, consider `SA_RESTART`. This restarts `select(2)` automatically rather than returning `-1/EINTR`, which avoids the spurious loop iteration. The scheduler tolerates both modes. + +--- + +## Limitations + +- **Not thread-safe.** All state lives in global/static variables. Use from a single thread, or wrap all calls in a mutex. +- **`select(2)` constraint.** File descriptors are limited to `[0, FD_SETSIZE)` (typically 1024). For higher fd counts, `poll(2)` or `epoll(7)` would be needed. +- **100 ms tick resolution.** The `select()` timeout is hardcoded to 100 ms. Tasks cannot run more frequently than this. +- **LIFO ordering.** New tasks are prepended, not appended. Execution order on each tick is newest-first. +- **No priorities.** All tasks are equal; there is no scheduling priority or weighting. +- **No recurring tasks built-in.** Tasks must explicitly return `SCHED_RUNNING` to be called again. + +--- + +## License + +See [LICENSE.md](LICENSE.md). ISC-style license — free to use, copy, modify, and distribute with attribution. diff --git a/config.mk b/config.mk @@ -0,0 +1 @@ +SRC+={{module.dirname}}/src/scheduler.c diff --git a/examples/.gitignore b/examples/.gitignore @@ -0,0 +1,6 @@ +/example_basic +/example_io +/example_multi +/example_periodic +/example_removal +/*.dSYM/ diff --git a/examples/Makefile b/examples/Makefile @@ -0,0 +1,27 @@ +CC ?= gcc +CFLAGS ?= -Wall -Wextra -g -I$(shell pwd)/.. +LDFLAGS ?= + +EXAMPLES = example_basic example_periodic example_io example_removal example_multi + +.PHONY: all clean + +all: $(EXAMPLES) + +example_basic: example_basic.c ../src/scheduler.c ../src/scheduler.h + $(CC) $(CFLAGS) -o $@ $< ../src/scheduler.c $(LDFLAGS) + +example_periodic: example_periodic.c ../src/scheduler.c ../src/scheduler.h + $(CC) $(CFLAGS) -o $@ $< ../src/scheduler.c $(LDFLAGS) + +example_io: example_io.c ../src/scheduler.c ../src/scheduler.h + $(CC) $(CFLAGS) -o $@ $< ../src/scheduler.c $(LDFLAGS) + +example_removal: example_removal.c ../src/scheduler.c ../src/scheduler.h + $(CC) $(CFLAGS) -o $@ $< ../src/scheduler.c $(LDFLAGS) + +example_multi: example_multi.c ../src/scheduler.c ../src/scheduler.h + $(CC) $(CFLAGS) -o $@ $< ../src/scheduler.c $(LDFLAGS) + +clean: + rm -f $(EXAMPLES) diff --git a/examples/README.md b/examples/README.md @@ -0,0 +1,39 @@ +# Examples + +Each file is a complete, compilable C program demonstrating a specific aspect of the scheduler. + +## Building + +From the repository root: + +```sh +make -C examples +``` + +Or compile a single example manually: + +```sh +gcc -o greet examples/example_basic.c src/scheduler.c +``` + +All examples are self-contained and require only `src/scheduler.h`, `src/scheduler.c`, and the C standard library. + +## Index + +| File | Description | +|------|-------------| +| [example_basic.c](example_basic.c) | Minimal one-shot task — runs once and exits | +| [example_periodic.c](example_periodic.c) | Periodic task using timestamps to run every second | +| [example_io.c](example_io.c) | I/O monitoring on a pipe; waits for data to be readable | +| [example_removal.c](example_removal.c) | Parent task spawns a child task, then removes it after a delay | +| [example_multi.c](example_multi.c) | Multiple tasks sharing a context; coordinated shutdown | + +## Running + +```sh +./example_basic # Prints "Hello, world!" and exits immediately +./example_periodic # Prints a timestamp every second, exits after 5s +./example_io # Reads from a pipe and prints each line +./example_removal # Prints tick counts from two tasks; parent kills child +./example_multi # Reader and writer tasks share a buffer via udata +``` diff --git a/examples/example_basic.c b/examples/example_basic.c @@ -0,0 +1,15 @@ +#include "src/scheduler.h" +#include <stdio.h> +#include <stdlib.h> + +static int greet(int64_t ts, pt_task_t *task) { + (void)ts; (void)task; + printf("Hello, world!\n"); + return SCHED_DONE; +} + +int main(void) { + sched_create(greet, NULL); + sched_main(); + return 0; +} diff --git a/examples/example_io.c b/examples/example_io.c @@ -0,0 +1,94 @@ +#include "src/scheduler.h" +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <time.h> +#include <unistd.h> + +typedef struct { + int pipe_fd; + int fds[2]; + int64_t last_write; +} WriterCtx; + +typedef struct { + int pipe_fd; + int fds[2]; + int done; +} ReaderCtx; + +static int writer_task(int64_t ts, pt_task_t *task) { + static int count = 0; + WriterCtx *ctx = task->udata; + + if (ts - ctx->last_write >= 200) { + char msg[32]; + int len = snprintf(msg, sizeof(msg), "message #%d\n", ++count); + write(ctx->pipe_fd, msg, len); + ctx->last_write = ts; + + if (count >= 3) { + snprintf(msg, sizeof(msg), "quit\n"); + write(ctx->pipe_fd, msg, 4); + close(ctx->pipe_fd); + free(ctx); + return SCHED_DONE; + } + } + + return SCHED_RUNNING; +} + +static int reader_task(int64_t ts, pt_task_t *task) { + (void)ts; + ReaderCtx *ctx = task->udata; + + int ready = sched_has_data(ctx->fds); + if (ready >= 0) { + char buf[256]; + ssize_t n = read(ready, buf, sizeof(buf) - 1); + if (n > 0) { + buf[n] = '\0'; + printf("[reader] %s", buf); + } + if (n == 0 || (n > 0 && strncmp(buf, "quit\n", 5) == 0)) { + ctx->done = 1; + } + } + + if (ctx->done) { + close(ctx->pipe_fd); + free(ctx); + return SCHED_DONE; + } + + return SCHED_RUNNING; +} + +int main(void) { + int pipefd[2]; + if (pipe(pipefd) < 0) { + perror("pipe"); + return 1; + } + + WriterCtx *wctx = calloc(1, sizeof(WriterCtx)); + wctx->pipe_fd = pipefd[1]; + + ReaderCtx *rctx = calloc(1, sizeof(ReaderCtx)); + rctx->pipe_fd = pipefd[0]; + rctx->fds[0] = 1; + rctx->fds[1] = rctx->pipe_fd; + + struct timeval now; + gettimeofday(&now, NULL); + wctx->last_write = (int64_t)now.tv_sec * 1000 + now.tv_usec / 1000; + + sched_create(writer_task, wctx); + sched_create(reader_task, rctx); + sched_main(); + return 0; +} diff --git a/examples/example_multi.c b/examples/example_multi.c @@ -0,0 +1,105 @@ +#include "src/scheduler.h" +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> +#include <unistd.h> + +#define BUF_SIZE 64 + +typedef struct { + int reader_fd; + int writer_fd; + int fds_reader[2]; + char buf[BUF_SIZE]; + int done; + int64_t last_write; +} SharedCtx; + +static int reader_task(int64_t ts, pt_task_t *task) { + (void)ts; + SharedCtx *ctx = task->udata; + + int ready = sched_has_data(ctx->fds_reader); + if (ready >= 0) { + ssize_t n = read(ready, ctx->buf, BUF_SIZE - 1); + if (n > 0) { + ctx->buf[n] = '\0'; + printf("[reader] received: \"%s\"\n", ctx->buf); + } + if (n == 0 || (n > 0 && strncmp(ctx->buf, "done\n", 5) == 0)) { + ctx->done = 1; + } + } + + if (ctx->done) { + close(ctx->reader_fd); + ctx->reader_fd = -1; + return SCHED_DONE; + } + + return SCHED_RUNNING; +} + +static int writer_task(int64_t ts, pt_task_t *task) { + SharedCtx *ctx = task->udata; + + if (ctx->done) { + return SCHED_DONE; + } + + if (ts - ctx->last_write >= 200) { + static int count = 0; + char msg[32]; + int len = snprintf(msg, sizeof(msg), "message #%d\n", ++count); + write(ctx->writer_fd, msg, len); + ctx->last_write = ts; + + if (count >= 3) { + snprintf(msg, sizeof(msg), "done\n"); + write(ctx->writer_fd, msg, 4); + close(ctx->writer_fd); + ctx->writer_fd = -1; + } + } + + return SCHED_RUNNING; +} + +static int shutdown_task(int64_t ts, pt_task_t *task) { + (void)ts; + SharedCtx *ctx = task->udata; + + if (ctx->done && ctx->reader_fd < 0 && ctx->writer_fd < 0) { + printf("[shutdown] all done, cleaning up.\n"); + free(ctx); + return SCHED_DONE; + } + + return SCHED_RUNNING; +} + +int main(void) { + int pipefd[2]; + if (pipe(pipefd) < 0) { + perror("pipe"); + return 1; + } + + SharedCtx *ctx = calloc(1, sizeof(SharedCtx)); + ctx->reader_fd = pipefd[0]; + ctx->writer_fd = pipefd[1]; + ctx->fds_reader[0] = 1; + ctx->fds_reader[1] = ctx->reader_fd; + + struct timeval now; + gettimeofday(&now, NULL); + ctx->last_write = (int64_t)now.tv_sec * 1000 + now.tv_usec / 1000; + + sched_create(writer_task, ctx); + sched_create(reader_task, ctx); + sched_create(shutdown_task, ctx); + sched_main(); + return 0; +} diff --git a/examples/example_periodic.c b/examples/example_periodic.c @@ -0,0 +1,41 @@ +#include "src/scheduler.h" +#include <stdio.h> +#include <stdlib.h> +#include <sys/time.h> + +typedef struct { + int64_t start; + int64_t interval_ms; + int64_t created_at; +} TimerCtx; + +static int ticker(int64_t ts, pt_task_t *task) { + TimerCtx *ctx = task->udata; + + if (ts - ctx->start >= ctx->interval_ms) { + printf("[%lld ms] tick!\n", (long long)(ts - ctx->created_at)); + ctx->start = ts; + + if (ts - ctx->created_at >= 5000) { + printf("5 seconds elapsed, stopping.\n"); + free(ctx); + return SCHED_DONE; + } + } + + return SCHED_RUNNING; +} + +int main(void) { + TimerCtx *ctx = calloc(1, sizeof(TimerCtx)); + + struct timeval now; + gettimeofday(&now, NULL); + ctx->created_at = (int64_t)now.tv_sec * 1000 + now.tv_usec / 1000; + ctx->start = ctx->created_at; + ctx->interval_ms = 1000; + + sched_create(ticker, ctx); + sched_main(); + return 0; +} diff --git a/examples/example_removal.c b/examples/example_removal.c @@ -0,0 +1,65 @@ +#include "src/scheduler.h" +#include <stdio.h> +#include <stdlib.h> +#include <sys/time.h> + +typedef struct { + int64_t start; + int ticks; + int removed; +} ChildCtx; + +typedef struct { + int64_t start; + int64_t max_age_ms; + ChildCtx *child_ctx; + int ticks; +} ParentCtx; + +static int child_task(int64_t ts, pt_task_t *task) { + ChildCtx *ctx = task->udata; + if (ctx->removed) { + free(ctx); + return SCHED_DONE; + } + ctx->ticks++; + printf(" [child] tick %d (age: %lld ms)\n", ctx->ticks, (long long)(ts - ctx->start)); + return SCHED_RUNNING; +} + +static int parent_task(int64_t ts, pt_task_t *task) { + ParentCtx *ctx = task->udata; + ctx->ticks++; + int64_t age = ts - ctx->start; + printf("[parent] tick %d (elapsed: %lld ms)\n", ctx->ticks, (long long)age); + + if (age >= ctx->max_age_ms) { + printf("[parent] child is old enough, signaling it to stop.\n"); + ctx->child_ctx->removed = 1; + free(ctx); + return SCHED_DONE; + } + + return SCHED_RUNNING; +} + +int main(void) { + struct timeval now; + gettimeofday(&now, NULL); + int64_t ts = (int64_t)now.tv_sec * 1000 + now.tv_usec / 1000; + + ChildCtx *child_ctx = calloc(1, sizeof(ChildCtx)); + child_ctx->start = ts; + + ParentCtx *parent_ctx = calloc(1, sizeof(ParentCtx)); + parent_ctx->start = ts; + parent_ctx->max_age_ms = 2500; + parent_ctx->child_ctx = child_ctx; + + sched_create(child_task, child_ctx); + sched_create(parent_task, parent_ctx); + sched_main(); + + printf("Both tasks are gone. Goodbye.\n"); + return 0; +} diff --git a/src/scheduler.c b/src/scheduler.c @@ -0,0 +1,116 @@ +#include "scheduler.h" + +#include <stdlib.h> +#include <sys/select.h> +#include <sys/time.h> + +#ifndef NULL +#define NULL ((void *)0) +#endif + +pt_task_t *pt_first = NULL; +fd_set g_select_result; +static fd_set g_want_fds; + +pt_task_t *sched_create(pt_task_fn fn, void *udata) { + if (!fn) return NULL; + + pt_task_t *node = calloc(1, sizeof(pt_task_t)); + node->next = pt_first; + node->func = fn; + node->udata = udata; + node->is_active = 1; + pt_first = node; + + return node; +} + +int sched_remove(pt_task_t *task) { + if (!task) return 1; + + pt_task_t *curr = pt_first; + pt_task_t *prev = NULL; + + while (curr) { + if (curr == task) { + if (prev) { + prev->next = curr->next; + } else { + pt_first = curr->next; + } + free(curr); + return 0; + } + prev = curr; + curr = curr->next; + } + + return 1; +} + +int sched_has_data(int *in_fds) { + if (!in_fds || in_fds[0] == 0) return -1; + + for (int i = 1; i <= in_fds[0]; i++) { + int fd = in_fds[i]; + if (fd >= 0) { + FD_SET(fd, &g_want_fds); + } + } + + for (int i = 1; i <= in_fds[0]; i++) { + int fd = in_fds[i]; + if (fd >= 0 && FD_ISSET(fd, &g_select_result)) { + FD_CLR(fd, &g_select_result); + return fd; + } + } + + return -1; +} + +int sched_main(void) { + if (!pt_first) return 0; + + struct timeval tv; + int maxfd; + + for (;;) { + maxfd = -1; + for (int fd = 0; fd < FD_SETSIZE; fd++) { + if (FD_ISSET(fd, &g_want_fds)) { + if (fd > maxfd) maxfd = fd; + } + } + + if (maxfd < 0) { + tv.tv_sec = 0; + tv.tv_usec = 100000; + select(0, NULL, NULL, NULL, &tv); + } else { + tv.tv_sec = 0; + tv.tv_usec = 100000; + select(maxfd + 1, &g_want_fds, NULL, NULL, &tv); + g_select_result = g_want_fds; + FD_ZERO(&g_want_fds); + } + + struct timeval now; + gettimeofday(&now, NULL); + int64_t timestamp = (int64_t)now.tv_sec * 1000 + now.tv_usec / 1000; + + pt_task_t *task = pt_first; + while (task) { + pt_task_t *next = task->next; + task->is_active = (task->func(timestamp, task) == SCHED_RUNNING); + if (!task->is_active) { + sched_remove(task); + } + task = next; + } + + if (!pt_first) break; + } + + return 0; +} diff --git a/src/scheduler.h b/src/scheduler.h @@ -0,0 +1,28 @@ +#ifndef __FINWO_SCHEDULER_H__ +#define __FINWO_SCHEDULER_H__ + +#include <stdint.h> +#include <sys/select.h> + +#define SCHED_RUNNING 0 +#define SCHED_DONE 1 +#define SCHED_ERROR 2 + +typedef struct pt_task pt_task_t; + +typedef int (*pt_task_fn)(int64_t timestamp, pt_task_t *task); + +struct pt_task { + struct pt_task *next; + pt_task_fn func; + void *udata; + char is_active; + int maxfd; +}; + +pt_task_t *sched_create(pt_task_fn fn, void *udata); +int sched_remove(pt_task_t *task); +int sched_main(void); +int sched_has_data(int *in_fds); + +#endif // __FINWO_SCHEDULER_H__