commit 1e7e821
delthas
·
2025-03-07 12:08:55 +0000 UTC
parent e6e8074
Clean up goroutines after App closes Co-authored-by: Antonio Mika <me@antoniomika.me>
4 files changed,
+67,
-32
M
app.go
+49,
-30
1@@ -22,6 +22,7 @@ import (
2 "strconv"
3 "strings"
4 "sync"
5+ "sync/atomic"
6 "time"
7 "unicode"
8 "unicode/utf8"
9@@ -112,7 +113,9 @@ type App struct {
10 sessions map[string]*irc.Session // map of network IDs to their current session
11 pasting bool
12 pastingInputOnly bool // true is pasting started when the editor input was empty
13- events chan event
14+
15+ // events MUST NOT be posted to directly; instead, use App.postEvent.
16+ events chan event
17
18 cfg Config
19 highlights []string
20@@ -142,6 +145,8 @@ type App struct {
21 uploadingProgress *float64
22
23 shownBouncerNotice bool
24+
25+ closing atomic.Bool
26 }
27
28 func NewApp(cfg Config) (app *App, err error) {
29@@ -203,10 +208,10 @@ func NewApp(cfg Config) (app *App, err error) {
30 }
31
32 ui.NotifyStart(func(ev *ui.NotifyEvent) {
33- app.events <- event{
34+ app.postEvent(event{
35 src: "*",
36 content: ev,
37- }
38+ })
39 })
40 app.win.SetPrompt(ui.Styled(">", vaxis.Style{
41 Foreground: app.cfg.Colors.Prompt,
42@@ -220,13 +225,25 @@ func NewApp(cfg Config) (app *App, err error) {
43
44 func (app *App) Close() {
45 app.win.Exit() // tell all instances of app.ircLoop to stop when possible
46- app.events <- event{ // tell app.eventLoop to stop
47+ app.postEvent(event{ // tell app.eventLoop to stop
48 src: "*",
49 content: nil,
50- }
51+ })
52 for _, session := range app.sessions {
53 session.Close()
54 }
55+ ui.NotifyStop()
56+ app.closing.Store(true)
57+ go func() {
58+ // drain remaining events
59+ for {
60+ select {
61+ case <-app.events:
62+ default:
63+ return
64+ }
65+ }
66+ }()
67 }
68
69 func (app *App) SwitchToBuffer(netID, buffer string) {
70@@ -319,11 +336,13 @@ func (app *App) eventLoop() {
71 app.win.SetTitle(title.String())
72 }
73 }
74- go func() {
75- // drain events until we close
76- for range app.events {
77- }
78- }()
79+}
80+
81+func (app *App) postEvent(ev event) {
82+ if app.closing.Load() {
83+ return
84+ }
85+ app.events <- ev
86 }
87
88 func (app *App) handleEvent(ev event) bool {
89@@ -386,16 +405,16 @@ func (app *App) ircLoop(netID string) {
90 out = app.debugOutputMessages(netID, out)
91 }
92 session := irc.NewSession(out, params)
93- app.events <- event{
94+ app.postEvent(event{
95 src: netID,
96 content: session,
97- }
98+ })
99 go func() {
100 for stop := range session.TypingStops() {
101- app.events <- event{
102+ app.postEvent(event{
103 src: netID,
104 content: stop,
105- }
106+ })
107 }
108 }()
109 for msg := range in {
110@@ -406,15 +425,15 @@ func (app *App) ircLoop(netID string) {
111 Body: ui.PlainString(msg.String()),
112 })
113 }
114- app.events <- event{
115+ app.postEvent(event{
116 src: netID,
117 content: msg,
118- }
119+ })
120 }
121- app.events <- event{
122+ app.postEvent(event{
123 src: netID,
124 content: nil,
125- }
126+ })
127 app.queueStatusLine(netID, ui.Line{
128 Head: "!!",
129 HeadColor: ui.ColorRed,
130@@ -514,10 +533,10 @@ func (app *App) debugOutputMessages(netID string, out chan<- irc.Message) chan<-
131 // handling in app.eventLoop().
132 func (app *App) uiLoop() {
133 for ev := range app.win.Events {
134- app.events <- event{
135+ app.postEvent(event{
136 src: "*",
137 content: ev,
138- }
139+ })
140 }
141 }
142
143@@ -1085,22 +1104,22 @@ func (app *App) handleLinkEvent(ev *events.EventClickLink) {
144 go func() {
145 img, err := app.fetchImage(ev.Link)
146 if err != nil {
147- app.events <- event{
148+ app.postEvent(event{
149 src: "*",
150 content: &events.EventImageLoaded{
151 Image: nil,
152 },
153- }
154+ })
155 if ev.Mouse {
156 open()
157 }
158 } else {
159- app.events <- event{
160+ app.postEvent(event{
161 src: "*",
162 content: &events.EventImageLoaded{
163 Image: img,
164 },
165- }
166+ })
167 }
168 }()
169 }
170@@ -1114,12 +1133,12 @@ func (app *App) upload(url string, f *os.File, size int64) (string, error) {
171 Reader: f,
172 period: 250 * time.Millisecond,
173 f: func(n int64) {
174- app.events <- event{
175+ app.postEvent(event{
176 src: "*",
177 content: &events.EventFileUpload{
178 Progress: float64(n) / float64(size),
179 },
180- }
181+ })
182 },
183 }
184 req, err := http.NewRequest("POST", url, &r)
185@@ -1171,19 +1190,19 @@ func (app *App) handleUpload(url string, f *os.File, size int64) {
186 go func() {
187 location, err := app.upload(url, f, size)
188 if err != nil {
189- app.events <- event{
190+ app.postEvent(event{
191 src: "*",
192 content: &events.EventFileUpload{
193 Error: err.Error(),
194 },
195- }
196+ })
197 } else {
198- app.events <- event{
199+ app.postEvent(event{
200 src: "*",
201 content: &events.EventFileUpload{
202 Location: location,
203 },
204- }
205+ })
206 }
207 }()
208 }
+2,
-0
1@@ -11,3 +11,5 @@ func (ui *UI) notify(target NotifyEvent, title, content string) int {
2 func notifyClose(id int) {}
3
4 func NotifyStart(f func(event *NotifyEvent)) {}
5+
6+func NotifyStop() {}
+14,
-0
1@@ -11,6 +11,7 @@ import (
2
3 var notificationsLock sync.Mutex
4 var notifications = make(map[int]*NotifyEvent)
5+var dbusConn *dbus.Conn
6
7 func notifyDBus(title, content string) int {
8 conn, err := dbus.SessionBus()
9@@ -71,6 +72,9 @@ func NotifyStart(opened func(*NotifyEvent)) {
10 }
11 c := make(chan *dbus.Signal, 64)
12 conn.Signal(c)
13+ notificationsLock.Lock()
14+ dbusConn = conn
15+ notificationsLock.Unlock()
16 go func() {
17 for v := range c {
18 switch v.Name {
19@@ -91,3 +95,13 @@ func NotifyStart(opened func(*NotifyEvent)) {
20 }
21 }()
22 }
23+
24+func NotifyStop() {
25+ notificationsLock.Lock()
26+ c := dbusConn
27+ dbusConn = nil
28+ notificationsLock.Unlock()
29+ if c != nil {
30+ c.Close()
31+ }
32+}
+2,
-2
1@@ -31,13 +31,13 @@ func (app *App) queueStatusLine(netID string, line ui.Line) {
2 if line.At.IsZero() {
3 line.At = time.Now()
4 }
5- app.events <- event{
6+ app.postEvent(event{
7 src: "*",
8 content: statusLine{
9 netID: netID,
10 line: line,
11 },
12- }
13+ })
14 }
15
16 func (app *App) addStatusLine(netID string, line ui.Line) {