From b31a4e01f50ca1fe5d2e3107b318d6ba986eae22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Giedrius=20Statkevi=C4=8Dius?= Date: Thu, 29 Oct 2026 13:38:02 +0200 Subject: [PATCH 1/2] env_docker: stop then kill services We should stop then kill services so that things such as Go coverage data could be written to a file. Without this, they are not written because there's no time for the process to react to a terminating signal. --- env.go | 2 +- env_docker.go | 40 ++++++++++++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/env.go b/env.go index 1b8fc1c..777765f 100644 --- a/env.go +++ b/env.go @@ -83,7 +83,7 @@ type Environment interface { Runnable(name string) RunnableBuilder // AddListener registers given listener to be notified on environment runnable changes. AddListener(listener EnvironmentListener) - // AddCloser registers function to be invoked on close, before all containers are sent kill signal. + // AddCloser registers function to be invoked on close, before all containers are stopped. AddCloser(func()) // Close shutdowns isolated environment and cleans its resources. Close() diff --git a/env_docker.go b/env_docker.go index 6133726..3153db0 100644 --- a/env_docker.go +++ b/env_docker.go @@ -17,6 +17,7 @@ import ( "runtime" "strconv" "strings" + "sync" "time" "github.com/efficientgo/e2e/host" @@ -47,8 +48,12 @@ type DockerEnvironment struct { cpus string registered map[string]struct{} - listeners []EnvironmentListener - started []Runnable + + mutex sync.Mutex + // Access to the following fields must be guarded + // by a mutex, as close() stops runnables concurrently. + listeners []EnvironmentListener + started []Runnable verbose bool closers []func() @@ -198,6 +203,8 @@ func (e *DockerEnvironment) Runnable(name string) RunnableBuilder { // AddListener registers given listener to be notified on environment runnable changes. func (e *DockerEnvironment) AddListener(listener EnvironmentListener) { + defer e.mutex.Unlock() + e.mutex.Lock() e.listeners = append(e.listeners, listener) } @@ -242,6 +249,8 @@ func (e *DockerEnvironment) register(name string) { } func (e *DockerEnvironment) registerStarted(r Runnable) error { + defer e.mutex.Unlock() + e.mutex.Lock() e.started = append(e.started, r) for _, l := range e.listeners { @@ -253,6 +262,8 @@ func (e *DockerEnvironment) registerStarted(r Runnable) error { } func (e *DockerEnvironment) registerStopped(name string) error { + defer e.mutex.Unlock() + e.mutex.Lock() for i, r := range e.started { if r.Name() == name { e.started = append(e.started[:i], e.started[i+1:]...) @@ -775,13 +786,26 @@ func (e *DockerEnvironment) close() { return } - // Kill the services in the opposite order. - for i := len(e.started) - 1; i >= 0; i-- { - n := e.started[i].Name() - if err := e.started[i].Kill(); err != nil { - _ = e.logger.Log("Unable to kill service", n, ":", err.Error()) - } + // Stop the services gracefully, so that they can e.g. flush coverage data on exit. Do it in + // parallel, as Stop waits up to 30s before killing services that ignore SIGTERM. + e.mutex.Lock() + started := append([]Runnable(nil), e.started...) + e.mutex.Unlock() + + var wg sync.WaitGroup + for _, r := range started { + wg.Add(1) + go func(r Runnable) { + defer wg.Done() + if err := r.Stop(); err != nil { + _ = e.logger.Log("Unable to stop service", r.Name(), ":", err.Error()) + if err := r.Kill(); err != nil { + _ = e.logger.Log("Unable to kill service", r.Name(), ":", err.Error()) + } + } + }(r) } + wg.Wait() // Ensure there are no leftover containers. if out, err := e.exec( From cc54e07e4a645c87780663395003d24a55b497e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Giedrius=20Statkevi=C4=8Dius?= Date: Thu, 29 Oct 2026 13:45:05 +0200 Subject: [PATCH 2/2] README: update lines --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 454684d..762e772 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ For runnables that are both instrumented and profiled you can use [`e2eobs.AsObs Sometimes tests might fail due to timing problems on highly CPU constrained systems such as GitHub actions. To facilitate fixing these issues, `e2e` supports limiting CPU time allocated to Docker containers through `E2E_DOCKER_CPUS` environment variable: -```go mdox-exec="sed -n '285,288p' env_docker.go" +```go mdox-exec="sed -n '296,299p' env_docker.go" dockerCPUsEnv := os.Getenv(dockerCPUEnvName) if dockerCPUsEnv != "" { dockerCPUsParam = dockerCPUsEnv