summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHubert Hirtz <hubert@hirtz.pm>2021-11-16 09:20:21 +0100
committerHubert Hirtz <hubert@hirtz.pm>2021-11-16 22:22:10 +0100
commitd40d8dc36a608a1349f8353c50c2c71649e6fa75 (patch)
treed43e01ac710680c5e765e4726ff8928ec2431499
parentDon't merge message bounds from multiple networks (diff)
Allow App.Close() and App.Run() to be run concurrently
-rw-r--r--app.go34
-rw-r--r--irc/session.go1
-rw-r--r--irc/typing.go17
-rw-r--r--ui/ui.go8
4 files changed, 43 insertions, 17 deletions
diff --git a/app.go b/app.go
index acbe32b..237d606 100644
--- a/app.go
+++ b/app.go
@@ -137,7 +137,11 @@ func NewApp(cfg Config) (app *App, err error) {
}
func (app *App) Close() {
- app.win.Close()
+ app.win.Exit() // tell all instances of app.ircLoop to stop when possible
+ app.events <- event{ // tell app.eventLoop to stop
+ src: "*",
+ content: nil,
+ }
for _, session := range app.sessions {
session.Close()
}
@@ -166,6 +170,8 @@ func (app *App) CurrentBuffer() (netID, buffer string) {
// eventLoop retrieves events (in batches) from the event channel and handle
// them, then draws the interface after each batch is handled.
func (app *App) eventLoop() {
+ defer app.win.Close()
+
evs := make([]event, 0, eventChanSize)
for !app.win.ShouldExit() {
ev := <-app.events
@@ -181,7 +187,17 @@ func (app *App) eventLoop() {
}
}
- app.handleEvents(evs)
+ for _, ev := range evs {
+ if ev.src == "*" {
+ if ev.content == nil {
+ return
+ }
+ app.handleUIEvent(ev.content)
+ } else {
+ app.handleIRCEvent(ev.src, ev.content)
+ }
+ }
+
if !app.pasting {
app.setStatus()
app.updatePrompt()
@@ -255,6 +271,9 @@ func (app *App) ircLoop(netID string) {
HeadColor: tcell.ColorRed,
Body: ui.PlainString("Connection lost"),
})
+ if app.win.ShouldExit() {
+ break
+ }
time.Sleep(10 * time.Second)
}
}
@@ -340,17 +359,6 @@ func (app *App) uiLoop() {
}
}
-// handleEvents handles a batch of events.
-func (app *App) handleEvents(evs []event) {
- for _, ev := range evs {
- if ev.src == "*" {
- app.handleUIEvent(ev.content)
- } else {
- app.handleIRCEvent(ev.src, ev.content)
- }
- }
-}
-
func (app *App) handleUIEvent(ev interface{}) {
switch ev := ev.(type) {
case *tcell.EventResize:
diff --git a/irc/session.go b/irc/session.go
index 084c6a7..07df816 100644
--- a/irc/session.go
+++ b/irc/session.go
@@ -170,6 +170,7 @@ func (s *Session) Close() {
return
}
s.closed = true
+ s.typings.Close()
close(s.out)
}
diff --git a/irc/typing.go b/irc/typing.go
index fd1576c..4a8cf55 100644
--- a/irc/typing.go
+++ b/irc/typing.go
@@ -16,6 +16,7 @@ type Typing struct {
// Typings keeps track of typing notification timeouts.
type Typings struct {
l sync.Mutex
+ closed bool // whether Close has been called
targets map[Typing]time.Time // @+typing TAGMSG timestamps.
timeouts chan Typing // transmits unfiltered timeout notifications.
stops chan Typing // transmits filtered timeout notifications.
@@ -45,10 +46,14 @@ func NewTypings() *Typings {
return ts
}
-// Stop cleanly closes all channels and stops all coroutines.
-func (ts *Typings) Stop() {
+// Close cleanly closes all channels and stops all goroutines.
+func (ts *Typings) Close() {
+ ts.l.Lock()
+ defer ts.l.Unlock()
+
close(ts.timeouts)
close(ts.stops)
+ ts.closed = true
}
// Stops is a channel that transmits typing timeouts.
@@ -65,7 +70,13 @@ func (ts *Typings) Active(target, name string) {
go func() {
time.Sleep(6 * time.Second)
- ts.timeouts <- t
+
+ ts.l.Lock()
+ defer ts.l.Unlock()
+
+ if !ts.closed {
+ ts.timeouts <- t
+ }
}()
}
diff --git a/ui/ui.go b/ui/ui.go
index cda5a06..ec1fd09 100644
--- a/ui/ui.go
+++ b/ui/ui.go
@@ -60,8 +60,14 @@ func New(config Config) (ui *UI, err error) {
ui.Events = make(chan tcell.Event, 128)
go func() {
for !ui.ShouldExit() {
- ui.Events <- ui.screen.PollEvent()
+ ev := ui.screen.PollEvent()
+ if ev == nil {
+ ui.Exit()
+ break
+ }
+ ui.Events <- ev
}
+ close(ui.Events)
}()
ui.bs = NewBufferList()