hst: configurable wait delay
All checks were successful
Test / Create distribution (push) Successful in 32s
Test / Sandbox (push) Successful in 1m58s
Test / Hakurei (push) Successful in 2m47s
Test / Sandbox (race detector) (push) Successful in 3m56s
Test / Planterette (push) Successful in 3m58s
Test / Hakurei (race detector) (push) Successful in 4m31s
Test / Flake checks (push) Successful in 1m17s

This is useful for programs that take a long time to clean up.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-07-29 03:06:49 +09:00
parent 940ee00ffe
commit f7bd28118c
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
13 changed files with 62 additions and 27 deletions

View File

@ -256,7 +256,7 @@ App
], ],
"container": { "container": {
"hostname": "localhost", "hostname": "localhost",
"immediate_termination": true, "wait_delay": -1,
"seccomp_flags": 1, "seccomp_flags": 1,
"seccomp_presets": 1, "seccomp_presets": 1,
"seccomp_compat": true, "seccomp_compat": true,
@ -384,7 +384,7 @@ App
], ],
"container": { "container": {
"hostname": "localhost", "hostname": "localhost",
"immediate_termination": true, "wait_delay": -1,
"seccomp_flags": 1, "seccomp_flags": 1,
"seccomp_presets": 1, "seccomp_presets": 1,
"seccomp_compat": true, "seccomp_compat": true,
@ -566,7 +566,7 @@ func Test_printPs(t *testing.T) {
], ],
"container": { "container": {
"hostname": "localhost", "hostname": "localhost",
"immediate_termination": true, "wait_delay": -1,
"seccomp_flags": 1, "seccomp_flags": 1,
"seccomp_presets": 1, "seccomp_presets": 1,
"seccomp_compat": true, "seccomp_compat": true,

View File

@ -1,6 +1,8 @@
package hst package hst
import ( import (
"time"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
) )
@ -10,8 +12,10 @@ type (
// container hostname // container hostname
Hostname string `json:"hostname,omitempty"` Hostname string `json:"hostname,omitempty"`
// do not interrupt and wait for initial process during termination // duration to wait for after interrupting a container's initial process in nanoseconds;
ImmediateTermination bool `json:"immediate_termination,omitempty"` // a negative value causes the container to be terminated immediately on cancellation
WaitDelay time.Duration `json:"wait_delay,omitempty"`
// extra seccomp flags // extra seccomp flags
SeccompFlags seccomp.ExportFlag `json:"seccomp_flags"` SeccompFlags seccomp.ExportFlag `json:"seccomp_flags"`
// extra seccomp presets // extra seccomp presets

View File

@ -57,18 +57,18 @@ func Template() *Config {
Groups: []string{"video", "dialout", "plugdev"}, Groups: []string{"video", "dialout", "plugdev"},
Container: &ContainerConfig{ Container: &ContainerConfig{
Hostname: "localhost", Hostname: "localhost",
Devel: true, Devel: true,
Userns: true, Userns: true,
Net: true, Net: true,
Device: true, Device: true,
ImmediateTermination: true, WaitDelay: -1,
SeccompFlags: seccomp.AllowMultiarch, SeccompFlags: seccomp.AllowMultiarch,
SeccompPresets: seccomp.PresetExt, SeccompPresets: seccomp.PresetExt,
SeccompCompat: true, SeccompCompat: true,
Tty: true, Tty: true,
Multiarch: true, Multiarch: true,
MapRealUID: true, MapRealUID: true,
// example API credentials pulled from Google Chrome // example API credentials pulled from Google Chrome
// DO NOT USE THESE IN A REAL BROWSER // DO NOT USE THESE IN A REAL BROWSER
Env: map[string]string{ Env: map[string]string{

View File

@ -80,7 +80,7 @@ func TestTemplate(t *testing.T) {
], ],
"container": { "container": {
"hostname": "localhost", "hostname": "localhost",
"immediate_termination": true, "wait_delay": -1,
"seccomp_flags": 1, "seccomp_flags": 1,
"seccomp_presets": 1, "seccomp_presets": 1,
"seccomp_compat": true, "seccomp_compat": true,

View File

@ -35,7 +35,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
// the container is canceled when shim is requested to exit or receives an interrupt or termination signal; // the container is canceled when shim is requested to exit or receives an interrupt or termination signal;
// this behaviour is implemented in the shim // this behaviour is implemented in the shim
ForwardCancel: !s.ImmediateTermination, ForwardCancel: s.WaitDelay >= 0,
} }
{ {

View File

@ -123,7 +123,15 @@ func (seal *outcome) Run(rs *RunState) error {
// this prevents blocking forever on an early failure // this prevents blocking forever on an early failure
waitErr, setupErr := make(chan error, 1), make(chan error, 1) waitErr, setupErr := make(chan error, 1), make(chan error, 1)
go func() { waitErr <- cmd.Wait(); cancel() }() go func() { waitErr <- cmd.Wait(); cancel() }()
go func() { setupErr <- e.Encode(&shimParams{os.Getpid(), seal.container, seal.user.data, hlog.Load()}) }() go func() {
setupErr <- e.Encode(&shimParams{
os.Getpid(),
seal.waitDelay,
seal.container,
seal.user.data,
hlog.Load(),
})
}()
select { select {
case err := <-setupErr: case err := <-setupErr:

View File

@ -15,6 +15,7 @@ import (
"strings" "strings"
"sync/atomic" "sync/atomic"
"syscall" "syscall"
"time"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
@ -79,6 +80,7 @@ type outcome struct {
sys *system.I sys *system.I
ctx context.Context ctx context.Context
waitDelay time.Duration
container *container.Params container *container.Params
env map[string]string env map[string]string
sync *os.File sync *os.File
@ -281,6 +283,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
var uid, gid int var uid, gid int
var err error var err error
seal.container, seal.env, err = newContainer(config.Container, sys, &uid, &gid) seal.container, seal.env, err = newContainer(config.Container, sys, &uid, &gid)
seal.waitDelay = config.Container.WaitDelay
if err != nil { if err != nil {
return hlog.WrapErrSuffix(err, return hlog.WrapErrSuffix(err,
"cannot initialise container configuration:") "cannot initialise container configuration:")

View File

@ -28,6 +28,10 @@ type shimParams struct {
// monitor pid, checked against ppid in signal handler // monitor pid, checked against ppid in signal handler
Monitor int Monitor int
// duration to wait for after interrupting a container's initial process before the container is killed;
// zero value defaults to [DefaultShimWaitDelay], values exceeding [MaxShimWaitDelay] becomes [MaxShimWaitDelay]
WaitDelay time.Duration
// finalised container params // finalised container params
Container *container.Params Container *container.Params
// path to outer home directory // path to outer home directory
@ -43,9 +47,8 @@ const (
// ShimExitOrphan is returned when the shim is orphaned before monitor delivers a signal. // ShimExitOrphan is returned when the shim is orphaned before monitor delivers a signal.
ShimExitOrphan = 3 ShimExitOrphan = 3
// ShimWaitDelay is the duration to wait after interrupting a container's initial process DefaultShimWaitDelay = 5 * time.Second
// before the container is fully killed off. MaxShimWaitDelay = 30 * time.Second
ShimWaitDelay = 5 * time.Second
) )
// ShimMain is the main function of the shim process and runs as the unconstrained target user. // ShimMain is the main function of the shim process and runs as the unconstrained target user.
@ -163,7 +166,14 @@ func ShimMain() {
z := container.New(ctx, name) z := container.New(ctx, name)
z.Params = *params.Container z.Params = *params.Container
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
z.WaitDelay = ShimWaitDelay
z.WaitDelay = params.WaitDelay
if z.WaitDelay == 0 {
z.WaitDelay = DefaultShimWaitDelay
}
if z.WaitDelay > MaxShimWaitDelay {
z.WaitDelay = MaxShimWaitDelay
}
if err := z.Start(); err != nil { if err := z.Start(); err != nil {
hlog.PrintBaseError(err, "cannot start container:") hlog.PrintBaseError(err, "cannot start container:")

View File

@ -128,7 +128,7 @@ in
container = { container = {
inherit (app) inherit (app)
immediate_termination wait_delay
devel devel
userns userns
net net

View File

@ -76,6 +76,7 @@ in
type = type =
let let
inherit (types) inherit (types)
int
ints ints
str str
bool bool
@ -195,7 +196,16 @@ in
''; '';
}; };
immediate_termination = mkEnableOption "immediate termination of the container on interrupt"; wait_delay = mkOption {
type = nullOr int;
default = null;
description = ''
Duration to wait for after interrupting a container's initial process in nanoseconds.
A negative value causes the container to be terminated immediately on cancellation.
Setting this to null defaults to five seconds.
'';
};
devel = mkEnableOption "debugging-related kernel interfaces"; devel = mkEnableOption "debugging-related kernel interfaces";
userns = mkEnableOption "user namespace creation"; userns = mkEnableOption "user namespace creation";
tty = mkEnableOption "access to the controlling terminal"; tty = mkEnableOption "access to the controlling terminal";

View File

@ -132,7 +132,7 @@
identity = 1; identity = 1;
shareUid = true; shareUid = true;
verbose = true; verbose = true;
immediate_termination = true; wait_delay = -1;
share = pkgs.foot; share = pkgs.foot;
packages = [ ]; packages = [ ];
command = "foot"; command = "foot";