A service is a unit that keeps running between requests. It owns I/O that a request handler cannot hold: child processes, pipes, sockets, timers. A request handler asks a service to do something by posting a message; the service answers through whatever channel fits, such as a WebSocket, the database, or the key-value cache. A pool of agents with their own processes, or a broker that keeps a connection open, is a service.
The shape of a service
Bearer owns the loop. Your code never blocks on a pipe or sleeps in a while. You write handlers, Bearer calls one for every event, each call is short and bounded, and the next event waits until the handler returns. This is the model of Erlang's gen_server and of Node's event loop, and it is why a service needs no locks.
Every service handler is a named SERVICE handler. The runtime's events are START, STOP, DATA, EXIT, and ACCEPT. Every other name is a signal your own code posts:
var ticks := 0\n\nfunction SERVICE:START(props : dval)\n{\n ticks = 0\n}\n\nfunction SERVICE:TICK(props : dval)\n{\n ticks = ticks + 1\n}\n\nfunction RENDER()\n{\n print("service unit ready\\n")\n}\n
Output
service unit ready\n
This unit is also an ordinary page: the RENDER handler answers requests while the SERVICE handlers answer events. A service is identified by its unit path, and there is at most one running service per unit.<")
print("/p>
State lives in unit variables
A service keeps one workspace for its whole life, so a top-level var is initialized once, when the service starts, and keeps its value across every handler call. That is where service state belongs:
var agents := {}\nvar seen := 0\n\nfunction SERVICE:START(props : dval)\n{\n agents.capacity = s64(props.count)\n}\n\nfunction SERVICE:DATA(props : dval)\n{\n seen = seen + 1\n agents.last = string(props.data)\n}\n\nfunction RENDER()\n{\n print("state in unit variables\\n")\n}\n
Every handler binds request implicitly, as handlers always do. For a service it is a synthetic request with no session, body, or form, built once for the life of the service. It carries the start arguments in request.props and the unit path in request.unit, and it lets request builtins such as ws_send() and unit_call() work inside a service. Do not use it as a place for state.
Start, stop, send
A request handler, a task, or another service talks to a service through four calls. service_start(unit, props) records that the service should run and starts it; calling it on a running service does nothing. service_stop(unit) posts a STOP and the worker exits once SERVICE:STOP returns. service_status(unit) reports state, pid, started_at, restarts, mailbox, and stale. service_send(unit, signal, props, delay_ms) posts a message and returns at once:
service_send("/agents/pool.capy", "assign", task: "review 42")
A send starts the service when ")
- print("it is not running, so callers never check liveness first. service_stop() therefore means stop now, not keep down: the next send brings the service back. A send returns false only when the mailbox is full or the unit does not exist. The mailbox is the kernel's datagram queue, about 500 frames per service; messages the service holds for a delay are not counted against it. A send to a listening service is one datagram, and a request never waits for the service itself.
delay_ms is the one-shot timer. A service that posts to itself with a delay gets a retry or a backoff:
service_send(request.unit, "respawn", {}, 2000)
Intervals
Inside a service, service_every(signal, interval_ms, props) delivers SERVICE:<signal> repeatedly. The loop owns the timer and re-arms it only after each delivery, so a slow handler stretches the interval instead of stacking ticks in the mailbox. One timer exists per signal name; calling it again replaces the interval, and an interval of zero cancels it.
function SERVICE:START(props : dval)\n{\n service_every("sweep", 30000)\n}
Ports
A port is a file descriptor the service owns and the loop watches. port_spawn(argv, env) starts a child with its standard streams as pipes, port_connect(host, port) opens a TCP connection, and port_listen(path) listens on a unix socket. All three return a handle. port_write(handle, data) queues bytes without blocking, and port_close(handle) closes the port and sends a spawned child SIGTERM.
Readiness arrives as handlers. SERVICE:DATA brings port, stream, and whatever bytes were r")
- print("eadable, at most one read per event, so line framing is the service's job. SERVICE:EXIT brings port and status after a child's last DATA. SERVICE:ACCEPT brings port and the accepted connection:
var buffers := {}\n\nfunction SERVICE:DATA(props : dval)\n{\n var key := string(props.port)\n buffers[key] = string(buffers[key], fallback: "") + string(props.data)\n}\n\nfunction SERVICE:EXIT(props : dval)\n{\n delete(buffers, string(props.port))\n}\n\nfunction RENDER()\n{\n print("port handlers ready\\n")\n}\n
Port calls exist only inside a service worker, because a descriptor needs an owner that outlives one invocation and a loop that watches it. Anywhere else they trap.
Failure
Each handler call runs under the invocation deadline. A handler that hangs is interrupted, that one event is dropped with an operator log line, and the service continues. What a service prints goes to the operator log, since there is no response to write to.
A service that exits without a stop is crashed. The supervisor restarts it with increasing delays, SERVICE:START runs again, and restarts in the status counts it. This is Erlang's advice: let it crash, and make START able to rebuild what the service needs, including its ports and intervals. Messages queued before the crash are gone, as they are in Erlang.
A service does not restart when its source changes. stale in the status says that the running workspace predates the source, so an admin page can offer a restart. Stop and start it, which recompiles the unit.
What a service is not
A service has no HTTP r")
- print("equest, no session, and no response. Do not pass a request to a service; pass identifiers and small options, as you would to a task, and let the service read what it needs. Do not wait for a service in a page request. Post the message, and let the service report through a channel the page already watches.