From 345728555f86f8c6e5a9fed2b7480d4ff9aad4be Mon Sep 17 00:00:00 2001 From: David Florness Date: Fri, 31 Dec 2021 16:42:16 -0600 Subject: [PATCH] Properly display errors from Sync goroutine Before, runtime errors in the Sync goroutine would get forced onto the screen even while the TUIs were running. --- cmd/tallyard/main.go | 26 +++++++++++++++++++------ ui/tui.go | 46 ++++++++++++++++++++++++++++---------------- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/cmd/tallyard/main.go b/cmd/tallyard/main.go index 7763a24..341b350 100644 --- a/cmd/tallyard/main.go +++ b/cmd/tallyard/main.go @@ -5,6 +5,7 @@ import ( "fmt" "io/ioutil" "os" + "runtime/debug" "time" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" @@ -60,20 +61,33 @@ func main() { syncer.OnEvent(client.Store.(*matrix.TallyardStore).UpdateState) electionsMap.SetupEventHooks(client, syncer) + kill := make(chan error, 1) go func() { + errorf := func(format string, args ...interface{}) { + err := fmt.Errorf(format, args...) + log.Error(err) + kill <- err + } + defer func() { + if r := recover(); r != nil { + errorf("panic: %s\n%s", r, debug.Stack()) + } + }() res, err := client.CreateFilter(electionFilter) if err != nil { - log.Panic(err) + errorf("couldn't create filter: %s", err) + return } client.Store.SaveFilterID(client.UserID, res.FilterID) err = client.Sync() if err != nil { - log.Panic(err) + errorf("error during sync: %s", err) + return } }() // select a room - room, err := ui.RoomListTUI(client.Store.(*matrix.TallyardStore), client.UserID, syncer) + room, err := ui.RoomListTUI(kill, client.Store.(*matrix.TallyardStore), client.UserID, syncer) if err != nil { log.Panic(err) } @@ -83,7 +97,7 @@ func main() { } // select an election - el, err := ui.RoomTUI(client, room, electionsMap, syncer) + el, err := ui.RoomTUI(kill, client, room, electionsMap, syncer) if err != nil { log.Panic(err) } @@ -93,7 +107,7 @@ func main() { } // wait for election to start if needed - err = ui.ElectionWaitTUI(client, el, electionsMap.EventStore) + err = ui.ElectionWaitTUI(kill, client, el, electionsMap.EventStore) if err != nil { log.Panic(err) } @@ -116,7 +130,7 @@ func main() { // vote if we need to (user may have voted in previous tallyard // invocation) if el.LocalVoter != nil && el.LocalVoter.Poly == nil { - ballot, err := ui.VoteTUI(el.Candidates) + ballot, err := ui.VoteTUI(kill, el.Candidates) if err != nil { log.Panic(err) } diff --git a/ui/tui.go b/ui/tui.go index 57b29ca..75d0c5a 100644 --- a/ui/tui.go +++ b/ui/tui.go @@ -2,6 +2,7 @@ package ui import ( "fmt" + "os" "runtime/debug" "sort" "strconv" @@ -22,8 +23,8 @@ import ( // displays a TUI of rooms that the user may choose from. This presumes that the // store is already properly saving rooms -func RoomListTUI(store *matrix.TallyardStore, localUserID id.UserID, syncer mautrix.ExtensibleSyncer) (*election.Room, error) { - app := newTallyardApplication() +func RoomListTUI(kill <-chan error, store *matrix.TallyardStore, localUserID id.UserID, syncer mautrix.ExtensibleSyncer) (*election.Room, error) { + app := newTallyardApplication(kill) defer func() { if r := recover(); r != nil { if app.alive { @@ -178,8 +179,8 @@ func (roomI *roomWithTitle) cmp(roomJ *roomWithTitle) bool { } -func RoomTUI(client *mautrix.Client, room *election.Room, electionsMap *election.ElectionsMap, syncer mautrix.ExtensibleSyncer) (el *election.Election, err error) { - app := newTallyardApplication() +func RoomTUI(kill <-chan error, client *mautrix.Client, room *election.Room, electionsMap *election.ElectionsMap, syncer mautrix.ExtensibleSyncer) (el *election.Election, err error) { + app := newTallyardApplication(kill) defer func() { if r := recover(); r != nil { if app.alive { @@ -241,9 +242,9 @@ func RoomTUI(client *mautrix.Client, room *election.Room, electionsMap *election } // ask user if s/he wants to join election (or display // results if it's already started) - if !electionConfirmation(el) { + if !electionConfirmation(kill, el) { // user needs to select a different election - el, err = RoomTUI(client, room, electionsMap, syncer) + el, err = RoomTUI(kill, client, room, electionsMap, syncer) } else if el.StartID == nil { // user wants to join the election err = el.JoinElection(client, electionsMap.EventStore) @@ -253,7 +254,7 @@ func RoomTUI(client *mautrix.Client, room *election.Room, electionsMap *election } // user opted to create election (i == 0) - title, candidates := CreateElectionTUI() + title, candidates := CreateElectionTUI(kill) if candidates == nil { return } @@ -278,8 +279,8 @@ func RoomTUI(client *mautrix.Client, room *election.Room, electionsMap *election return } -func electionConfirmation(el *election.Election) (shouldJoin bool) { - app := newTallyardApplication() +func electionConfirmation(kill <-chan error, el *election.Election) (shouldJoin bool) { + app := newTallyardApplication(kill) defer func() { if r := recover(); r != nil { if app.alive { @@ -325,10 +326,10 @@ func electionConfirmation(el *election.Election) (shouldJoin bool) { return } -func CreateElectionTUI() (title string, candidates []election.Candidate) { +func CreateElectionTUI(kill <-chan error) (title string, candidates []election.Candidate) { var form *tview.Form n := 2 - app := newTallyardApplication() + app := newTallyardApplication(kill) defer func() { if r := recover(); r != nil { if app.alive { @@ -383,11 +384,11 @@ func CreateElectionTUI() (title string, candidates []election.Candidate) { return title, candidates } -func ElectionWaitTUI(client *mautrix.Client, el *election.Election, eventStore *election.EventStore) (err error) { +func ElectionWaitTUI(kill <-chan error, client *mautrix.Client, el *election.Election, eventStore *election.EventStore) (err error) { votersTextView := tview.NewTextView() frame := tview.NewFrame(votersTextView) frame.SetTitle(el.Title).SetBorder(true) - app := newTallyardApplication() + app := newTallyardApplication(kill) defer func() { if r := recover(); r != nil { if app.alive { @@ -487,8 +488,8 @@ func ElectionWaitTUI(client *mautrix.Client, el *election.Election, eventStore * } // displays a voting UI to the user and returns the encoded ballot -func VoteTUI(candidates []election.Candidate) (ballot *[][]byte, err error) { - app := newTallyardApplication() +func VoteTUI(kill <-chan error, candidates []election.Candidate) (ballot *[][]byte, err error) { + app := newTallyardApplication(kill) defer func() { if r := recover(); r != nil { if app.alive { @@ -552,8 +553,19 @@ type tallyardApplication struct { alive bool } -func newTallyardApplication() *tallyardApplication { - return &tallyardApplication{tview.NewApplication(), true} +func newTallyardApplication(kill <-chan error) *tallyardApplication { + app := &tallyardApplication{tview.NewApplication(), true} + go func() { + select { + case err := <-kill: + if !app.alive { + return + } + app.Stop() + fmt.Fprintln(os.Stderr, err) + } + }() + return app } func (t *tallyardApplication) Run() error { -- 2.38.4