mirror of
https://github.com/strukturag/nextcloud-spreed-signaling
synced 2024-05-02 22:03:09 +02:00
Support multiple waiters for the same key.
This commit is contained in:
parent
04d315b0a4
commit
60a88b327b
50
notifier.go
50
notifier.go
|
@ -28,12 +28,14 @@ import (
|
||||||
|
|
||||||
type Waiter struct {
|
type Waiter struct {
|
||||||
key string
|
key string
|
||||||
ch chan bool
|
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Waiter) Wait(ctx context.Context) error {
|
func (w *Waiter) Wait(ctx context.Context) error {
|
||||||
select {
|
select {
|
||||||
case <-w.ch:
|
case <-w.ctx.Done():
|
||||||
return nil
|
return nil
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
|
@ -44,25 +46,41 @@ type Notifier struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
|
|
||||||
waiters map[string]*Waiter
|
waiters map[string]*Waiter
|
||||||
|
waiterMap map[string]map[*Waiter]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notifier) NewWaiter(key string) *Waiter {
|
func (n *Notifier) NewWaiter(key string) *Waiter {
|
||||||
n.Lock()
|
n.Lock()
|
||||||
defer n.Unlock()
|
defer n.Unlock()
|
||||||
|
|
||||||
_, found := n.waiters[key]
|
waiter, found := n.waiters[key]
|
||||||
if found {
|
if found {
|
||||||
panic("already waiting")
|
w := &Waiter{
|
||||||
|
key: key,
|
||||||
|
ctx: waiter.ctx,
|
||||||
|
cancel: waiter.cancel,
|
||||||
|
}
|
||||||
|
n.waiterMap[key][w] = true
|
||||||
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
waiter := &Waiter{
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
waiter = &Waiter{
|
||||||
key: key,
|
key: key,
|
||||||
ch: make(chan bool, 1),
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
}
|
}
|
||||||
if n.waiters == nil {
|
if n.waiters == nil {
|
||||||
n.waiters = make(map[string]*Waiter)
|
n.waiters = make(map[string]*Waiter)
|
||||||
}
|
}
|
||||||
|
if n.waiterMap == nil {
|
||||||
|
n.waiterMap = make(map[string]map[*Waiter]bool)
|
||||||
|
}
|
||||||
n.waiters[key] = waiter
|
n.waiters[key] = waiter
|
||||||
|
if _, found := n.waiterMap[key]; !found {
|
||||||
|
n.waiterMap[key] = make(map[*Waiter]bool)
|
||||||
|
}
|
||||||
|
n.waiterMap[key][waiter] = true
|
||||||
return waiter
|
return waiter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,18 +89,24 @@ func (n *Notifier) Reset() {
|
||||||
defer n.Unlock()
|
defer n.Unlock()
|
||||||
|
|
||||||
for _, w := range n.waiters {
|
for _, w := range n.waiters {
|
||||||
close(w.ch)
|
w.cancel()
|
||||||
}
|
}
|
||||||
n.waiters = nil
|
n.waiters = nil
|
||||||
|
n.waiterMap = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notifier) Release(w *Waiter) {
|
func (n *Notifier) Release(w *Waiter) {
|
||||||
n.Lock()
|
n.Lock()
|
||||||
defer n.Unlock()
|
defer n.Unlock()
|
||||||
|
|
||||||
if _, found := n.waiters[w.key]; found {
|
if waiters, found := n.waiterMap[w.key]; found {
|
||||||
|
if _, found := waiters[w]; found {
|
||||||
|
delete(waiters, w)
|
||||||
|
if len(waiters) == 0 {
|
||||||
delete(n.waiters, w.key)
|
delete(n.waiters, w.key)
|
||||||
close(w.ch)
|
w.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,10 +115,8 @@ func (n *Notifier) Notify(key string) {
|
||||||
defer n.Unlock()
|
defer n.Unlock()
|
||||||
|
|
||||||
if w, found := n.waiters[key]; found {
|
if w, found := n.waiters[key]; found {
|
||||||
select {
|
w.cancel()
|
||||||
case w.ch <- true:
|
delete(n.waiters, w.key)
|
||||||
default:
|
delete(n.waiterMap, w.key)
|
||||||
// Ignore, already notified
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,22 @@ func TestNotifierWaitClosed(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNotifierWaitClosedMulti(t *testing.T) {
|
||||||
|
var notifier Notifier
|
||||||
|
|
||||||
|
waiter1 := notifier.NewWaiter("foo")
|
||||||
|
waiter2 := notifier.NewWaiter("foo")
|
||||||
|
notifier.Release(waiter1)
|
||||||
|
notifier.Release(waiter2)
|
||||||
|
|
||||||
|
if err := waiter1.Wait(context.Background()); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := waiter2.Wait(context.Background()); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNotifierResetWillNotify(t *testing.T) {
|
func TestNotifierResetWillNotify(t *testing.T) {
|
||||||
var notifier Notifier
|
var notifier Notifier
|
||||||
|
|
||||||
|
@ -103,18 +119,32 @@ func TestNotifierResetWillNotify(t *testing.T) {
|
||||||
|
|
||||||
func TestNotifierDuplicate(t *testing.T) {
|
func TestNotifierDuplicate(t *testing.T) {
|
||||||
var notifier Notifier
|
var notifier Notifier
|
||||||
|
var wgStart sync.WaitGroup
|
||||||
|
var wgEnd sync.WaitGroup
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
wgStart.Add(1)
|
||||||
|
wgEnd.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wgEnd.Done()
|
||||||
waiter := notifier.NewWaiter("foo")
|
waiter := notifier.NewWaiter("foo")
|
||||||
defer notifier.Release(waiter)
|
defer notifier.Release(waiter)
|
||||||
|
|
||||||
defer func() {
|
// Goroutine has created the waiter and is ready.
|
||||||
if e := recover(); e != nil {
|
wgStart.Done()
|
||||||
if e.(string) != "already waiting" {
|
|
||||||
t.Errorf("Expected error about already waiting, got %+v", e)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||||
}
|
defer cancel()
|
||||||
|
if err := waiter.Wait(ctx); err != nil {
|
||||||
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
}
|
||||||
// Creating a waiter for an existing key will panic.
|
|
||||||
notifier.NewWaiter("foo")
|
wgStart.Wait()
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
notifier.Notify("foo")
|
||||||
|
wgEnd.Wait()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue