/
shutdownstate.go
99 lines (86 loc) · 2.15 KB
/
shutdownstate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package main
import (
"errors"
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/decred/slog"
)
// shutdownState tracks the shutdown progress once the app has been commanded
// to shutdown.
type shutdownState struct {
initless
cancel func()
err error
wg *sync.WaitGroup
}
type shutdownDone struct{}
func (ss shutdownState) waitShutdown() tea.Msg {
ss.cancel()
ss.wg.Wait()
return shutdownDone{}
}
func (ss shutdownState) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg.(type) {
case shutdownDone:
// All cleanup done. Time to go.
return ss, tea.Quit
}
return ss, nil
}
func (ss shutdownState) View() string {
if ss.err == nil || errors.Is(ss.err, errQuitRequested) {
return "Shutting down..."
}
return fmt.Sprintf("Shutting down due to err: %v", ss.err)
}
func maybeShutdown(as *appState, msg tea.Msg) (tea.Model, tea.Cmd) {
crash := isCrashMsg(msg)
if err := isQuitMsg(msg); err != nil || crash {
if crash {
as.storeCrash()
}
ss := shutdownState{
wg: &as.wg,
cancel: as.cancel,
err: err,
}
return ss, ss.waitShutdown
}
return nil, nil
}
// listenToCrashSignals blocks until an abort signal is received or programDone
// is closed. If an abort signal is received, a crashApp{} message is sent to
// the program and after a few seconds, the program is forcefully terminated.
func listenToCrashSignals(p *tea.Program, programDone chan struct{}, log slog.Logger) {
// Listen to one of the abort signals.
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGABRT, syscall.SIGQUIT)
select {
case s := <-c:
log.Warnf("Received signal %s (%d)", s, s)
case <-programDone:
signal.Reset()
return
}
signal.Reset()
go p.Send(crashApp{})
// Wait for crash to be processed and program to terminate.
select {
case <-time.After(3 * time.Second):
case <-programDone:
return
}
// If we're still here a second after the SIGABRT, then crashApp{} was
// not processed, so capture and log the current stack frame and hard
// kill the program.
log.Warnf("Hard terminating program")
stack := string(allStack())
log.Info(stack)
p.Kill()
panic(stack)
}