Skip to content

Begin typing to search this documentation.

2. Inside a Hoplite worker

Hoplite embeds a Hara runtime into each Nginx worker. Nginx owns network event readiness; Hara owns application values and execution. The boundary between them is narrow and explicit.

Nginx worker
├─ request routing and socket readiness
├─ one worker-local Hara runtime
│ ├─ prepared handler calls
│ ├─ Promises and suspended coroutines
│ └─ stream and Relay values
└─ trusted host providers
├─ request and response data plane
└─ worker-local RTC sockets and timers

Production workers load deterministic application artifacts rather than parsing HAL source for each request. The worker validates the application, resolves handler Vars, and prepares calls before publishing readiness. Request dispatch therefore begins from an already known function and route.

This is both a performance mechanism and a maintenance property. Startup owns configuration failure; request handling owns application behavior. The two failure domains do not blur together.

A handler begins as an ordinary call:

(defn health [_request]
{:status 200
:headers {"content-type" "application/json"}
:body "{\"ready\":true}\n"})

If it returns without suspension, Hoplite uses the direct response path. It does not allocate a Promise/work record merely because the runtime also supports asynchronous handlers.

When a handler awaits a pending host operation, Hara retains the continuation and returns control to the Nginx event loop:

(defn delayed [_request]
(co/await (Host/call "nginx" "sleep" [25]))
{:status 200 :body "resumed\n"})

The worker can service other ready events while the operation is pending. When the provider completes it, execution resumes in the owning runtime. This is concurrency without assigning an operating-system thread to every waiting request.

Client disconnect, timeout, response completion, worker shutdown, and provider failure must release retained continuations and native handles. A request body handle cannot outlive its request. An RTC handle cannot outlive its worker. A streaming response must stop pulling when Nginx can no longer send the result.

Keeping these transitions explicit prevents the common maintenance failure in which the success path is clear but shutdown behavior is distributed across callbacks.

Workers scale independent request execution across CPU cores. They do not form a shared Hara heap. Increase worker count for parallel serving, but place durable or cross-worker state behind an explicit service. Worker locality avoids locking inside ordinary application values; it also means that affinity and state placement must be designed rather than assumed.

Worked example: direct and suspended handlers

Section titled “Worked example: direct and suspended handlers”

These handlers return the same response shape, but only one can suspend:

(defn current-status [_request]
{:status 200 :body "ready\n"})
(defn status-after-check [_request]
(co/await (Host/call "nginx" "sleep" [5]))
{:status 200 :body "ready\n"})

current-status stays on the direct path. status-after-check begins synchronously and allocates retained asynchronous work only if the host Promise is pending.

Worked example: keep portable state separate

Section titled “Worked example: keep portable state separate”
(def session-summary
{:peer-id "peer-17"
:connected-at 1720000000})

This map may be serialized or stored. The live RTC handle used to produce it may not: it belongs to one worker and one native session lifetime. Separating the portable summary from live authority prevents accidental cross-worker use.

Next: Standard Hara application design.