I started building Kraken (an HTTP server in C) and quickly realized I needed data structures the C standard library doesn't give you: a hashmap for routing, a queue for the thread pool's task buffer, a linked list underneath it. Rather than reaching for a different dependency for each one, I built them up into a small standalone library.
Collections
The hashmap is open-addressed with Robin Hood hashing: on a collision, the entry that has probed farther from its ideal slot takes the spot, which evens out probe lengths and keeps lookups cache-friendly as the table fills. It ships SipHash-2-4 and MurmurHash3 as built-in hash functions and takes a custom comparator, so any struct can be a key. This is what backs Kraken's routing: register a path and a handler, and lookups are O(1).
owl_hashmap_t *map = owl_hashmap_new(
sizeof(struct user), 0, 0, 0,
user_hash, user_compare, NULL, NULL
);
owl_hashmap_set(map, &(struct user){.name = "Alice", .age = 30});
struct user *u = owl_hashmap_get(map, &(struct user){.name = "Alice"});The queue is a capacity-bounded FIFO built on the linked list: enqueue appends at the tail, dequeue pops the head, and it rejects new items once it hits the configured limit. That's the shape a producer-consumer buffer wants, and it's what sits between the thread pool's two sides: the accept loop pushes connection tasks in, the workers pull them out. The linked list itself is a plain singly-linked list, the substrate for anything that grows without a known size upfront.
Thread pool
The thread pool spins up a fixed set of worker threads over a shared task queue, coordinated by a mutex and a condition variable (so idle workers sleep instead of spinning on an empty queue). The lifecycle is the interesting part:
enqueuelocks the queue, appends the task, unlocks, and signals the condition variable;- a sleeping worker wakes, dequeues a task under the lock, releases the lock, and runs the task outside it so the queue stays open for the others;
- on shutdown, an active flag flips to zero and the condition variable is broadcast to wake every worker;
- the workers keep draining until the queue is empty, then exit, and the pool joins all of them before it frees anything.
That drain-then-join is what makes shutdown safe: tasks already in the queue still run to completion instead of being dropped.
owl_thread_pool_t *tp = owl_thread_pool_init(4);
owl_worker_task_t task = owl_worker_task_init(process_request, NULL);
owl_thread_pool_enqueue_task(tp, &task);
owl_thread_pool_free(tp);This was the backbone of Kraken's concurrency model. Instead of a thread per connection, the server dispatches into a fixed pool, which caps concurrency and rules out unbounded thread creation under load.
Design decisions
Owl's own collections own what you put in them. Insert copies the value's bytes into a heap allocation the container holds; remove unlinks the node, frees the node, and hands the data pointer back for the caller to free. Ownership transfers explicitly on the way out, the same discipline Rust encodes in its type system, here kept by convention. The copy on insert isn't free, and a value that never outlives its scope is better left on the stack, but a container has to survive the frame that filled it, and owning its storage is what keeps that lifetime unambiguous.
The one piece I didn't write is the hashmap core: it's adapted from tidwall/hashmap.c, which is also where the Robin Hood implementation and the two hash functions come from. The routing layer and the API around it are mine, and replacing the backing implementation with my own is a planned follow-up. There's nothing else to pull in: the library builds from its own source with no third-party dependencies to link.