Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support print stack #1114

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
35 changes: 35 additions & 0 deletions cmd/nerdctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@
package main

import (
"context"
"errors"
"fmt"
"os"
"os/signal"
"path/filepath"
"runtime"
"strings"

Expand Down Expand Up @@ -71,11 +74,16 @@ func main() {
}

func xmain() error {
signals := make(chan os.Signal, 2048)
ctx, cancel := context.WithCancel(context.Background())
handleSignals(ctx, signals, cancel)
signal.Notify(signals, handledSignals...)
if len(os.Args) == 3 && os.Args[1] == logging.MagicArgv1 {
// containerd runtime v2 logging plugin mode.
// "binary://BIN?KEY=VALUE" URI is parsed into Args {BIN, KEY, VALUE}.
return logging.Main(os.Args[2])
}

// nerdctl CLI mode
app, err := newApp()
if err != nil {
Expand Down Expand Up @@ -450,3 +458,30 @@ func AddPersistentStringArrayFlag(cmd *cobra.Command, name string, aliases, nonP
}
}
}

func dumpStacks(writeToFile bool) {
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]
logrus.Debugf("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf)

if writeToFile {
// Also write to file to aid gathering diagnostics
name := filepath.Join(os.TempDir(), fmt.Sprintf("nerdctl.%d.stacks.log", os.Getpid()))
f, err := os.Create(name)
if err != nil {
return
}
defer f.Close()
f.WriteString(string(buf))
logrus.Debugf("goroutine stack dump written to %s", name)
}
}
40 changes: 40 additions & 0 deletions cmd/nerdctl/main_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,21 @@
package main

import (
"context"
"os"

"github.com/containerd/containerd/log"
"github.com/spf13/cobra"
"golang.org/x/sys/unix"
)

var handledSignals = []os.Signal{
unix.SIGTERM,
unix.SIGINT,
unix.SIGUSR1,
unix.SIGPIPE,
}

func appNeedsRootlessParentMain(cmd *cobra.Command, args []string) bool {
return false
}
Expand All @@ -35,3 +47,31 @@ func addApparmorCommand(rootCmd *cobra.Command) {
func addCpCommand(rootCmd *cobra.Command) {
// NOP
}

func handleSignals(ctx context.Context, signals chan os.Signal, cancel func()) chan struct{} {
done := make(chan struct{}, 1)
go func() {
for {
select {
case s := <-signals:

// Do not print message when dealing with SIGPIPE, which may cause
// nested signals and consume lots of cpu bandwidth.
if s == unix.SIGPIPE {
continue
}

log.G(ctx).WithField("signal", s).Debug("received signal")
switch s {
case unix.SIGUSR1:
dumpStacks(true)
default:
cancel()
close(done)
return
}
}
}
}()
return done
}
40 changes: 40 additions & 0 deletions cmd/nerdctl/main_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,24 @@
package main

import (
"context"
"os"

"github.com/containerd/containerd/log"
ncdefaults "github.com/containerd/nerdctl/pkg/defaults"
"github.com/containerd/nerdctl/pkg/rootlessutil"
"github.com/containerd/nerdctl/pkg/strutil"
"github.com/spf13/cobra"
"golang.org/x/sys/unix"
)

var handledSignals = []os.Signal{
unix.SIGTERM,
unix.SIGINT,
unix.SIGUSR1,
unix.SIGPIPE,
}

func appNeedsRootlessParentMain(cmd *cobra.Command, args []string) bool {
commands := []string{}
for tcmd := cmd; tcmd != nil; tcmd = tcmd.Parent() {
Expand Down Expand Up @@ -72,3 +84,31 @@ func addApparmorCommand(rootCmd *cobra.Command) {
func addCpCommand(rootCmd *cobra.Command) {
rootCmd.AddCommand(newCpCommand())
}

func handleSignals(ctx context.Context, signals chan os.Signal, cancel func()) chan struct{} {
done := make(chan struct{}, 1)
go func() {
for {
select {
case s := <-signals:

// Do not print message when dealing with SIGPIPE, which may cause
// nested signals and consume lots of cpu bandwidth.
if s == unix.SIGPIPE {
continue
}

log.G(ctx).WithField("signal", s).Debug("received signal")
switch s {
case unix.SIGUSR1:
dumpStacks(true)
default:
cancel()
close(done)
return
}
}
}
}()
return done
}
61 changes: 61 additions & 0 deletions cmd/nerdctl/main_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,22 @@
package main

import (
"context"
"fmt"
"os"
"unsafe"

"github.com/containerd/containerd/log"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/sys/windows"
)

var (
handledSignals = []os.Signal{
windows.SIGTERM,
windows.SIGINT,
}
)

func appNeedsRootlessParentMain(cmd *cobra.Command, args []string) bool {
Expand All @@ -43,3 +58,49 @@ func addApparmorCommand(rootCmd *cobra.Command) {
func addCpCommand(rootCmd *cobra.Command) {
// NOP
}

func handleSignals(ctx context.Context, signals chan os.Signal, cancel func()) chan struct{} {
done := make(chan struct{})
go func() {
for {
select {
case s := <-signals:
log.G(ctx).WithField("signal", s).Debug("received signal")
cancel()
close(done)
return
}
}
}()
setupDumpStacks()
return done
}

func setupDumpStacks() {
// Windows does not support signals like *nix systems. So instead of
// trapping on SIGUSR1 to dump stacks, we wait on a Win32 event to be
// signaled. ACL'd to builtin administrators and local system
event := "Global\\stackdump-" + fmt.Sprint(os.Getpid())
ev, _ := windows.UTF16PtrFromString(event)
sd, err := windows.SecurityDescriptorFromString("D:P(A;;GA;;;BA)(A;;GA;;;SY)")
if err != nil {
logrus.Errorf("failed to get security descriptor for debug stackdump event %s: %s", event, err.Error())
return
}
var sa windows.SecurityAttributes
sa.Length = uint32(unsafe.Sizeof(sa))
sa.InheritHandle = 1
sa.SecurityDescriptor = sd
h, err := windows.CreateEvent(&sa, 0, 0, ev)
if h == 0 || err != nil {
logrus.Errorf("failed to create debug stackdump event %s: %s", event, err.Error())
return
}
go func() {
logrus.Debugf("Stackdump - waiting signal at %s", event)
for {
windows.WaitForSingleObject(h, windows.INFINITE)
dumpStacks(true)
}
}()
}