From 644a56a8251f2a2667ff11a5010cab9957e12334 Mon Sep 17 00:00:00 2001 From: Chennamma-Hotkar Date: Mon, 18 May 2026 17:22:10 +0000 Subject: [PATCH] fix(unikontainers): add default hook timeout to prevent Exec() hang Add DefaultHookTimeoutSec field to UruncConfig (default 30s) and use it as fallback in executeHook() when hook.Timeout is not set. Prevents container startup hang when a hook has no timeout defined. Fixes: #700 Signed-off-by: Chennamma-Hotkar --- pkg/unikontainers/unikontainers.go | 4 ++-- pkg/unikontainers/urunc_config.go | 18 ++++++++++-------- pkg/unikontainers/urunc_config_test.go | 1 + pkg/unikontainers/utils.go | 4 +++- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/pkg/unikontainers/unikontainers.go b/pkg/unikontainers/unikontainers.go index b7d61def7..36f1923bb 100644 --- a/pkg/unikontainers/unikontainers.go +++ b/pkg/unikontainers/unikontainers.go @@ -890,7 +890,7 @@ func (u *Unikontainer) executeHooksConcurrently(name string, hooks []specs.Hook, wg.Add(1) go func(h specs.Hook) { defer wg.Done() - err := executeHook(h, s) + err := executeHook(h, s, u.UruncCfg.DefaultHookTimeoutSec) if err != nil { uniklog.WithFields(logrus.Fields{ "id": u.State.ID, @@ -930,7 +930,7 @@ func (u *Unikontainer) executeHooksSequentially(name string, hooks []specs.Hook, "args": hooks[i].Args, }).Debug("Executing hook") - err := executeHook(hooks[i], s) + err := executeHook(hooks[i], s, u.UruncCfg.DefaultHookTimeoutSec) if err != nil { uniklog.WithFields(logrus.Fields{ "id": u.State.ID, diff --git a/pkg/unikontainers/urunc_config.go b/pkg/unikontainers/urunc_config.go index 871645046..cc47ab914 100644 --- a/pkg/unikontainers/urunc_config.go +++ b/pkg/unikontainers/urunc_config.go @@ -35,10 +35,11 @@ type UruncTimestamps struct { } type UruncConfig struct { - Log UruncLog `toml:"log"` - Timestamps UruncTimestamps `toml:"timestamps"` - Monitors map[string]types.MonitorConfig `toml:"monitors"` - ExtraBins map[string]types.ExtraBinConfig `toml:"extra_binaries"` + Log UruncLog `toml:"log"` + Timestamps UruncTimestamps `toml:"timestamps"` + Monitors map[string]types.MonitorConfig `toml:"monitors"` + ExtraBins map[string]types.ExtraBinConfig `toml:"extra_binaries"` + DefaultHookTimeoutSec uint `toml:"default_hook_timeout_sec"` } // this struct is used to parse only the log and timestamp section of the urunc config file @@ -96,10 +97,11 @@ func defaultExtraBinConfig() map[string]types.ExtraBinConfig { func defaultUruncConfig() *UruncConfig { return &UruncConfig{ - Log: defaultLogConfig(), - Timestamps: defaultTimestampsConfig(), - Monitors: defaultMonitorsConfig(), - ExtraBins: defaultExtraBinConfig(), + Log: defaultLogConfig(), + Timestamps: defaultTimestampsConfig(), + Monitors: defaultMonitorsConfig(), + ExtraBins: defaultExtraBinConfig(), + DefaultHookTimeoutSec: 30, } } diff --git a/pkg/unikontainers/urunc_config_test.go b/pkg/unikontainers/urunc_config_test.go index 89328847e..b25728c19 100644 --- a/pkg/unikontainers/urunc_config_test.go +++ b/pkg/unikontainers/urunc_config_test.go @@ -525,6 +525,7 @@ func TestDefaultConfigs(t *testing.T) { assert.Equal(t, testTimestampsPath, config.Timestamps.Destination) assert.Len(t, config.Monitors, 5) assert.Len(t, config.ExtraBins, 1) + assert.Equal(t, uint(30), config.DefaultHookTimeoutSec) }) t.Run("defaultLogMetricsConfig", func(t *testing.T) { diff --git a/pkg/unikontainers/utils.go b/pkg/unikontainers/utils.go index 3f2c64bc6..eb195bd65 100644 --- a/pkg/unikontainers/utils.go +++ b/pkg/unikontainers/utils.go @@ -304,7 +304,7 @@ func rmMultipleDirs(prefixPath string, dirs []string) error { return nil } -func executeHook(hook specs.Hook, state []byte) error { +func executeHook(hook specs.Hook, state []byte, defaultTimeoutSec uint) error { var stdout, stderr bytes.Buffer var cancel context.CancelFunc ctx := context.Background() @@ -312,6 +312,8 @@ func executeHook(hook specs.Hook, state []byte) error { // Apply hook-specific timeout if set, otherwise use global config timeout if hook.Timeout != nil && *hook.Timeout > 0 { ctx, cancel = context.WithTimeout(ctx, time.Duration(*hook.Timeout)*time.Second) + } else if defaultTimeoutSec > 0 { + ctx, cancel = context.WithTimeout(ctx, time.Duration(defaultTimeoutSec)*time.Second) } if cancel != nil { defer cancel()