Skip to content

Commit 425ff14

Browse files
committed
[FAB-6186] MSP cache should not use RWMutex
The MSP cache has RW locks and uses RLock when doing cache lookup, and WLock when doing cache updates. This may result in the an error if done concurrently while the cache has more than 1 item and only cache lookups are performed, because the cache lookup operation shifts and moves items in the internal list of the cache (that specifies eviction order, since its an LRU cache). The test was changed and would fail wit a null pointer panic if the cache.go file would be reverted back. Change-Id: I97f8b8cce0ee21090da0b036814da52acbf3be60 Signed-off-by: yacovm <yacovm@il.ibm.com>
1 parent bb2cdd2 commit 425ff14

File tree

2 files changed

+42
-18
lines changed

2 files changed

+42
-18
lines changed

msp/cache/cache.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package cache
88

99
import (
1010
"fmt"
11-
1211
"sync"
1312

1413
"github.com/golang/groupcache/lru"
@@ -45,24 +44,24 @@ type cachedMSP struct {
4544
// cache for DeserializeIdentity.
4645
deserializeIdentityCache *lru.Cache
4746

48-
dicMutex sync.RWMutex // synchronize access to cache
47+
dicMutex sync.Mutex // synchronize access to cache
4948

5049
// cache for validateIdentity
5150
validateIdentityCache *lru.Cache
5251

53-
vicMutex sync.RWMutex // synchronize access to cache
52+
vicMutex sync.Mutex // synchronize access to cache
5453

5554
// basically a map of principals=>identities=>stringified to booleans
5655
// specifying whether this identity satisfies this principal
5756
satisfiesPrincipalCache *lru.Cache
5857

59-
spcMutex sync.RWMutex // synchronize access to cache
58+
spcMutex sync.Mutex // synchronize access to cache
6059
}
6160

6261
func (c *cachedMSP) DeserializeIdentity(serializedIdentity []byte) (msp.Identity, error) {
63-
c.dicMutex.RLock()
62+
c.dicMutex.Lock()
6463
cached, ok := c.deserializeIdentityCache.Get(string(serializedIdentity))
65-
c.dicMutex.RUnlock()
64+
c.dicMutex.Unlock()
6665
if ok {
6766
return cached.(msp.Identity), nil
6867
}
@@ -86,9 +85,9 @@ func (c *cachedMSP) Validate(id msp.Identity) error {
8685
identifier := id.GetIdentifier()
8786
key := string(identifier.Mspid + ":" + identifier.Id)
8887

89-
c.vicMutex.RLock()
88+
c.vicMutex.Lock()
9089
_, ok := c.validateIdentityCache.Get(key)
91-
c.vicMutex.RUnlock()
90+
c.vicMutex.Unlock()
9291
if ok {
9392
// cache only stores if the identity is valid.
9493
return nil
@@ -110,9 +109,9 @@ func (c *cachedMSP) SatisfiesPrincipal(id msp.Identity, principal *pmsp.MSPPrinc
110109
principalKey := string(principal.PrincipalClassification) + string(principal.Principal)
111110
key := identityKey + principalKey
112111

113-
c.spcMutex.RLock()
112+
c.spcMutex.Lock()
114113
v, ok := c.satisfiesPrincipalCache.Get(key)
115-
c.spcMutex.RUnlock()
114+
c.spcMutex.Unlock()
116115
if ok {
117116
if v == nil {
118117
return nil

msp/cache/cache_test.go

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0
77
package cache
88

99
import (
10+
"sync"
1011
"testing"
1112

1213
"github.com/hyperledger/fabric/msp"
@@ -115,23 +116,47 @@ func TestGetTLSIntermediateCerts(t *testing.T) {
115116

116117
func TestDeserializeIdentity(t *testing.T) {
117118
mockMSP := &mocks.MockMSP{}
118-
i, err := New(mockMSP)
119+
wrappedMSP, err := New(mockMSP)
119120
assert.NoError(t, err)
120121

121122
// Check id is cached
122123
mockIdentity := &mocks.MockIdentity{ID: "Alice"}
124+
mockIdentity2 := &mocks.MockIdentity{ID: "Bob"}
123125
serializedIdentity := []byte{1, 2, 3}
126+
serializedIdentity2 := []byte{4, 5, 6}
124127
mockMSP.On("DeserializeIdentity", serializedIdentity).Return(mockIdentity, nil)
125-
id, err := i.DeserializeIdentity(serializedIdentity)
126-
assert.NoError(t, err)
127-
assert.Equal(t, mockIdentity, id)
128+
mockMSP.On("DeserializeIdentity", serializedIdentity2).Return(mockIdentity2, nil)
129+
// Prime the cache
130+
wrappedMSP.DeserializeIdentity(serializedIdentity)
131+
wrappedMSP.DeserializeIdentity(serializedIdentity2)
132+
133+
// Stress the cache and ensure concurrent operations
134+
// do not result in a failure
135+
var wg sync.WaitGroup
136+
wg.Add(10000)
137+
for i := 0; i < 10000; i++ {
138+
go func(m msp.MSP) {
139+
sIdentity := serializedIdentity
140+
expectedIdentity := mockIdentity
141+
defer wg.Done()
142+
if i%2 == 0 {
143+
sIdentity = serializedIdentity2
144+
expectedIdentity = mockIdentity2
145+
}
146+
id, err := wrappedMSP.DeserializeIdentity(sIdentity)
147+
assert.NoError(t, err)
148+
assert.Equal(t, expectedIdentity, id)
149+
}(wrappedMSP)
150+
}
151+
wg.Wait()
152+
128153
mockMSP.AssertExpectations(t)
129154
// Check the cache
130-
_, ok := i.(*cachedMSP).deserializeIdentityCache.Get(string(serializedIdentity))
155+
_, ok := wrappedMSP.(*cachedMSP).deserializeIdentityCache.Get(string(serializedIdentity))
131156
assert.True(t, ok)
132157

133158
// Check the same object is returned
134-
id, err = i.DeserializeIdentity(serializedIdentity)
159+
id, err := wrappedMSP.DeserializeIdentity(serializedIdentity)
135160
assert.NoError(t, err)
136161
assert.True(t, mockIdentity == id)
137162
mockMSP.AssertExpectations(t)
@@ -140,12 +165,12 @@ func TestDeserializeIdentity(t *testing.T) {
140165
mockIdentity = &mocks.MockIdentity{ID: "Bob"}
141166
serializedIdentity = []byte{1, 2, 3, 4}
142167
mockMSP.On("DeserializeIdentity", serializedIdentity).Return(mockIdentity, errors.New("Invalid identity"))
143-
id, err = i.DeserializeIdentity(serializedIdentity)
168+
id, err = wrappedMSP.DeserializeIdentity(serializedIdentity)
144169
assert.Error(t, err)
145170
assert.Contains(t, err.Error(), "Invalid identity")
146171
mockMSP.AssertExpectations(t)
147172

148-
_, ok = i.(*cachedMSP).deserializeIdentityCache.Get(string(serializedIdentity))
173+
_, ok = wrappedMSP.(*cachedMSP).deserializeIdentityCache.Get(string(serializedIdentity))
149174
assert.False(t, ok)
150175
}
151176

0 commit comments

Comments
 (0)