From 4fa143d3eb95db6c896bd2bb8850520a172fba15 Mon Sep 17 00:00:00 2001 From: dawn Date: Wed, 24 Jun 2026 19:30:01 +0300 Subject: [PATCH] spindle/microvm: check for the existence of toplevel path in caches Signed-off-by: dawn --- spindle/engines/microvm/engine.go | 41 +++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/spindle/engines/microvm/engine.go b/spindle/engines/microvm/engine.go index 02e9bb4e..5dfe3ebb 100644 --- a/spindle/engines/microvm/engine.go +++ b/spindle/engines/microvm/engine.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "log/slog" + "net/http" "os" "path/filepath" "slices" @@ -434,8 +435,15 @@ func (e *Engine) activateConfig(ctx context.Context, wid models.WorkflowId, stat if record, ok, err := state.NixOSToplevelCache.Lookup(configKey); err != nil { return err } else if ok { - cachedToplevel = record.Toplevel - fmt.Fprintf(out, "realizing cached NixOS config %s\n", cachedToplevel) + // todo(dawn): we should probably use gc roots to eliminate TOCTOU + // the spindle will have to manage the gc roots, and for remote we have to + // ssh in to the host and add / remove gc root. + // we need to have this check anyway since the only check http caches can + // use is this one, since we cant manage gc roots there... + if e.anyCacheHasPath(ctx, state, record.Toplevel) { + cachedToplevel = record.Toplevel + fmt.Fprintf(out, "realizing cached NixOS config %s\n", cachedToplevel) + } } } if cachedToplevel == "" { @@ -480,6 +488,35 @@ func (e *Engine) activateConfig(ctx context.Context, wid models.WorkflowId, stat return nil } +func (e *Engine) anyCacheHasPath(ctx context.Context, state *workflowState, storePath string) bool { + upstreams, err := BuildCacheUpstreams(e.cfg.NixCache.ReadURLs, state.CacheReadURLs) + if err != nil { + e.l.Warn("config cache check: build upstreams failed; treating as absent", "path", storePath, "error", err) + return false + } + if len(upstreams) == 0 { + return false + } + hash, _, err := parseStorePath(storePath) + if err != nil { + e.l.Warn("config cache check: invalid toplevel path; treating as absent", "path", storePath, "error", err) + return false + } + req, err := http.NewRequestWithContext(ctx, http.MethodHead, "http://upstream/"+hash+".narinfo", nil) + if err != nil { + e.l.Warn("config cache check: build request failed; treating as absent", "path", storePath, "error", err) + return false + } + resp, err := newNarinfoExistenceTransport(upstreams, e.l).RoundTrip(req) + if err != nil { + e.l.Warn("config cache check: narinfo probe failed; treating as absent", "path", storePath, "error", err) + return false + } + defer resp.Body.Close() + _, _ = io.Copy(io.Discard, resp.Body) + return resp.StatusCode == http.StatusOK +} + func (e *Engine) DestroyWorkflow(ctx context.Context, wid models.WorkflowId) error { fns := e.drainCleanups(wid) -- 2.51.2