Context Design
The csilk_ctx_t (request context) is the central object in csilk. It carries the request state, response buffer, handler chain, WebSocket callbacks, and an Arena allocator across the entire request lifecycle.
Context Structure
graph TB
subgraph "csilk_ctx_t"
HW["handler_index + handlers[]\n(Middleware chain state)"]
AB["aborted (chain termination flag)"]
JB["jump_buffer\n(setjmp/longjmp seat)"]
subgraph Request["csilk_request_t"]
RM["method (GET/POST/etc)"]
RP["path (/api/v1/users)"]
RB["body + body_len"]
RH["headers (hash map, 64 buckets)"]
RQ["query_params (hash map)"]
end
subgraph Response["csilk_response_t"]
RS["status (HTTP code)"]
RBODY["body + body_len"]
RH2["headers (hash map)"]
end
subgraph Params["Path Params"]
P1["params[0..19]"]
end
subgraph Storage["User KV Storage"]
ST["storage_head (linked list)"]
end
AR["arena (bump allocator)"]
WS["is_websocket + on_ws_message"]
SE["is_sse"]
AS["is_async + response_started"]
end
Context Lifecycle
stateDiagram-v2
[*] --> Pooled: pool_get() acquires client
state Pooled {
[*] --> Created: uv_tcp_init() + uv_accept()
}
state Created {
[*] --> Init: csilk_arena_new()
[*] --> Init: parser.data = client
Init --> Parsing: uv_read_start()
state Parsing {
[*] --> Headers: llhttp callbacks
Headers --> Body: on_body() called
Body --> Complete: on_message_complete()
}
}
Parsing --> Matched: csilk_router_match_ctx()
Matched --> Chaining: Global middleware prepended
Chaining --> Executing: csilk_next(ctx)
state Executing {
[*] --> handler1: Handler 1 (e.g. Recovery)
handler1 --> handler2: csilk_next()
handler2 --> handler3: csilk_next()
handler3 --> handlerN: csilk_next()
handlerN --> [*]
--
[*] --> Panic: longjmp (csilk_panic)
Panic --> [*]: Returns to Recovery
}
Executing --> Sending: Response body set OR aborted
Sending --> Cleanup: _csilk_send_response()
state Cleanup {
[*] --> Reset: csilk_arena_reset()
Reset --> ClearParams: Free params
ClearParams --> ClearBody: Free body
ClearBody --> ResetFlags: Reset flags
ResetFlags --> [*]
}
Cleanup --> KeepAlive: Connection: keep-alive
KeepAlive --> Parsing: Wait for next request
Cleanup --> Closing: Connection: close
Closing --> Freed: csilk_arena_free() + pool_put(client)
Freed --> [*]
Multi-Thread Safety
In multi-worker mode (configured via worker_threads > 1), the on_new_connection callback and thus pool_get/pool_put execute on whichever event loop accepted the connection. A dedicated pool_mutex in csilk_server_s serializes access to the client object free list (client_pool / client_pool_count), preventing two threads from acquiring the same csilk_client_t.
Handler Chain Execution
The handler chain uses a simple index-based iterator pattern:
sequenceDiagram
participant S as csilk_next()
participant C as csilk_ctx_t
participant H0 as Handler[0] (Recovery)
participant H1 as Handler[1] (Auth)
participant H2 as Handler[2] (Business)
Note over C: handler_index = -1
S->>C: handler_index++ → 0
S->>H0: handlers[0](ctx)
H0->>H0: setjmp(jump_buffer)
H0->>S: csilk_next(ctx)
S->>C: handler_index++ → 1
S->>H1: handlers[1](ctx)
H1->>H1: Check auth token
H1->>S: csilk_next(ctx)
S->>C: handler_index++ → 2
S->>H2: handlers[2](ctx)
H2->>H2: Process business logic
H2->>C: csilk_string(ctx, 200, "OK")
Note over C: Response set, no more csilk_next()
Note over S: Return path: H2 → H1 → H0
Note over H1: Run post-auth logic (e.g., audit)
Note over H0: Run post-recovery logic (e.g., timing)
Header Hash Map
Headers use a case-insensitive DJB2 hash map with 16 fixed buckets and chaining:
graph TB
subgraph "Header Hash Map (16 buckets)"
B0["bucket[0]"] --> H1["Key: host\nValue: localhost:8080"]
H1 --> H2["Key: content-type\nValue: application/json"]
B1["bucket[1]"]
B2["bucket[2]"]
B3["bucket[3]"] --> H3["Key: authorization\nValue: Bearer xyz"]
B4["bucket[4..15]"]
end
subgraph "Lookup 'Content-Type'"
IN["csilk_get_header(ctx, 'Content-Type')"]
IN --> HASH["djb2_hash('content-type') % 16 → bucket 0"]
HASH --> SCAN["strcasecmp walk list"]
SCAN --> MATCH["Found: 'application/json'"]
end
Response Generation Flow
flowchart TB
subgraph "Sync Response"
SYNC_H["Handler calls csilk_string/json/redirect"] --> SYNC_SET["ctx->response.status = 200\nctx->response.body = 'OK'"]
SYNC_SET --> SYNC_RET["Handler returns without calling csilk_next()"]
SYNC_RET --> SYNC_SEND["_csilk_send_response(ctx)\nSerializes HTTP + headers + body\nuv_write() to socket"]
end
subgraph "Async / Streaming Response"
ASYNC_H["Handler sets ctx->is_async = 1"] --> ASYNC_RET["Returns control to libuv"]
ASYNC_RET --> ASYNC_WAIT["Later: csilk_response_write()"]
ASYNC_WAIT --> ASYNC_SEND["send_chunked_headers() (first call)\nwrite_chunk_frame() (per chunk)\ncsilk_response_end() (terminal chunk)"]
end
subgraph "WebSocket Response"
WS_H["Handler calls csilk_ws_handshake()"] --> WS_101["Status 101 Switching Protocols\nctx->is_websocket = 1"]
WS_101 --> WS_READ["uv_read_start() continues\nBut parser switches to csilk_ws_parse_frame()"]
end
Context Cleanup
Between requests (keep-alive), csilk_ctx_cleanup() efficiently resets state:
csilk_arena_reset()- O(1) pointer reset; all per-request allocations freedfree()path parameters (keys/values)free()request bodyfree()request pathmemset()header/query/response maps to zero- Reset all flags:
aborted,is_websocket,is_sse,is_async,response_started - Reset
handler_index = -1,storage_head = NULL