commit d40d8dc

Hubert Hirtz  ·  2021-11-16 08:20:21 +0000 UTC
parent 0e73077
Allow App.Close() and App.Run() to be run concurrently
4 files changed,  +43, -17
M app.go
M app.go
+21, -13
 1@@ -137,7 +137,11 @@ func NewApp(cfg Config) (app *App, err error) {
 2 }
 3 
 4 func (app *App) Close() {
 5-	app.win.Close()
 6+	app.win.Exit()       // tell all instances of app.ircLoop to stop when possible
 7+	app.events <- event{ // tell app.eventLoop to stop
 8+		src:     "*",
 9+		content: nil,
10+	}
11 	for _, session := range app.sessions {
12 		session.Close()
13 	}
14@@ -166,6 +170,8 @@ func (app *App) CurrentBuffer() (netID, buffer string) {
15 // eventLoop retrieves events (in batches) from the event channel and handle
16 // them, then draws the interface after each batch is handled.
17 func (app *App) eventLoop() {
18+	defer app.win.Close()
19+
20 	evs := make([]event, 0, eventChanSize)
21 	for !app.win.ShouldExit() {
22 		ev := <-app.events
23@@ -181,7 +187,17 @@ func (app *App) eventLoop() {
24 			}
25 		}
26 
27-		app.handleEvents(evs)
28+		for _, ev := range evs {
29+			if ev.src == "*" {
30+				if ev.content == nil {
31+					return
32+				}
33+				app.handleUIEvent(ev.content)
34+			} else {
35+				app.handleIRCEvent(ev.src, ev.content)
36+			}
37+		}
38+
39 		if !app.pasting {
40 			app.setStatus()
41 			app.updatePrompt()
42@@ -255,6 +271,9 @@ func (app *App) ircLoop(netID string) {
43 			HeadColor: tcell.ColorRed,
44 			Body:      ui.PlainString("Connection lost"),
45 		})
46+		if app.win.ShouldExit() {
47+			break
48+		}
49 		time.Sleep(10 * time.Second)
50 	}
51 }
52@@ -340,17 +359,6 @@ func (app *App) uiLoop() {
53 	}
54 }
55 
56-// handleEvents handles a batch of events.
57-func (app *App) handleEvents(evs []event) {
58-	for _, ev := range evs {
59-		if ev.src == "*" {
60-			app.handleUIEvent(ev.content)
61-		} else {
62-			app.handleIRCEvent(ev.src, ev.content)
63-		}
64-	}
65-}
66-
67 func (app *App) handleUIEvent(ev interface{}) {
68 	switch ev := ev.(type) {
69 	case *tcell.EventResize:
+1, -0
1@@ -170,6 +170,7 @@ func (s *Session) Close() {
2 		return
3 	}
4 	s.closed = true
5+	s.typings.Close()
6 	close(s.out)
7 }
8 
+14, -3
 1@@ -16,6 +16,7 @@ type Typing struct {
 2 // Typings keeps track of typing notification timeouts.
 3 type Typings struct {
 4 	l        sync.Mutex
 5+	closed   bool                 // whether Close has been called
 6 	targets  map[Typing]time.Time // @+typing TAGMSG timestamps.
 7 	timeouts chan Typing          // transmits unfiltered timeout notifications.
 8 	stops    chan Typing          // transmits filtered timeout notifications.
 9@@ -45,10 +46,14 @@ func NewTypings() *Typings {
10 	return ts
11 }
12 
13-// Stop cleanly closes all channels and stops all coroutines.
14-func (ts *Typings) Stop() {
15+// Close cleanly closes all channels and stops all goroutines.
16+func (ts *Typings) Close() {
17+	ts.l.Lock()
18+	defer ts.l.Unlock()
19+
20 	close(ts.timeouts)
21 	close(ts.stops)
22+	ts.closed = true
23 }
24 
25 // Stops is a channel that transmits typing timeouts.
26@@ -65,7 +70,13 @@ func (ts *Typings) Active(target, name string) {
27 
28 	go func() {
29 		time.Sleep(6 * time.Second)
30-		ts.timeouts <- t
31+
32+		ts.l.Lock()
33+		defer ts.l.Unlock()
34+
35+		if !ts.closed {
36+			ts.timeouts <- t
37+		}
38 	}()
39 }
40 
+7, -1
 1@@ -60,8 +60,14 @@ func New(config Config) (ui *UI, err error) {
 2 	ui.Events = make(chan tcell.Event, 128)
 3 	go func() {
 4 		for !ui.ShouldExit() {
 5-			ui.Events <- ui.screen.PollEvent()
 6+			ev := ui.screen.PollEvent()
 7+			if ev == nil {
 8+				ui.Exit()
 9+				break
10+			}
11+			ui.Events <- ev
12 		}
13+		close(ui.Events)
14 	}()
15 
16 	ui.bs = NewBufferList()