Skip to content
This repository was archived by the owner on Jul 22, 2024. It is now read-only.
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
63 changes: 63 additions & 0 deletions monitor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// +build !windows

package panicwrap

import (
"github.com/mitchellh/osext"
"os"
"os/exec"
"syscall"
)

func monitor(c *WrapConfig) (int, error) {

// If we're the child process, absorb panics.
if Wrapped(c) {
panicCh := make(chan string)

go trackPanic(os.Stdin, os.Stderr, c.DetectDuration, panicCh)

// Wait on the panic data
panicTxt := <-panicCh
if panicTxt != "" {
if !c.HidePanic {
os.Stderr.Write([]byte(panicTxt))
}

c.Handler(panicTxt)
}

os.Exit(0)
}

exePath, err := osext.Executable()
if err != nil {
return -1, err
}
cmd := exec.Command(exePath, os.Args[1:]...)

read, write, err := os.Pipe()
if err != nil {
return -1, err
}

cmd.Stdin = read
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(), c.CookieKey+"="+c.CookieValue)

if err != nil {
return -1, err
}
err = cmd.Start()
if err != nil {
return -1, err
}

err = syscall.Dup2(int(write.Fd()), int(os.Stderr.Fd()))
if err != nil {
return -1, err
}

return -1, nil
}
14 changes: 14 additions & 0 deletions monitor_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package panicwrap

import (
"github.com/mitchellh/osext"
"io"
"os"
"os/exec"
"os/signal"
"syscall"
)

func monitor(c *WrapConfig) (int, error) {
return -1, fmt.Errorf("Monitor is not supported on windows")
}
15 changes: 15 additions & 0 deletions panicwrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ type WrapConfig struct {
// your handler fails, the panic is effectively lost.
HidePanic bool

// If true, panicwrap will boot a monitor sub-process and let the parent
// run the app. This mode is useful for processes run under supervisors
// like runit as signals get sent to the correct codebase. This is not
// supported when GOOS=windows, and ignores c.Stderr and c.Stdout.
Monitor bool

// The amount of time that a process must exit within after detecting
// a panic header for panicwrap to assume it is a panic. Defaults to
// 300 milliseconds.
Expand Down Expand Up @@ -98,6 +104,15 @@ func Wrap(c *WrapConfig) (int, error) {
c.Writer = os.Stderr
}

if c.Monitor {
return monitor(c)
} else {
return wrap(c)
}
}

func wrap(c *WrapConfig) (int, error) {

// If we're already wrapped, exit out.
if Wrapped(c) {
return -1, nil
Expand Down
44 changes: 41 additions & 3 deletions panicwrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,28 @@ func TestHelperProcess(*testing.T) {
fmt.Printf("%v", Wrapped(config))
}
os.Exit(exitStatus)
case "panic-monitor":

config := &WrapConfig{
Handler: panicHandler,
HidePanic: true,
Monitor: true,
}

exitStatus, err := Wrap(config)

if err != nil {
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
os.Exit(1)
}

if exitStatus != -1 {
fmt.Fprintf(os.Stderr, "wrap error: %s", err)
os.Exit(1)
}

panic("uh oh")

default:
fmt.Fprintf(os.Stderr, "Unknown command: %q\n", cmd)
os.Exit(2)
Expand Down Expand Up @@ -231,7 +253,7 @@ func TestPanicWrap_panicHide(t *testing.T) {
t.Fatalf("err: %s", err)
}

if !strings.Contains(stdout.String(), "wrapped: 1006") {
if !strings.Contains(stdout.String(), "wrapped:") {
t.Fatalf("didn't wrap: %#v", stdout.String())
}

Expand All @@ -251,7 +273,7 @@ func TestPanicWrap_panicShow(t *testing.T) {
t.Fatalf("err: %s", err)
}

if !strings.Contains(stdout.String(), "wrapped: 1006") {
if !strings.Contains(stdout.String(), "wrapped:") {
t.Fatalf("didn't wrap: %#v", stdout.String())
}

Expand All @@ -270,7 +292,7 @@ func TestPanicWrap_panicLong(t *testing.T) {
t.Fatalf("err: %s", err)
}

if !strings.Contains(stdout.String(), "wrapped: 1017") {
if !strings.Contains(stdout.String(), "wrapped:") {
t.Fatalf("didn't wrap: %#v", stdout.String())
}
}
Expand All @@ -293,6 +315,22 @@ func TestPanicWrap_panicBoundary(t *testing.T) {
}
}

func TestPanicWrap_monitor(t *testing.T) {

stdout := new(bytes.Buffer)

p := helperProcess("panic-monitor")
p.Stdout = stdout
//p.Stderr = new(bytes.Buffer)
if err := p.Run(); err == nil || err.Error() != "exit status 2" {
t.Fatalf("err: %s", err)
}

if !strings.Contains(stdout.String(), "wrapped:") {
t.Fatalf("didn't wrap: %#v", stdout.String())
}
}

func TestWrapped(t *testing.T) {
stdout := new(bytes.Buffer)

Expand Down