Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

spindle/microvm: check for the existence of toplevel path in caches

Signed-off-by: dawn <dawn@tangled.org>

author
dawn
date (Jun 24, 2026, 8:04 PM +0300) commit 4fa143d3 parent 7c4e0e3b change-id ysqknplt
+39 -2
+39 -2
spindle/engines/microvm/engine.go
··· 7 7 "fmt" 8 8 "io" 9 9 "log/slog" 10 + "net/http" 10 11 "os" 11 12 "path/filepath" 12 13 "slices" ··· 434 435 if record, ok, err := state.NixOSToplevelCache.Lookup(configKey); err != nil { 435 436 return err 436 437 } else if ok { 437 - cachedToplevel = record.Toplevel 438 - fmt.Fprintf(out, "realizing cached NixOS config %s\n", cachedToplevel) 438 + // todo(dawn): we should probably use gc roots to eliminate TOCTOU 439 + // the spindle will have to manage the gc roots, and for remote we have to 440 + // ssh in to the host and add / remove gc root. 441 + // we need to have this check anyway since the only check http caches can 442 + // use is this one, since we cant manage gc roots there... 443 + if e.anyCacheHasPath(ctx, state, record.Toplevel) { 444 + cachedToplevel = record.Toplevel 445 + fmt.Fprintf(out, "realizing cached NixOS config %s\n", cachedToplevel) 446 + } 439 447 } 440 448 } 441 449 if cachedToplevel == "" { ··· 478 486 } 479 487 fmt.Fprintf(out, "committed config cache metadata %s -> %s\n", configKey, result.Toplevel) 480 488 return nil 489 + } 490 + 491 + func (e *Engine) anyCacheHasPath(ctx context.Context, state *workflowState, storePath string) bool { 492 + upstreams, err := BuildCacheUpstreams(e.cfg.NixCache.ReadURLs, state.CacheReadURLs) 493 + if err != nil { 494 + e.l.Warn("config cache check: build upstreams failed; treating as absent", "path", storePath, "error", err) 495 + return false 496 + } 497 + if len(upstreams) == 0 { 498 + return false 499 + } 500 + hash, _, err := parseStorePath(storePath) 501 + if err != nil { 502 + e.l.Warn("config cache check: invalid toplevel path; treating as absent", "path", storePath, "error", err) 503 + return false 504 + } 505 + req, err := http.NewRequestWithContext(ctx, http.MethodHead, "http://upstream/"+hash+".narinfo", nil) 506 + if err != nil { 507 + e.l.Warn("config cache check: build request failed; treating as absent", "path", storePath, "error", err) 508 + return false 509 + } 510 + resp, err := newNarinfoExistenceTransport(upstreams, e.l).RoundTrip(req) 511 + if err != nil { 512 + e.l.Warn("config cache check: narinfo probe failed; treating as absent", "path", storePath, "error", err) 513 + return false 514 + } 515 + defer resp.Body.Close() 516 + _, _ = io.Copy(io.Discard, resp.Body) 517 + return resp.StatusCode == http.StatusOK 481 518 } 482 519 483 520 func (e *Engine) DestroyWorkflow(ctx context.Context, wid models.WorkflowId) error {