Skip to content

Commit 5549160

Browse files
authored
Merge pull request #47 from dispatchrun/dangling-process-fix
run: kill child process if CLI panics
2 parents e8b1a06 + a910001 commit 5549160

File tree

1 file changed

+36
-22
lines changed

1 file changed

+36
-22
lines changed

cli/run.go

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,35 @@ Run 'dispatch help run' to learn about Dispatch sessions.`, BridgeSession)
130130

131131
slog.Info("starting session", "session_id", BridgeSession)
132132

133+
ctx, cancel := context.WithCancel(context.Background())
134+
defer cancel()
135+
133136
// Execute the command, forwarding the environment and
134137
// setting the necessary extra DISPATCH_* variables.
135138
cmd := exec.Command(args[0], args[1:]...)
136139

140+
cleanup := func() {
141+
if err := recover(); err != nil {
142+
// Don't leave behind a dangling process if a panic occurs.
143+
if cmd != nil && cmd.Process != nil {
144+
_ = cmd.Process.Kill()
145+
}
146+
panic(err)
147+
}
148+
}
149+
defer cleanup()
150+
151+
var wg sync.WaitGroup
152+
backgroundGoroutine := func(fn func()) {
153+
wg.Add(1)
154+
go func() {
155+
defer wg.Done()
156+
defer cleanup()
157+
158+
fn()
159+
}()
160+
}
161+
137162
cmd.Stdin = os.Stdin
138163

139164
// Pipe stdout/stderr streams through a writer that adds a prefix,
@@ -168,19 +193,11 @@ Run 'dispatch help run' to learn about Dispatch sessions.`, BridgeSession)
168193
cmd.SysProcAttr = &syscall.SysProcAttr{}
169194
setSysProcAttr(cmd.SysProcAttr)
170195

171-
ctx, cancel := context.WithCancel(context.Background())
172-
defer cancel()
173-
174-
var wg sync.WaitGroup
175-
176196
// Setup signal handler.
177197
signals := make(chan os.Signal, 2)
178198
signal.Notify(signals, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
179199
var signaled bool
180-
wg.Add(1)
181-
go func() {
182-
defer wg.Done()
183-
200+
backgroundGoroutine(func() {
184201
for {
185202
select {
186203
case <-ctx.Done():
@@ -194,18 +211,16 @@ Run 'dispatch help run' to learn about Dispatch sessions.`, BridgeSession)
194211
}
195212
}
196213
}
197-
}()
214+
})
198215

199216
// Initialize the TUI.
200217
if tui != nil {
201218
p := tea.NewProgram(tui,
202219
tea.WithContext(ctx),
203220
tea.WithoutSignalHandler(),
204221
tea.WithoutCatchPanics())
205-
wg.Add(1)
206-
go func() {
207-
defer wg.Done()
208222

223+
backgroundGoroutine(func() {
209224
if _, err := p.Run(); err != nil && !errors.Is(err, tea.ErrProgramKilled) {
210225
panic(err)
211226
}
@@ -214,17 +229,15 @@ Run 'dispatch help run' to learn about Dispatch sessions.`, BridgeSession)
214229
case signals <- syscall.SIGINT:
215230
default:
216231
}
217-
}()
232+
})
218233
}
219234

220235
bridgeSessionURL := fmt.Sprintf("%s/sessions/%s", DispatchBridgeUrl, BridgeSession)
221236

222237
// Poll for work in the background.
223238
var successfulPolls int64
224-
wg.Add(1)
225-
go func() {
226-
defer wg.Done()
227239

240+
backgroundGoroutine(func() {
228241
for ctx.Err() == nil {
229242
// Fetch a request from the API.
230243
requestID, res, err := poll(ctx, httpClient, bridgeSessionURL)
@@ -266,24 +279,25 @@ Run 'dispatch help run' to learn about Dispatch sessions.`, BridgeSession)
266279
// is misbehaving, or a shutdown sequence has been initiated.
267280
ctx, cancel := context.WithTimeout(context.Background(), cleanupTimeout)
268281
defer cancel()
269-
if err := cleanup(ctx, httpClient, bridgeSessionURL, requestID); err != nil {
282+
if err := deleteRequest(ctx, httpClient, bridgeSessionURL, requestID); err != nil {
270283
slog.Debug(err.Error())
271284
}
272285
}
273286
}()
274287
}
275-
}()
288+
})
276289

277290
if err = cmd.Start(); err != nil {
278291
return fmt.Errorf("failed to start %s: %v", strings.Join(args, " "), err)
279292
}
280293

281294
// Add a prefix to the local application's logs.
282295
appLogPrefix := []byte(appLogPrefixStyle.Render(pad(arg0, prefixWidth)) + logPrefixSeparatorStyle.Render(" | "))
283-
go printPrefixedLines(logWriter, stdout, appLogPrefix)
284-
go printPrefixedLines(logWriter, stderr, appLogPrefix)
296+
backgroundGoroutine(func() { printPrefixedLines(logWriter, stdout, appLogPrefix) })
297+
backgroundGoroutine(func() { printPrefixedLines(logWriter, stderr, appLogPrefix) })
285298

286299
err = cmd.Wait()
300+
cmd = nil
287301

288302
// Cancel the context and wait for all goroutines to return.
289303
cancel()
@@ -539,7 +553,7 @@ func invoke(ctx context.Context, client *http.Client, url, requestID string, bri
539553
}
540554
}
541555

542-
func cleanup(ctx context.Context, client *http.Client, url, requestID string) error {
556+
func deleteRequest(ctx context.Context, client *http.Client, url, requestID string) error {
543557
slog.Debug("cleaning up request", "request_id", requestID)
544558

545559
req, err := http.NewRequestWithContext(ctx, "DELETE", url, nil)

0 commit comments

Comments
 (0)