mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #42641 from thaJeztah/make_signal_selfcontained
This commit is contained in:
commit
9a6ff685a8
14 changed files with 151 additions and 95 deletions
|
@ -32,6 +32,7 @@ import (
|
|||
buildkit "github.com/docker/docker/builder/builder-next"
|
||||
"github.com/docker/docker/builder/dockerfile"
|
||||
"github.com/docker/docker/cli/debug"
|
||||
"github.com/docker/docker/cmd/dockerd/trap"
|
||||
"github.com/docker/docker/daemon"
|
||||
"github.com/docker/docker/daemon/cluster"
|
||||
"github.com/docker/docker/daemon/config"
|
||||
|
@ -44,7 +45,6 @@ import (
|
|||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/pidfile"
|
||||
"github.com/docker/docker/pkg/plugingetter"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/pkg/sysinfo"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/plugin"
|
||||
|
@ -183,7 +183,7 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
|
|||
stopc := make(chan bool)
|
||||
defer close(stopc)
|
||||
|
||||
signal.Trap(func() {
|
||||
trap.Trap(func() {
|
||||
cli.stop()
|
||||
<-stopc // wait for daemonCli.start() to return
|
||||
}, logrus.StandardLogger())
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/cmd/dockerd/trap"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -15,7 +15,7 @@ func main() {
|
|||
"QUIT": syscall.SIGQUIT,
|
||||
"INT": os.Interrupt,
|
||||
}
|
||||
signal.Trap(func() {
|
||||
trap.Trap(func() {
|
||||
time.Sleep(time.Second)
|
||||
os.Exit(99)
|
||||
}, logrus.StandardLogger())
|
|
@ -1,17 +1,13 @@
|
|||
package signal // import "github.com/docker/docker/pkg/signal"
|
||||
package trap // import "github.com/docker/docker/cmd/dockerd/trap"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
gosignal "os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/docker/docker/pkg/stack"
|
||||
)
|
||||
|
||||
// Trap sets up a simplified signal "trap", appropriate for common
|
||||
|
@ -58,7 +54,7 @@ func Trap(cleanup func(), logger interface {
|
|||
logger.Info("Forcing docker daemon shutdown without cleanup; 3 interrupts received")
|
||||
}
|
||||
case syscall.SIGQUIT:
|
||||
DumpStacks("")
|
||||
stack.Dump()
|
||||
logger.Info("Forcing docker daemon shutdown without cleanup on SIGQUIT")
|
||||
}
|
||||
// for the SIGINT/TERM, and SIGQUIT non-clean shutdown case, exit with 128 + signal #
|
||||
|
@ -67,38 +63,3 @@ func Trap(cleanup func(), logger interface {
|
|||
}
|
||||
}()
|
||||
}
|
||||
|
||||
const stacksLogNameTemplate = "goroutine-stacks-%s.log"
|
||||
|
||||
// DumpStacks appends the runtime stack into file in dir and returns full path
|
||||
// to that file.
|
||||
func DumpStacks(dir string) (string, error) {
|
||||
var (
|
||||
buf []byte
|
||||
stackSize int
|
||||
)
|
||||
bufferLen := 16384
|
||||
for stackSize == len(buf) {
|
||||
buf = make([]byte, bufferLen)
|
||||
stackSize = runtime.Stack(buf, true)
|
||||
bufferLen *= 2
|
||||
}
|
||||
buf = buf[:stackSize]
|
||||
var f *os.File
|
||||
if dir != "" {
|
||||
path := filepath.Join(dir, fmt.Sprintf(stacksLogNameTemplate, strings.Replace(time.Now().Format(time.RFC3339), ":", "", -1)))
|
||||
var err error
|
||||
f, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to open file to write the goroutine stacks")
|
||||
}
|
||||
defer f.Close()
|
||||
defer f.Sync()
|
||||
} else {
|
||||
f = os.Stderr
|
||||
}
|
||||
if _, err := f.Write(buf); err != nil {
|
||||
return "", errors.Wrap(err, "failed to write goroutine stacks")
|
||||
}
|
||||
return f.Name(), nil
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
// +build linux
|
||||
|
||||
package signal // import "github.com/docker/docker/pkg/signal"
|
||||
package trap // import "github.com/docker/docker/cmd/dockerd/trap"
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
@ -64,20 +64,3 @@ func TestTrap(t *testing.T) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDumpStacks(t *testing.T) {
|
||||
directory, err := ioutil.TempDir("", "test-dump-tasks")
|
||||
assert.Check(t, err)
|
||||
defer os.RemoveAll(directory)
|
||||
dumpPath, err := DumpStacks(directory)
|
||||
assert.Check(t, err)
|
||||
readFile, _ := ioutil.ReadFile(dumpPath)
|
||||
fileData := string(readFile)
|
||||
assert.Check(t, is.Contains(fileData, "goroutine"))
|
||||
}
|
||||
|
||||
func TestDumpStacksWithEmptyInput(t *testing.T) {
|
||||
path, err := DumpStacks("")
|
||||
assert.Check(t, err)
|
||||
assert.Check(t, is.Equal(os.Stderr.Name(), path))
|
||||
}
|
|
@ -54,7 +54,7 @@ import (
|
|||
"github.com/docker/docker/daemon/cluster/controllers/plugin"
|
||||
executorpkg "github.com/docker/docker/daemon/cluster/executor"
|
||||
lncluster "github.com/docker/docker/libnetwork/cluster"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/pkg/stack"
|
||||
swarmapi "github.com/docker/swarmkit/api"
|
||||
swarmnode "github.com/docker/swarmkit/node"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -393,7 +393,7 @@ func (c *Cluster) Cleanup() {
|
|||
|
||||
if err := node.Stop(); err != nil {
|
||||
logrus.Errorf("failed to shut down cluster node: %v", err)
|
||||
signal.DumpStacks("")
|
||||
stack.Dump()
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
"github.com/docker/docker/daemon/cluster/convert"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/pkg/stack"
|
||||
swarmapi "github.com/docker/swarmkit/api"
|
||||
"github.com/docker/swarmkit/manager/encryption"
|
||||
swarmnode "github.com/docker/swarmkit/node"
|
||||
|
@ -399,7 +399,7 @@ func (c *Cluster) Leave(force bool) error {
|
|||
// release readers in here
|
||||
if err := nr.Stop(); err != nil {
|
||||
logrus.Errorf("failed to shut down cluster node: %v", err)
|
||||
signal.DumpStacks("")
|
||||
stack.Dump()
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"os"
|
||||
"os/signal"
|
||||
|
||||
stackdump "github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/pkg/stack"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
@ -16,7 +16,7 @@ func (daemon *Daemon) setupDumpStackTrap(root string) {
|
|||
signal.Notify(c, unix.SIGUSR1)
|
||||
go func() {
|
||||
for range c {
|
||||
path, err := stackdump.DumpStacks(root)
|
||||
path, err := stack.DumpToFile(root)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("failed to write goroutines dump")
|
||||
} else {
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"os"
|
||||
"unsafe"
|
||||
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/pkg/stack"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
@ -34,7 +34,7 @@ func (daemon *Daemon) setupDumpStackTrap(root string) {
|
|||
logrus.Debugf("Stackdump - waiting signal at %s", event)
|
||||
for {
|
||||
windows.WaitForSingleObject(h, windows.INFINITE)
|
||||
path, err := signal.DumpStacks(root)
|
||||
path, err := stack.DumpToFile(root)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("failed to write goroutines dump")
|
||||
} else {
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"sync/atomic"
|
||||
|
||||
"github.com/docker/docker/libnetwork/internal/caller"
|
||||
stackdump "github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/pkg/stack"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -169,7 +169,7 @@ func stackTrace(ctx interface{}, w http.ResponseWriter, r *http.Request) {
|
|||
log := logrus.WithFields(logrus.Fields{"component": "diagnostic", "remoteIP": r.RemoteAddr, "method": caller.Name(0), "url": r.URL.String()})
|
||||
log.Info("stack trace")
|
||||
|
||||
path, err := stackdump.DumpStacks("/tmp/")
|
||||
path, err := stack.DumpToFile("/tmp/")
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to write goroutines dump")
|
||||
HTTPReply(w, FailCommand(err), json) // nolint:errcheck
|
||||
|
|
8
pkg/signal/signal_deprecated.go
Normal file
8
pkg/signal/signal_deprecated.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package signal
|
||||
|
||||
import "github.com/docker/docker/pkg/stack"
|
||||
|
||||
// DumpStacks appends the runtime stack into file in dir and returns full path
|
||||
// to that file.
|
||||
// Deprecated: use github.com/docker/docker/pkg/stack.Dump instead.
|
||||
var DumpStacks = stack.DumpToFile
|
|
@ -7,9 +7,6 @@ import (
|
|||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestCatchAll(t *testing.T) {
|
||||
|
@ -28,9 +25,11 @@ func TestCatchAll(t *testing.T) {
|
|||
|
||||
for sigStr := range listOfSignals {
|
||||
if signal, ok := SignalMap[sigStr]; ok {
|
||||
syscall.Kill(syscall.Getpid(), signal)
|
||||
_ = syscall.Kill(syscall.Getpid(), signal)
|
||||
s := <-sigs
|
||||
assert.Check(t, is.Equal(s.String(), signal.String()))
|
||||
if s.String() != signal.String() {
|
||||
t.Errorf("expected: %q, got: %q", signal, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +40,9 @@ func TestCatchAllIgnoreSigUrg(t *testing.T) {
|
|||
defer StopCatch(sigs)
|
||||
|
||||
err := syscall.Kill(syscall.Getpid(), syscall.SIGURG)
|
||||
assert.NilError(t, err)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
timer := time.NewTimer(1 * time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
|
@ -55,11 +56,15 @@ func TestStopCatch(t *testing.T) {
|
|||
signal := SignalMap["HUP"]
|
||||
channel := make(chan os.Signal, 1)
|
||||
CatchAll(channel)
|
||||
syscall.Kill(syscall.Getpid(), signal)
|
||||
_ = syscall.Kill(syscall.Getpid(), signal)
|
||||
signalString := <-channel
|
||||
assert.Check(t, is.Equal(signalString.String(), signal.String()))
|
||||
if signalString.String() != signal.String() {
|
||||
t.Errorf("expected: %q, got: %q", signal, signalString)
|
||||
}
|
||||
|
||||
StopCatch(channel)
|
||||
_, ok := <-channel
|
||||
assert.Check(t, is.Equal(ok, false))
|
||||
if ok {
|
||||
t.Error("expected: !ok, got: ok")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,32 +3,43 @@ package signal // import "github.com/docker/docker/pkg/signal"
|
|||
import (
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestParseSignal(t *testing.T) {
|
||||
_, checkAtoiError := ParseSignal("0")
|
||||
assert.Check(t, is.Error(checkAtoiError, "Invalid signal: 0"))
|
||||
_, err := ParseSignal("0")
|
||||
expectedErr := "Invalid signal: 0"
|
||||
if err == nil || err.Error() != expectedErr {
|
||||
t.Errorf("expected %q, but got %v", expectedErr, err)
|
||||
}
|
||||
|
||||
_, error := ParseSignal("SIG")
|
||||
assert.Check(t, is.Error(error, "Invalid signal: SIG"))
|
||||
_, err = ParseSignal("SIG")
|
||||
expectedErr = "Invalid signal: SIG"
|
||||
if err == nil || err.Error() != expectedErr {
|
||||
t.Errorf("expected %q, but got %v", expectedErr, err)
|
||||
}
|
||||
|
||||
for sigStr := range SignalMap {
|
||||
responseSignal, error := ParseSignal(sigStr)
|
||||
assert.Check(t, error)
|
||||
responseSignal, err := ParseSignal(sigStr)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
signal := SignalMap[sigStr]
|
||||
assert.Check(t, is.DeepEqual(signal, responseSignal))
|
||||
if responseSignal != signal {
|
||||
t.Errorf("expected: %q, got: %q", signal, responseSignal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidSignalForPlatform(t *testing.T) {
|
||||
isValidSignal := ValidSignalForPlatform(syscall.Signal(0))
|
||||
assert.Check(t, is.Equal(false, isValidSignal))
|
||||
if isValidSignal {
|
||||
t.Error("expected !isValidSignal")
|
||||
}
|
||||
|
||||
for _, sigN := range SignalMap {
|
||||
isValidSignal = ValidSignalForPlatform(sigN)
|
||||
assert.Check(t, is.Equal(true, isValidSignal))
|
||||
if !isValidSignal {
|
||||
t.Error("expected isValidSignal")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
57
pkg/stack/stackdump.go
Normal file
57
pkg/stack/stackdump.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package stack // import "github.com/docker/docker/pkg/stack"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const stacksLogNameTemplate = "goroutine-stacks-%s.log"
|
||||
|
||||
// Dump outputs the runtime stack to os.StdErr.
|
||||
func Dump() {
|
||||
_ = dump(os.Stderr)
|
||||
}
|
||||
|
||||
// DumpToFile appends the runtime stack into a file named "goroutine-stacks-<timestamp>.log"
|
||||
// in dir and returns the full path to that file. If no directory name is
|
||||
// provided, it outputs to os.Stderr.
|
||||
func DumpToFile(dir string) (string, error) {
|
||||
var f *os.File
|
||||
if dir != "" {
|
||||
path := filepath.Join(dir, fmt.Sprintf(stacksLogNameTemplate, strings.Replace(time.Now().Format(time.RFC3339), ":", "", -1)))
|
||||
var err error
|
||||
f, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to open file to write the goroutine stacks")
|
||||
}
|
||||
defer f.Close()
|
||||
defer f.Sync()
|
||||
} else {
|
||||
f = os.Stderr
|
||||
}
|
||||
return f.Name(), dump(f)
|
||||
}
|
||||
|
||||
func dump(f *os.File) error {
|
||||
var (
|
||||
buf []byte
|
||||
stackSize int
|
||||
)
|
||||
bufferLen := 16384
|
||||
for stackSize == len(buf) {
|
||||
buf = make([]byte, bufferLen)
|
||||
stackSize = runtime.Stack(buf, true)
|
||||
bufferLen *= 2
|
||||
}
|
||||
buf = buf[:stackSize]
|
||||
if _, err := f.Write(buf); err != nil {
|
||||
return errors.Wrap(err, "failed to write goroutine stacks")
|
||||
}
|
||||
return nil
|
||||
}
|
31
pkg/stack/stackdump_test.go
Normal file
31
pkg/stack/stackdump_test.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package stack // import "github.com/docker/docker/pkg/stack"
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestDump(t *testing.T) {
|
||||
Dump()
|
||||
}
|
||||
|
||||
func TestDumpToFile(t *testing.T) {
|
||||
directory, err := ioutil.TempDir("", "test-dump-tasks")
|
||||
assert.Check(t, err)
|
||||
defer os.RemoveAll(directory)
|
||||
dumpPath, err := DumpToFile(directory)
|
||||
assert.Check(t, err)
|
||||
readFile, _ := ioutil.ReadFile(dumpPath)
|
||||
fileData := string(readFile)
|
||||
assert.Check(t, is.Contains(fileData, "goroutine"))
|
||||
}
|
||||
|
||||
func TestDumpToFileWithEmptyInput(t *testing.T) {
|
||||
path, err := DumpToFile("")
|
||||
assert.Check(t, err)
|
||||
assert.Check(t, is.Equal(os.Stderr.Name(), path))
|
||||
}
|
Loading…
Reference in a new issue