Skip to content

The trip floor, not the trip count: how dl reaches into a running container #390

Description

@blooop

Question

How does dl reach into a running container, given that the trip itself — not the
number of trips — is now the dominant cost?

A cold launch makes three devpod ssh trips (probe, transfer, attach) and a warm
launch makes one. Each carries a 2.07-2.33s floor with an empty payload, so
the floor alone is ~7s of a 13-15s cold launch and ~75% of a 2.7-3.5s warm one.
Trip-removal has run out: #157
took the cold path 4->3 and refused the probe->transfer merge, because merging
either always streams the payload on the very path the three-state probe exists to
keep free, or hands the container back its own copy of the official-layout
relation — the two-predicate drift #151
spent three review rounds removing. That refusal stands and this ticket does not
reopen it. It attacks the other factor.

Two candidate answers, and they are alternatives to one question rather than two
tickets, which is why they are decided together:

(a) Multiplex the ssh connections. ControlMaster=auto +
ControlPersist over devpod's own generated ssh config takes a second trip from
1.85s to 0.010s, measured. A pty-requesting trip with SendEnv reuses the
master too (1.92s -> 0.015s, env still arriving), so the attach shape is not
excluded. Because ControlPersist outlives the process, a master opened by one
dl is reusable by the next dl — which is the only thing that would help the
warm path, where there is a single trip and nothing to amortise within one run.

(b) docker exec for the non-interactive trips. 0.07s against 2.3s on the
same container, and the transfer measures 0.43s against 1.76s (below). But this
cuts against a documented architectural commitment: dl's only subprocess is
devpod (CLAUDE.md, and the reason the devcontainer insists on the project env's
devpod rather than whatever is on PATH). A docker path is provider-specific —
it is simply absent for any non-docker provider — so it can only ever be a fast
path with the ssh path underneath it, which is a second way of doing one thing
and owes principle 2 an argument.

What the answer has to settle:

  • Which of (a), (b), both-with-(b)-as-a-fast-path, or neither. Consistency with
    the route already walked matters here: this map has repeatedly preferred one
    mechanism over a fast path plus a fallback.
  • Where the control path lives and who cleans it up. A socket per workspace under
    XDG_RUNTIME_DIR or the cache dir; what ControlPersist value; what happens
    to a master whose container was recreated under it (a devpod up --recreate
    between two launches is the case), and whether that fails closed.
  • Whether dl invoking ssh directly is acceptable at all, since it means dl
    grows a second subprocess dependency and has to know devpod's ssh-config path
    and host-alias shape (<id>.devpod) — both of which are devpod's private
    arrangements today.
  • What it does to interpret() / StderrFilter, which read devpod's stderr to
    recover the remote exit status. Bypassing devpod means recovering that
    status a different way, and getting it wrong silently converts a remote
    failure into a success.
  • How it is tested. The spawn-count suite is this map's existing structural pin
    (test_devpod_spawn_counts-style, plus the cold pins One cold-path setup pass: fold the hostname into the probe's round trip, with per-stage outcomes #168 added); a trip that
    becomes cheap must not become invisible to that pin, and "connection
    reused" wants a pin of its own.

Blocked on the research ticket for the devpod-side facts.

Where these numbers come from

All figures below were measured on 2026-08-24 against the Rust build
(rust/target/release/dl, dl 0.13.0), driven from inside this repo's own
devcontainer
(nested docker-in-docker, devpod 0.26.1, docker 29.7.2, 8 CPUs).
DEVPOD_HOME, DEVPOD_SSH_CONFIG and XDG_CACHE_HOME were scoped to a scratch
directory; the target was blooop/dotfiles with a warm image and a warm bare
clone cache.

Caveat, and it is not small: nested DinD is not the host. Absolute seconds
here run higher than a host launch, and this is not the CI trend point
#292 is about. What transfers
is the ratios and the floors — the pieces are A/B'd against each other in one
environment. The host cross-check is that README's own host medians (warm
2.176s, cold-recreate 15.899s) sit close to what was measured here (warm
2.7-3.5s, cold 13.1-15.5s).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions