diff --git a/cmd/status/utils.go b/cmd/status/utils.go index f7a9950bf..1f5fd9ac2 100644 --- a/cmd/status/utils.go +++ b/cmd/status/utils.go @@ -1101,6 +1101,11 @@ func startTestNode(t *testing.T) <-chan struct{} { t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent) return } + if envelope.Type == geth.EventNodeCrashed { + geth.TriggerDefaultNodeNotificationHandler(jsonEvent) + return + } + if envelope.Type == geth.EventTransactionQueued { } if envelope.Type == geth.EventNodeStarted { diff --git a/geth/node.go b/geth/node.go index 664222011..5450d48bb 100644 --- a/geth/node.go +++ b/geth/node.go @@ -9,6 +9,7 @@ import ( "path/filepath" "reflect" "runtime" + "runtime/debug" "strings" "syscall" @@ -43,6 +44,7 @@ const ( DatabaseCacheSize = 128 // Megabytes of memory allocated to internal caching (min 16MB / database forced) EventNodeStarted = "node.started" + EventNodeCrashed = "node.crashed" ) // Gas price settings @@ -313,5 +315,24 @@ func Fatalf(reason interface{}, args ...interface{}) { fmt.Fprintf(w, "Fatal Failure: %v\n", reason.(error)) } + debug.PrintStack() + os.Exit(1) } + +// HaltOnPanic recovers from panic, logs issue, sends upward notification, and exits +func HaltOnPanic() { + if r := recover(); r != nil { + err := fmt.Errorf("%v: %v", ErrNodeStartFailure, r) + + // send signal up to native app + SendSignal(SignalEnvelope{ + Type: EventNodeCrashed, + Event: NodeCrashEvent{ + Error: err.Error(), + }, + }) + + Fatalf(err) // os.exit(1) is called internally + } +} diff --git a/geth/node_manager.go b/geth/node_manager.go index ad91b3c7e..a1ea43145 100644 --- a/geth/node_manager.go +++ b/geth/node_manager.go @@ -67,6 +67,8 @@ var ( // CreateAndRunNode creates and starts running Geth node locally (exposing given RPC port along the way) func CreateAndRunNode(dataDir string, rpcPort int) error { + defer HaltOnPanic() + nodeManager := NewNodeManager(dataDir, rpcPort) if nodeManager.NodeInited() { @@ -101,6 +103,8 @@ func NodeManagerInstance() *NodeManager { // RunNode starts Geth node func (m *NodeManager) RunNode() { go func() { + defer HaltOnPanic() + m.StartNode() if _, err := m.AccountManager(); err != nil { diff --git a/geth/node_manager_test.go b/geth/node_manager_test.go index 0568c39bb..edba680dd 100644 --- a/geth/node_manager_test.go +++ b/geth/node_manager_test.go @@ -1,6 +1,8 @@ package geth_test import ( + "encoding/json" + "fmt" "os" "path/filepath" "testing" @@ -37,6 +39,15 @@ func TestMain(m *testing.M) { } geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) { + var envelope geth.SignalEnvelope + if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil { + panic(fmt.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)) + } + if envelope.Type == geth.EventNodeCrashed { + geth.TriggerDefaultNodeNotificationHandler(jsonEvent) + return + } + if jsonEvent == `{"type":"node.started","event":{}}` { signalRecieved <- struct{}{} } diff --git a/geth/types.go b/geth/types.go index 85dd3ac57..7be22ffc5 100644 --- a/geth/types.go +++ b/geth/types.go @@ -21,6 +21,10 @@ type JSONError struct { Error string `json:"error"` } +type NodeCrashEvent struct { + Error string `json:"error"` +} + type AddPeerResult struct { Success bool `json:"success"` Error string `json:"error"` diff --git a/geth/utils.go b/geth/utils.go index 6456b9f79..c4860492e 100644 --- a/geth/utils.go +++ b/geth/utils.go @@ -31,15 +31,19 @@ const ( type NodeNotificationHandler func(jsonEvent string) -var notificationHandler NodeNotificationHandler = func(jsonEvent string) { // internal signal handler (used in tests) - glog.V(logger.Info).Infof("notification received (default notification handler): %s\n", jsonEvent) -} +var notificationHandler NodeNotificationHandler = TriggerDefaultNodeNotificationHandler +// SetDefaultNodeNotificationHandler sets notification handler to invoke on SendSignal func SetDefaultNodeNotificationHandler(fn NodeNotificationHandler) { notificationHandler = fn } -// SendSignal sends application signal (JSON, normally) upwards +// TriggerDefaultNodeNotificationHandler triggers default notification handler (helpful in tests) +func TriggerDefaultNodeNotificationHandler(jsonEvent string) { + glog.V(logger.Info).Infof("notification received (default notification handler): %s\n", jsonEvent) +} + +// SendSignal sends application signal (JSON, normally) upwards to application (via default notification handler) func SendSignal(signal SignalEnvelope) { data, _ := json.Marshal(&signal) C.StatusServiceSignalEvent(C.CString(string(data))) @@ -98,6 +102,8 @@ func PrepareTestNode() (err error) { return nil } + defer HaltOnPanic() + syncRequired := false if _, err := os.Stat(filepath.Join(TestDataDir, "testnet")); os.IsNotExist(err) { syncRequired = true @@ -122,7 +128,10 @@ func PrepareTestNode() (err error) { // start geth node and wait for it to initialize // internally once.Do() is used, so call below is thread-safe - CreateAndRunNode(dataDir, 8546) // to avoid conflicts with running react-native app, run on different port + err = CreateAndRunNode(dataDir, 8546) // to avoid conflicts with running react-native app, run on different port + if err != nil { + panic(err) + } manager = NodeManagerInstance() if !manager.NodeInited() {