From 5e87b7bc989ea3fc70f48195ac09fb96654e2a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Dachary?= Date: Mon, 13 Jun 2022 21:55:36 +0200 Subject: [PATCH] test demonstrating orphaned process are not killed with their parent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This test fails and demonstrates that when Gitea kills one of its children (for instance when mirroring a repository timesout), the grand children are not killed and become orphaned that linger and will eventually become zombies. This is explained in detail in these blog posts: * https://hostea.org/blog/zombies/ * https://hostea.org/blog/zombies-part-2/ I'd be happy to work on implementing a bug fix for Gitea. Signed-off-by: Loïc Dachary --- modules/process/manager_exec_test.go | 73 ++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 modules/process/manager_exec_test.go diff --git a/modules/process/manager_exec_test.go b/modules/process/manager_exec_test.go new file mode 100644 index 0000000000000..6e8933c750ac1 --- /dev/null +++ b/modules/process/manager_exec_test.go @@ -0,0 +1,73 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package process + +import ( + "context" + "fmt" + "os/exec" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func retry(t *testing.T, fun func() error, tries int) { + var err interface{} + for i := 0; i < tries; i++ { + err = fun() + if err == nil { + return + } + <-time.After(1 * time.Second) + } + assert.Fail(t, fmt.Sprintf("Retry: failed \n%v", err)) +} + +func TestManagerKillGrandChildren(t *testing.T) { + tmp := t.TempDir() + + ctx, cancel := context.WithCancel(context.Background()) + pm := &Manager{ + processMap: make(map[IDType]*process), + next: 1, + } + + go func() { + // blocks forever because of the firewall at 4.4.4.4 + _, _, _ = pm.ExecDir(ctx, -1, tmp, "GIT description", "git", "clone", "https://4.4.4.4", "something") + }() + + // the git clone process forks a grand child git-remote-https, wait for it + pattern := "git-remote-https origin https://4.4.4.4" + ps := func() string { + cmd := exec.Command("ps", "-x", "-o", "pid,ppid,pgid,args") + output, err := cmd.CombinedOutput() + assert.NoError(t, err) + return string(output) + } + + retry(t, func() error { + out := ps() + if !strings.Contains(out, pattern) { + return fmt.Errorf(out + "Does not contain " + pattern) + } + return nil + }, 5) + + // canceling the parent context will cause the child process to be killed + cancel() + <-ctx.Done() + + // wait for the git-remote-https grand child process to terminate + retry(t, func() error { + out := ps() + if strings.Contains(out, pattern) { + return fmt.Errorf(out + "Contains " + pattern) + } + return nil + }, 5) +}