Skip to content

Commit 5a97ee6

Browse files
authored
Merge pull request #2911 from nats-io/fix_lock_inversions
[FIXED] Some lock inversions
2 parents 3538aea + 0fae806 commit 5a97ee6

File tree

5 files changed

+63
-43
lines changed

5 files changed

+63
-43
lines changed

locksordering.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Here is the list of some established lock ordering.
2+
3+
In this list, A -> B means that you can have A.Lock() then B.Lock(), not the opposite.
4+
5+
jetStream -> jsAccount -> Server -> client-> Account

server/accounts.go

Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,27 @@ func (a *Account) nextEventID() string {
292292
return id
293293
}
294294

295+
// Returns a slice of clients stored in the account, or nil if none is present.
296+
// Lock is held on entry.
297+
func (a *Account) getClientsLocked() []*client {
298+
if len(a.clients) == 0 {
299+
return nil
300+
}
301+
clients := make([]*client, 0, len(a.clients))
302+
for c := range a.clients {
303+
clients = append(clients, c)
304+
}
305+
return clients
306+
}
307+
308+
// Returns a slice of clients stored in the account, or nil if none is present.
309+
func (a *Account) getClients() []*client {
310+
a.mu.RLock()
311+
clients := a.getClientsLocked()
312+
a.mu.RUnlock()
313+
return clients
314+
}
315+
295316
// Called to track a remote server and connections and leafnodes it
296317
// has for this account.
297318
func (a *Account) updateRemoteServer(m *AccountNumConns) []*client {
@@ -312,10 +333,7 @@ func (a *Account) updateRemoteServer(m *AccountNumConns) []*client {
312333
// conservative and bit harsh here. Clients will reconnect if we over compensate.
313334
var clients []*client
314335
if mtce {
315-
clients = make([]*client, 0, len(a.clients))
316-
for c := range a.clients {
317-
clients = append(clients, c)
318-
}
336+
clients := a.getClientsLocked()
319337
sort.Slice(clients, func(i, j int) bool {
320338
return clients[i].start.After(clients[j].start)
321339
})
@@ -670,9 +688,13 @@ func (a *Account) AddWeightedMappings(src string, dests ...*MapDest) error {
670688

671689
// If we have connected leafnodes make sure to update.
672690
if len(a.lleafs) > 0 {
673-
for _, lc := range a.lleafs {
691+
leafs := append([]*client(nil), a.lleafs...)
692+
// Need to release because lock ordering is client -> account
693+
a.mu.Unlock()
694+
for _, lc := range leafs {
674695
lc.forceAddToSmap(src)
675696
}
697+
a.mu.Lock()
676698
}
677699
return nil
678700
}
@@ -963,8 +985,6 @@ func (a *Account) addServiceExportWithResponseAndAccountPos(
963985
}
964986

965987
a.mu.Lock()
966-
defer a.mu.Unlock()
967-
968988
if a.exports.services == nil {
969989
a.exports.services = make(map[string]*serviceExport)
970990
}
@@ -981,15 +1001,24 @@ func (a *Account) addServiceExportWithResponseAndAccountPos(
9811001

9821002
if accounts != nil || accountPos > 0 {
9831003
if err := setExportAuth(&se.exportAuth, subject, accounts, accountPos); err != nil {
1004+
a.mu.Unlock()
9841005
return err
9851006
}
9861007
}
9871008
lrt := a.lowestServiceExportResponseTime()
9881009
se.acc = a
9891010
se.respThresh = DEFAULT_SERVICE_EXPORT_RESPONSE_THRESHOLD
9901011
a.exports.services[subject] = se
991-
if nlrt := a.lowestServiceExportResponseTime(); nlrt != lrt {
992-
a.updateAllClientsServiceExportResponseTime(nlrt)
1012+
1013+
var clients []*client
1014+
nlrt := a.lowestServiceExportResponseTime()
1015+
if nlrt != lrt && len(a.clients) > 0 {
1016+
clients = a.getClientsLocked()
1017+
}
1018+
// Need to release because lock ordering is client -> Account
1019+
a.mu.Unlock()
1020+
if len(clients) > 0 {
1021+
updateAllClientsServiceExportResponseTime(clients, nlrt)
9931022
}
9941023
return nil
9951024
}
@@ -1353,9 +1382,8 @@ func (a *Account) sendTrackingLatency(si *serviceImport, responder *client) bool
13531382

13541383
// This will check to make sure our response lower threshold is set
13551384
// properly in any clients doing rrTracking.
1356-
// Lock should be held.
1357-
func (a *Account) updateAllClientsServiceExportResponseTime(lrt time.Duration) {
1358-
for c := range a.clients {
1385+
func updateAllClientsServiceExportResponseTime(clients []*client, lrt time.Duration) {
1386+
for _, c := range clients {
13591387
c.mu.Lock()
13601388
if c.rrTracking != nil && lrt != c.rrTracking.lrt {
13611389
c.rrTracking.lrt = lrt
@@ -2234,18 +2262,27 @@ func (a *Account) ServiceExportResponseThreshold(export string) (time.Duration,
22342262
// from a service export responder.
22352263
func (a *Account) SetServiceExportResponseThreshold(export string, maxTime time.Duration) error {
22362264
a.mu.Lock()
2237-
defer a.mu.Unlock()
22382265
if a.isClaimAccount() {
2266+
a.mu.Unlock()
22392267
return fmt.Errorf("claim based accounts can not be updated directly")
22402268
}
22412269
lrt := a.lowestServiceExportResponseTime()
22422270
se := a.getServiceExport(export)
22432271
if se == nil {
2272+
a.mu.Unlock()
22442273
return fmt.Errorf("no export defined for %q", export)
22452274
}
22462275
se.respThresh = maxTime
2247-
if nlrt := a.lowestServiceExportResponseTime(); nlrt != lrt {
2248-
a.updateAllClientsServiceExportResponseTime(nlrt)
2276+
2277+
var clients []*client
2278+
nlrt := a.lowestServiceExportResponseTime()
2279+
if nlrt != lrt && len(a.clients) > 0 {
2280+
clients = a.getClientsLocked()
2281+
}
2282+
// Need to release because lock ordering is client -> Account
2283+
a.mu.Unlock()
2284+
if len(clients) > 0 {
2285+
updateAllClientsServiceExportResponseTime(clients, nlrt)
22492286
}
22502287
return nil
22512288
}
@@ -2569,10 +2606,7 @@ func (a *Account) streamActivationExpired(exportAcc *Account, subject string) {
25692606

25702607
a.mu.Lock()
25712608
si.invalid = true
2572-
clients := make([]*client, 0, len(a.clients))
2573-
for c := range a.clients {
2574-
clients = append(clients, c)
2575-
}
2609+
clients := a.getClientsLocked()
25762610
awcsti := map[string]struct{}{a.Name: {}}
25772611
a.mu.Unlock()
25782612
for _, c := range clients {
@@ -2779,13 +2813,7 @@ func (a *Account) expiredTimeout() {
27792813
a.mu.Unlock()
27802814

27812815
// Collect the clients and expire them.
2782-
cs := make([]*client, 0, len(a.clients))
2783-
a.mu.RLock()
2784-
for c := range a.clients {
2785-
cs = append(cs, c)
2786-
}
2787-
a.mu.RUnlock()
2788-
2816+
cs := a.getClients()
27892817
for _, c := range cs {
27902818
c.accountAuthExpired()
27912819
}
@@ -3001,16 +3029,6 @@ func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaim
30013029
s.registerSystemImports(a)
30023030
}
30033031

3004-
gatherClients := func() []*client {
3005-
a.mu.RLock()
3006-
clients := make([]*client, 0, len(a.clients))
3007-
for c := range a.clients {
3008-
clients = append(clients, c)
3009-
}
3010-
a.mu.RUnlock()
3011-
return clients
3012-
}
3013-
30143032
jsEnabled := s.JetStreamEnabled()
30153033
if jsEnabled && a == s.SystemAccount() {
30163034
s.checkJetStreamExports()
@@ -3144,7 +3162,7 @@ func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaim
31443162
// Now let's apply any needed changes from import/export changes.
31453163
if !a.checkStreamImportsEqual(old) {
31463164
awcsti := map[string]struct{}{a.Name: {}}
3147-
for _, c := range gatherClients() {
3165+
for _, c := range a.getClients() {
31483166
c.processSubsOnConfigReload(awcsti)
31493167
}
31503168
}
@@ -3266,9 +3284,9 @@ func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaim
32663284
}
32673285

32683286
a.updated = time.Now().UTC()
3287+
clients := a.getClientsLocked()
32693288
a.mu.Unlock()
32703289

3271-
clients := gatherClients()
32723290
// Sort if we are over the limit.
32733291
if a.MaxTotalConnectionsReached() {
32743292
sort.Slice(clients, func(i, j int) bool {

server/jetstream_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4524,7 +4524,7 @@ func TestJetStreamSnapshotsAPI(t *testing.T) {
45244524
if err != nil {
45254525
t.Fatalf("Expected to find a stream for %q", mname)
45264526
}
4527-
state = mset.state()
4527+
mset.state()
45284528
mset.delete()
45294529

45304530
rreq.Config.Name = "NEW_STREAM"
@@ -15195,7 +15195,7 @@ func TestJetStreamPullConsumerHeartBeats(t *testing.T) {
1519515195
}
1519615196
}()
1519715197

15198-
start, msgs = time.Now(), doReq(10, 75*time.Millisecond, 350*time.Millisecond, 6)
15198+
msgs = doReq(10, 75*time.Millisecond, 350*time.Millisecond, 6)
1519915199
// The first 5 should be msgs, no HBs.
1520015200
for i := 0; i < 5; i++ {
1520115201
if m := msgs[i].msg; len(m.Header) > 0 {

server/monitor_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,6 @@ func TestConnzLastActivity(t *testing.T) {
677677
if barLA.Equal(nextLA) {
678678
t.Fatalf("Publish should have triggered update to LastActivity\n")
679679
}
680-
barLA = nextLA
681680

682681
// Message delivery on ncFoo should have triggered as well.
683682
nextLA = ciFoo.LastActivity

test/service_latency_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,6 @@ func TestServiceLatencyClientRTTSlowerVsServiceRTT(t *testing.T) {
361361
}
362362

363363
// Send the request.
364-
start = time.Now()
365364
_, err := nc2.Request("ngs.usage", []byte("1h"), time.Second)
366365
if err != nil {
367366
t.Fatalf("Expected a response")
@@ -1500,7 +1499,6 @@ func TestServiceLatencyRequestorSharesConfig(t *testing.T) {
15001499
t.Fatalf("Error on server reload: %v", err)
15011500
}
15021501

1503-
start = time.Now()
15041502
if _, err = nc2.Request("SVC", []byte("1h"), time.Second); err != nil {
15051503
t.Fatalf("Expected a response")
15061504
}

0 commit comments

Comments
 (0)