internal/pkg: move dependency flooding to cache
All checks were successful
Test / Create distribution (push) Successful in 47s
Test / Sandbox (push) Successful in 2m50s
Test / ShareFS (push) Successful in 4m46s
Test / Sandbox (race detector) (push) Successful in 5m16s
Test / Hpkg (push) Successful in 5m24s
Test / Hakurei (push) Successful in 5m40s
Test / Hakurei (race detector) (push) Successful in 7m27s
Test / Flake checks (push) Successful in 1m42s

This imposes a hard upper limit to concurrency during dependency satisfaction and moves all dependency-related code out of individual implementations of Artifact. This change also includes ctx and msg as part of Cache.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2026-01-09 01:51:39 +09:00
parent f2430b5f5e
commit f712466714
11 changed files with 680 additions and 409 deletions

View File

@@ -5,7 +5,6 @@ import (
"context"
"errors"
"os"
"runtime"
"slices"
"strconv"
"syscall"
@@ -15,7 +14,6 @@ import (
"hakurei.app/container/check"
"hakurei.app/container/fhs"
"hakurei.app/container/std"
"hakurei.app/message"
)
// AbsWork is the container pathname [CureContext.GetWorkDir] is mounted on.
@@ -30,7 +28,7 @@ type ExecPath struct {
// If there are multiple entries or W is true, P is set up as an overlay
// mount, and entries of A must not implement [File].
A []Artifact
// Whether to make the mount point writable via an invisible tmpfs upperdir.
// Whether to make the mount point writable via the temp directory.
W bool
}
@@ -50,17 +48,8 @@ func MustPath(pathname string, writable bool, a ...Artifact) ExecPath {
// Methods of execArtifact does not modify any struct field or underlying arrays
// referred to by slices.
type execArtifact struct {
// Caller-supplied context.
ctx context.Context
// Caller-supplied inner mount points.
paths []ExecPath
// Caller-supplied logging facility, passed through to [container] and used
// internally to produce verbose output.
msg message.Msg
// Number of [Artifact] to concurrently cure. A value of 0 or lower is
// equivalent to the value returned by [runtime.NumCPU].
cures int
// Passed through to [container.Params].
dir *check.Absolute
@@ -93,10 +82,10 @@ func (a *execNetArtifact) Params() []byte {
return slices.Concat(a.checksum[:], a.execArtifact.Params())
}
// Cure cures the [Artifact] by curing all its dependencies then running the
// container described by the caller. The container retains host networking.
func (a *execNetArtifact) Cure(c *CureContext) error {
return a.cure(c, true)
// Cure cures the [Artifact] in the container described by the caller. The
// container retains host networking.
func (a *execNetArtifact) Cure(f *FContext) error {
return a.cure(f, true)
}
// NewExec returns a new [Artifact] bounded by ctx, it cures all [Artifact]
@@ -114,13 +103,7 @@ func (a *execNetArtifact) Cure(c *CureContext) error {
//
// If checksum is non-nil, the resulting [Artifact] implements [KnownChecksum]
// and its container runs in the host net namespace.
//
// A cures value of 0 or lower is equivalent to the value returned by
// [runtime.NumCPU].
func NewExec(
ctx context.Context,
msg message.Msg,
cures int,
checksum *Checksum,
dir *check.Absolute,
@@ -130,7 +113,7 @@ func NewExec(
paths ...ExecPath,
) Artifact {
a := execArtifact{ctx, paths, msg, cures, dir, env, path, args}
a := execArtifact{paths, dir, env, path, args}
if checksum == nil {
return &a
}
@@ -186,10 +169,9 @@ func (a *execArtifact) Dependencies() []Artifact {
return slices.Concat(artifacts...)
}
// Cure cures the [Artifact] by curing all its dependencies then running the
// container described by the caller.
func (a *execArtifact) Cure(c *CureContext) (err error) {
return a.cure(c, false)
// Cure cures the [Artifact] in the container described by the caller.
func (a *execArtifact) Cure(f *FContext) (err error) {
return a.cure(f, false)
}
const (
@@ -199,20 +181,8 @@ const (
// cure is like Cure but allows optional host net namespace. This is used for
// the [KnownChecksum] variant where networking is allowed.
func (a *execArtifact) cure(c *CureContext, hostNet bool) (err error) {
cures := a.cures
if cures < 1 {
cures = runtime.NumCPU()
}
func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
overlayWorkIndex := -1
type curePath struct {
// Copied from ExecPath.P.
dst *check.Absolute
// Cured from ExecPath.A.
src []*check.Absolute
}
paths := make([]curePath, len(a.paths))
for i, p := range a.paths {
if p.P == nil || len(p.A) == 0 {
return os.ErrInvalid
@@ -220,8 +190,6 @@ func (a *execArtifact) cure(c *CureContext, hostNet bool) (err error) {
if p.P.Is(AbsWork) {
overlayWorkIndex = i
}
paths[i].dst = p.P
paths[i].src = make([]*check.Absolute, len(p.A))
}
var artifactCount int
@@ -229,71 +197,10 @@ func (a *execArtifact) cure(c *CureContext, hostNet bool) (err error) {
artifactCount += len(p.A)
}
if len(paths) > 0 {
type cureArtifact struct {
// Index of pending Artifact in paths.
index [2]int
// Pending artifact.
a Artifact
}
ac := make(chan cureArtifact, artifactCount)
for i, p := range a.paths {
for j, d := range p.A {
ac <- cureArtifact{[2]int{i, j}, d}
}
}
type cureRes struct {
// Index of result in paths.
index [2]int
// Cured pathname.
pathname *check.Absolute
// Error returned by c.
err error
}
res := make(chan cureRes)
for i := 0; i < cures; i++ {
go func() {
for d := range ac {
// computing and encoding identifier is expensive
if a.msg.IsVerbose() {
a.msg.Verbosef("curing %s...", Encode(Ident(d.a)))
}
var cr cureRes
cr.index = d.index
cr.pathname, _, cr.err = c.Cure(d.a)
res <- cr
}
}()
}
var count int
errs := make([]error, 0, artifactCount)
for cr := range res {
count++
if cr.err != nil {
errs = append(errs, cr.err)
} else {
paths[cr.index[0]].src[cr.index[1]] = cr.pathname
}
if count == artifactCount {
break
}
}
close(ac)
if err = errors.Join(errs...); err != nil {
return
}
}
ctx, cancel := context.WithCancel(a.ctx)
ctx, cancel := context.WithCancel(f.Unwrap())
defer cancel()
z := container.New(ctx, a.msg)
z := container.New(ctx, f.GetMessage())
z.WaitDelay = execWaitDelay
z.SeccompPresets |= std.PresetStrict
z.ParentPerm = 0700
@@ -303,15 +210,20 @@ func (a *execArtifact) cure(c *CureContext, hostNet bool) (err error) {
z.Hostname = "cure-net"
}
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
if a.msg.IsVerbose() {
if f.GetMessage().IsVerbose() {
z.Stdout, z.Stderr = os.Stdout, os.Stderr
}
z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args
z.Grow(len(paths) + 4)
z.Grow(len(a.paths) + 4)
temp, work := f.GetTempDir(), f.GetWorkDir()
for i, b := range a.paths {
layers := make([]*check.Absolute, len(b.A))
for j, d := range b.A {
layers[j] = f.Pathname(d)
}
temp, work := c.GetTempDir(), c.GetWorkDir()
for i, b := range paths {
if i == overlayWorkIndex {
if err = os.MkdirAll(work.String(), 0700); err != nil {
return
@@ -324,7 +236,7 @@ func (a *execArtifact) cure(c *CureContext, hostNet bool) (err error) {
AbsWork,
work,
tempWork,
b.src...,
layers...,
)
continue
}
@@ -341,11 +253,11 @@ func (a *execArtifact) cure(c *CureContext, hostNet bool) (err error) {
if err = os.MkdirAll(tempWork.String(), 0700); err != nil {
return
}
z.Overlay(b.dst, tempUpper, tempWork, b.src...)
} else if len(b.src) == 1 {
z.Bind(b.src[0], b.dst, 0)
z.Overlay(b.P, tempUpper, tempWork, layers...)
} else if len(layers) == 1 {
z.Bind(layers[0], b.P, 0)
} else {
z.OverlayReadonly(b.dst, b.src...)
z.OverlayReadonly(b.P, layers...)
}
}
if overlayWorkIndex < 0 {
@@ -356,7 +268,7 @@ func (a *execArtifact) cure(c *CureContext, hostNet bool) (err error) {
)
}
z.Bind(
c.GetTempDir(),
f.GetTempDir(),
fhs.AbsTmp,
std.BindWritable|std.BindEnsure,
)