@@ -258,7 +258,16 @@ func requireEnvoy(t testing.TB,
258258 envoyYamlPath := t .TempDir () + "/envoy.yaml"
259259 require .NoError (t , os .WriteFile (envoyYamlPath , []byte (processedConfig ), 0o600 ))
260260
261- cmd := testsinternal .GoToolCmdContext (t .Context (), "func-e" , "run" ,
261+ // Note: do not pass t.Context() to CommandContext, as it's canceled
262+ // *before* t.Cleanup functions are called.
263+ //
264+ // > Context returns a context that is canceled just before
265+ // > Cleanup-registered functions are called.
266+ //
267+ // That means the subprocess gets killed before we can send it an interrupt
268+ // signal for graceful shutdown, which results in orphaned subprocesses.
269+ ctx , cancel := context .WithCancel (context .Background ())
270+ cmd := testsinternal .GoToolCmdContext (ctx , "func-e" , "run" ,
262271 "-c" , envoyYamlPath ,
263272 "--concurrency" , strconv .Itoa (max (runtime .NumCPU (), 2 )),
264273 // This allows multiple Envoy instances to run in parallel.
@@ -268,6 +277,20 @@ func requireEnvoy(t testing.TB,
268277 )
269278 // Use the existing environment for func-e.
270279 cmd .Env = os .Environ ()
280+ cmd .WaitDelay = 3 * time .Second // auto-kill after 3 seconds.
281+ t .Cleanup (func () {
282+ defer cancel ()
283+ // Graceful shutdown, should kill the Envoy subprocess, too.
284+ if err := cmd .Process .Signal (os .Interrupt ); err != nil {
285+ t .Logf ("Failed to send interrupt to aigw process: %v" , err )
286+ }
287+ // Wait for the process to exit gracefully, in worst case this is
288+ // killed in 3 seconds by WaitDelay above. In that case, you may
289+ // have a zombie Envoy process left behind!
290+ if _ , err := cmd .Process .Wait (); err != nil {
291+ t .Logf ("Failed to wait for aigw process to exit: %v" , err )
292+ }
293+ })
271294
272295 // wait for the ready message or exit.
273296 StartAndAwaitReady (t , cmd , stdout , stderr , "starting main dispatch loop" )
0 commit comments