-
Notifications
You must be signed in to change notification settings - Fork 0
/
channelmanager.go
178 lines (153 loc) · 4.78 KB
/
channelmanager.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Copyright (c) 2017 Shivaram Lingamneni <slingamn@cs.stanford.edu>
// released under the MIT license
package irc
import (
"sync"
)
type channelManagerEntry struct {
channel *Channel
// this is a refcount for joins, so we can avoid a race where we incorrectly
// think the channel is empty (without holding a lock across the entire Channel.Join()
// call)
pendingJoins int
}
// ChannelManager keeps track of all the channels on the server,
// providing synchronization for creation of new channels on first join,
// cleanup of empty channels on last part, and renames.
type ChannelManager struct {
sync.RWMutex // tier 2
chans map[string]*channelManagerEntry
}
// NewChannelManager returns a new ChannelManager.
func NewChannelManager() *ChannelManager {
return &ChannelManager{
chans: make(map[string]*channelManagerEntry),
}
}
// Get returns an existing channel with name equivalent to `name`, or nil
func (cm *ChannelManager) Get(name string) *Channel {
name, err := CasefoldChannel(name)
if err == nil {
cm.RLock()
defer cm.RUnlock()
entry := cm.chans[name]
if entry != nil {
return entry.channel
}
}
return nil
}
// Join causes `client` to join the channel named `name`, creating it if necessary.
func (cm *ChannelManager) Join(client *Client, name string, key string, rb *ResponseBuffer) error {
server := client.server
casefoldedName, err := CasefoldChannel(name)
if err != nil || len(casefoldedName) > server.Limits().ChannelLen {
return errNoSuchChannel
}
cm.Lock()
entry := cm.chans[casefoldedName]
if entry == nil {
// XXX give up the lock to check for a registration, then check again
// to see if we need to create the channel. we could solve this by doing LoadChannel
// outside the lock initially on every join, so this is best thought of as an
// optimization to avoid that.
cm.Unlock()
info := client.server.channelRegistry.LoadChannel(casefoldedName)
cm.Lock()
entry = cm.chans[casefoldedName]
if entry == nil {
entry = &channelManagerEntry{
channel: NewChannel(server, name, true, info),
pendingJoins: 0,
}
cm.chans[casefoldedName] = entry
}
}
entry.pendingJoins += 1
cm.Unlock()
entry.channel.Join(client, key, rb)
cm.maybeCleanup(entry.channel, true)
return nil
}
func (cm *ChannelManager) maybeCleanup(channel *Channel, afterJoin bool) {
cm.Lock()
defer cm.Unlock()
entry := cm.chans[channel.NameCasefolded()]
if entry == nil || entry.channel != channel {
return
}
if afterJoin {
entry.pendingJoins -= 1
}
// TODO(slingamn) right now, registered channels cannot be cleaned up.
// this is because once ChannelManager becomes the source of truth about a channel,
// we can't move the source of truth back to the database unless we do an ACID
// store while holding the ChannelManager's Lock(). This is pending more decisions
// about where the database transaction lock fits into the overall lock model.
if !entry.channel.IsRegistered() && entry.channel.IsEmpty() && entry.pendingJoins == 0 {
// reread the name, handling the case where the channel was renamed
casefoldedName := entry.channel.NameCasefolded()
delete(cm.chans, casefoldedName)
// invalidate the entry (otherwise, a subsequent cleanup attempt could delete
// a valid, distinct entry under casefoldedName):
entry.channel = nil
}
}
// Part parts `client` from the channel named `name`, deleting it if it's empty.
func (cm *ChannelManager) Part(client *Client, name string, message string, rb *ResponseBuffer) error {
casefoldedName, err := CasefoldChannel(name)
if err != nil {
return errNoSuchChannel
}
cm.RLock()
entry := cm.chans[casefoldedName]
cm.RUnlock()
if entry == nil {
return errNoSuchChannel
}
entry.channel.Part(client, message, rb)
return nil
}
func (cm *ChannelManager) Cleanup(channel *Channel) {
cm.maybeCleanup(channel, false)
}
// Rename renames a channel (but does not notify the members)
func (cm *ChannelManager) Rename(name string, newname string) error {
cfname, err := CasefoldChannel(name)
if err != nil {
return errNoSuchChannel
}
cfnewname, err := CasefoldChannel(newname)
if err != nil {
return errInvalidChannelName
}
cm.Lock()
defer cm.Unlock()
if cm.chans[cfnewname] != nil {
return errChannelNameInUse
}
entry := cm.chans[cfname]
if entry == nil {
return errNoSuchChannel
}
delete(cm.chans, cfname)
cm.chans[cfnewname] = entry
entry.channel.setName(newname)
entry.channel.setNameCasefolded(cfnewname)
return nil
}
// Len returns the number of channels
func (cm *ChannelManager) Len() int {
cm.RLock()
defer cm.RUnlock()
return len(cm.chans)
}
// Channels returns a slice containing all current channels
func (cm *ChannelManager) Channels() (result []*Channel) {
cm.RLock()
defer cm.RUnlock()
for _, entry := range cm.chans {
result = append(result, entry.channel)
}
return
}