1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-07-03 01:28:30 +00:00
FrankerFaceZ/socketserver/internal/server/publisher.go

173 lines
4.6 KiB
Go
Raw Normal View History

2015-10-25 03:21:50 -07:00
package server
// This is the scariest code I've written yet for the server.
// If I screwed up the locking, I won't know until it's too late.
import (
"sync"
"time"
"net/http"
)
type SubscriberList struct {
sync.RWMutex
Members []chan <- ClientMessage
}
var ChatSubscriptionInfo map[string]*SubscriberList
var ChatSubscriptionLock sync.RWMutex
var WatchingSubscriptionInfo map[string]*SubscriberList
var WatchingSubscriptionLock sync.RWMutex
func PublishToChat(channel string, msg ClientMessage) {
ChatSubscriptionLock.RLock()
list := ChatSubscriptionInfo[channel]
if list != nil {
list.RLock()
for _, ch := range list.Members {
ch <- msg
}
list.RUnlock()
}
ChatSubscriptionLock.RUnlock()
}
func PublishToWatchers(channel string, msg ClientMessage) {
WatchingSubscriptionLock.RLock()
list := WatchingSubscriptionInfo[channel]
if list != nil {
list.RLock()
for _, ch := range list.Members {
ch <- msg
}
list.RUnlock()
}
WatchingSubscriptionLock.RUnlock()
}
func HandlePublishRequest(w http.ResponseWriter, r *http.Request) {
if r.TLS {
PeerCertificates
}
}
// Add a channel to the subscriptions while holding a read-lock to the map.
// Locks:
// - ALREADY HOLDING a read-lock to the 'which' top-level map via the rlocker object
// - possible write lock to the 'which' top-level map via the wlocker object
// - write lock to SubscriptionInfo (if not creating new)
func _subscribeWhileRlocked(which map[string]*SubscriberList, channelName string, value chan <- ClientMessage, rlocker sync.Locker, wlocker sync.Locker) {
list := which[channelName]
if list == nil {
// Not found, so create it
rlocker.Unlock()
wlocker.Lock()
list = &SubscriberList{}
list.Members = &[]chan <- ClientMessage{value} // Create it populated, to avoid reaper
which[channelName] = list
wlocker.Unlock()
rlocker.Lock()
} else {
list.Lock()
AddToSliceC(&list.Members, value)
list.Unlock()
}
}
// Locks:
// - read lock to top-level maps
// - possible write lock to top-level maps
// - write lock to SubscriptionInfos
func SubscribeBatch(client *ClientInfo, chatSubs, channelSubs []string) {
mchan := client.MessageChannel
if len(chatSubs) > 0 {
rlocker := ChatSubscriptionLock.RLocker()
rlocker.Lock()
for _, v := range chatSubs {
_subscribeWhileRlocked(ChatSubscriptionInfo, v, mchan, rlocker, ChatSubscriptionLock)
}
rlocker.Unlock()
}
if len(channelSubs) > 0 {
rlocker := WatchingSubscriptionLock.RLocker()
rlocker.Lock()
for _, v := range channelSubs {
_subscribeWhileRlocked(WatchingSubscriptionInfo, v, mchan, rlocker, WatchingSubscriptionLock)
}
rlocker.Unlock()
}
}
// Unsubscribe the client from all channels, AND clear the CurrentChannels / WatchingChannels fields.
// Locks:
// - read lock to top-level maps
// - write lock to SubscriptionInfos
// - write lock to ClientInfo
func UnsubscribeAll(client *ClientInfo) {
ChatSubscriptionLock.RLock()
client.Mutex.Lock()
for _, v := range client.CurrentChannels {
list := ChatSubscriptionInfo[v]
list.Lock()
RemoveFromSliceC(&list.Members, client.MessageChannel)
list.Unlock()
}
client.CurrentChannels = nil
client.Mutex.Unlock()
ChatSubscriptionLock.RUnlock()
WatchingSubscriptionLock.RLock()
client.Mutex.Lock()
for _, v := range client.WatchingChannels {
list := WatchingSubscriptionInfo[v]
list.Lock()
RemoveFromSliceC(&list.Members, client.MessageChannel)
list.Unlock()
}
client.WatchingChannels = nil
client.Mutex.Unlock()
WatchingSubscriptionLock.RUnlock()
}
func UnsubscribeSingleChat(client *ClientInfo, channelName string) {
ChatSubscriptionLock.RLock()
list := ChatSubscriptionInfo[channelName]
list.Lock()
RemoveFromSliceC(&list.Members, client.MessageChannel)
list.Unlock()
ChatSubscriptionLock.RUnlock()
}
func UnsubscribeSingleChannel(client *ClientInfo, channelName string) {
WatchingSubscriptionLock.RLock()
list := WatchingSubscriptionInfo[channelName]
list.Lock()
RemoveFromSliceC(&list.Members, client.MessageChannel)
list.Unlock()
WatchingSubscriptionLock.RUnlock()
}
const ReapingDelay = 120 * time.Minute
// Checks each of ChatSubscriptionInfo / WatchingSubscriptionInfo
// for entries with no subscribers every ReapingDelay.
// Started from SetupServer().
func deadChannelReaper() {
for {
time.Sleep(ReapingDelay / 2)
ChatSubscriptionLock.Lock()
for key, val := range ChatSubscriptionInfo {
if len(val.Members) == 0 {
ChatSubscriptionInfo[key] = nil
}
}
ChatSubscriptionLock.Unlock()
time.Sleep(ReapingDelay / 2)
WatchingSubscriptionLock.Lock()
for key, val := range WatchingSubscriptionInfo {
if len(val.Members) == 0 {
WatchingSubscriptionInfo[key] = nil
}
}
}
}