Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions cmd/soft/serve/reap_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//go:build linux

package serve

import (
"context"
"time"

"charm.land/log/v2"
"golang.org/x/sys/unix"
)

// reapZombies periodically reaps zombie child processes.
//
// When soft-serve runs as PID 1 in a container, orphaned descendant
// processes (e.g. grandchild git pack-objects left behind when a git
// parent exits before waiting for them) are reparented to PID 1.
// Without an init system these processes become zombies because the
// Go runtime only tracks children spawned via os/exec, not reparented
// orphans. This goroutine periodically calls waitpid(-1, WNOHANG) to
// clean them up.
func reapZombies(ctx context.Context, logger *log.Logger) {
go func() {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
for {
var status unix.WaitStatus
pid, err := unix.Wait4(-1, &status, unix.WNOHANG, nil)
if err != nil || pid <= 0 {
break
}
logger.Debugf("reaped zombie child pid=%d status=%d", pid, status)
}
}
}
}()
}
13 changes: 13 additions & 0 deletions cmd/soft/serve/reap_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build !linux

package serve

import (
"context"

"charm.land/log/v2"
)

// reapZombies is a no-op on non-Linux platforms.
// See reap_linux.go for the Linux implementation.
func reapZombies(_ context.Context, _ *log.Logger) {}
5 changes: 5 additions & 0 deletions cmd/soft/serve/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ var (
return fmt.Errorf("start server: %w", err)
}

// Start zombie child reaper for container environments where
// soft-serve may run as PID 1 without an init supervisor.
// See https://github.com/charmbracelet/soft-serve/issues/891
reapZombies(ctx, s.logger)

if syncHooks {
be := backend.FromContext(ctx)
if err := cmd.InitializeHooks(ctx, cfg, be); err != nil {
Expand Down