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
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) {