Browse Source

refactor ClientManager

tags/v0.10.3
Shivaram Lingamneni 6 years ago
parent
commit
52b0fb71e7
13 changed files with 145 additions and 205 deletions
  1. 19
    0
      DEVELOPING.md
  2. 1
    1
      README.md
  3. 1
    1
      irc/channel.go
  4. 6
    33
      irc/client.go
  5. 72
    97
      irc/client_lookup_set.go
  6. 2
    4
      irc/dline.go
  7. 1
    1
      irc/idletimer.go
  8. 2
    4
      irc/kline.go
  9. 1
    1
      irc/monitor.go
  10. 29
    43
      irc/nickname.go
  11. 9
    18
      irc/server.go
  12. 1
    1
      irc/snomanager.go
  13. 1
    1
      irc/whowas.go

+ 19
- 0
DEVELOPING.md View File

@@ -80,3 +80,22 @@ To debug a hang, the best thing to do is to get a stack trace. Go's nice, and yo
80 80
     $ kill -ABRT <procid>
81 81
 
82 82
 This will kill Oragono and print out a stack trace for you to take a look at.
83
+
84
+## Concurrency design
85
+
86
+Oragono involves a fair amount of shared state. Here are some of the main points:
87
+
88
+1. Each client has a separate goroutine that listens for incoming messages and synchronously processes them.
89
+1. All sends to clients are asynchronous; `client.Send` appends the message to a queue, which is then processed on a separate goroutine. It is always safe to call `client.Send`.
90
+1. The server has a few of its own goroutines, for listening on sockets and handing off new client connections to their dedicated goroutines.
91
+1. A few tasks are done asynchronously in ad-hoc goroutines.
92
+
93
+In consequence, there is a lot of state (in particular, server and channel state) that can be read and written from multiple goroutines. This state is protected with mutexes. To avoid deadlocks, mutexes are arranged in "tiers"; while holding a mutex of one tier, you're only allowed to acquire mutexes of a strictly *higher* tier. The tiers are:
94
+
95
+1. Tier 1 mutexes: these are the "innermost" mutexes. They typically protect getters and setters on objects, or invariants that are local to the state of a single object. Example: `Channel.stateMutex`.
96
+1. Tier 2 mutexes: these protect some invariants of their own, but also need to access fields on other objects that themselves require synchronization. Example: `ChannelManager.RWMutex`.
97
+1. Tier 3 mutexes: these protect macroscopic operations, where it doesn't make sense for more than one to occur concurrently. Example; `Server.rehashMutex`, which prevents rehashes from overlapping.
98
+
99
+There are some mutexes that are "tier 0": anything in a subpackage of `irc` (e.g., `irc/logger` or `irc/connection_limits`) shouldn't acquire mutexes defined in `irc`.
100
+
101
+We are using `buntdb` for persistence; a `buntdb.DB` has an `RWMutex` inside it, with read-write transactions getting the `Lock()` and read-only transactions getting the `RLock()`. We haven't completely decided where this lock fits into the overall lock model. For now, it's probably better to err on the side of caution: if possible, don't acquire new locks inside the `buntdb` transaction, and be careful about what locks are held around the transaction as well.

+ 1
- 1
README.md View File

@@ -68,7 +68,7 @@ The `stable` branch contains the latest release. You can run this for a producti
68 68
 
69 69
 [![Build Status](https://travis-ci.org/oragono/oragono.svg?branch=master)](https://travis-ci.org/oragono/oragono)
70 70
 
71
-Clone the appropriate branch. From the root folder, run `make` to generate all release files for all of our target OSes:
71
+Clone the appropriate branch. If necessary, do `git submodule update --init` to set up vendored dependencies. From the root folder, run `make` to generate all release files for all of our target OSes:
72 72
 ```
73 73
 make
74 74
 ```

+ 1
- 1
irc/channel.go View File

@@ -35,7 +35,7 @@ type Channel struct {
35 35
 	createdTime       time.Time
36 36
 	registeredFounder string
37 37
 	registeredTime    time.Time
38
-	stateMutex        sync.RWMutex
38
+	stateMutex        sync.RWMutex // tier 1
39 39
 	topic             string
40 40
 	topicSetBy        string
41 41
 	topicSetTime      time.Time

+ 6
- 33
irc/client.go View File

@@ -73,7 +73,7 @@ type Client struct {
73 73
 	saslValue          string
74 74
 	server             *Server
75 75
 	socket             *Socket
76
-	stateMutex         sync.RWMutex // generic protection for mutable state
76
+	stateMutex         sync.RWMutex // tier 1
77 77
 	username           string
78 78
 	vhost              string
79 79
 	whoisLine          string
@@ -313,11 +313,15 @@ func (client *Client) IdleSeconds() uint64 {
313 313
 
314 314
 // HasNick returns true if the client's nickname is set (used in registration).
315 315
 func (client *Client) HasNick() bool {
316
+	client.stateMutex.RLock()
317
+	defer client.stateMutex.RUnlock()
316 318
 	return client.nick != "" && client.nick != "*"
317 319
 }
318 320
 
319 321
 // HasUsername returns true if the client's username is set (used in registration).
320 322
 func (client *Client) HasUsername() bool {
323
+	client.stateMutex.RLock()
324
+	defer client.stateMutex.RUnlock()
321 325
 	return client.username != "" && client.username != "*"
322 326
 }
323 327
 
@@ -403,6 +407,7 @@ func (client *Client) updateNickMask(nick string) {
403 407
 	}
404 408
 
405 409
 	client.stateMutex.Lock()
410
+	defer client.stateMutex.Unlock()
406 411
 
407 412
 	if len(client.vhost) > 0 {
408 413
 		client.hostname = client.vhost
@@ -419,8 +424,6 @@ func (client *Client) updateNickMask(nick string) {
419 424
 
420 425
 	client.nickMaskString = nickMaskString
421 426
 	client.nickMaskCasefolded = nickMaskCasefolded
422
-
423
-	client.stateMutex.Unlock()
424 427
 }
425 428
 
426 429
 // AllNickmasks returns all the possible nickmasks for the client.
@@ -449,36 +452,6 @@ func (client *Client) AllNickmasks() []string {
449 452
 	return masks
450 453
 }
451 454
 
452
-// SetNickname sets the very first nickname for the client.
453
-func (client *Client) SetNickname(nickname string) error {
454
-	if client.HasNick() {
455
-		client.server.logger.Error("nick", fmt.Sprintf("%s nickname already set, something is wrong with server consistency", client.nickMaskString))
456
-		return ErrNickAlreadySet
457
-	}
458
-
459
-	err := client.server.clients.Add(client, nickname)
460
-	if err == nil {
461
-		client.updateNick(nickname)
462
-	}
463
-	return err
464
-}
465
-
466
-// ChangeNickname changes the existing nickname of the client.
467
-func (client *Client) ChangeNickname(nickname string) error {
468
-	origNickMask := client.nickMaskString
469
-	err := client.server.clients.Replace(client.nick, nickname, client)
470
-	if err == nil {
471
-		client.server.logger.Debug("nick", fmt.Sprintf("%s changed nickname to %s", client.nick, nickname))
472
-		client.server.snomasks.Send(sno.LocalNicks, fmt.Sprintf(ircfmt.Unescape("$%s$r changed nickname to %s"), client.nick, nickname))
473
-		client.server.whoWas.Append(client)
474
-		client.updateNickMask(nickname)
475
-		for friend := range client.Friends() {
476
-			friend.Send(nil, origNickMask, "NICK", nickname)
477
-		}
478
-	}
479
-	return err
480
-}
481
-
482 455
 // LoggedIntoAccount returns true if this client is logged into an account.
483 456
 func (client *Client) LoggedIntoAccount() bool {
484 457
 	return client.account != nil && client.account != &NoAccount

+ 72
- 97
irc/client_lookup_set.go View File

@@ -18,9 +18,8 @@ import (
18 18
 )
19 19
 
20 20
 var (
21
-	ErrNickMissing      = errors.New("nick missing")
22
-	ErrNicknameInUse    = errors.New("nickname in use")
23
-	ErrNicknameMismatch = errors.New("nickname mismatch")
21
+	ErrNickMissing   = errors.New("nick missing")
22
+	ErrNicknameInUse = errors.New("nickname in use")
24 23
 )
25 24
 
26 25
 // ExpandUserHost takes a userhost, and returns an expanded version.
@@ -37,132 +36,108 @@ func ExpandUserHost(userhost string) (expanded string) {
37 36
 	return
38 37
 }
39 38
 
40
-// ClientLookupSet represents a way to store, search and lookup clients.
41
-type ClientLookupSet struct {
42
-	ByNickMutex sync.RWMutex
43
-	ByNick      map[string]*Client
39
+// ClientManager keeps track of clients by nick, enforcing uniqueness of casefolded nicks
40
+type ClientManager struct {
41
+	sync.RWMutex // tier 2
42
+	byNick      map[string]*Client
44 43
 }
45 44
 
46
-// NewClientLookupSet returns a new lookup set.
47
-func NewClientLookupSet() *ClientLookupSet {
48
-	return &ClientLookupSet{
49
-		ByNick: make(map[string]*Client),
45
+// NewClientManager returns a new ClientManager.
46
+func NewClientManager() *ClientManager {
47
+	return &ClientManager{
48
+		byNick: make(map[string]*Client),
50 49
 	}
51 50
 }
52 51
 
53
-// Count returns how many clients are in the lookup set.
54
-func (clients *ClientLookupSet) Count() int {
55
-	clients.ByNickMutex.RLock()
56
-	defer clients.ByNickMutex.RUnlock()
57
-	count := len(clients.ByNick)
52
+// Count returns how many clients are in the manager.
53
+func (clients *ClientManager) Count() int {
54
+	clients.RLock()
55
+	defer clients.RUnlock()
56
+	count := len(clients.byNick)
58 57
 	return count
59 58
 }
60 59
 
61
-// Has returns whether or not the given client exists.
62
-//TODO(dan): This seems like ripe ground for a race, if code does Has then Get, and assumes the Get will return a client.
63
-func (clients *ClientLookupSet) Has(nick string) bool {
60
+// Get retrieves a client from the manager, if they exist.
61
+func (clients *ClientManager) Get(nick string) *Client {
64 62
 	casefoldedName, err := CasefoldName(nick)
65 63
 	if err == nil {
66
-		return false
67
-	}
68
-	clients.ByNickMutex.RLock()
69
-	defer clients.ByNickMutex.RUnlock()
70
-	_, exists := clients.ByNick[casefoldedName]
71
-	return exists
72
-}
73
-
74
-// getNoMutex is used internally, for getting clients when no mutex is required (i.e. is already set).
75
-func (clients *ClientLookupSet) getNoMutex(nick string) *Client {
76
-	casefoldedName, err := CasefoldName(nick)
77
-	if err == nil {
78
-		cli := clients.ByNick[casefoldedName]
79
-		return cli
80
-	}
81
-	return nil
82
-}
83
-
84
-// Get retrieves a client from the set, if they exist.
85
-func (clients *ClientLookupSet) Get(nick string) *Client {
86
-	casefoldedName, err := CasefoldName(nick)
87
-	if err == nil {
88
-		clients.ByNickMutex.RLock()
89
-		defer clients.ByNickMutex.RUnlock()
90
-		cli := clients.ByNick[casefoldedName]
64
+		clients.RLock()
65
+		defer clients.RUnlock()
66
+		cli := clients.byNick[casefoldedName]
91 67
 		return cli
92 68
 	}
93 69
 	return nil
94 70
 }
95 71
 
96
-// Add adds a client to the lookup set.
97
-func (clients *ClientLookupSet) Add(client *Client, nick string) error {
98
-	nick, err := CasefoldName(nick)
99
-	if err != nil {
100
-		return err
101
-	}
102
-	clients.ByNickMutex.Lock()
103
-	defer clients.ByNickMutex.Unlock()
104
-	if clients.getNoMutex(nick) != nil {
105
-		return ErrNicknameInUse
72
+func (clients *ClientManager) removeInternal(client *Client) (removed bool) {
73
+	// requires holding ByNickMutex
74
+	oldcfnick := client.NickCasefolded()
75
+	currentEntry, present := clients.byNick[oldcfnick]
76
+	if present {
77
+		if currentEntry == client {
78
+			delete(clients.byNick, oldcfnick)
79
+			removed = true
80
+		} else {
81
+			// this shouldn't happen, but we can ignore it
82
+			client.server.logger.Warning("internal", fmt.Sprintf("clients for nick %s out of sync", oldcfnick))
83
+		}
106 84
 	}
107
-	clients.ByNick[nick] = client
108
-	return nil
85
+	return
109 86
 }
110 87
 
111 88
 // Remove removes a client from the lookup set.
112
-func (clients *ClientLookupSet) Remove(client *Client) error {
89
+func (clients *ClientManager) Remove(client *Client) error {
90
+	clients.Lock()
91
+	defer clients.Unlock()
92
+
113 93
 	if !client.HasNick() {
114 94
 		return ErrNickMissing
115 95
 	}
116
-	clients.ByNickMutex.Lock()
117
-	defer clients.ByNickMutex.Unlock()
118
-	if clients.getNoMutex(client.nick) != client {
119
-		return ErrNicknameMismatch
120
-	}
121
-	delete(clients.ByNick, client.nickCasefolded)
96
+	clients.removeInternal(client)
122 97
 	return nil
123 98
 }
124 99
 
125
-// Replace renames an existing client in the lookup set.
126
-func (clients *ClientLookupSet) Replace(oldNick, newNick string, client *Client) error {
127
-	// get casefolded nicknames
128
-	oldNick, err := CasefoldName(oldNick)
129
-	if err != nil {
130
-		return err
131
-	}
132
-	newNick, err = CasefoldName(newNick)
100
+// SetNick sets a client's nickname, validating it against nicknames in use
101
+func (clients *ClientManager) SetNick(client *Client, newNick string) error {
102
+	newcfnick, err := CasefoldName(newNick)
133 103
 	if err != nil {
134 104
 		return err
135 105
 	}
136 106
 
137
-	// remove and replace
138
-	clients.ByNickMutex.Lock()
139
-	defer clients.ByNickMutex.Unlock()
107
+	clients.Lock()
108
+	defer clients.Unlock()
140 109
 
141
-	oldClient := clients.ByNick[newNick]
142
-	if oldClient == nil || oldClient == client {
143
-		// whoo
144
-	} else {
110
+	clients.removeInternal(client)
111
+	currentNewEntry := clients.byNick[newcfnick]
112
+	// the client may just be changing case
113
+	if currentNewEntry != nil && currentNewEntry != client {
145 114
 		return ErrNicknameInUse
146 115
 	}
116
+	clients.byNick[newcfnick] = client
117
+	client.updateNickMask(newNick)
118
+	return nil
119
+}
147 120
 
148
-	if oldNick == newNick {
149
-		// if they're only changing case, don't need to remove+re-add them
150
-		return nil
121
+func (clients *ClientManager) AllClients() (result []*Client) {
122
+	clients.RLock()
123
+	defer clients.RUnlock()
124
+	result = make([]*Client, len(clients.byNick))
125
+	i := 0
126
+	for _, client := range(clients.byNick) {
127
+		result[i] = client
128
+		i++
151 129
 	}
152
-
153
-	delete(clients.ByNick, oldNick)
154
-	clients.ByNick[newNick] = client
155
-	return nil
130
+	return
156 131
 }
157 132
 
158 133
 // AllWithCaps returns all clients with the given capabilities.
159
-func (clients *ClientLookupSet) AllWithCaps(capabs ...caps.Capability) (set ClientSet) {
134
+func (clients *ClientManager) AllWithCaps(capabs ...caps.Capability) (set ClientSet) {
160 135
 	set = make(ClientSet)
161 136
 
162
-	clients.ByNickMutex.RLock()
163
-	defer clients.ByNickMutex.RUnlock()
137
+	clients.RLock()
138
+	defer clients.RUnlock()
164 139
 	var client *Client
165
-	for _, client = range clients.ByNick {
140
+	for _, client = range clients.byNick {
166 141
 		// make sure they have all the required caps
167 142
 		for _, capab := range capabs {
168 143
 			if !client.capabilities.Has(capab) {
@@ -177,7 +152,7 @@ func (clients *ClientLookupSet) AllWithCaps(capabs ...caps.Capability) (set Clie
177 152
 }
178 153
 
179 154
 // FindAll returns all clients that match the given userhost mask.
180
-func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) {
155
+func (clients *ClientManager) FindAll(userhost string) (set ClientSet) {
181 156
 	set = make(ClientSet)
182 157
 
183 158
 	userhost, err := Casefold(ExpandUserHost(userhost))
@@ -186,9 +161,9 @@ func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) {
186 161
 	}
187 162
 	matcher := ircmatch.MakeMatch(userhost)
188 163
 
189
-	clients.ByNickMutex.RLock()
190
-	defer clients.ByNickMutex.RUnlock()
191
-	for _, client := range clients.ByNick {
164
+	clients.RLock()
165
+	defer clients.RUnlock()
166
+	for _, client := range clients.byNick {
192 167
 		if matcher.Match(client.nickMaskCasefolded) {
193 168
 			set.Add(client)
194 169
 		}
@@ -198,7 +173,7 @@ func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) {
198 173
 }
199 174
 
200 175
 // Find returns the first client that matches the given userhost mask.
201
-func (clients *ClientLookupSet) Find(userhost string) *Client {
176
+func (clients *ClientManager) Find(userhost string) *Client {
202 177
 	userhost, err := Casefold(ExpandUserHost(userhost))
203 178
 	if err != nil {
204 179
 		return nil
@@ -206,9 +181,9 @@ func (clients *ClientLookupSet) Find(userhost string) *Client {
206 181
 	matcher := ircmatch.MakeMatch(userhost)
207 182
 	var matchedClient *Client
208 183
 
209
-	clients.ByNickMutex.RLock()
210
-	defer clients.ByNickMutex.RUnlock()
211
-	for _, client := range clients.ByNick {
184
+	clients.RLock()
185
+	defer clients.RUnlock()
186
+	for _, client := range clients.byNick {
212 187
 		if matcher.Match(client.nickMaskCasefolded) {
213 188
 			matchedClient = client
214 189
 			break

+ 2
- 4
irc/dline.go View File

@@ -82,7 +82,7 @@ type dLineNet struct {
82 82
 
83 83
 // DLineManager manages and dlines.
84 84
 type DLineManager struct {
85
-	sync.RWMutex
85
+	sync.RWMutex // tier 1
86 86
 	// addresses that are dlined
87 87
 	addresses map[string]*dLineAddr
88 88
 	// networks that are dlined
@@ -386,8 +386,7 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
386 386
 		var killedClientNicks []string
387 387
 		var toKill bool
388 388
 
389
-		server.clients.ByNickMutex.RLock()
390
-		for _, mcl := range server.clients.ByNick {
389
+		for _, mcl := range server.clients.AllClients() {
391 390
 			if hostNet == nil {
392 391
 				toKill = hostAddr.Equal(mcl.IP())
393 392
 			} else {
@@ -399,7 +398,6 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
399 398
 				killedClientNicks = append(killedClientNicks, mcl.nick)
400 399
 			}
401 400
 		}
402
-		server.clients.ByNickMutex.RUnlock()
403 401
 
404 402
 		for _, mcl := range clientsToKill {
405 403
 			mcl.exitedSnomaskSent = true

+ 1
- 1
irc/idletimer.go View File

@@ -29,7 +29,7 @@ const (
29 29
 )
30 30
 
31 31
 type IdleTimer struct {
32
-	sync.Mutex
32
+	sync.Mutex // tier 1
33 33
 
34 34
 	// immutable after construction
35 35
 	registerTimeout time.Duration

+ 2
- 4
irc/kline.go View File

@@ -35,7 +35,7 @@ type KLineInfo struct {
35 35
 
36 36
 // KLineManager manages and klines.
37 37
 type KLineManager struct {
38
-	sync.RWMutex
38
+	sync.RWMutex // tier 1
39 39
 	// kline'd entries
40 40
 	entries map[string]*KLineInfo
41 41
 }
@@ -282,8 +282,7 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
282 282
 		var clientsToKill []*Client
283 283
 		var killedClientNicks []string
284 284
 
285
-		server.clients.ByNickMutex.RLock()
286
-		for _, mcl := range server.clients.ByNick {
285
+		for _, mcl := range server.clients.AllClients() {
287 286
 			for _, clientMask := range mcl.AllNickmasks() {
288 287
 				if matcher.Match(clientMask) {
289 288
 					clientsToKill = append(clientsToKill, mcl)
@@ -291,7 +290,6 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
291 290
 				}
292 291
 			}
293 292
 		}
294
-		server.clients.ByNickMutex.RUnlock()
295 293
 
296 294
 		for _, mcl := range clientsToKill {
297 295
 			mcl.exitedSnomaskSent = true

+ 1
- 1
irc/monitor.go View File

@@ -15,7 +15,7 @@ import (
15 15
 
16 16
 // MonitorManager keeps track of who's monitoring which nicks.
17 17
 type MonitorManager struct {
18
-	sync.RWMutex
18
+	sync.RWMutex // tier 2
19 19
 	// client -> nicks it's watching
20 20
 	watching map[*Client]map[string]bool
21 21
 	// nick -> clients watching it

+ 29
- 43
irc/nickname.go View File

@@ -8,7 +8,9 @@ import (
8 8
 	"fmt"
9 9
 	"strings"
10 10
 
11
+	"github.com/goshuirc/irc-go/ircfmt"
11 12
 	"github.com/goshuirc/irc-go/ircmsg"
13
+	"github.com/oragono/oragono/irc/sno"
12 14
 )
13 15
 
14 16
 var (
@@ -26,7 +28,11 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
26 28
 		return true
27 29
 	}
28 30
 
29
-	nicknameRaw := strings.TrimSpace(msg.Params[0])
31
+	return performNickChange(server, client, client, msg.Params[0])
32
+}
33
+
34
+func performNickChange(server *Server, client *Client, target *Client, newnick string) bool {
35
+	nicknameRaw := strings.TrimSpace(newnick)
30 36
 	nickname, err := CasefoldName(nicknameRaw)
31 37
 
32 38
 	if len(nicknameRaw) < 1 {
@@ -39,16 +45,14 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
39 45
 		return false
40 46
 	}
41 47
 
42
-	if client.nick == nickname {
48
+	if target.Nick() == nicknameRaw {
43 49
 		return false
44 50
 	}
45 51
 
46
-	// bleh, this will be replaced and done below
47
-	if client.registered {
48
-		err = client.ChangeNickname(nicknameRaw)
49
-	} else {
50
-		err = client.SetNickname(nicknameRaw)
51
-	}
52
+	hadNick := target.HasNick()
53
+	origNick := target.Nick()
54
+	origNickMask := target.NickMaskString()
55
+	err = client.server.clients.SetNick(target, nickname)
52 56
 	if err == ErrNicknameInUse {
53 57
 		client.Send(nil, server.name, ERR_NICKNAMEINUSE, client.nick, nicknameRaw, "Nickname is already in use")
54 58
 		return false
@@ -56,49 +60,31 @@ func nickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
56 60
 		client.Send(nil, server.name, ERR_UNKNOWNERROR, client.nick, "NICK", fmt.Sprintf("Could not set or change nickname: %s", err.Error()))
57 61
 		return false
58 62
 	}
59
-	if client.registered {
60
-		client.server.monitorManager.AlertAbout(client, true)
63
+
64
+	client.server.logger.Debug("nick", fmt.Sprintf("%s changed nickname to %s", origNickMask, nickname))
65
+	if hadNick {
66
+		target.server.snomasks.Send(sno.LocalNicks, fmt.Sprintf(ircfmt.Unescape("$%s$r changed nickname to %s"), origNick, nicknameRaw))
67
+		target.server.whoWas.Append(client)
68
+		for friend := range target.Friends() {
69
+			friend.Send(nil, origNickMask, "NICK", nicknameRaw)
70
+		}
71
+	}
72
+
73
+	if target.registered {
74
+		client.server.monitorManager.AlertAbout(target, true)
75
+	} else {
76
+		server.tryRegister(target)
61 77
 	}
62
-	server.tryRegister(client)
63 78
 	return false
64 79
 }
65 80
 
66 81
 // SANICK <oldnick> <nickname>
67 82
 func sanickHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
68
-	if !client.authorized {
69
-		client.Quit("Bad password")
70
-		return true
71
-	}
72
-
73
-	oldnick, oerr := CasefoldName(msg.Params[0])
74
-	nickname, err := CasefoldName(msg.Params[1])
75
-
76
-	if len(nickname) < 1 {
77
-		client.Send(nil, server.name, ERR_NONICKNAMEGIVEN, client.nick, "No nickname given")
78
-		return false
79
-	}
80
-
81
-	if oerr != nil || err != nil || len(strings.TrimSpace(msg.Params[1])) > server.limits.NickLen || restrictedNicknames[nickname] {
82
-		client.Send(nil, server.name, ERR_ERRONEUSNICKNAME, client.nick, msg.Params[0], "Erroneous nickname")
83
-		return false
84
-	}
85
-
86
-	if client.nick == msg.Params[1] {
87
-		return false
88
-	}
89
-
90
-	target := server.clients.Get(oldnick)
83
+	targetNick := strings.TrimSpace(msg.Params[0])
84
+	target := server.clients.Get(targetNick)
91 85
 	if target == nil {
92 86
 		client.Send(nil, server.name, ERR_NOSUCHNICK, client.nick, msg.Params[0], "No such nick")
93 87
 		return false
94 88
 	}
95
-
96
-	//TODO(dan): There's probably some races here, we should be changing this in the primary server thread
97
-	if server.clients.Get(nickname) != nil && server.clients.Get(nickname) != target {
98
-		client.Send(nil, server.name, ERR_NICKNAMEINUSE, client.nick, msg.Params[0], "Nickname is already in use")
99
-		return false
100
-	}
101
-
102
-	target.ChangeNickname(msg.Params[1])
103
-	return false
89
+	return performNickChange(server, client, target, msg.Params[1])
104 90
 }

+ 9
- 18
irc/server.go View File

@@ -74,7 +74,7 @@ type ListenerWrapper struct {
74 74
 	// lets the ListenerWrapper inform the server that it has stopped:
75 75
 	stopEvent chan bool
76 76
 	// protects atomic update of tlsConfig and shouldStop:
77
-	configMutex sync.Mutex
77
+	configMutex sync.Mutex // tier 1
78 78
 }
79 79
 
80 80
 // Server is the main Oragono server.
@@ -86,10 +86,10 @@ type Server struct {
86 86
 	channels                     *ChannelManager
87 87
 	channelRegistry              *ChannelRegistry
88 88
 	checkIdent                   bool
89
-	clients                      *ClientLookupSet
89
+	clients                      *ClientManager
90 90
 	commands                     chan Command
91 91
 	configFilename               string
92
-	configurableStateMutex       sync.RWMutex // generic protection for server state modified by rehash()
92
+	configurableStateMutex       sync.RWMutex // tier 1; generic protection for server state modified by rehash()
93 93
 	connectionLimiter            *connection_limits.Limiter
94 94
 	connectionThrottler          *connection_limits.Throttler
95 95
 	ctime                        time.Time
@@ -113,7 +113,7 @@ type Server struct {
113 113
 	password                     []byte
114 114
 	passwords                    *passwd.SaltedManager
115 115
 	recoverFromErrors            bool
116
-	rehashMutex                  sync.Mutex
116
+	rehashMutex                  sync.Mutex // tier 3
117 117
 	rehashSignal                 chan os.Signal
118 118
 	proxyAllowedFrom             []string
119 119
 	signals                      chan os.Signal
@@ -149,7 +149,7 @@ func NewServer(config *Config, logger *logger.Manager) (*Server, error) {
149 149
 	server := &Server{
150 150
 		accounts:            make(map[string]*ClientAccount),
151 151
 		channels:            NewChannelManager(),
152
-		clients:             NewClientLookupSet(),
152
+		clients:             NewClientManager(),
153 153
 		commands:            make(chan Command),
154 154
 		connectionLimiter:   connection_limits.NewLimiter(),
155 155
 		connectionThrottler: connection_limits.NewThrottler(),
@@ -238,11 +238,9 @@ func loadChannelList(channel *Channel, list string, maskMode Mode) {
238 238
 // Shutdown shuts down the server.
239 239
 func (server *Server) Shutdown() {
240 240
 	//TODO(dan): Make sure we disallow new nicks
241
-	server.clients.ByNickMutex.RLock()
242
-	for _, client := range server.clients.ByNick {
241
+	for _, client := range server.clients.AllClients() {
243 242
 		client.Notice("Server is shutting down")
244 243
 	}
245
-	server.clients.ByNickMutex.RUnlock()
246 244
 
247 245
 	if err := server.store.Close(); err != nil {
248 246
 		server.logger.Error("shutdown", fmt.Sprintln("Could not close datastore:", err))
@@ -1332,11 +1330,9 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
1332 1330
 		server.configurableStateMutex.Unlock()
1333 1331
 
1334 1332
 		// update on all clients
1335
-		server.clients.ByNickMutex.RLock()
1336
-		for _, sClient := range server.clients.ByNick {
1333
+		for _, sClient := range server.clients.AllClients() {
1337 1334
 			sClient.socket.MaxSendQBytes = config.Server.MaxSendQBytes
1338 1335
 		}
1339
-		server.clients.ByNickMutex.RUnlock()
1340 1336
 	}
1341 1337
 
1342 1338
 	// set RPL_ISUPPORT
@@ -1370,8 +1366,7 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
1370 1366
 
1371 1367
 	if !initial {
1372 1368
 		// push new info to all of our clients
1373
-		server.clients.ByNickMutex.RLock()
1374
-		for _, sClient := range server.clients.ByNick {
1369
+		for _, sClient := range server.clients.AllClients() {
1375 1370
 			for _, tokenline := range newISupportReplies {
1376 1371
 				sClient.Send(nil, server.name, RPL_ISUPPORT, append([]string{sClient.nick}, tokenline...)...)
1377 1372
 			}
@@ -1380,7 +1375,6 @@ func (server *Server) applyConfig(config *Config, initial bool) error {
1380 1375
 				sClient.Notice(rawIONotice)
1381 1376
 			}
1382 1377
 		}
1383
-		server.clients.ByNickMutex.RUnlock()
1384 1378
 	}
1385 1379
 
1386 1380
 	return nil
@@ -2008,10 +2002,7 @@ func lusersHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
2008 2002
 	//TODO(vegax87) Fix network statistics and additional parameters
2009 2003
 	var totalcount, invisiblecount, opercount int
2010 2004
 
2011
-	server.clients.ByNickMutex.RLock()
2012
-	defer server.clients.ByNickMutex.RUnlock()
2013
-
2014
-	for _, onlineusers := range server.clients.ByNick {
2005
+	for _, onlineusers := range server.clients.AllClients() {
2015 2006
 		totalcount++
2016 2007
 		if onlineusers.flags[Invisible] {
2017 2008
 			invisiblecount++

+ 1
- 1
irc/snomanager.go View File

@@ -10,7 +10,7 @@ import (
10 10
 
11 11
 // SnoManager keeps track of which clients to send snomasks to.
12 12
 type SnoManager struct {
13
-	sendListMutex sync.RWMutex
13
+	sendListMutex sync.RWMutex // tier 2
14 14
 	sendLists     map[sno.Mask]map[*Client]bool
15 15
 }
16 16
 

+ 1
- 1
irc/whowas.go View File

@@ -14,7 +14,7 @@ type WhoWasList struct {
14 14
 	start  int
15 15
 	end    int
16 16
 
17
-	accessMutex sync.RWMutex
17
+	accessMutex sync.RWMutex // tier 2
18 18
 }
19 19
 
20 20
 // WhoWas is an entry in the WhoWasList.

Loading…
Cancel
Save