DEV Community

Cover image for Dev servers as graph nodes: readiness instead of sleep
VX
VX

Posted on Originally published at vznjs.github.io

Dev servers as graph nodes: readiness instead of sleep

Some tasks do not finish. A dev server, a watcher, a database for the
integration tests. Most runners either refuse to model them or model
them as "run this and never wait," which leaves the interesting problem
to a sleep 5 in a shell script.

vx models them as persistent tasks and gives them the two things a
graph node needs: a notion of ready, and a guaranteed end.

dev: {
  exec: {
    command: 'vite',
    persistent: { readyWhen: 'Local:' },
    timeout: 30_000,
  },
},
e2e: {
  dependsOn: ['dev'],
  exec: { command: 'playwright test' },
},
Enter fullscreen mode Exit fullscreen mode

Ready is a line of output

readyWhen is a regex matched against the task's output. The moment a
line matches, the task is ready and its dependents are released; e2e
starts against a server that is actually listening. The match also sees
a trailing partial line, so a prompt with no newline (Listening on
:3000
) works.

exec.timeout bounds the wait. If the pattern never appears, vx kills
the process and fails the task instead of hanging the run. A persistent
task with no readyWhen is ready on spawn, which is right for a daemon
nothing gates on.

No polling loop, no wait-on package, no guessed number of seconds.
The server tells you when it is up, and you wrote down what it says.

The end is guaranteed

When the run finishes, every persistent task is sent SIGTERM, given a
grace period, then SIGKILLed if it is still there. Nothing is left
listening on a port after vx run e2e returns, whether the tests
passed, failed, timed out or you pressed Ctrl-C. That last case is
its own post.

Readiness escalates the same way. A server that ignores the timeout's
SIGTERM gets SIGKILL after the grace, so a wedged process cannot
hold the run open.

In the foreground

vx run dev with nothing depending on it is the other common case: you
want the server in your terminal and you want vx to stay attached.
When the requested tasks are persistent, vx stays in the foreground
until one of them exits, then reports which one and stops the rest:

vx: web#dev exited with code 1; stopping 2 other persistent tasks
Enter fullscreen mode Exit fullscreen mode

A crashed server makes vx exit 1.

What it costs elsewhere

A persistent task is pinned to this machine, and so is everything that
depends on it: a remote worker cannot reach a port on your laptop, and
the placement stage knows that without being told. A persistent task
under a sandbox gets the same grants and walls as any other, but no
violation report, because the report reads the trace after the child
exits and a server exits only when the run tears it down.

Persistent tasks are not cached. They have no end state to store. Their
dependents can be; e2e's key includes dev's key, so a config change
to the server re-runs the tests. And a server only its cache hits need is
never started: when every dependant is a confirmed hit, the run does not
boot it.

The guide is Configure › Dev tasks.


Originally published on the vx blog. vx is an MIT task runner and build cache for JS monorepos: github.com/vznjs/vx.

Written with AI assistance.

Top comments (0)