Browse Source

safely capture and display panics while rendering any pages in the web http view. safely print out stack traces from crashed goroutines without destroying the terminal

Alan Shreve 12 years ago
parent
commit
ea088b69f2

+ 10 - 7
src/ngrok/client/controller.go

@@ -85,14 +85,17 @@ func (ctl *Controller) PlayRequest(tunnel mvc.Tunnel, payload []byte) {
 }
 
 func (ctl *Controller) Go(fn func()) {
-	defer func() {
-		if r := recover(); r != nil {
-			ctl.Error("goroutine crashed: %v", r)
-			// XXX
-		}
-	}()
+	go func() {
+		defer func() {
+			if r := recover(); r != nil {
+				err := util.MakePanicTrace(r)
+				ctl.Error(err)
+				ctl.Shutdown(err)
+			}
+		}()
 
-	go fn()
+		fn()
+	}()
 }
 
 // private functions

+ 2 - 2
src/ngrok/client/update_release.go

@@ -26,7 +26,7 @@ func autoUpdate(ctl mvc.Controller, token string) {
 
 		download := update.NewDownload()
 		downloadComplete := make(chan int)
-		go func() {
+		ctl.Go(func() {
 			for {
 				select {
 				case progress, ok := <-download.Progress:
@@ -47,7 +47,7 @@ func autoUpdate(ctl mvc.Controller, token string) {
 					}
 				}
 			}
-		}()
+		})
 
 		log.Info("Checking for update")
 		err := download.UpdateFromUrl(updateEndpoint + "?" + params.Encode())

+ 1 - 1
src/ngrok/client/views/term/http.go

@@ -44,7 +44,7 @@ func newTermHttpView(ctl mvc.Controller, termView *TermView, proto *proto.Http,
 		termView:     termView,
 		Logger:       log.NewPrefixLogger("view", "term", "http"),
 	}
-	go v.Run()
+	ctl.Go(v.Run)
 	return v
 }
 

+ 16 - 0
src/ngrok/client/views/web/http.go

@@ -214,6 +214,14 @@ func (whv *WebHttpView) updateHttp() {
 
 func (whv *WebHttpView) register() {
 	http.HandleFunc("/http/in/replay", func(w http.ResponseWriter, r *http.Request) {
+		defer func() {
+			if r := recover(); r != nil {
+				err := util.MakePanicTrace(r)
+				whv.Error("Replay failed: %v", err)
+				http.Error(w, err, 500)
+			}
+		}()
+
 		r.ParseForm()
 		txnid := r.Form.Get("txnid")
 		if txn, ok := whv.idToTxn[txnid]; ok {
@@ -232,6 +240,14 @@ func (whv *WebHttpView) register() {
 	})
 
 	http.HandleFunc("/http/in", func(w http.ResponseWriter, r *http.Request) {
+		defer func() {
+			if r := recover(); r != nil {
+				err := util.MakePanicTrace(r)
+				whv.Error("HTTP web view failed: %v", err)
+				http.Error(w, err, 500)
+			}
+		}()
+
 		pageTmpl, err := assets.ReadAsset("assets/client/page.html")
 		if err != nil {
 			panic(err)

+ 1 - 2
src/ngrok/client/views/web/view.go

@@ -16,7 +16,6 @@ import (
 type WebView struct {
 	log.Logger
 
-	// saved only for creating subviews
 	ctl mvc.Controller
 
 	// messages sent over this broadcast are sent too all websocket connections
@@ -68,7 +67,7 @@ func NewWebView(ctl mvc.Controller, port int) *WebView {
 	})
 
 	wv.Info("Serving web interface on localhost:%d", port)
-	go http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
+	wv.ctl.Go(func() { http.ListenAndServe(fmt.Sprintf(":%d", port), nil) })
 	return wv
 }
 

+ 12 - 0
src/ngrok/util/trace.go

@@ -0,0 +1,12 @@
+package util
+
+import (
+	"fmt"
+	"runtime"
+)
+
+func MakePanicTrace(err interface{}) string {
+	stackBuf := make([]byte, 4096)
+	n := runtime.Stack(stackBuf, false)
+	return fmt.Sprintf("panic: %v\n\n%s", err, stackBuf[:n])
+}