feat: error handling
This commit is contained in:
parent
96117c0a15
commit
99766c8230
@ -106,7 +106,12 @@ func Run(ctx context.Context, params RunParams) error {
|
|||||||
applyServerState(serverState, state)
|
applyServerState(serverState, state)
|
||||||
updateUI()
|
updateUI()
|
||||||
case mpState := <-mp.C():
|
case mpState := <-mp.C():
|
||||||
applyMultiplexerState(mpState, state)
|
destErrors := applyMultiplexerState(mpState, state)
|
||||||
|
|
||||||
|
for _, destError := range destErrors {
|
||||||
|
handleDestError(destError, mp, ui)
|
||||||
|
}
|
||||||
|
|
||||||
updateUI()
|
updateUI()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,18 +122,49 @@ func applyServerState(serverState domain.Source, appState *domain.AppState) {
|
|||||||
appState.Source = serverState
|
appState.Source = serverState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// destinationError holds the information needed to display a destination
|
||||||
|
// error.
|
||||||
|
type destinationError struct {
|
||||||
|
name string
|
||||||
|
url string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
// applyMultiplexerState applies the current multiplexer state to the app state.
|
// applyMultiplexerState applies the current multiplexer state to the app state.
|
||||||
func applyMultiplexerState(mpState multiplexer.State, appState *domain.AppState) {
|
//
|
||||||
for i, dest := range appState.Destinations {
|
// It returns a list of destination errors that should be displayed to the user.
|
||||||
|
func applyMultiplexerState(mpState multiplexer.State, appState *domain.AppState) []destinationError {
|
||||||
|
var errorsToDisplay []destinationError
|
||||||
|
|
||||||
|
for i := range appState.Destinations {
|
||||||
|
dest := &appState.Destinations[i]
|
||||||
|
|
||||||
if dest.URL != mpState.URL {
|
if dest.URL != mpState.URL {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
appState.Destinations[i].Container = mpState.Container
|
if dest.Container.Err == nil && mpState.Container.Err != nil {
|
||||||
appState.Destinations[i].Status = mpState.Status
|
errorsToDisplay = append(errorsToDisplay, destinationError{
|
||||||
|
name: dest.Name,
|
||||||
|
url: dest.URL,
|
||||||
|
err: mpState.Container.Err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
dest.Container = mpState.Container
|
||||||
|
dest.Status = mpState.Status
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return errorsToDisplay
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleDestError displays a modal to the user, and stops the destination.
|
||||||
|
func handleDestError(destError destinationError, mp *multiplexer.Actor, ui *terminal.UI) {
|
||||||
|
ui.ShowDestinationErrorModal(destError.name, destError.err)
|
||||||
|
|
||||||
|
mp.StopDestination(destError.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newStateFromRunParams creates a new app state from the run parameters.
|
// newStateFromRunParams creates a new app state from the run parameters.
|
||||||
|
@ -263,11 +263,8 @@ func (a *Client) runContainerLoop(
|
|||||||
case resp := <-respC:
|
case resp := <-respC:
|
||||||
// Check if the container is restarting. If it is not then we don't
|
// Check if the container is restarting. If it is not then we don't
|
||||||
// want to wait for it again and can return early.
|
// want to wait for it again and can return early.
|
||||||
//
|
|
||||||
// Low priority: is the API call necessary?
|
|
||||||
ctr, err := a.apiClient.ContainerInspect(ctx, containerID)
|
ctr, err := a.apiClient.ContainerInspect(ctx, containerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: error handling?
|
|
||||||
a.logger.Error("Error inspecting container", "err", err, "id", shortID(containerID))
|
a.logger.Error("Error inspecting container", "err", err, "id", shortID(containerID))
|
||||||
containerErrC <- err
|
containerErrC <- err
|
||||||
return
|
return
|
||||||
@ -282,7 +279,6 @@ func (a *Client) runContainerLoop(
|
|||||||
}
|
}
|
||||||
case err := <-errC:
|
case err := <-errC:
|
||||||
// Otherwise, this is probably unexpected and we need to handle it.
|
// Otherwise, this is probably unexpected and we need to handle it.
|
||||||
// TODO: improve handling
|
|
||||||
containerErrC <- err
|
containerErrC <- err
|
||||||
return
|
return
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
@ -39,6 +39,7 @@ type Source struct {
|
|||||||
ExitReason string
|
ExitReason string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DestinationStatus reflects the high-level status of a single destination.
|
||||||
type DestinationStatus int
|
type DestinationStatus int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -81,4 +82,5 @@ type Container struct {
|
|||||||
RxSince time.Time
|
RxSince time.Time
|
||||||
RestartCount int
|
RestartCount int
|
||||||
ExitCode *int
|
ExitCode *int
|
||||||
|
Err error // Err is set if any error was received from the container client.
|
||||||
}
|
}
|
||||||
|
@ -234,6 +234,7 @@ func (s *Actor) actorLoop(containerStateC <-chan domain.Container, errC <-chan e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: refactor to use container.Err?
|
||||||
func (s *Actor) handleContainerExit(err error) {
|
func (s *Actor) handleContainerExit(err error) {
|
||||||
if s.state.Container.ExitCode != nil {
|
if s.state.Container.ExitCode != nil {
|
||||||
s.state.ExitReason = fmt.Sprintf("Server process exited with code %d.", *s.state.Container.ExitCode)
|
s.state.ExitReason = fmt.Sprintf("Server process exited with code %d.", *s.state.Container.ExitCode)
|
||||||
|
@ -84,6 +84,9 @@ func (a *Actor) StartDestination(url string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.nextIndex++
|
||||||
|
a.currURLs[url] = struct{}{}
|
||||||
|
|
||||||
a.logger.Info("Starting live stream", "url", url)
|
a.logger.Info("Starting live stream", "url", url)
|
||||||
|
|
||||||
containerStateC, errC := a.containerClient.RunContainer(a.ctx, container.RunContainerParams{
|
containerStateC, errC := a.containerClient.RunContainer(a.ctx, container.RunContainerParams{
|
||||||
@ -108,9 +111,6 @@ func (a *Actor) StartDestination(url string) {
|
|||||||
NetworkCountConfig: container.NetworkCountConfig{Rx: "eth1", Tx: "eth0"},
|
NetworkCountConfig: container.NetworkCountConfig{Rx: "eth1", Tx: "eth0"},
|
||||||
})
|
})
|
||||||
|
|
||||||
a.nextIndex++
|
|
||||||
a.currURLs[url] = struct{}{}
|
|
||||||
|
|
||||||
a.wg.Add(1)
|
a.wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer a.wg.Done()
|
defer a.wg.Done()
|
||||||
@ -146,12 +146,6 @@ func (a *Actor) StopDestination(url string) {
|
|||||||
|
|
||||||
// destLoop is the actor loop for a destination stream.
|
// destLoop is the actor loop for a destination stream.
|
||||||
func (a *Actor) destLoop(url string, containerStateC <-chan domain.Container, errC <-chan error) {
|
func (a *Actor) destLoop(url string, containerStateC <-chan domain.Container, errC <-chan error) {
|
||||||
defer func() {
|
|
||||||
a.actorC <- func() {
|
|
||||||
delete(a.currURLs, url)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
state := &State{URL: url}
|
state := &State{URL: url}
|
||||||
sendState := func() { a.stateC <- *state }
|
sendState := func() { a.stateC <- *state }
|
||||||
|
|
||||||
@ -171,10 +165,11 @@ func (a *Actor) destLoop(url string, containerStateC <-chan domain.Container, er
|
|||||||
}
|
}
|
||||||
sendState()
|
sendState()
|
||||||
case err := <-errC:
|
case err := <-errC:
|
||||||
// TODO: error handling
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Error("Error from container client", "err", err)
|
a.logger.Error("Error from container client", "err", err)
|
||||||
}
|
}
|
||||||
|
state.Container.Err = err
|
||||||
|
sendState()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -251,7 +251,6 @@ func (ui *UI) ShowStartupCheckModal() bool {
|
|||||||
[]string{"Continue", "Exit"},
|
[]string{"Continue", "Exit"},
|
||||||
func(buttonIndex int, _ string) {
|
func(buttonIndex int, _ string) {
|
||||||
if buttonIndex == 0 {
|
if buttonIndex == 0 {
|
||||||
ui.pages.RemovePage("modal")
|
|
||||||
ui.app.SetFocus(ui.destView)
|
ui.app.SetFocus(ui.destView)
|
||||||
done <- true
|
done <- true
|
||||||
} else {
|
} else {
|
||||||
@ -264,6 +263,27 @@ func (ui *UI) ShowStartupCheckModal() bool {
|
|||||||
return <-done
|
return <-done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ui *UI) ShowDestinationErrorModal(name string, err error) {
|
||||||
|
done := make(chan struct{})
|
||||||
|
|
||||||
|
ui.app.QueueUpdateDraw(func() {
|
||||||
|
ui.showModal(
|
||||||
|
modalGroupStartupCheck,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Streaming to %s failed:\n\n%s",
|
||||||
|
cmp.Or(name, "this destination"),
|
||||||
|
err,
|
||||||
|
),
|
||||||
|
[]string{"Ok"},
|
||||||
|
func(int, string) {
|
||||||
|
done <- struct{}{}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
// AllowQuit enables the quit action.
|
// AllowQuit enables the quit action.
|
||||||
func (ui *UI) AllowQuit() {
|
func (ui *UI) AllowQuit() {
|
||||||
ui.mu.Lock()
|
ui.mu.Lock()
|
||||||
@ -580,7 +600,6 @@ func (ui *UI) confirmQuit() {
|
|||||||
[]string{"Quit", "Cancel"},
|
[]string{"Quit", "Cancel"},
|
||||||
func(buttonIndex int, _ string) {
|
func(buttonIndex int, _ string) {
|
||||||
if buttonIndex == 0 {
|
if buttonIndex == 0 {
|
||||||
ui.logger.Info("quitting")
|
|
||||||
ui.commandCh <- CommandQuit{}
|
ui.commandCh <- CommandQuit{}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user