go-ssb-room/internal/broadcasts/attendants.go

128 lines
2.4 KiB
Go

// SPDX-FileCopyrightText: 2021 The NGI Pointer Secure-Scuttlebutt Team of 2020/2021
//
// SPDX-License-Identifier: MIT
package broadcasts
import (
"io"
"sync"
refs "github.com/ssbc/go-ssb-refs"
"github.com/ssbc/go-ssb-room/v2/internal/maybemod/multierror"
)
type AttendantsEmitter interface {
Joined(member refs.FeedRef) error
Left(member refs.FeedRef) error
io.Closer
}
// NewAttendantsEmitter returns the Sink, to write to the broadcaster, and the new
// broadcast instance.
func NewAttendantsEmitter() (AttendantsEmitter, *AttendantsBroadcast) {
bcst := AttendantsBroadcast{
mu: &sync.Mutex{},
sinks: make(map[*AttendantsEmitter]struct{}),
}
return (*attendantsSink)(&bcst), &bcst
}
// AttendantsBroadcast is an interface for registering one or more Sinks to recieve
// updates.
type AttendantsBroadcast struct {
mu *sync.Mutex
sinks map[*AttendantsEmitter]struct{}
}
// Register a Sink for updates to be sent. also returns
func (bcst *AttendantsBroadcast) Register(sink AttendantsEmitter) func() {
bcst.mu.Lock()
defer bcst.mu.Unlock()
bcst.sinks[&sink] = struct{}{}
return func() {
bcst.mu.Lock()
defer bcst.mu.Unlock()
delete(bcst.sinks, &sink)
sink.Close()
}
}
type attendantsSink AttendantsBroadcast
func (bcst *attendantsSink) Joined(member refs.FeedRef) error {
bcst.mu.Lock()
for s := range bcst.sinks {
err := (*s).Joined(member)
if err != nil {
delete(bcst.sinks, s)
}
}
bcst.mu.Unlock()
return nil
}
func (bcst *attendantsSink) Left(member refs.FeedRef) error {
bcst.mu.Lock()
for s := range bcst.sinks {
err := (*s).Left(member)
if err != nil {
delete(bcst.sinks, s)
}
}
bcst.mu.Unlock()
return nil
}
// Close implements the Sink interface.
func (bcst *attendantsSink) Close() error {
bcst.mu.Lock()
defer bcst.mu.Unlock()
sinks := make([]AttendantsEmitter, 0, len(bcst.sinks))
for sink := range bcst.sinks {
sinks = append(sinks, *sink)
}
bcst.mu.Lock()
defer bcst.mu.Unlock()
sinks = make([]AttendantsEmitter, 0, len(bcst.sinks))
for sink := range bcst.sinks {
sinks = append(sinks, *sink)
}
var (
wg sync.WaitGroup
me multierror.List
)
// might be fine without the waitgroup and concurrency
wg.Add(len(sinks))
for _, sink_ := range sinks {
go func(sink AttendantsEmitter) {
defer wg.Done()
err := sink.Close()
if err != nil {
me.Errs = append(me.Errs, err)
return
}
}(sink_)
}
wg.Wait()
if len(me.Errs) == 0 {
return nil
}
return me
}