@@ -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