|
|
|
@@ -2,8 +2,8 @@ function COMPONENT(props : dval)
|
|
|
|
|
{
|
|
|
|
|
print("<div class=\"doc-detail-layout\"><main class=\"content doc-page doc-detail\"><div class=\"doc-title\"><h2>12. Services</h2></div><div class=\"doc-section content\"><p>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.</p><h2>The shape of a service</h2><p>Bearer owns the loop. Your code never blocks on a pipe or sleeps in a <code>while</code>. 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 <code>gen_server</code> and of Node's event loop, and it is why a service needs no locks.</p><p>Every service handler is a named <code>SERVICE</code> handler. The runtime's events are <code>START</code>, <code>STOP</code>, <code>DATA</code>, <code>EXIT</code>, and <code>ACCEPT</code>. Every other name is a signal your own code posts:</p><pre><code class=\"language-capy\">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</code></pre><p><strong>Output</strong></p><pre><code class=\"language-text\">service unit ready\n</code></pre><p>This unit is also an ordinary page: the <code>RENDER</code> handler answers requests while the <code>SERVICE</code> handlers answer events. A service is identified by its unit path, and there is at most one running service per unit.<")
|
|
|
|
|
print("/p><h2>State lives in unit variables</h2><p>A service keeps one workspace for its whole life, so a top-level <code>var</code> is initialized once, when the service starts, and keeps its value across every handler call. That is where service state belongs:</p><pre><code class=\"language-capy\">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</code></pre><p>Every handler binds <code>request</code> 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 <code>request.props</code> and the unit path in <code>request.unit</code>, and it lets request builtins such as <code>ws_send()</code> and <code>unit_call()</code> work inside a service. Do not use it as a place for state.</p><h2>Start, stop, send</h2><p>A request handler, a task, or another service talks to a service through four calls. <code>service_start(unit, props)</code> records that the service should run and starts it; calling it on a running service does nothing. <code>service_stop(unit)</code> posts a <code>STOP</code> and the worker exits once <code>SERVICE:STOP</code> returns. <code>service_status(unit)</code> reports <code>state</code>, <code>pid</code>, <code>started_at</code>, <code>restarts</code>, <code>mailbox</code>, and <code>stale</code>. <code>service_send(unit, signal, props, delay_ms)</code> posts a message and returns at once:</p><p>service_send("/agents/pool.capy", "assign", task: "review 42")</p><p>A send starts the service when ")
|
|
|
|
|
print("it is not running, so callers never check liveness first. <code>service_stop()</code> 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.</p><p><code>delay_ms</code> is the one-shot timer. A service that posts to itself with a delay gets a retry or a backoff:</p><p>service_send(request.unit, "respawn", {}, 2000)</p><h2>Intervals</h2><p>Inside a service, <code>service_every(signal, interval_ms, props)</code> delivers <code>SERVICE:<signal></code> 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.</p><p>function SERVICE:START(props : dval)\n{\n service_every("sweep", 30000)\n}</p><h2>Ports</h2><p>A port is a file descriptor the service owns and the loop watches. <code>port_spawn(argv, env)</code> starts a child with its standard streams as pipes, <code>port_connect(host, port)</code> opens a TCP connection, and <code>port_listen(path)</code> listens on a unix socket. All three return a handle. <code>port_write(handle, data)</code> queues bytes without blocking, and <code>port_close(handle)</code> closes the port and sends a spawned child <code>SIGTERM</code>.</p><p>Readiness arrives as handlers. <code>SERVICE:DATA</code> brings <code>port</code>, <code>stream</code>, and whatever bytes were r")
|
|
|
|
|
print("eadable, at most one read per event, so line framing is the service's job. <code>SERVICE:EXIT</code> brings <code>port</code> and <code>status</code> after a child's last <code>DATA</code>. <code>SERVICE:ACCEPT</code> brings <code>port</code> and the accepted <code>connection</code>:</p><pre><code class=\"language-capy\">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</code></pre><p>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.</p><h2>Failure</h2><p>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.</p><p>A service that exits without a stop is crashed. The supervisor restarts it with increasing delays, <code>SERVICE:START</code> runs again, and <code>restarts</code> in the status counts it. This is Erlang's advice: let it crash, and make <code>START</code> able to rebuild what the service needs, including its ports and intervals. Messages queued before the crash are gone, as they are in Erlang.</p><p>A service does not restart when its source changes. <code>stale</code> 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.</p><h2>What a service is not</h2><p>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.</p></div><nav class=\"guide-navigation\" aria-label=\"Capy guide navigation\"><div class=\"guide-previous\">Previous: <a href=\"/doc/guide/tasks-and-jobs/\">11. Tasks and jobs</a></div><div class=\"guide-index\"><a href=\"/doc/\">Guide index</a></div><div class=\"guide-next\">Next: <a href=\"/doc/guide/errors-testing-and-style/\">13. Errors and testing</a></div></nav></main><aside class=\"doc-section see-also\"><h3>See also</h3><a class=\"related-link\" href=\"/doc/guide/install-and-first-program/\">1. Getting started</a><a class=\"related-link\" href=\"/doc/guide/source-structure-and-syntax/\">2. Basic syntax</a><a class=\"related-link\" href=\"/doc/guide/values-and-types/\">3. Types</a><a class=\"related-link\" href=\"/doc/guide/expressions-and-control-flow/\">4. Expressions and control flow</a><a class=\"related-link\" href=\"/doc/guide/functions-and-closures/\">5. Functions</a><a class=\"related-link\" href=\"/doc/guide/strings-and-markup/\">6. Strings and HTML</a><a class=\"related-link\" href=\"/doc/guide/collections-and-records/\">7. Arrays and structs</a><a class=\"related-link\" href=\"/doc/guide/dynamic-values/\">8. Dynamic values</a><a class=\"related-link\" href=\"/doc/guide/web-handlers-and-requests/\">9. Handling a web request</a><a class=\"related-link\" href=\"/doc/guide/units-components-and-exports/\">10. Units and components</a><a class=\"related-link\" href=\"/doc/guide/tasks-and-jobs/\">11. Tasks and jobs</a><a class=\"related-link\" href=\"/doc/guide/errors-testing-and-style/\">13. Errors and testing</a><a class=")
|
|
|
|
|
print("\"related-link\" href=\"/doc/guide/coming-from-react/\">14. Coming from React</a><a class=\"related-link\" href=\"/doc/handler/service/\">SERVICE</a></aside></div>")
|
|
|
|
|
print("it is not running, so callers never check liveness first. <code>service_stop()</code> therefore means stop now, not keep down: the next send brings the service back. A send returns false only when the mailbox is full, the message is larger than one datagram, 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.</p><p><code>delay_ms</code> is the one-shot timer. A service that posts to itself with a delay gets a retry or a backoff:</p><p>service_send(request.unit, "respawn", {}, 2000)</p><h2>Intervals</h2><p>Inside a service, <code>service_every(signal, interval_ms, props)</code> delivers <code>SERVICE:<signal></code> 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.</p><p>function SERVICE:START(props : dval)\n{\n service_every("sweep", 30000)\n}</p><h2>Ports</h2><p>A port is a file descriptor the service owns and the loop watches. <code>port_spawn(argv, env)</code> starts a child with its standard streams as pipes, <code>port_connect(host, port)</code> opens a TCP connection, and <code>port_listen(path)</code> listens on a unix socket. All three return a handle. <code>port_write(handle, data)</code> queues bytes without blocking, and <code>port_close(handle)</code> closes the port and sends a spawned child <code>SIGTERM</code>.</p><p>Readiness arrives as handlers. <code>SERVICE:DATA</code> brings <code>port</code>, <cod")
|
|
|
|
|
print("e>stream</code>, and whatever bytes were readable, at most one read per event, so line framing is the service's job. <code>SERVICE:EXIT</code> brings <code>port</code> and <code>status</code> after a child's last <code>DATA</code>. <code>SERVICE:ACCEPT</code> brings <code>port</code> and the accepted <code>connection</code>:</p><pre><code class=\"language-capy\">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</code></pre><p>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.</p><h2>Failure</h2><p>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.</p><p>A service that exits without a stop is crashed. The supervisor restarts it with increasing delays, <code>SERVICE:START</code> runs again, and <code>restarts</code> in the status counts it. This is Erlang's advice: let it crash, and make <code>START</code> able to rebuild what the service needs, including its ports and intervals. Messages queued before the crash are gone, as they are in Erlang.</p><p>A service does not restart when its source changes. <code>stale</code> 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.</p><h2>What a ser")
|
|
|
|
|
print("vice is not</h2><p>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.</p></div><nav class=\"guide-navigation\" aria-label=\"Capy guide navigation\"><div class=\"guide-previous\">Previous: <a href=\"/doc/guide/tasks-and-jobs/\">11. Tasks and jobs</a></div><div class=\"guide-index\"><a href=\"/doc/\">Guide index</a></div><div class=\"guide-next\">Next: <a href=\"/doc/guide/errors-testing-and-style/\">13. Errors and testing</a></div></nav></main><aside class=\"doc-section see-also\"><h3>See also</h3><a class=\"related-link\" href=\"/doc/guide/install-and-first-program/\">1. Getting started</a><a class=\"related-link\" href=\"/doc/guide/source-structure-and-syntax/\">2. Basic syntax</a><a class=\"related-link\" href=\"/doc/guide/values-and-types/\">3. Types</a><a class=\"related-link\" href=\"/doc/guide/expressions-and-control-flow/\">4. Expressions and control flow</a><a class=\"related-link\" href=\"/doc/guide/functions-and-closures/\">5. Functions</a><a class=\"related-link\" href=\"/doc/guide/strings-and-markup/\">6. Strings and HTML</a><a class=\"related-link\" href=\"/doc/guide/collections-and-records/\">7. Arrays and structs</a><a class=\"related-link\" href=\"/doc/guide/dynamic-values/\">8. Dynamic values</a><a class=\"related-link\" href=\"/doc/guide/web-handlers-and-requests/\">9. Handling a web request</a><a class=\"related-link\" href=\"/doc/guide/units-components-and-exports/\">10. Units and components</a><a class=\"related-link\" href=\"/doc/guide/tasks-and-jobs/\">11. Tasks and jobs</a><a class=\"related-link\" href=\"/doc/guide/errors-testing-and-s")
|
|
|
|
|
print("tyle/\">13. Errors and testing</a><a class=\"related-link\" href=\"/doc/guide/coming-from-react/\">14. Coming from React</a><a class=\"related-link\" href=\"/doc/handler/service/\">SERVICE</a></aside></div>")
|
|
|
|
|
}
|