~edwargix/tallyard

345728555f86f8c6e5a9fed2b7480d4ff9aad4be — David Florness 2 years ago 8acf369
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.
2 files changed, 49 insertions(+), 23 deletions(-)

M cmd/tallyard/main.go
M ui/tui.go
M cmd/tallyard/main.go => cmd/tallyard/main.go +20 -6
@@ 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)
		}

M ui/tui.go => ui/tui.go +29 -17
@@ 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 {