Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- **A workspace gets one reusable OpenSSH connection instead of a new one per
run.** `dl <ws> -- <cmd>` typed at a terminal has gone into the container over
OpenSSH since M3, through the host alias `devpod up` publishes. It just opened a
fresh connection every time, and connection setup is nearly the whole cost of
that trip. It now carries `ControlMaster=auto`, a derived `ControlPath` and
`ControlPersist=60`: the first trip into a live workspace opens a master, every
trip for the next minute joins it, and nothing pre-warms anything, so `dl` starts
no process it did not start before.

Two savings, and one caveat that belongs on both numbers. Measured on a loaded
host (load average 21 throughout, so the absolute seconds sit two to three times
above a quiet machine and only the ratio travels): a fresh `ssh -t` cost 2590ms
to 3140ms against a reused 16ms to 28ms, about 100x. That is most of two seconds
off every repeat command into a workspace that is already up, and nothing at all
off a first launch into a cold one. The second saving is the one that is not a
stopwatch reading: trips that are not multiplexed serialize on a per workspace
lock, so eight commands fired at one workspace at once used to finish over a 9.9s
to 23.9s staircase, where eight over one master all finished at 8.02s. A fleet of
agents attaching to one workspace is this repository's own daily shape.

The socket path is derived, and its digest covers the host alias, the `SendEnv`
permit list and `$SSH_AUTH_SOCK`. That is load bearing rather than tidy: a master
filters `SendEnv` against **its own** permit list, in silence, at exit 0, so
without it a master opened by a run with no GitHub token would hand the next run
an empty `GH_TOKEN` and an unauthenticated `gh` with nothing anywhere to say so.
A run whose permit list differs from the master's cannot find that master, so the
mismatch has nowhere to happen. The sockets live under `ssh-control` in `dl`'s
own cache directory, in a directory this user alone can read, and `dl --purge`
takes them with everything else. A socket path too long for a unix socket, or a
directory `dl` cannot create, means the session runs unmultiplexed rather than
failing. [docs/performance.md](docs/performance.md) has the rest.

- **`dl --purge` names where each surviving workspace came from.** The list of
workspaces a purge is leaving standing printed ids and nothing else, and an id
is the one thing you cannot decide on: `pythontemplate` reads exactly the same
Expand Down Expand Up @@ -117,6 +149,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **A captured command no longer pays the drain grace twice.** When a command
exits while a descendant it forked still holds the pipes open, `dl` waits a
short grace for the last of the output rather than waiting for an end of file
that is never coming. That grace was charged per pipe and the two pipes were
drained one after the other, so a single stuck descendant, which holds stdout
and stderr together, cost 1s of dead waiting on a 500ms grace. It is now one
deadline shared by both drains, so the grace bounds the drain. Nothing is given
up by sharing it: both drains start before the wait for the command does, so
both have had the same grace to finish in. The shape used to be an occasional
`git fetch` over ssh; with a connection master now open on `dl`'s own hottest
path it is the common one.

- **Sixty-six citations that pointed at nothing now point at something, and a
guard keeps it that way.** Comments across `rust/` name the test that pins the
behaviour they describe, which is most of what makes them worth reading.
Expand Down
38 changes: 38 additions & 0 deletions docs/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,44 @@ naming it and then the tools probe, rides a single setup pass. So an
interactive `dl <ws>` and a one-shot
`dl <ws> -- <cmd>` cost the same trips.

## One connection per workspace

The trip that carries `dl <ws> -- <cmd>` at a terminal is OpenSSH, over the host
alias `devpod up` publishes, and that connection is now reused. Three options go
on an argv `dl` already built: `ControlMaster=auto`, a derived `ControlPath`, and
`ControlPersist=60`. The first trip into a live workspace opens a master, every
trip for the next minute joins it, and nothing pre-warms anything, so `dl` starts
no process it did not start before.

What it saves, measured on one loaded host (load average 21 for the whole run, so
the absolute seconds sit two to three times above a quiet machine and only the
ratio travels): a fresh `ssh -t` cost 2590ms to 3140ms and a reused one 16ms to
28ms. About 100x, and it is worth being plain about where it lands. A first launch
into a cold workspace moves by nothing. A repeat command into a workspace that is
already up saves most of two seconds. And the case it helps most is not a stopwatch
reading at all: trips that are not multiplexed serialize on a per workspace lock,
so eight commands fired at one workspace at once used to finish over a 9.9s to
23.9s staircase, where eight over one master all finished at 8.02s. A fleet of
agents attaching to one workspace is this repository's own daily shape.

The socket path is derived rather than configured, and what goes into the digest
is the point of the whole thing. A master filters `SendEnv` against **its own**
permit list, in silence, at exit 0, so a master opened by a run with no GitHub
token would hand the next run an empty `GH_TOKEN` and an unauthenticated `gh` with
nothing anywhere to say so. The digest therefore covers the host alias, the
`SendEnv` permit list and `$SSH_AUTH_SOCK`: a run whose permit list differs from
the master's cannot find that master, so the mismatch has nowhere to happen. The
sockets live under `ssh-control` in `dl`'s own cache directory, in a directory
this user alone can read, and `dl --purge` takes them with everything else. Losing
one costs the next trip its couple of seconds and nothing more.

It fails closed in both directions. A socket path too long for a unix socket, or a
directory `dl` cannot create, means the session runs unmultiplexed rather than
failing. A master that has gone away leaves a socket the next client unlinks, and
`ControlPersist` is a minute rather than an hour because a live master holds a
resident `devpod ssh --stdio` process and a `docker exec` per key, and `dl` must
not be the reason a container never goes idle.

## Measuring launch time

Set `DEVLAUNCH_TIMING=1` and a `dl` command ends with one summary on stderr,
Expand Down
Loading
Loading