Skip to content

Service Lifecycle

This tutorial walks through every stage of a lifecycle so you can build intuition for what happens under the hood.

Prerequisites

  • xs installed and on your PATH (see )
  • Two terminal windows, both running Nushell with use xs.nu *

Serve

Start a store in terminal 1:

Terminal window
xs serve ./store

Monitor the stream

In terminal 2, start a live monitor so we can watch lifecycle frames as they appear:

Terminal window
.cat -f | each {
if $in.hash != null { insert content { .cas $in.hash } } else { } | print ($in | table -e)
}

Keep this running — every frame we discuss will show up here.

Spawn a service

Create a temporary file for the service to watch:

Terminal window
touch /tmp/log.txt

Now spawn a service that tails the file:

Terminal window
r#'{
run: {|| ^tail -F /tmp/log.txt | lines }
}'# | .append log.spawn

Your monitor shows two frames:

Terminal window
.cat | where { $in.topic | str starts-with "log." }
-#-+---topic-----+------------id-------------+-hash-+----------meta----------
0 | log.spawn | 03abc0000000000000000000a | ... |
1 | log.running | 03abc0000000000000000000b | | -------------+---------
| | | | source_id | 03ab...
| | | | -------------+---------
-#-+---topic-----+------------id-------------+-hash-+----------meta----------

log.spawn carries the script in CAS. log.running signals the pipeline is live.

See output

Write some lines to the file:

Terminal window
"hello\nworld\n" | save -a /tmp/log.txt

Two log.recv frames appear — one per line. Read the content back:

Terminal window
.last log.recv | .cas $in.hash
world

Each line the service produces is stored as a separate log.recv frame with its content in CAS.

Auto-restart

Remove the file so tail exits:

Terminal window
rm /tmp/log.txt

The monitor shows the service stop and restart:

Terminal window
.cat | where topic == "log.stopped" | last | get meta
-----------+--------------------------
reason | finished
source_id | 03ab...
-----------+--------------------------

The reason is finished — the pipeline exited on its own. After a 1-second pause, a new log.running frame appears and the service is alive again.

Recreate the file and write to it:

Terminal window
touch /tmp/log.txt
"back in business\n" | save -a /tmp/log.txt

A fresh log.recv frame appears. The service recovered automatically.

Hot reload

Append a new script to log.spawn while the service is running:

Terminal window
r#'{
run: {|| ^tail -F /tmp/log.txt | lines | each {|line| $"[LOG] ($line)" } }
}'# | .append log.spawn

The running service picks up the new spawn, stops the old pipeline, and starts the new one:

Terminal window
.cat | where topic == "log.stopped" | last | get meta
-----------+--------------------------
reason | update
source_id | 03ab...
update_id | 03ab...
-----------+--------------------------

The reason is update and update_id points to the new spawn frame. A log.running frame follows immediately.

Verify the new behavior:

Terminal window
"reloaded\n" | save -a /tmp/log.txt
Terminal window
.last log.recv | .cas $in.hash
[LOG] reloaded

The output now includes the [LOG] prefix from the updated script.

Terminate

Stop the service explicitly:

Terminal window
.append log.terminate

The monitor shows three frames in sequence:

  1. log.terminate — your request
  2. log.stopped with meta.reason set to terminate
  3. log.shutdown — the service loop has fully exited
Terminal window
.cat | where topic == "log.stopped" | last | get meta
-----------+--------------------------
reason | terminate
source_id | 03ab...
-----------+--------------------------

After log.shutdown, the dispatcher evicts the service. It will not restart.

Graceful shutdown

When xs itself stops (e.g. Ctrl+C), it emits an xs.stopping frame. Every running service sees this frame, interrupts its pipeline, and emits the usual .stopped / .shutdown sequence — just like a terminate, but with a distinct reason.

Restart the service so we can see this in action:

Terminal window
r#'{
run: {|| ^tail -F /tmp/log.txt | lines }
}'# | .append log.spawn

Wait for log.running to appear in the monitor, then press Ctrl+C in terminal 1 (where xs serve is running).

The monitor shows:

  1. xs.stopping — emitted by xs before exit
  2. log.stopped with meta.reason set to shutdown
  3. log.shutdown — the service loop has fully exited
Terminal window
.cat | where topic == "log.stopped" | last | get meta
-----------+--------------------------
reason | shutdown
source_id | 03ab...
-----------+--------------------------

xs waits up to a few seconds for all services to finish before exiting. This gives services a chance to flush output and clean up resources.

Recap

SuffixEmitted when
.spawnYou append a script to start (or reload) the service
.runningThe pipeline is live and producing output
.recvEach line or chunk of output from the pipeline
.stoppedThe pipeline exited — check meta.reason for why
.shutdownThe service loop has fully exited (terminate or shutdown)

Stopped reasons:

ReasonWhat happenedWhat follows
finishedPipeline exited on its ownAuto-restart after 1s
errorPipeline failedAuto-restart after 1s
updateNew .spawn frame arrivedImmediate restart
terminate.terminate frame arrived.shutdown, no restart
shutdownxs.stopping frame arrived.shutdown, no restart