Skip to content

Commit 52d98dd

Browse files
mjuragaGopher Bot
authored andcommitted
BUG/MEDIUM: runtime: fix EventListener Close() deadlock and channel panic
Close() used to close the events channel directly while the internal listen goroutine could still be sending on it, causing either a deadlock (send blocked forever) or a panic (send on closed channel). Fix by introducing a done channel: Close() signals via done and closes the connection, while the listen goroutine is the sole owner of the events channel and closes it on exit via defer. The send in listen() now uses a select to also check the done channel.
1 parent c595f09 commit 52d98dd

File tree

1 file changed

+14
-4
lines changed

1 file changed

+14
-4
lines changed

runtime/runtime_event_listener.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type EventListener struct {
5959
// Message delimiter. Either \n or 0 (zero).
6060
delim byte
6161
events chan Event
62+
done chan struct{}
6263
lastError error
6364
closed atomic.Bool
6465
}
@@ -82,6 +83,7 @@ func NewEventListener(network, address, sink string, timeout time.Duration, flag
8283
WriteTimeout: timeout,
8384
delim: '\n',
8485
events: make(chan Event),
86+
done: make(chan struct{}),
8587
}
8688

8789
if slices.Contains(flags, "-0") {
@@ -121,10 +123,11 @@ func (l *EventListener) Listen(ctx context.Context) (Event, error) {
121123
}
122124
}
123125

124-
// Close the EventListener cleanly.
126+
// Close the EventListener cleanly. The events channel will be closed
127+
// asynchronously by the internal listen goroutine after it exits.
125128
func (l *EventListener) Close() error {
126129
if l.closed.CompareAndSwap(false, true) {
127-
defer close(l.events)
130+
close(l.done)
128131
if err := l.conn.Close(); err != nil {
129132
return l.errorf("%w", err)
130133
}
@@ -133,8 +136,11 @@ func (l *EventListener) Close() error {
133136
return nil
134137
}
135138

136-
// Listen for events and push them on the events channel.
139+
// listen reads events from the connection and pushes them on the events channel.
140+
// It is the sole owner of the events channel and closes it on exit.
137141
func (l *EventListener) listen() {
142+
defer close(l.events)
143+
138144
if l.WriteTimeout > 0 {
139145
_ = l.conn.SetWriteDeadline(time.Now().Add(l.WriteTimeout))
140146
}
@@ -161,7 +167,11 @@ func (l *EventListener) listen() {
161167
return
162168
}
163169

164-
l.events <- event
170+
select {
171+
case l.events <- event:
172+
case <-l.done:
173+
return
174+
}
165175
}
166176
}
167177

0 commit comments

Comments
 (0)