Browse Source

remove registeredChannelsMutex

This moves channel registration to an eventual consistency model,
where the in-memory datastructures (Channel and ChannelManager)
are the exclusive source of truth, and updates to them get persisted
asynchronously to the DB.
tags/v0.10.2
Shivaram Lingamneni 6 years ago
parent
commit
d4cb15354f
7 changed files with 317 additions and 246 deletions
  1. 94
    66
      irc/channel.go
  2. 20
    5
      irc/channelmanager.go
  3. 150
    56
      irc/channelreg.go
  4. 25
    46
      irc/chanserv.go
  5. 18
    0
      irc/getters.go
  6. 2
    33
      irc/modes.go
  7. 8
    40
      irc/server.go

+ 94
- 66
irc/channel.go View File

@@ -6,6 +6,7 @@
6 6
 package irc
7 7
 
8 8
 import (
9
+	"errors"
9 10
 	"fmt"
10 11
 	"strconv"
11 12
 	"time"
@@ -14,7 +15,10 @@ import (
14 15
 
15 16
 	"github.com/goshuirc/irc-go/ircmsg"
16 17
 	"github.com/oragono/oragono/irc/caps"
17
-	"github.com/tidwall/buntdb"
18
+)
19
+
20
+var (
21
+	ChannelAlreadyRegistered = errors.New("Channel is already registered")
18 22
 )
19 23
 
20 24
 // Channel represents a channel that clients can join.
@@ -29,6 +33,8 @@ type Channel struct {
29 33
 	nameCasefolded    string
30 34
 	server            *Server
31 35
 	createdTime       time.Time
36
+	registeredFounder string
37
+	registeredTime    time.Time
32 38
 	stateMutex        sync.RWMutex
33 39
 	topic             string
34 40
 	topicSetBy        string
@@ -38,7 +44,7 @@ type Channel struct {
38 44
 
39 45
 // NewChannel creates a new channel from a `Server` and a `name`
40 46
 // string, which must be unique on the server.
41
-func NewChannel(s *Server, name string, addDefaultModes bool) *Channel {
47
+func NewChannel(s *Server, name string, addDefaultModes bool, regInfo *RegisteredChannel) *Channel {
42 48
 	casefoldedName, err := CasefoldChannel(name)
43 49
 	if err != nil {
44 50
 		s.logger.Error("internal", fmt.Sprintf("Bad channel name %s: %v", name, err))
@@ -46,7 +52,8 @@ func NewChannel(s *Server, name string, addDefaultModes bool) *Channel {
46 52
 	}
47 53
 
48 54
 	channel := &Channel{
49
-		flags: make(ModeSet),
55
+		createdTime: time.Now(), // may be overwritten by applyRegInfo
56
+		flags:       make(ModeSet),
50 57
 		lists: map[Mode]*UserMaskSet{
51 58
 			BanMask:    NewUserMaskSet(),
52 59
 			ExceptMask: NewUserMaskSet(),
@@ -64,9 +71,80 @@ func NewChannel(s *Server, name string, addDefaultModes bool) *Channel {
64 71
 		}
65 72
 	}
66 73
 
74
+	if regInfo != nil {
75
+		channel.applyRegInfo(regInfo)
76
+	}
77
+
67 78
 	return channel
68 79
 }
69 80
 
81
+// read in channel state that was persisted in the DB
82
+func (channel *Channel) applyRegInfo(chanReg *RegisteredChannel) {
83
+	channel.registeredFounder = chanReg.Founder
84
+	channel.registeredTime = chanReg.RegisteredAt
85
+	channel.topic = chanReg.Topic
86
+	channel.topicSetBy = chanReg.TopicSetBy
87
+	channel.topicSetTime = chanReg.TopicSetTime
88
+	channel.name = chanReg.Name
89
+	channel.createdTime = chanReg.RegisteredAt
90
+	for _, mask := range chanReg.Banlist {
91
+		channel.lists[BanMask].Add(mask)
92
+	}
93
+	for _, mask := range chanReg.Exceptlist {
94
+		channel.lists[ExceptMask].Add(mask)
95
+	}
96
+	for _, mask := range chanReg.Invitelist {
97
+		channel.lists[InviteMask].Add(mask)
98
+	}
99
+}
100
+
101
+// obtain a consistent snapshot of the channel state that can be persisted to the DB
102
+func (channel *Channel) ExportRegistration(includeLists bool) (info RegisteredChannel) {
103
+	channel.stateMutex.RLock()
104
+	defer channel.stateMutex.RUnlock()
105
+
106
+	info.Name = channel.name
107
+	info.Topic = channel.topic
108
+	info.TopicSetBy = channel.topicSetBy
109
+	info.TopicSetTime = channel.topicSetTime
110
+	info.Founder = channel.registeredFounder
111
+	info.RegisteredAt = channel.registeredTime
112
+
113
+	if includeLists {
114
+		for mask := range channel.lists[BanMask].masks {
115
+			info.Banlist = append(info.Banlist, mask)
116
+		}
117
+		for mask := range channel.lists[ExceptMask].masks {
118
+			info.Exceptlist = append(info.Exceptlist, mask)
119
+		}
120
+		for mask := range channel.lists[InviteMask].masks {
121
+			info.Invitelist = append(info.Invitelist, mask)
122
+		}
123
+	}
124
+
125
+	return
126
+}
127
+
128
+// SetRegistered registers the channel, returning an error if it was already registered.
129
+func (channel *Channel) SetRegistered(founder string) error {
130
+	channel.stateMutex.Lock()
131
+	defer channel.stateMutex.Unlock()
132
+
133
+	if channel.registeredFounder != "" {
134
+		return ChannelAlreadyRegistered
135
+	}
136
+	channel.registeredFounder = founder
137
+	channel.registeredTime = time.Now()
138
+	return nil
139
+}
140
+
141
+// IsRegistered returns whether the channel is registered.
142
+func (channel *Channel) IsRegistered() bool {
143
+	channel.stateMutex.RLock()
144
+	defer channel.stateMutex.RUnlock()
145
+	return channel.registeredFounder != ""
146
+}
147
+
70 148
 func (channel *Channel) regenerateMembersCache() {
71 149
 	// this is eventually consistent even without holding stateMutex.Lock()
72 150
 	// throughout the update; all updates to `members` while holding Lock()
@@ -340,59 +418,25 @@ func (channel *Channel) Join(client *Client, key string) {
340 418
 	client.addChannel(channel)
341 419
 
342 420
 	// give channel mode if necessary
343
-	var newChannel bool
421
+	newChannel := firstJoin && !channel.IsRegistered()
344 422
 	var givenMode *Mode
345
-	client.server.registeredChannelsMutex.Lock()
346
-	defer client.server.registeredChannelsMutex.Unlock()
347
-	client.server.store.Update(func(tx *buntdb.Tx) error {
348
-		chanReg := client.server.loadChannelNoMutex(tx, channel.nameCasefolded)
349
-
350
-		if chanReg == nil {
351
-			if firstJoin {
352
-				channel.stateMutex.Lock()
353
-				channel.createdTime = time.Now()
354
-				channel.members[client][ChannelOperator] = true
355
-				channel.stateMutex.Unlock()
356
-				givenMode = &ChannelOperator
357
-				newChannel = true
358
-			}
359
-		} else {
360
-			// we should only do this on registered channels
361
-			if client.account != nil && client.account.Name == chanReg.Founder {
362
-				channel.stateMutex.Lock()
363
-				channel.members[client][ChannelFounder] = true
364
-				channel.stateMutex.Unlock()
365
-				givenMode = &ChannelFounder
366
-			}
367
-			if firstJoin {
368
-				// apply other details if new channel
369
-				channel.stateMutex.Lock()
370
-				channel.topic = chanReg.Topic
371
-				channel.topicSetBy = chanReg.TopicSetBy
372
-				channel.topicSetTime = chanReg.TopicSetTime
373
-				channel.name = chanReg.Name
374
-				channel.createdTime = chanReg.RegisteredAt
375
-				for _, mask := range chanReg.Banlist {
376
-					channel.lists[BanMask].Add(mask)
377
-				}
378
-				for _, mask := range chanReg.Exceptlist {
379
-					channel.lists[ExceptMask].Add(mask)
380
-				}
381
-				for _, mask := range chanReg.Invitelist {
382
-					channel.lists[InviteMask].Add(mask)
383
-				}
384
-				channel.stateMutex.Unlock()
385
-			}
386
-		}
387
-		return nil
388
-	})
423
+	if client.AccountName() == channel.registeredFounder {
424
+		givenMode = &ChannelFounder
425
+	} else if newChannel {
426
+		givenMode = &ChannelOperator
427
+	}
428
+	if givenMode != nil {
429
+		channel.stateMutex.Lock()
430
+		channel.members[client][*givenMode] = true
431
+		channel.stateMutex.Unlock()
432
+	}
389 433
 
390 434
 	if client.capabilities.Has(caps.ExtendedJoin) {
391 435
 		client.Send(nil, client.nickMaskString, "JOIN", channel.name, client.account.Name, client.realname)
392 436
 	} else {
393 437
 		client.Send(nil, client.nickMaskString, "JOIN", channel.name)
394 438
 	}
395
-	// don't sent topic when it's an entirely new channel
439
+	// don't send topic when it's an entirely new channel
396 440
 	if !newChannel {
397 441
 		channel.SendTopic(client)
398 442
 	}
@@ -468,23 +512,7 @@ func (channel *Channel) SetTopic(client *Client, topic string) {
468 512
 		member.Send(nil, client.nickMaskString, "TOPIC", channel.name, topic)
469 513
 	}
470 514
 
471
-	// update saved channel topic for registered chans
472
-	client.server.registeredChannelsMutex.Lock()
473
-	defer client.server.registeredChannelsMutex.Unlock()
474
-
475
-	client.server.store.Update(func(tx *buntdb.Tx) error {
476
-		chanInfo := client.server.loadChannelNoMutex(tx, channel.nameCasefolded)
477
-
478
-		if chanInfo == nil {
479
-			return nil
480
-		}
481
-
482
-		chanInfo.Topic = topic
483
-		chanInfo.TopicSetBy = client.nickMaskString
484
-		chanInfo.TopicSetTime = time.Now()
485
-		client.server.saveChannelNoMutex(tx, channel.nameCasefolded, *chanInfo)
486
-		return nil
487
-	})
515
+	go channel.server.channelRegistry.StoreChannel(channel, false)
488 516
 }
489 517
 
490 518
 // CanSpeak returns true if the client can speak on this channel.

+ 20
- 5
irc/channelmanager.go View File

@@ -59,11 +59,21 @@ func (cm *ChannelManager) Join(client *Client, name string, key string) error {
59 59
 	cm.Lock()
60 60
 	entry := cm.chans[casefoldedName]
61 61
 	if entry == nil {
62
-		entry = &channelManagerEntry{
63
-			channel:      NewChannel(server, name, true),
64
-			pendingJoins: 0,
62
+		// XXX give up the lock to check for a registration, then check again
63
+		// to see if we need to create the channel. we could solve this by doing LoadChannel
64
+		// outside the lock initially on every join, so this is best thought of as an
65
+		// optimization to avoid that.
66
+		cm.Unlock()
67
+		info := client.server.channelRegistry.LoadChannel(casefoldedName)
68
+		cm.Lock()
69
+		entry = cm.chans[casefoldedName]
70
+		if entry == nil {
71
+			entry = &channelManagerEntry{
72
+				channel:      NewChannel(server, name, true, info),
73
+				pendingJoins: 0,
74
+			}
75
+			cm.chans[casefoldedName] = entry
65 76
 		}
66
-		cm.chans[casefoldedName] = entry
67 77
 	}
68 78
 	entry.pendingJoins += 1
69 79
 	cm.Unlock()
@@ -85,7 +95,12 @@ func (cm *ChannelManager) maybeCleanup(entry *channelManagerEntry, afterJoin boo
85 95
 	if afterJoin {
86 96
 		entry.pendingJoins -= 1
87 97
 	}
88
-	if entry.channel.IsEmpty() && entry.pendingJoins == 0 {
98
+	// TODO(slingamn) right now, registered channels cannot be cleaned up.
99
+	// this is because once ChannelManager becomes the source of truth about a channel,
100
+	// we can't move the source of truth back to the database unless we do an ACID
101
+	// store while holding the ChannelManager's Lock(). This is pending more decisions
102
+	// about where the database transaction lock fits into the overall lock model.
103
+	if !entry.channel.IsRegistered() && entry.channel.IsEmpty() && entry.pendingJoins == 0 {
89 104
 		// reread the name, handling the case where the channel was renamed
90 105
 		casefoldedName := entry.channel.NameCasefolded()
91 106
 		delete(cm.chans, casefoldedName)

+ 150
- 56
irc/channelreg.go View File

@@ -4,9 +4,9 @@
4 4
 package irc
5 5
 
6 6
 import (
7
-	"errors"
8 7
 	"fmt"
9 8
 	"strconv"
9
+	"sync"
10 10
 	"time"
11 11
 
12 12
 	"encoding/json"
@@ -14,6 +14,9 @@ import (
14 14
 	"github.com/tidwall/buntdb"
15 15
 )
16 16
 
17
+// this is exclusively the *persistence* layer for channel registration;
18
+// channel creation/tracking/destruction is in channelmanager.go
19
+
17 20
 const (
18 21
 	keyChannelExists       = "channel.exists %s"
19 22
 	keyChannelName         = "channel.name %s" // stores the 'preferred name' of the channel, not casemapped
@@ -28,7 +31,18 @@ const (
28 31
 )
29 32
 
30 33
 var (
31
-	errChanExists = errors.New("Channel already exists")
34
+	channelKeyStrings = []string{
35
+		keyChannelExists,
36
+		keyChannelName,
37
+		keyChannelRegTime,
38
+		keyChannelFounder,
39
+		keyChannelTopic,
40
+		keyChannelTopicSetBy,
41
+		keyChannelTopicSetTime,
42
+		keyChannelBanlist,
43
+		keyChannelExceptlist,
44
+		keyChannelInvitelist,
45
+	}
32 46
 )
33 47
 
34 48
 // RegisteredChannel holds details about a given registered channel.
@@ -53,62 +67,142 @@ type RegisteredChannel struct {
53 67
 	Invitelist []string
54 68
 }
55 69
 
56
-// deleteChannelNoMutex deletes a given channel from our store.
57
-func (server *Server) deleteChannelNoMutex(tx *buntdb.Tx, channelKey string) {
58
-	tx.Delete(fmt.Sprintf(keyChannelExists, channelKey))
59
-	server.registeredChannels[channelKey] = nil
70
+type ChannelRegistry struct {
71
+	// this serializes operations of the form (read channel state, synchronously persist it);
72
+	// this is enough to guarantee eventual consistency of the database with the
73
+	// ChannelManager and Channel objects, which are the source of truth.
74
+	// Wwe could use the buntdb RW transaction lock for this purpose but we share
75
+	// that with all the other modules, so let's not.
76
+	sync.Mutex // tier 2
77
+	server     *Server
78
+	channels   map[string]*RegisteredChannel
60 79
 }
61 80
 
62
-// loadChannelNoMutex loads a channel from the store.
63
-func (server *Server) loadChannelNoMutex(tx *buntdb.Tx, channelKey string) *RegisteredChannel {
64
-	// return loaded chan if it already exists
65
-	if server.registeredChannels[channelKey] != nil {
66
-		return server.registeredChannels[channelKey]
81
+func NewChannelRegistry(server *Server) *ChannelRegistry {
82
+	return &ChannelRegistry{
83
+		server: server,
67 84
 	}
68
-	_, err := tx.Get(fmt.Sprintf(keyChannelExists, channelKey))
69
-	if err == buntdb.ErrNotFound {
70
-		// chan does not already exist, return
85
+}
86
+
87
+// StoreChannel obtains a consistent view of a channel, then persists it to the store.
88
+func (reg *ChannelRegistry) StoreChannel(channel *Channel, includeLists bool) {
89
+	if !reg.server.ChannelRegistrationEnabled() {
90
+		return
91
+	}
92
+
93
+	reg.Lock()
94
+	defer reg.Unlock()
95
+
96
+	key := channel.NameCasefolded()
97
+	info := channel.ExportRegistration(includeLists)
98
+	if info.Founder == "" {
99
+		// sanity check, don't try to store an unregistered channel
100
+		return
101
+	}
102
+
103
+	reg.server.store.Update(func(tx *buntdb.Tx) error {
104
+		reg.saveChannel(tx, key, info, includeLists)
105
+		return nil
106
+	})
107
+}
108
+
109
+// LoadChannel loads a channel from the store.
110
+func (reg *ChannelRegistry) LoadChannel(nameCasefolded string) (info *RegisteredChannel) {
111
+	if !reg.server.ChannelRegistrationEnabled() {
71 112
 		return nil
72 113
 	}
73 114
 
74
-	// channel exists, load it
75
-	name, _ := tx.Get(fmt.Sprintf(keyChannelName, channelKey))
76
-	regTime, _ := tx.Get(fmt.Sprintf(keyChannelRegTime, channelKey))
77
-	regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
78
-	founder, _ := tx.Get(fmt.Sprintf(keyChannelFounder, channelKey))
79
-	topic, _ := tx.Get(fmt.Sprintf(keyChannelTopic, channelKey))
80
-	topicSetBy, _ := tx.Get(fmt.Sprintf(keyChannelTopicSetBy, channelKey))
81
-	topicSetTime, _ := tx.Get(fmt.Sprintf(keyChannelTopicSetTime, channelKey))
82
-	topicSetTimeInt, _ := strconv.ParseInt(topicSetTime, 10, 64)
83
-	banlistString, _ := tx.Get(fmt.Sprintf(keyChannelBanlist, channelKey))
84
-	exceptlistString, _ := tx.Get(fmt.Sprintf(keyChannelExceptlist, channelKey))
85
-	invitelistString, _ := tx.Get(fmt.Sprintf(keyChannelInvitelist, channelKey))
86
-
87
-	var banlist []string
88
-	_ = json.Unmarshal([]byte(banlistString), &banlist)
89
-	var exceptlist []string
90
-	_ = json.Unmarshal([]byte(exceptlistString), &exceptlist)
91
-	var invitelist []string
92
-	_ = json.Unmarshal([]byte(invitelistString), &invitelist)
93
-
94
-	chanInfo := RegisteredChannel{
95
-		Name:         name,
96
-		RegisteredAt: time.Unix(regTimeInt, 0),
97
-		Founder:      founder,
98
-		Topic:        topic,
99
-		TopicSetBy:   topicSetBy,
100
-		TopicSetTime: time.Unix(topicSetTimeInt, 0),
101
-		Banlist:      banlist,
102
-		Exceptlist:   exceptlist,
103
-		Invitelist:   invitelist,
115
+	channelKey := nameCasefolded
116
+	// nice to have: do all JSON (de)serialization outside of the buntdb transaction
117
+	reg.server.store.View(func(tx *buntdb.Tx) error {
118
+		_, err := tx.Get(fmt.Sprintf(keyChannelExists, channelKey))
119
+		if err == buntdb.ErrNotFound {
120
+			// chan does not already exist, return
121
+			return nil
122
+		}
123
+
124
+		// channel exists, load it
125
+		name, _ := tx.Get(fmt.Sprintf(keyChannelName, channelKey))
126
+		regTime, _ := tx.Get(fmt.Sprintf(keyChannelRegTime, channelKey))
127
+		regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
128
+		founder, _ := tx.Get(fmt.Sprintf(keyChannelFounder, channelKey))
129
+		topic, _ := tx.Get(fmt.Sprintf(keyChannelTopic, channelKey))
130
+		topicSetBy, _ := tx.Get(fmt.Sprintf(keyChannelTopicSetBy, channelKey))
131
+		topicSetTime, _ := tx.Get(fmt.Sprintf(keyChannelTopicSetTime, channelKey))
132
+		topicSetTimeInt, _ := strconv.ParseInt(topicSetTime, 10, 64)
133
+		banlistString, _ := tx.Get(fmt.Sprintf(keyChannelBanlist, channelKey))
134
+		exceptlistString, _ := tx.Get(fmt.Sprintf(keyChannelExceptlist, channelKey))
135
+		invitelistString, _ := tx.Get(fmt.Sprintf(keyChannelInvitelist, channelKey))
136
+
137
+		var banlist []string
138
+		_ = json.Unmarshal([]byte(banlistString), &banlist)
139
+		var exceptlist []string
140
+		_ = json.Unmarshal([]byte(exceptlistString), &exceptlist)
141
+		var invitelist []string
142
+		_ = json.Unmarshal([]byte(invitelistString), &invitelist)
143
+
144
+		info = &RegisteredChannel{
145
+			Name:         name,
146
+			RegisteredAt: time.Unix(regTimeInt, 0),
147
+			Founder:      founder,
148
+			Topic:        topic,
149
+			TopicSetBy:   topicSetBy,
150
+			TopicSetTime: time.Unix(topicSetTimeInt, 0),
151
+			Banlist:      banlist,
152
+			Exceptlist:   exceptlist,
153
+			Invitelist:   invitelist,
154
+		}
155
+		return nil
156
+	})
157
+
158
+	return info
159
+}
160
+
161
+// Rename handles the persistence part of a channel rename: the channel is
162
+// persisted under its new name, and the old name is cleaned up if necessary.
163
+func (reg *ChannelRegistry) Rename(channel *Channel, casefoldedOldName string) {
164
+	if !reg.server.ChannelRegistrationEnabled() {
165
+		return
166
+	}
167
+
168
+	reg.Lock()
169
+	defer reg.Unlock()
170
+
171
+	includeLists := true
172
+	oldKey := casefoldedOldName
173
+	key := channel.NameCasefolded()
174
+	info := channel.ExportRegistration(includeLists)
175
+	if info.Founder == "" {
176
+		return
104 177
 	}
105
-	server.registeredChannels[channelKey] = &chanInfo
106 178
 
107
-	return &chanInfo
179
+	reg.server.store.Update(func(tx *buntdb.Tx) error {
180
+		reg.deleteChannel(tx, oldKey, info)
181
+		reg.saveChannel(tx, key, info, includeLists)
182
+		return nil
183
+	})
184
+}
185
+
186
+// delete a channel, unless it was overwritten by another registration of the same channel
187
+func (reg *ChannelRegistry) deleteChannel(tx *buntdb.Tx, key string, info RegisteredChannel) {
188
+	_, err := tx.Get(fmt.Sprintf(keyChannelExists, key))
189
+	if err == nil {
190
+		regTime, _ := tx.Get(fmt.Sprintf(keyChannelRegTime, key))
191
+		regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
192
+		registeredAt := time.Unix(regTimeInt, 0)
193
+		founder, _ := tx.Get(fmt.Sprintf(keyChannelFounder, key))
194
+
195
+		// to see if we're deleting the right channel, confirm the founder and the registration time
196
+		if founder == info.Founder && registeredAt == info.RegisteredAt {
197
+			for _, keyFmt := range channelKeyStrings {
198
+				tx.Delete(fmt.Sprintf(keyFmt, key))
199
+			}
200
+		}
201
+	}
108 202
 }
109 203
 
110
-// saveChannelNoMutex saves a channel to the store.
111
-func (server *Server) saveChannelNoMutex(tx *buntdb.Tx, channelKey string, channelInfo RegisteredChannel) {
204
+// saveChannel saves a channel to the store.
205
+func (reg *ChannelRegistry) saveChannel(tx *buntdb.Tx, channelKey string, channelInfo RegisteredChannel, includeLists bool) {
112 206
 	tx.Set(fmt.Sprintf(keyChannelExists, channelKey), "1", nil)
113 207
 	tx.Set(fmt.Sprintf(keyChannelName, channelKey), channelInfo.Name, nil)
114 208
 	tx.Set(fmt.Sprintf(keyChannelRegTime, channelKey), strconv.FormatInt(channelInfo.RegisteredAt.Unix(), 10), nil)
@@ -117,12 +211,12 @@ func (server *Server) saveChannelNoMutex(tx *buntdb.Tx, channelKey string, chann
117 211
 	tx.Set(fmt.Sprintf(keyChannelTopicSetBy, channelKey), channelInfo.TopicSetBy, nil)
118 212
 	tx.Set(fmt.Sprintf(keyChannelTopicSetTime, channelKey), strconv.FormatInt(channelInfo.TopicSetTime.Unix(), 10), nil)
119 213
 
120
-	banlistString, _ := json.Marshal(channelInfo.Banlist)
121
-	tx.Set(fmt.Sprintf(keyChannelBanlist, channelKey), string(banlistString), nil)
122
-	exceptlistString, _ := json.Marshal(channelInfo.Exceptlist)
123
-	tx.Set(fmt.Sprintf(keyChannelExceptlist, channelKey), string(exceptlistString), nil)
124
-	invitelistString, _ := json.Marshal(channelInfo.Invitelist)
125
-	tx.Set(fmt.Sprintf(keyChannelInvitelist, channelKey), string(invitelistString), nil)
126
-
127
-	server.registeredChannels[channelKey] = &channelInfo
214
+	if includeLists {
215
+		banlistString, _ := json.Marshal(channelInfo.Banlist)
216
+		tx.Set(fmt.Sprintf(keyChannelBanlist, channelKey), string(banlistString), nil)
217
+		exceptlistString, _ := json.Marshal(channelInfo.Exceptlist)
218
+		tx.Set(fmt.Sprintf(keyChannelExceptlist, channelKey), string(exceptlistString), nil)
219
+		invitelistString, _ := json.Marshal(channelInfo.Invitelist)
220
+		tx.Set(fmt.Sprintf(keyChannelInvitelist, channelKey), string(invitelistString), nil)
221
+	}
128 222
 }

+ 25
- 46
irc/chanserv.go View File

@@ -6,12 +6,10 @@ package irc
6 6
 import (
7 7
 	"fmt"
8 8
 	"strings"
9
-	"time"
10 9
 
11 10
 	"github.com/goshuirc/irc-go/ircfmt"
12 11
 	"github.com/goshuirc/irc-go/ircmsg"
13 12
 	"github.com/oragono/oragono/irc/sno"
14
-	"github.com/tidwall/buntdb"
15 13
 )
16 14
 
17 15
 // csHandler handles the /CS and /CHANSERV commands
@@ -56,9 +54,6 @@ func (server *Server) chanservReceivePrivmsg(client *Client, message string) {
56 54
 			return
57 55
 		}
58 56
 
59
-		server.registeredChannelsMutex.Lock()
60
-		defer server.registeredChannelsMutex.Unlock()
61
-
62 57
 		channelName := params[1]
63 58
 		channelKey, err := CasefoldChannel(channelName)
64 59
 		if err != nil {
@@ -67,57 +62,41 @@ func (server *Server) chanservReceivePrivmsg(client *Client, message string) {
67 62
 		}
68 63
 
69 64
 		channelInfo := server.channels.Get(channelKey)
70
-		if channelInfo == nil {
65
+		if channelInfo == nil || !channelInfo.ClientIsAtLeast(client, ChannelOperator) {
71 66
 			client.ChanServNotice("You must be an oper on the channel to register it")
72 67
 			return
73 68
 		}
74 69
 
75
-		if !channelInfo.ClientIsAtLeast(client, ChannelOperator) {
76
-			client.ChanServNotice("You must be an oper on the channel to register it")
70
+		if client.account == &NoAccount {
71
+			client.ChanServNotice("You must be logged in to register a channel")
77 72
 			return
78 73
 		}
79 74
 
80
-		server.store.Update(func(tx *buntdb.Tx) error {
81
-			currentChan := server.loadChannelNoMutex(tx, channelKey)
82
-			if currentChan != nil {
83
-				client.ChanServNotice("Channel is already registered")
84
-				return nil
85
-			}
75
+		// this provides the synchronization that allows exactly one registration of the channel:
76
+		err = channelInfo.SetRegistered(client.AccountName())
77
+		if err != nil {
78
+			client.ChanServNotice(err.Error())
79
+			return
80
+		}
86 81
 
87
-			account := client.account
88
-			if account == &NoAccount {
89
-				client.ChanServNotice("You must be logged in to register a channel")
90
-				return nil
91
-			}
82
+		// registration was successful: make the database reflect it
83
+		go server.channelRegistry.StoreChannel(channelInfo, true)
92 84
 
93
-			chanRegInfo := RegisteredChannel{
94
-				Name:         channelName,
95
-				RegisteredAt: time.Now(),
96
-				Founder:      account.Name,
97
-				Topic:        channelInfo.topic,
98
-				TopicSetBy:   channelInfo.topicSetBy,
99
-				TopicSetTime: channelInfo.topicSetTime,
100
-			}
101
-			server.saveChannelNoMutex(tx, channelKey, chanRegInfo)
102
-
103
-			client.ChanServNotice(fmt.Sprintf("Channel %s successfully registered", channelName))
104
-
105
-			server.logger.Info("chanserv", fmt.Sprintf("Client %s registered channel %s", client.nick, channelName))
106
-			server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Channel registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), channelName, client.nickMaskString))
107
-
108
-			// give them founder privs
109
-			change := channelInfo.applyModeMemberNoMutex(client, ChannelFounder, Add, client.nickCasefolded)
110
-			if change != nil {
111
-				//TODO(dan): we should change the name of String and make it return a slice here
112
-				//TODO(dan): unify this code with code in modes.go
113
-				args := append([]string{channelName}, strings.Split(change.String(), " ")...)
114
-				for _, member := range channelInfo.Members() {
115
-					member.Send(nil, fmt.Sprintf("ChanServ!services@%s", client.server.name), "MODE", args...)
116
-				}
117
-			}
85
+		client.ChanServNotice(fmt.Sprintf("Channel %s successfully registered", channelName))
118 86
 
119
-			return nil
120
-		})
87
+		server.logger.Info("chanserv", fmt.Sprintf("Client %s registered channel %s", client.nick, channelName))
88
+		server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Channel registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), channelName, client.nickMaskString))
89
+
90
+		// give them founder privs
91
+		change := channelInfo.applyModeMemberNoMutex(client, ChannelFounder, Add, client.NickCasefolded())
92
+		if change != nil {
93
+			//TODO(dan): we should change the name of String and make it return a slice here
94
+			//TODO(dan): unify this code with code in modes.go
95
+			args := append([]string{channelName}, strings.Split(change.String(), " ")...)
96
+			for _, member := range channelInfo.Members() {
97
+				member.Send(nil, fmt.Sprintf("ChanServ!services@%s", client.server.name), "MODE", args...)
98
+			}
99
+		}
121 100
 	} else {
122 101
 		client.ChanServNotice("Sorry, I don't know that command")
123 102
 	}

+ 18
- 0
irc/getters.go View File

@@ -47,6 +47,12 @@ func (server *Server) DefaultChannelModes() Modes {
47 47
 	return server.defaultChannelModes
48 48
 }
49 49
 
50
+func (server *Server) ChannelRegistrationEnabled() bool {
51
+	server.configurableStateMutex.RLock()
52
+	defer server.configurableStateMutex.RUnlock()
53
+	return server.channelRegistrationEnabled
54
+}
55
+
50 56
 func (client *Client) Nick() string {
51 57
 	client.stateMutex.RLock()
52 58
 	defer client.stateMutex.RUnlock()
@@ -95,6 +101,12 @@ func (client *Client) Destroyed() bool {
95 101
 	return client.isDestroyed
96 102
 }
97 103
 
104
+func (client *Client) AccountName() string {
105
+	client.stateMutex.RLock()
106
+	defer client.stateMutex.RUnlock()
107
+	return client.account.Name
108
+}
109
+
98 110
 func (client *Client) HasMode(mode Mode) bool {
99 111
 	client.stateMutex.RLock()
100 112
 	defer client.stateMutex.RUnlock()
@@ -174,6 +186,12 @@ func (channel *Channel) HasMode(mode Mode) bool {
174 186
 	return channel.flags[mode]
175 187
 }
176 188
 
189
+func (channel *Channel) Founder() string {
190
+	channel.stateMutex.RLock()
191
+	defer channel.stateMutex.RUnlock()
192
+	return channel.registeredFounder
193
+}
194
+
177 195
 // set a channel mode, return whether it was already set
178 196
 func (channel *Channel) setMode(mode Mode, enable bool) (already bool) {
179 197
 	channel.stateMutex.Lock()

+ 2
- 33
irc/modes.go View File

@@ -11,7 +11,6 @@ import (
11 11
 
12 12
 	"github.com/goshuirc/irc-go/ircmsg"
13 13
 	"github.com/oragono/oragono/irc/sno"
14
-	"github.com/tidwall/buntdb"
15 14
 )
16 15
 
17 16
 // ModeOp is an operation performed with modes
@@ -645,39 +644,9 @@ func cmodeHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
645 644
 		}
646 645
 	}
647 646
 
648
-	server.registeredChannelsMutex.Lock()
649
-	if 0 < len(applied) && server.registeredChannels[channel.nameCasefolded] != nil && (banlistUpdated || exceptlistUpdated || invexlistUpdated) {
650
-		server.store.Update(func(tx *buntdb.Tx) error {
651
-			chanInfo := server.loadChannelNoMutex(tx, channel.nameCasefolded)
652
-
653
-			if banlistUpdated {
654
-				var banlist []string
655
-				for mask := range channel.lists[BanMask].masks {
656
-					banlist = append(banlist, mask)
657
-				}
658
-				chanInfo.Banlist = banlist
659
-			}
660
-			if exceptlistUpdated {
661
-				var exceptlist []string
662
-				for mask := range channel.lists[ExceptMask].masks {
663
-					exceptlist = append(exceptlist, mask)
664
-				}
665
-				chanInfo.Exceptlist = exceptlist
666
-			}
667
-			if invexlistUpdated {
668
-				var invitelist []string
669
-				for mask := range channel.lists[InviteMask].masks {
670
-					invitelist = append(invitelist, mask)
671
-				}
672
-				chanInfo.Invitelist = invitelist
673
-			}
674
-
675
-			server.saveChannelNoMutex(tx, channel.nameCasefolded, *chanInfo)
676
-
677
-			return nil
678
-		})
647
+	if (banlistUpdated || exceptlistUpdated || invexlistUpdated) && channel.IsRegistered() {
648
+		go server.channelRegistry.StoreChannel(channel, true)
679 649
 	}
680
-	server.registeredChannelsMutex.Unlock()
681 650
 
682 651
 	// send out changes
683 652
 	if len(applied) > 0 {

+ 8
- 40
irc/server.go View File

@@ -84,6 +84,7 @@ type Server struct {
84 84
 	accounts                     map[string]*ClientAccount
85 85
 	channelRegistrationEnabled   bool
86 86
 	channels                     *ChannelManager
87
+	channelRegistry              *ChannelRegistry
87 88
 	checkIdent                   bool
88 89
 	clients                      *ClientLookupSet
89 90
 	commands                     chan Command
@@ -112,8 +113,6 @@ type Server struct {
112 113
 	password                     []byte
113 114
 	passwords                    *passwd.SaltedManager
114 115
 	recoverFromErrors            bool
115
-	registeredChannels           map[string]*RegisteredChannel
116
-	registeredChannelsMutex      sync.RWMutex
117 116
 	rehashMutex                  sync.Mutex
118 117
 	rehashSignal                 chan os.Signal
119 118
 	proxyAllowedFrom             []string
@@ -158,7 +157,6 @@ func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
158 157
 		logger:              logger,
159 158
 		monitorManager:      NewMonitorManager(),
160 159
 		newConns:            make(chan clientConn),
161
-		registeredChannels:  make(map[string]*RegisteredChannel),
162 160
 		rehashSignal:        make(chan os.Signal, 1),
163 161
 		signals:             make(chan os.Signal, len(ServerExitSignals)),
164 162
 		snomasks:            NewSnoManager(),
@@ -558,10 +556,6 @@ func pongHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
558 556
 func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage) (result bool) {
559 557
 	result = false
560 558
 
561
-	// TODO(slingamn, #152) clean up locking here
562
-	server.registeredChannelsMutex.Lock()
563
-	defer server.registeredChannelsMutex.Unlock()
564
-
565 559
 	errorResponse := func(err error, name string) {
566 560
 		// TODO: send correct error codes, e.g., ERR_CANNOTRENAME, ERR_CHANNAMEINUSE
567 561
 		var code string
@@ -591,11 +585,6 @@ func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage) (resul
591 585
 		errorResponse(InvalidChannelName, oldName)
592 586
 		return
593 587
 	}
594
-	casefoldedNewName, err := CasefoldChannel(newName)
595
-	if err != nil {
596
-		errorResponse(InvalidChannelName, newName)
597
-		return
598
-	}
599 588
 
600 589
 	reason := "No reason"
601 590
 	if 2 < len(msg.Params) {
@@ -613,20 +602,8 @@ func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage) (resul
613 602
 		return
614 603
 	}
615 604
 
616
-	var canEdit bool
617
-	server.store.Update(func(tx *buntdb.Tx) error {
618
-		chanReg := server.loadChannelNoMutex(tx, casefoldedOldName)
619
-		if chanReg == nil || !client.LoggedIntoAccount() || client.account.Name == chanReg.Founder {
620
-			canEdit = true
621
-		}
622
-
623
-		chanReg = server.loadChannelNoMutex(tx, casefoldedNewName)
624
-		if chanReg != nil {
625
-			canEdit = false
626
-		}
627
-		return nil
628
-	})
629
-	if !canEdit {
605
+	founder := channel.Founder()
606
+	if founder != "" && founder != client.AccountName() {
630 607
 		//TODO(dan): Change this to ERR_CANNOTRENAME
631 608
 		client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "RENAME", oldName, "Only channel founders can change registered channels")
632 609
 		return false
@@ -639,20 +616,8 @@ func renameHandler(server *Server, client *Client, msg ircmsg.IrcMessage) (resul
639 616
 		return
640 617
 	}
641 618
 
642
-	// rename stored channel info if any exists
643
-	server.store.Update(func(tx *buntdb.Tx) error {
644
-		chanReg := server.loadChannelNoMutex(tx, casefoldedOldName)
645
-		if chanReg == nil {
646
-			return nil
647
-		}
648
-
649
-		server.deleteChannelNoMutex(tx, casefoldedOldName)
650
-
651
-		chanReg.Name = newName
652
-
653
-		server.saveChannelNoMutex(tx, casefoldedNewName, *chanReg)
654
-		return nil
655
-	})
619
+	// rename succeeded, persist it
620
+	go server.channelRegistry.Rename(channel, casefoldedOldName)
656 621
 
657 622
 	// send RENAME messages
658 623
 	for _, mcl := range channel.Members() {
@@ -1494,6 +1459,9 @@ func (server *Server) loadDatastore(datastorePath string) error {
1494 1459
 	if err != nil {
1495 1460
 		return fmt.Errorf("Could not load salt: %s", err.Error())
1496 1461
 	}
1462
+
1463
+	server.channelRegistry = NewChannelRegistry(server)
1464
+
1497 1465
 	return nil
1498 1466
 }
1499 1467
 

Loading…
Cancel
Save