:title
12. Services

:content
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:

:example capy render
var ticks := 0

function SERVICE:START(props : dval)
{
	ticks = 0
}

function SERVICE:TICK(props : dval)
{
	ticks = ticks + 1
}

function RENDER()
{
	print("service unit ready\n")
}

:output
service unit ready

:content

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.

## 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:

:example capy render
var agents := {}
var seen := 0

function SERVICE:START(props : dval)
{
	agents.capacity = s64(props.count)
}

function SERVICE:DATA(props : dval)
{
	seen = seen + 1
	agents.last = string(props.data)
}

function RENDER()
{
	print("state in unit variables\n")
}

:content

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 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)
    {
    	service_every("sweep", 30000)
    }

## 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 readable, 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`:

:example capy render
var buffers := {}

function SERVICE:DATA(props : dval)
{
	var key := string(props.port)
	buffers[key] = string(buffers[key], fallback: "") + string(props.data)
}

function SERVICE:EXIT(props : dval)
{
	delete(buffers, string(props.port))
}

function RENDER()
{
	print("port handlers ready\n")
}

:content

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 request, 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.

:see-group
guides

:see
handler/service
capy-10-units-components-and-exports
capy-11-tasks-and-jobs
capy-13-errors-testing-and-style
