mirror of
https://github.com/moby/buildkit.git
synced 2026-06-30 19:57:39 +00:00
This patch makes the process handling consistent between runc.Run and runc.Exec usage. Previously runc.Run would use context.Background for the runc.Run process and would monitor the request context for shutdown requests, sending a SIGKILL to the container pid1 process. This allowed runc.Run to gracefully shutdown and reap child processes. This logic was not used for runc.Exec where instead we were passing in the request context to runc.Exec, and if that request context was cancelled the runc process would immediately terminate preventing runc from reaping the child process. In this scenario the extra pid will remain forever and then when the pid1 process will get wedged in zap_pid_ns_processes syscall upon shutdown waiting fo the zombie pid to exit. With this fix both runc.Run and runc.Exec will use context.Background for runc processes and monitor the request context for shutdown request triggering a SIGKILL to the pid being monitored by runc. Signed-off-by: coryb <cbennett@netflix.com>
156 lines
3.8 KiB
Go
156 lines
3.8 KiB
Go
package runcexecutor
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"syscall"
|
|
|
|
"github.com/containerd/console"
|
|
runc "github.com/containerd/go-runc"
|
|
"github.com/moby/buildkit/executor"
|
|
"github.com/moby/buildkit/util/bklog"
|
|
"github.com/moby/sys/signal"
|
|
"github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
func updateRuncFieldsForHostOS(runtime *runc.Runc) {
|
|
// PdeathSignal only supported on unix platforms
|
|
runtime.PdeathSignal = syscall.SIGKILL // this can still leak the process
|
|
}
|
|
|
|
func (w *runcExecutor) run(ctx context.Context, id, bundle string, process executor.ProcessInfo, started func()) error {
|
|
return w.callWithIO(ctx, id, bundle, process, started, func(ctx context.Context, started chan<- int, io runc.IO) error {
|
|
_, err := w.runc.Run(ctx, id, bundle, &runc.CreateOpts{
|
|
NoPivot: w.noPivot,
|
|
Started: started,
|
|
IO: io,
|
|
})
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (w *runcExecutor) exec(ctx context.Context, id, bundle string, specsProcess *specs.Process, process executor.ProcessInfo, started func()) error {
|
|
return w.callWithIO(ctx, id, bundle, process, started, func(ctx context.Context, started chan<- int, io runc.IO) error {
|
|
return w.runc.Exec(ctx, id, *specsProcess, &runc.ExecOpts{
|
|
Started: started,
|
|
IO: io,
|
|
})
|
|
})
|
|
}
|
|
|
|
type runcCall func(ctx context.Context, started chan<- int, io runc.IO) error
|
|
|
|
func (w *runcExecutor) callWithIO(ctx context.Context, id, bundle string, process executor.ProcessInfo, started func(), call runcCall) error {
|
|
runcProcess, ctx := runcProcessHandle(ctx, id)
|
|
defer runcProcess.Release()
|
|
|
|
eg, ctx := errgroup.WithContext(ctx)
|
|
defer eg.Wait()
|
|
defer runcProcess.Shutdown()
|
|
|
|
startedCh := make(chan int, 1)
|
|
eg.Go(func() error {
|
|
return runcProcess.WaitForStart(ctx, startedCh, started)
|
|
})
|
|
|
|
eg.Go(func() error {
|
|
return handleSignals(ctx, runcProcess, process.Signal)
|
|
})
|
|
|
|
if !process.Meta.Tty {
|
|
return call(ctx, startedCh, &forwardIO{stdin: process.Stdin, stdout: process.Stdout, stderr: process.Stderr})
|
|
}
|
|
|
|
ptm, ptsName, err := console.NewPty()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pts, err := os.OpenFile(ptsName, os.O_RDWR|syscall.O_NOCTTY, 0)
|
|
if err != nil {
|
|
ptm.Close()
|
|
return err
|
|
}
|
|
|
|
defer func() {
|
|
if process.Stdin != nil {
|
|
process.Stdin.Close()
|
|
}
|
|
pts.Close()
|
|
ptm.Close()
|
|
runcProcess.Shutdown()
|
|
err := eg.Wait()
|
|
if err != nil {
|
|
bklog.G(ctx).Warningf("error while shutting down tty io: %s", err)
|
|
}
|
|
}()
|
|
|
|
if process.Stdin != nil {
|
|
eg.Go(func() error {
|
|
_, err := io.Copy(ptm, process.Stdin)
|
|
// stdin might be a pipe, so this is like EOF
|
|
if errors.Is(err, io.ErrClosedPipe) {
|
|
return nil
|
|
}
|
|
return err
|
|
})
|
|
}
|
|
|
|
if process.Stdout != nil {
|
|
eg.Go(func() error {
|
|
_, err := io.Copy(process.Stdout, ptm)
|
|
// ignore `read /dev/ptmx: input/output error` when ptm is closed
|
|
var ptmClosedError *os.PathError
|
|
if errors.As(err, &ptmClosedError) {
|
|
if ptmClosedError.Op == "read" &&
|
|
ptmClosedError.Path == "/dev/ptmx" &&
|
|
ptmClosedError.Err == syscall.EIO {
|
|
return nil
|
|
}
|
|
}
|
|
return err
|
|
})
|
|
}
|
|
|
|
eg.Go(func() error {
|
|
err := runcProcess.WaitForReady(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case resize := <-process.Resize:
|
|
err = ptm.Resize(console.WinSize{
|
|
Height: uint16(resize.Rows),
|
|
Width: uint16(resize.Cols),
|
|
})
|
|
if err != nil {
|
|
bklog.G(ctx).Errorf("failed to resize ptm: %s", err)
|
|
}
|
|
err = runcProcess.Process.Signal(signal.SIGWINCH)
|
|
if err != nil {
|
|
bklog.G(ctx).Errorf("failed to send SIGWINCH to process: %s", err)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
runcIO := &forwardIO{}
|
|
if process.Stdin != nil {
|
|
runcIO.stdin = pts
|
|
}
|
|
if process.Stdout != nil {
|
|
runcIO.stdout = pts
|
|
}
|
|
if process.Stderr != nil {
|
|
runcIO.stderr = pts
|
|
}
|
|
|
|
return call(ctx, startedCh, runcIO)
|
|
}
|