internal/pkg: exec with specific timeout
All checks were successful
Test / Create distribution (push) Successful in 47s
Test / Sandbox (push) Successful in 2m58s
Test / ShareFS (push) Successful in 5m1s
Test / Sandbox (race detector) (push) Successful in 5m24s
Test / Hpkg (push) Successful in 5m29s
Test / Hakurei (push) Successful in 5m49s
Test / Hakurei (race detector) (push) Successful in 7m37s
Test / Flake checks (push) Successful in 1m47s

This change also updates the documentation of NewExec.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2026-01-11 17:46:04 +09:00
parent 63e137856e
commit 7ccc2fc5ec
2 changed files with 37 additions and 14 deletions

View File

@@ -42,6 +42,13 @@ func MustPath(pathname string, writable bool, a ...Artifact) ExecPath {
return ExecPath{check.MustAbs(pathname), a, writable} return ExecPath{check.MustAbs(pathname), a, writable}
} }
const (
// ExecTimeoutDefault replaces out of range [NewExec] timeout values.
ExecTimeoutDefault = 15 * time.Minute
// ExecTimeoutMax is the arbitrary upper bound of [NewExec] timeout.
ExecTimeoutMax = 48 * time.Hour
)
// An execArtifact is an [Artifact] that produces output by running a program // An execArtifact is an [Artifact] that produces output by running a program
// part of another [Artifact] in a [container] to produce its output. // part of another [Artifact] in a [container] to produce its output.
// //
@@ -59,6 +66,11 @@ type execArtifact struct {
path *check.Absolute path *check.Absolute
// Passed through to [container.Params]. // Passed through to [container.Params].
args []string args []string
// Duration the initial process is allowed to run. The zero value is
// equivalent to execTimeoutDefault. This value is never encoded in Params
// because it cannot affect outcome.
timeout time.Duration
} }
// execNetArtifact is like execArtifact but implements [KnownChecksum] and has // execNetArtifact is like execArtifact but implements [KnownChecksum] and has
@@ -88,10 +100,9 @@ func (a *execNetArtifact) Cure(f *FContext) error {
return a.cure(f, true) return a.cure(f, true)
} }
// NewExec returns a new [Artifact] bounded by ctx, it cures all [Artifact] // NewExec returns a new [Artifact] that executes the program path in a
// in paths at the specified maximum concurrent cures limit. Specified paths are // container with specified paths bind mounted read-only in order. A private
// bind mounted read-only in the specified order in the resulting container. // instance of /proc and /dev is made available to the container.
// A private instance of /proc and /dev is made available to the container.
// //
// The working and temporary directories are both created and mounted writable // The working and temporary directories are both created and mounted writable
// on [AbsWork] and [fhs.AbsTmp] respectively. If one or more paths target // on [AbsWork] and [fhs.AbsTmp] respectively. If one or more paths target
@@ -103,8 +114,14 @@ func (a *execNetArtifact) Cure(f *FContext) error {
// //
// If checksum is non-nil, the resulting [Artifact] implements [KnownChecksum] // If checksum is non-nil, the resulting [Artifact] implements [KnownChecksum]
// and its container runs in the host net namespace. // and its container runs in the host net namespace.
//
// The container is allowed to run for the specified duration before the initial
// process and all processes originating from it is terminated. A zero or
// negative timeout value is equivalent tp [ExecTimeoutDefault], a timeout value
// greater than [ExecTimeoutMax] is equivalent to [ExecTimeoutMax].
func NewExec( func NewExec(
checksum *Checksum, checksum *Checksum,
timeout time.Duration,
dir *check.Absolute, dir *check.Absolute,
env []string, env []string,
@@ -113,7 +130,13 @@ func NewExec(
paths ...ExecPath, paths ...ExecPath,
) Artifact { ) Artifact {
a := execArtifact{paths, dir, env, path, args} if timeout <= 0 {
timeout = ExecTimeoutDefault
}
if timeout > ExecTimeoutMax {
timeout = ExecTimeoutMax
}
a := execArtifact{paths, dir, env, path, args, timeout}
if checksum == nil { if checksum == nil {
return &a return &a
} }
@@ -197,7 +220,7 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
artifactCount += len(p.A) artifactCount += len(p.A)
} }
ctx, cancel := context.WithCancel(f.Unwrap()) ctx, cancel := context.WithTimeout(f.Unwrap(), a.timeout)
defer cancel() defer cancel()
z := container.New(ctx, f.GetMessage()) z := container.New(ctx, f.GetMessage())

View File

@@ -38,7 +38,7 @@ func TestExec(t *testing.T) {
cureMany(t, c, []cureStep{ cureMany(t, c, []cureStep{
{"container", pkg.NewExec( {"container", pkg.NewExec(
nil, nil, 0,
pkg.AbsWork, pkg.AbsWork,
[]string{"HAKUREI_TEST=1"}, []string{"HAKUREI_TEST=1"},
check.MustAbs("/opt/bin/testtool"), check.MustAbs("/opt/bin/testtool"),
@@ -61,7 +61,7 @@ func TestExec(t *testing.T) {
), ignorePathname, wantChecksumOffline, nil}, ), ignorePathname, wantChecksumOffline, nil},
{"error passthrough", pkg.NewExec( {"error passthrough", pkg.NewExec(
nil, nil, 0,
pkg.AbsWork, pkg.AbsWork,
[]string{"HAKUREI_TEST=1"}, []string{"HAKUREI_TEST=1"},
check.MustAbs("/opt/bin/testtool"), check.MustAbs("/opt/bin/testtool"),
@@ -77,7 +77,7 @@ func TestExec(t *testing.T) {
), nil, pkg.Checksum{}, errors.Join(stub.UniqueError(0xcafe))}, ), nil, pkg.Checksum{}, errors.Join(stub.UniqueError(0xcafe))},
{"invalid paths", pkg.NewExec( {"invalid paths", pkg.NewExec(
nil, nil, 0,
pkg.AbsWork, pkg.AbsWork,
[]string{"HAKUREI_TEST=1"}, []string{"HAKUREI_TEST=1"},
check.MustAbs("/opt/bin/testtool"), check.MustAbs("/opt/bin/testtool"),
@@ -90,7 +90,7 @@ func TestExec(t *testing.T) {
// check init failure passthrough // check init failure passthrough
var exitError *exec.ExitError var exitError *exec.ExitError
if _, _, err := c.Cure(pkg.NewExec( if _, _, err := c.Cure(pkg.NewExec(
nil, nil, 0,
pkg.AbsWork, pkg.AbsWork,
nil, nil,
check.MustAbs("/opt/bin/testtool"), check.MustAbs("/opt/bin/testtool"),
@@ -112,7 +112,7 @@ func TestExec(t *testing.T) {
) )
cureMany(t, c, []cureStep{ cureMany(t, c, []cureStep{
{"container", pkg.NewExec( {"container", pkg.NewExec(
&wantChecksum, &wantChecksum, 0,
pkg.AbsWork, pkg.AbsWork,
[]string{"HAKUREI_TEST=1"}, []string{"HAKUREI_TEST=1"},
check.MustAbs("/opt/bin/testtool"), check.MustAbs("/opt/bin/testtool"),
@@ -144,7 +144,7 @@ func TestExec(t *testing.T) {
cureMany(t, c, []cureStep{ cureMany(t, c, []cureStep{
{"container", pkg.NewExec( {"container", pkg.NewExec(
nil, nil, 0,
pkg.AbsWork, pkg.AbsWork,
[]string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}, []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"},
check.MustAbs("/opt/bin/testtool"), check.MustAbs("/opt/bin/testtool"),
@@ -170,7 +170,7 @@ func TestExec(t *testing.T) {
cureMany(t, c, []cureStep{ cureMany(t, c, []cureStep{
{"container", pkg.NewExec( {"container", pkg.NewExec(
nil, nil, 0,
pkg.AbsWork, pkg.AbsWork,
[]string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}, []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"},
check.MustAbs("/work/bin/testtool"), check.MustAbs("/work/bin/testtool"),
@@ -201,7 +201,7 @@ func TestExec(t *testing.T) {
cureMany(t, c, []cureStep{ cureMany(t, c, []cureStep{
{"container", pkg.NewExec( {"container", pkg.NewExec(
nil, nil, 0,
pkg.AbsWork, pkg.AbsWork,
[]string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}, []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"},
check.MustAbs("/opt/bin/testtool"), check.MustAbs("/opt/bin/testtool"),