Skip to content

Commit 54deea6

Browse files
authored
Merge pull request #1035 from gotify/panic
fix: panic when write on closed channel
2 parents 6933034 + eec23c7 commit 54deea6

3 files changed

Lines changed: 29 additions & 8 deletions

File tree

‎api/stream/client.go‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type client struct {
2424
conn *websocket.Conn
2525
onClose func(*client)
2626
write chan *model.MessageExternal
27+
closed chan struct{}
2728
userID uint
2829
token string
2930
once once
@@ -33,6 +34,7 @@ func newClient(conn *websocket.Conn, userID uint, token string, onClose func(*cl
3334
return &client{
3435
conn: conn,
3536
write: make(chan *model.MessageExternal, 1),
37+
closed: make(chan struct{}),
3638
userID: userID,
3739
token: token,
3840
onClose: onClose,
@@ -43,15 +45,15 @@ func newClient(conn *websocket.Conn, userID uint, token string, onClose func(*cl
4345
func (c *client) Close() {
4446
c.once.Do(func() {
4547
c.conn.Close()
46-
close(c.write)
48+
close(c.closed)
4749
})
4850
}
4951

5052
// NotifyClose closes the connection and notifies that the connection was closed.
5153
func (c *client) NotifyClose() {
5254
c.once.Do(func() {
5355
c.conn.Close()
54-
close(c.write)
56+
close(c.closed)
5557
c.onClose(c)
5658
})
5759
}
@@ -87,11 +89,9 @@ func (c *client) startWriteHandler(pingPeriod time.Duration) {
8789

8890
for {
8991
select {
90-
case message, ok := <-c.write:
91-
if !ok {
92-
return
93-
}
94-
92+
case <-c.closed:
93+
return
94+
case message := <-c.write:
9595
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
9696
if err := writeJSON(c.conn, message); err != nil {
9797
printWebSocketError("WriteError", err)

‎api/stream/stream.go‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,10 @@ func (a *API) Notify(userID uint, msg *model.MessageExternal) {
8484
defer a.lock.RUnlock()
8585
if clients, ok := a.clients[userID]; ok {
8686
for _, c := range clients {
87-
c.write <- msg
87+
select {
88+
case c.write <- msg:
89+
case <-c.closed:
90+
}
8891
}
8992
}
9093
}

‎api/stream/stream_test.go‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,24 @@ func TestWritePingFails(t *testing.T) {
9999
user.expectNoMessage()
100100
}
101101

102+
func TestNotifyDoesNotPanicWhenClientIsClosed(t *testing.T) {
103+
mode.Set(mode.TestDev)
104+
defer leaktest.Check(t)()
105+
106+
server, api := bootTestServer(staticUserID())
107+
defer server.Close()
108+
defer api.Close()
109+
110+
ws, _, err := websocket.DefaultDialer.Dial(wsURL(server.URL), nil)
111+
assert.Nil(t, err)
112+
defer ws.Close()
113+
114+
waitForConnectedClients(api, 1)
115+
116+
clients(api, 1)[0].Close()
117+
api.Notify(1, &model.MessageExternal{Message: "after close"})
118+
}
119+
102120
func TestPing(t *testing.T) {
103121
mode.Set(mode.TestDev)
104122

0 commit comments

Comments
 (0)