Real-time task scheduler with deadline awareness, priority ordering, dependency resolution, and resource constraints. Written in C with POSIX threads.
A single scheduler thread manages the ready queue on a 100ms tick. A fixed-size worker thread pool (default 4) executes tasks. The scheduler never executes tasks directly — it delegates to workers, keeping scheduling latency predictable regardless of task duration.
┌──────────────────┐ ┌──────────────┐
│ Scheduler Thread │────▶│ Ready Queue │ (min-heap: deadline, then priority)
│ 100ms tick │ │ O(log n) │
└──────────────────┘ └──────┬───────┘
│ assign
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Worker 0 │ │ Worker 1 │ ... │ Worker N │
└────────────┘ └────────────┘ └────────────┘
INITIALIZED ──▶ RUNNING ──▶ PAUSED
│
▼
SHUTTING_DOWN ──▶ STOPPED
- Only
RUNNINGexecutes tasks PAUSEDqueues tasks but does not run themSHUTTING_DOWNfinishes current tasks, then stopsSTOPPEDis terminal
| Decision | Choice | Rationale |
|---|---|---|
| Scheduling algorithm | EDF (Earliest Deadline First) with priority tiebreaker | Optimal for deadline-constrained single-processor scheduling. Priority alone risks missing deadlines for lower-priority but more urgent tasks. |
| Ready queue | Binary min-heap | O(log n) insert and extract. Simple, cache-friendly, no external dependencies. |
| Worker threads | Fixed pool (default 4) | Avoids dynamic thread creation overhead. Matches typical core count. Configurable via --workers. |
| Timing | clock_gettime(CLOCK_MONOTONIC) |
Nanosecond precision, immune to wall-clock adjustments. |
| Synchronization | Per-component mutexes with documented lock ordering | Prevents deadlocks. Workers use per-worker condition variables to avoid thundering herd. |
| Memory | Pre-allocated fixed pools | No malloc/free during operation. Deterministic memory footprint. |
| Shutdown | 3-phase graceful | Soft stop → timeout wait → force kill. State persisted to disk for crash recovery. |
# Debug build (default)
make
# Release build (optimized)
make release
# Run tests
make test
# Memory leak check
make valgrind
# Clean
make cleanRequires: GCC, pthreads, POSIX. Compiles with -Wall -Wextra -Werror -pthread.
./scheduler --workers 4 --max-tasks 1000 --log-level INFOOptions:
--workers N— Worker thread count (default: 4)--max-tasks N— Maximum task pool size (default: 1000)--log-level LEVEL— DEBUG, INFO, WARN, ERROR (default: INFO)--save-state-dir DIR— Directory for state persistence--log-file FILE— Log output file--port N— Monitoring port (default: 9001)
Compile with -DPTSS_NO_MAIN to exclude main(). Link against all .o files.
#include "protocol.h"
int my_task(void *arg) {
// Task work here
return 0; // 0 = success, non-zero = failure
}
int main(void) {
scheduler_config_t cfg = {0};
cfg.num_workers = 4;
scheduler_init(&cfg);
scheduler_start();
// Add tasks
uint32_t id = scheduler_add_task("my-task", my_task, NULL,
/*priority=*/5, /*timeout=*/30,
/*deadline=*/60);
// Add dependencies
scheduler_add_dependency(id_b, id_a);
// Query status
task_status_report_t status = monitor_get_task_status(id);
scheduler_metrics_t metrics = monitor_get_metrics();
// Shutdown
scheduler_shutdown();
scheduler_destroy();
}| Function | Description |
|---|---|
scheduler_add_task(name, func, arg, priority, timeout, deadline) |
Register a task. Returns task ID. |
scheduler_add_dependency(task_id, depends_on) |
Add dependency: task_id runs after depends_on completes. |
scheduler_cancel_task(task_id) |
Cancel a pending/running task. |
| Function | Description |
|---|---|
scheduler_init(config) |
Initialize with configuration. |
scheduler_start() |
Begin scheduling (INITIALIZED → RUNNING). |
scheduler_pause() |
Pause scheduling (RUNNING → PAUSED). |
scheduler_resume() |
Resume scheduling (PAUSED → RUNNING). |
scheduler_shutdown() |
Graceful shutdown with state persistence. |
scheduler_destroy() |
Free all resources. |
| Function | Description |
|---|---|
monitor_get_state() |
Current scheduler state. |
monitor_get_task_status(id) |
Status report for a specific task. |
monitor_get_metrics() |
Aggregate performance metrics. |
monitor_print_dashboard() |
Print human-readable status to stdout. |
| File | Lines | Purpose |
|---|---|---|
protocol.h |
~300 | Structures, constants, error codes, function declarations |
scheduler.c |
~790 | Main scheduling loop, task management, signal handling, CLI |
executor.c |
~300 | Worker thread pool, task execution, crash recovery |
heap.c |
~200 | Min-heap priority queue (thread-safe) |
log.c |
~250 | Structured logging, execution log, state persistence |
monitor.c |
~150 | Status queries, metrics, dashboard |
test_ptss.c |
~610 | 7-test comprehensive test suite |
TEST 1: Basic Scheduling (Priority Order) — PASS
TEST 2: Deadline Enforcement — PASS
TEST 3: Concurrent Execution (Thread Pool) — PASS
TEST 4: Dependency Chain (A → B → C) — PASS
TEST 5: Graceful Shutdown — PASS
TEST 6: Error Recovery (Failing Tasks) — PASS
TEST 7: High Load (100 tasks, 4 workers) — PASS
Throughput: ~40 tasks/sec (4 workers, 1ms tasks)
Peak queue depth: 96
Avg execution time: 1.1 ms
- Single machine only — no network distribution
- No task migration — once assigned to a worker, runs to completion
- Dependencies are direct only — no transitive resolution
- CPU monitoring is best-effort (Linux
/procbased, not guaranteed) - Maximum 1000 tasks and 16 workers (compile-time constants)
- Task function pointers cannot be persisted — must be re-registered after restore
To prevent deadlocks, locks are always acquired in this order:
task_pool_lockready_queue.lockrunning_tasks_lockstate_lockworker[i].locktask[i].locklog_lock
Never acquire a higher-numbered lock while holding a lower-numbered lock.
| Code | Name | Meaning |
|---|---|---|
| 0 | PTSS_OK |
Success |
| -1 | PTSS_ERR_INVALID_ARG |
Invalid argument |
| -2 | PTSS_ERR_FULL |
Pool or queue is full |
| -3 | PTSS_ERR_NOT_FOUND |
Task not found |
| -4 | PTSS_ERR_INVALID_STATE |
Invalid state transition |
| -5 | PTSS_ERR_DEPENDENCY |
Dependency not satisfied |
| -6 | PTSS_ERR_TIMEOUT |
Operation timed out |
| -7 | PTSS_ERR_RESOURCE |
Resource limit exceeded |
| -8 | PTSS_ERR_IO |
File I/O error |
| -9 | PTSS_ERR_THREAD |
Thread operation error |
| -10 | PTSS_ERR_MEMORY |
Memory allocation error |
This project is licensed under the MIT License.
- Telegram:
@wailcs - Matrix:
@wailcs:matrix.org - Discord:
@wailcs - X:
@wailcsx - Email:
wail@tutamail.com
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEaX31qRYJKwYBBAHaRw8BAQdA4NAOMdNRqRdEl5VTzEDgWh9PbzWjMk/QZ7Do
htbIPtS0GHdhaWwgPHdhaWxAdHV0YW1haWwuY29tPoiZBBMWCgBBFiEE2hDNLVN+
cOGq0wcon7QDDWC8ApoFAml99akCGwMFCQWjmoAFCwkIBwICIgIGFQoJCAsCBBYC
AwECHgcCF4AACgkQn7QDDWC8AppwpgEApF5DddWsIJ0Q+rbp+DVax9mw/Bdnj1ip
qKWX5MGqeLMA/3pm620peWjlP113AwPcKrfgPB/BJcNwc8DoYUVAMDMMuDgEaX31
qRIKKwYBBAGXVQEFAQEHQDC5PkpGUHv1Pd0rObwaqHg2rzi6xGoZMTp35PqnPMJJ
AwEIB4h+BBgWCgAmFiEE2hDNLVN+cOGq0wcon7QDDWC8ApoFAml99akCGwwFCQWj
moAACgkQn7QDDWC8ApqXgQEA1eoTiQbFX17t3PeIL1Bxzz2jZfuMEHu0aAkQ+MOF
JCMBAMKcc4AfCxQgaj9nHoW4jYs7VCap4+F2R8nN/7JLbcAG
=Ln+f
-----END PGP PUBLIC KEY BLOCK-----
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJum3lcA0XQXBf93nSxYZAIOkfdqTmeAIeP4R6HWqfdw