Prevent deadlock on stream.Close()

GR = goroutine
[GR#1] http server gets closed
[GR#2] client.NotifyClose() will be executed
[GR#2] client.once.Do will be executed (lock's client.once.m)
[GR#1] stream.Close will be executed (lock's stream.lock)
[GR#1] client.Close will be executed (waits for client.once.m)
[GR#2] stream.remove will be executed (waits for stream.lock)

GR#1 holds lock stream.lock and waits for client.once.m
GR#2 holds lock client.once.m and waits for stream.lock

We prevent the deadlock with releasing the client.once.m lock earlier.
This commit is contained in:
Jannis Mattheis 2018-11-18 12:32:24 +01:00
parent a992bc1506
commit 79e1dc9c9a
2 changed files with 37 additions and 2 deletions

View File

@ -1,7 +1,6 @@
package stream package stream
import ( import (
"sync"
"time" "time"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
@ -26,7 +25,7 @@ type client struct {
write chan *model.Message write chan *model.Message
userID uint userID uint
token string token string
once sync.Once once once
} }
func newClient(conn *websocket.Conn, userID uint, token string, onClose func(*client)) *client { func newClient(conn *websocket.Conn, userID uint, token string, onClose func(*client)) *client {

36
api/stream/once.go Normal file
View File

@ -0,0 +1,36 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package stream
import (
"sync/atomic"
"sync"
)
// Modified version of sync.Once (https://github.com/golang/go/blob/master/src/sync/once.go)
// This version unlocks the mutex early and therefore doesn't hold the lock while executing func f().
type once struct {
m sync.Mutex
done uint32
}
func (o *once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 1 {
return
}
if o.mayExecute() {
f()
}
}
func (o *once) mayExecute() bool {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
atomic.StoreUint32(&o.done, 1)
return true
}
return false
}