Skip to content

Commit

Permalink
pkg/signal: move signal.DumpStacks() to a separate package
Browse files Browse the repository at this point in the history
It is not directly related to signal-handling, so can well live
in its own package.

Also added a variant that doesn't take a directory to write files
to, for easier consumption / better match to how it's used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
  • Loading branch information
thaJeztah committed Jul 15, 2021
1 parent b2e31eb commit ea5c94c
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 68 deletions.
4 changes: 2 additions & 2 deletions daemon/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions daemon/cluster/swarm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}

Expand Down
4 changes: 2 additions & 2 deletions daemon/debugtrap_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions daemon/debugtrap_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions libnetwork/diagnostic/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions pkg/signal/signal_deprecated.go
Original file line number Diff line number Diff line change
@@ -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
43 changes: 2 additions & 41 deletions pkg/signal/trap.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@ 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
Expand Down Expand Up @@ -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 #
Expand All @@ -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
}
17 changes: 0 additions & 17 deletions pkg/signal/trap_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
57 changes: 57 additions & 0 deletions pkg/stack/stackdump.go
Original file line number Diff line number Diff line change
@@ -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 changes: 31 additions & 0 deletions pkg/stack/stackdump_test.go
Original file line number Diff line number Diff line change
@@ -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))
}

0 comments on commit ea5c94c

Please sign in to comment.