9
9
"sync"
10
10
"time"
11
11
12
+ "github.com/cockroachdb/pebble/internal/invariants"
12
13
"github.com/cockroachdb/swiss"
13
14
)
14
15
@@ -39,16 +40,15 @@ import (
39
40
//
40
41
// Design choices and motivation:
41
42
//
42
- // - readShard is tightly integrated with a cache shard: At its core,
43
- // readShard is a map with synchronization. For the same reason the cache is
44
- // sharded (for higher concurrency by sharding the mutex), it is beneficial
45
- // to shard synchronization on readShard. By making readShard a member of
46
- // shard, this sharding is trivially accomplished. Additionally, the code
47
- // feels cleaner when there isn't a race between a cache miss, followed by
48
- // creating a readEntry that is no longer needed because someone else has
49
- // done the read since the miss and inserted into the cache. By making the
50
- // readShard use shard.mu, such a race is avoided. A side benefit is that
51
- // the cache interaction can be hidden behind readEntry.SetReadValue. One
43
+ // - At its core, readShard is a map with synchronization. For the same reason
44
+ // the cache is sharded (for higher concurrency by sharding the mutex), it
45
+ // is beneficial to shard synchronization on readShard. By making readShard
46
+ // a member of shard, this sharding is trivially accomplished. readShard has
47
+ // its own mutex (separate from shard.mu), in order to avoid write-locking
48
+ // shard.mu when we start a read.
49
+ //
50
+ // - readShard is integrated with the corresponding cache shard; this allows
51
+ // the cache interaction to be hidden behind readEntry.SetReadValue. One
52
52
// disadvantage of this tightly integrated design is that it does not
53
53
// encompass readers that will put the read value into a block.BufferPool --
54
54
// we don't worry about those since block.BufferPool is only used for
@@ -69,10 +69,8 @@ type readShard struct {
69
69
// shard is only used for locking, and calling shard.Set.
70
70
shard * shard
71
71
// Protected by shard.mu.
72
- //
73
- // shard.mu is never held when acquiring readEntry.mu. shard.mu is a shared
74
- // resource and must be released quickly.
75
- shardMu struct {
72
+ mu struct {
73
+ sync.Mutex
76
74
readMap swiss.Map [key , * readEntry ]
77
75
}
78
76
}
@@ -82,27 +80,35 @@ func (rs *readShard) Init(shard *shard) *readShard {
82
80
shard : shard ,
83
81
}
84
82
// Choice of 16 is arbitrary.
85
- rs .shardMu .readMap .Init (16 )
83
+ rs .mu .readMap .Init (16 )
86
84
return rs
87
85
}
88
86
89
- // acquireReadEntryLocked gets a *readEntry for (id, fileNum, offset). shard.mu is
90
- // already write locked.
91
- func (rs * readShard ) acquireReadEntryLocked (k key ) * readEntry {
92
- e , ok := rs .shardMu .readMap .Get (k )
93
- if ! ok {
94
- e = newReadEntry (rs , k )
95
- rs .shardMu .readMap .Put (k , e )
96
- } else {
97
- e .refCount .acquireAllowZero ()
87
+ // acquireReadEntry acquires a *readEntry for (id, fileNum, offset), creating
88
+ // one if necessary.
89
+ func (rs * readShard ) acquireReadEntry (k key ) * readEntry {
90
+ rs .mu .Lock ()
91
+ defer rs .mu .Unlock ()
92
+
93
+ if e , ok := rs .mu .readMap .Get (k ); ok {
94
+ // An entry we found in the map while holding the mutex must have a non-zero
95
+ // reference count.
96
+ if e .refCount < 1 {
97
+ panic ("invalid reference count" )
98
+ }
99
+ e .refCount ++
100
+ return e
98
101
}
102
+
103
+ e := newReadEntry (rs , k )
104
+ rs .mu .readMap .Put (k , e )
99
105
return e
100
106
}
101
107
102
108
func (rs * readShard ) lenForTesting () int {
103
- rs .shard . mu .Lock ()
104
- defer rs .shard . mu .Unlock ()
105
- return rs .shardMu .readMap .Len ()
109
+ rs .mu .Lock ()
110
+ defer rs .mu .Unlock ()
111
+ return rs .mu .readMap .Len ()
106
112
}
107
113
108
114
// readEntry is used to coordinate between concurrent attempted readers of the
@@ -146,10 +152,8 @@ type readEntry struct {
146
152
errorDuration time.Duration
147
153
readStart time.Time
148
154
}
149
- // Count of ReadHandles that refer to this readEntry. Increments always hold
150
- // shard.mu. So if this is found to be 0 while holding shard.mu, it is safe
151
- // to delete readEntry from readShard.shardMu.readMap.
152
- refCount refcnt
155
+ // Count of ReadHandles that refer to this readEntry. Protected by readShard.mu.
156
+ refCount int32
153
157
}
154
158
155
159
var readEntryPool = sync.Pool {
@@ -163,8 +167,8 @@ func newReadEntry(rs *readShard, k key) *readEntry {
163
167
* e = readEntry {
164
168
readShard : rs ,
165
169
key : k ,
170
+ refCount : 1 ,
166
171
}
167
- e .refCount .init (1 )
168
172
return e
169
173
}
170
174
@@ -261,40 +265,26 @@ func (e *readEntry) waitForReadPermissionOrHandle(
261
265
262
266
// unrefAndTryRemoveFromMap reduces the reference count of e and removes e.key
263
267
// => e from the readMap if necessary.
264
- //
265
- // It is possible that after unreffing that s.e has already been removed, and
266
- // is now back in the sync.Pool, or being reused (for the same or different
267
- // key). This is because after unreffing, which caused the s.e.refCount to
268
- // become zero, but before acquiring shard.mu, it could have been incremented
269
- // and decremented concurrently, and some other goroutine could have observed
270
- // a different decrement to 0, and raced ahead and deleted s.e from the
271
- // readMap.
272
268
func (e * readEntry ) unrefAndTryRemoveFromMap () {
273
- // Save the fields we need from entry; once we release the last refcount, it
274
- // is possible that the entry is found and reused and then freed.
275
269
rs := e .readShard
276
- k := e .key
277
- if ! e .refCount .release () {
270
+ rs .mu .Lock ()
271
+ e .refCount --
272
+ if e .refCount > 0 {
273
+ // Entry still in use.
274
+ rs .mu .Unlock ()
278
275
return
279
276
}
280
- // Once we release the refcount, it is possible that it the entry is reused
281
- // again and freed before we get the lock.
282
- rs .shard .mu .Lock ()
283
- e2 , ok := rs .shardMu .readMap .Get (k )
284
- if ! ok || e2 != e {
285
- // Already removed.
286
- rs .shard .mu .Unlock ()
287
- return
277
+ if e .refCount < 0 {
278
+ panic ("invalid reference count" )
288
279
}
289
- if e .refCount .value () != 0 {
290
- // The entry was reused.
291
- rs .shard .mu .Unlock ()
292
- return
280
+ // The refcount is now 0; remove from the map.
281
+ if invariants .Enabled {
282
+ if e2 , ok := rs .mu .readMap .Get (e .key ); ! ok || e2 != e {
283
+ panic ("entry not in readMap" )
284
+ }
293
285
}
294
- // e.refCount == 0. And it cannot be incremented since
295
- // shard.mu.Lock() is held. So remove from map.
296
- rs .shardMu .readMap .Delete (k )
297
- rs .shard .mu .Unlock ()
286
+ rs .mu .readMap .Delete (e .key )
287
+ rs .mu .Unlock ()
298
288
299
289
// Free s.e.
300
290
e .mu .v .Release ()
0 commit comments