Skip to content
This repository was archived by the owner on Jun 19, 2023. It is now read-only.

Commit a019fdb

Browse files
committedApr 8, 2021
fix(arc): Per-CID locking. Sync Map to CID lock
1 parent 0838a9b commit a019fdb

File tree

1 file changed

+86
-15
lines changed

1 file changed

+86
-15
lines changed
 

‎arc_cache.go

+86-15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package blockstore
22

33
import (
44
"context"
5+
"sync"
56

67
lru "github.com/hashicorp/golang-lru"
78
blocks "github.com/ipfs/go-block-format"
@@ -16,7 +17,10 @@ type cacheSize int
1617
// block Cids. This provides block access-time improvements, allowing
1718
// to short-cut many searches without query-ing the underlying datastore.
1819
type arccache struct {
19-
arc *lru.TwoQueueCache
20+
arc *lru.TwoQueueCache
21+
22+
arcLks sync.Map
23+
2024
blockstore Blockstore
2125

2226
hits metrics.Counter
@@ -28,28 +32,72 @@ func newARCCachedBS(ctx context.Context, bs Blockstore, lruSize int) (*arccache,
2832
if err != nil {
2933
return nil, err
3034
}
31-
c := &arccache{arc: arc, blockstore: bs}
35+
c := &arccache{arc: arc, arcLks: sync.Map{}, blockstore: bs}
3236
c.hits = metrics.NewCtx(ctx, "arc.hits_total", "Number of ARC cache hits").Counter()
3337
c.total = metrics.NewCtx(ctx, "arc_total", "Total number of ARC cache requests").Counter()
3438

3539
return c, nil
3640
}
3741

38-
func (b *arccache) DeleteBlock(k cid.Cid) error {
39-
if has, _, ok := b.hasCached(k); ok && !has {
40-
return nil
42+
// if ok == false has is inconclusive, calling release is a noop
43+
// if ok == true then has response to question: is it contained and a lock will be held on content `k` until `release` is called.
44+
// it is the callers responsibility to call `release` when they are done writing/reading data.
45+
func (b *arccache) hasCachedSync(k cid.Cid) (has bool, size int, ok bool, release func()) {
46+
// default return ops
47+
has = false
48+
size = -1
49+
ok = false
50+
release = func() {}
51+
52+
b.total.Inc()
53+
if !k.Defined() {
54+
log.Error("undefined cid in arccache")
55+
// Return cache invalid so the call to blockstore happens
56+
// in case of invalid key and correct error is created.
57+
return
4158
}
4259

43-
b.arc.Remove(k) // Invalidate cache before deleting.
44-
err := b.blockstore.DeleteBlock(k)
45-
if err == nil {
46-
b.cacheHave(k, false)
60+
h, ok := b.arc.Get(string(k.Hash()))
61+
if ok {
62+
b.hits.Inc()
63+
switch h := h.(type) {
64+
case cacheHave:
65+
has = bool(h)
66+
size = -1
67+
ok = true
68+
case cacheSize:
69+
has = true
70+
size = int(h)
71+
ok = true
72+
}
4773
}
48-
return err
74+
75+
// check if we have a lock for this content.
76+
v, hasLk := b.arcLks.Load(k)
77+
if has && hasLk {
78+
// cache and lock hit.
79+
lk := v.(*sync.Mutex)
80+
lk.Lock()
81+
release = func() { lk.Unlock() }
82+
83+
return
84+
} else if has && !hasLk {
85+
// cache hit and lock miss, create the lock, lock it, and add it to the lockMap
86+
lk := new(sync.Mutex)
87+
b.arcLks.Store(k, lk)
88+
89+
lk.Lock()
90+
release = func() { lk.Unlock() }
91+
} else if !has && hasLk {
92+
// cache miss and lock hit, remove lock from map
93+
b.arcLks.Delete(k)
94+
}
95+
// else cache miss and lock miss, noop
96+
return
4997
}
5098

5199
// if ok == false has is inconclusive
52-
// if ok == true then has respons to question: is it contained
100+
// if ok == true then has response to question: is it contained
53101
func (b *arccache) hasCached(k cid.Cid) (has bool, size int, ok bool) {
54102
b.total.Inc()
55103
if !k.Defined() {
@@ -72,6 +120,21 @@ func (b *arccache) hasCached(k cid.Cid) (has bool, size int, ok bool) {
72120
return false, -1, false
73121
}
74122

123+
func (b *arccache) DeleteBlock(k cid.Cid) error {
124+
has, _, ok, release := b.hasCachedSync(k)
125+
defer release()
126+
if ok && !has {
127+
return nil
128+
}
129+
130+
b.arc.Remove(k) // Invalidate cache before deleting.
131+
err := b.blockstore.DeleteBlock(k)
132+
if err == nil {
133+
b.cacheHave(k, false)
134+
}
135+
return err
136+
}
137+
75138
func (b *arccache) Has(k cid.Cid) (bool, error) {
76139
if has, _, ok := b.hasCached(k); ok {
77140
return has, nil
@@ -85,7 +148,9 @@ func (b *arccache) Has(k cid.Cid) (bool, error) {
85148
}
86149

87150
func (b *arccache) GetSize(k cid.Cid) (int, error) {
88-
if has, blockSize, ok := b.hasCached(k); ok {
151+
has, blockSize, ok, release := b.hasCachedSync(k)
152+
defer release()
153+
if ok {
89154
if !has {
90155
// don't have it, return
91156
return -1, ErrNotFound
@@ -111,7 +176,9 @@ func (b *arccache) Get(k cid.Cid) (blocks.Block, error) {
111176
return nil, ErrNotFound
112177
}
113178

114-
if has, _, ok := b.hasCached(k); ok && !has {
179+
has, _, ok, release := b.hasCachedSync(k)
180+
defer release()
181+
if ok && !has {
115182
return nil, ErrNotFound
116183
}
117184

@@ -125,7 +192,9 @@ func (b *arccache) Get(k cid.Cid) (blocks.Block, error) {
125192
}
126193

127194
func (b *arccache) Put(bl blocks.Block) error {
128-
if has, _, ok := b.hasCached(bl.Cid()); ok && has {
195+
has, _, ok, release := b.hasCachedSync(bl.Cid())
196+
defer release()
197+
if ok && has {
129198
return nil
130199
}
131200

@@ -141,9 +210,11 @@ func (b *arccache) PutMany(bs []blocks.Block) error {
141210
for _, block := range bs {
142211
// call put on block if result is inconclusive or we are sure that
143212
// the block isn't in storage
144-
if has, _, ok := b.hasCached(block.Cid()); !ok || (ok && !has) {
213+
has, _, ok, release := b.hasCachedSync(block.Cid())
214+
if !ok || (ok && !has) {
145215
good = append(good, block)
146216
}
217+
defer release()
147218
}
148219
err := b.blockstore.PutMany(good)
149220
if err != nil {

0 commit comments

Comments
 (0)