Skip to content

Commit 99e963d

Browse files
yroblataskbot
andauthored
feat: properly manage interrupt signal on foreground process (#1185)
Co-authored-by: taskbot <[email protected]>
1 parent 6d51fc3 commit 99e963d

File tree

1 file changed

+53
-9
lines changed

1 file changed

+53
-9
lines changed

cmd/thv/app/run.go

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ import (
55
"fmt"
66
"net"
77
"os"
8+
"os/signal"
9+
"syscall"
10+
"time"
811

912
"github.com/spf13/cobra"
1013

1114
"github.com/stacklok/toolhive/pkg/container"
1215
"github.com/stacklok/toolhive/pkg/groups"
1316
"github.com/stacklok/toolhive/pkg/logger"
17+
"github.com/stacklok/toolhive/pkg/process"
1418
"github.com/stacklok/toolhive/pkg/runner"
1519
"github.com/stacklok/toolhive/pkg/workloads"
1620
)
@@ -77,6 +81,27 @@ func init() {
7781
AddOIDCFlags(runCmd)
7882
}
7983

84+
func cleanupAndWait(workloadManager workloads.Manager, name string, cancel context.CancelFunc, errCh <-chan error) {
85+
cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 30*time.Second)
86+
defer cleanupCancel()
87+
88+
group, err := workloadManager.DeleteWorkloads(cleanupCtx, []string{name})
89+
if err != nil {
90+
logger.Warnf("Failed to delete workload %q: %v", name, err)
91+
} else if group != nil {
92+
if err := group.Wait(); err != nil {
93+
logger.Warnf("DeleteWorkloads group error for %q: %v", name, err)
94+
}
95+
}
96+
97+
cancel()
98+
select {
99+
case <-errCh:
100+
case <-time.After(5 * time.Second):
101+
logger.Warnf("Timeout waiting for workload to stop")
102+
}
103+
}
104+
80105
func runCmdFunc(cmd *cobra.Command, args []string) error {
81106
ctx := cmd.Context()
82107

@@ -107,6 +132,8 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
107132
workloadName = serverOrImage
108133
}
109134

135+
isForeground := runFlags.Foreground
136+
110137
if runFlags.Group != "" {
111138
groupManager, err := groups.NewManager()
112139
if err != nil {
@@ -145,18 +172,35 @@ func runCmdFunc(cmd *cobra.Command, args []string) error {
145172
}
146173
workloadManager := workloads.NewManagerFromRuntime(rt)
147174

148-
if runFlags.Foreground {
149-
err = workloadManager.RunWorkload(ctx, runnerConfig)
150-
} else {
151-
// Run the workload in detached mode
152-
err = workloadManager.RunWorkloadDetached(ctx, runnerConfig)
153-
}
154-
if err != nil {
155-
return err
175+
if isForeground {
176+
return runForeground(ctx, workloadManager, runnerConfig)
156177
}
178+
return workloadManager.RunWorkloadDetached(ctx, runnerConfig)
179+
}
157180

158-
return nil
181+
func runForeground(ctx context.Context, workloadManager workloads.Manager, runnerConfig *runner.RunConfig) error {
182+
ctx, cancel := context.WithCancel(ctx)
183+
defer cancel()
159184

185+
sigCh := make(chan os.Signal, 1)
186+
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
187+
defer signal.Stop(sigCh)
188+
189+
errCh := make(chan error, 1)
190+
go func() {
191+
errCh <- workloadManager.RunWorkload(ctx, runnerConfig)
192+
}()
193+
194+
select {
195+
case sig := <-sigCh:
196+
if !process.IsDetached() {
197+
logger.Infof("Received signal: %v, stopping server %q", sig, runnerConfig.BaseName)
198+
cleanupAndWait(workloadManager, runnerConfig.BaseName, cancel, errCh)
199+
}
200+
return nil
201+
case err := <-errCh:
202+
return err
203+
}
160204
}
161205

162206
// parseCommandArguments processes command-line arguments to find everything after the -- separator

0 commit comments

Comments
 (0)