Skip to content

Commit 4e2e537

Browse files
Merge pull request #3809 from ipfs/faster-pin-ls
Fix #3783: Improve IsPinned() lookups for indirect pins
2 parents 1cd1efd + 71a7a90 commit 4e2e537

File tree

2 files changed

+140
-9
lines changed

2 files changed

+140
-9
lines changed

pin/pin.go

+13-9
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,9 @@ func (p *pinner) isPinnedWithType(c *cid.Cid, mode PinMode) (string, bool, error
276276
}
277277

278278
// Default is Indirect
279+
visitedSet := cid.NewSet()
279280
for _, rc := range p.recursePin.Keys() {
280-
has, err := hasChild(p.dserv, rc, c)
281+
has, err := hasChild(p.dserv, rc, c, visitedSet.Visit)
281282
if err != nil {
282283
return "", false, err
283284
}
@@ -519,7 +520,9 @@ func (p *pinner) PinWithMode(c *cid.Cid, mode PinMode) {
519520
}
520521
}
521522

522-
func hasChild(ds mdag.LinkService, root *cid.Cid, child *cid.Cid) (bool, error) {
523+
// hasChild recursively looks for a Cid among the children of a root Cid.
524+
// The visit function can be used to shortcut already-visited branches.
525+
func hasChild(ds mdag.LinkService, root *cid.Cid, child *cid.Cid, visit func(*cid.Cid) bool) (bool, error) {
523526
links, err := ds.GetLinks(context.Background(), root)
524527
if err != nil {
525528
return false, err
@@ -529,14 +532,15 @@ func hasChild(ds mdag.LinkService, root *cid.Cid, child *cid.Cid) (bool, error)
529532
if lnk.Cid.Equals(child) {
530533
return true, nil
531534
}
535+
if visit(c) {
536+
has, err := hasChild(ds, c, child, visit)
537+
if err != nil {
538+
return false, err
539+
}
532540

533-
has, err := hasChild(ds, c, child)
534-
if err != nil {
535-
return false, err
536-
}
537-
538-
if has {
539-
return has, nil
541+
if has {
542+
return has, nil
543+
}
540544
}
541545
}
542546
return false, nil

pin/pin_test.go

+127
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ func assertPinned(t *testing.T, p Pinner, c *cid.Cid, failmsg string) {
3535
}
3636
}
3737

38+
func assertUnpinned(t *testing.T, p Pinner, c *cid.Cid, failmsg string) {
39+
_, pinned, err := p.IsPinned(c)
40+
if err != nil {
41+
t.Fatal(err)
42+
}
43+
44+
if pinned {
45+
t.Fatal(failmsg)
46+
}
47+
}
48+
3849
func TestPinnerBasic(t *testing.T) {
3950
ctx := context.Background()
4051

@@ -145,6 +156,122 @@ func TestPinnerBasic(t *testing.T) {
145156
assertPinned(t, np, bk, "could not find recursively pinned node")
146157
}
147158

159+
func TestIsPinnedLookup(t *testing.T) {
160+
// We are going to test that lookups work in pins which share
161+
// the same branches. For that we will construct this tree:
162+
//
163+
// A5->A4->A3->A2->A1->A0
164+
// / /
165+
// B------- /
166+
// \ /
167+
// C---------------
168+
//
169+
// We will ensure that IsPinned works for all objects both when they
170+
// are pinned and once they have been unpinned.
171+
aBranchLen := 6
172+
if aBranchLen < 3 {
173+
t.Fatal("set aBranchLen to at least 3")
174+
}
175+
176+
ctx := context.Background()
177+
dstore := dssync.MutexWrap(ds.NewMapDatastore())
178+
bstore := blockstore.NewBlockstore(dstore)
179+
bserv := bs.New(bstore, offline.Exchange(bstore))
180+
181+
dserv := mdag.NewDAGService(bserv)
182+
183+
// TODO does pinner need to share datastore with blockservice?
184+
p := NewPinner(dstore, dserv, dserv)
185+
186+
aNodes := make([]*mdag.ProtoNode, aBranchLen, aBranchLen)
187+
aKeys := make([]*cid.Cid, aBranchLen, aBranchLen)
188+
for i := 0; i < aBranchLen; i++ {
189+
a, _ := randNode()
190+
if i >= 1 {
191+
err := a.AddNodeLink("child", aNodes[i-1])
192+
if err != nil {
193+
t.Fatal(err)
194+
}
195+
}
196+
197+
ak, err := dserv.Add(a)
198+
if err != nil {
199+
t.Fatal(err)
200+
}
201+
//t.Logf("a[%d] is %s", i, ak)
202+
aNodes[i] = a
203+
aKeys[i] = ak
204+
}
205+
206+
// Pin A5 recursively
207+
if err := p.Pin(ctx, aNodes[aBranchLen-1], true); err != nil {
208+
t.Fatal(err)
209+
}
210+
211+
// Create node B and add A3 as child
212+
b, _ := randNode()
213+
if err := b.AddNodeLink("mychild", aNodes[3]); err != nil {
214+
t.Fatal(err)
215+
}
216+
217+
// Create C node
218+
c, _ := randNode()
219+
// Add A0 as child of C
220+
if err := c.AddNodeLink("child", aNodes[0]); err != nil {
221+
t.Fatal(err)
222+
}
223+
224+
// Add C
225+
ck, err := dserv.Add(c)
226+
if err != nil {
227+
t.Fatal(err)
228+
}
229+
//t.Logf("C is %s", ck)
230+
231+
// Add C to B and Add B
232+
if err := b.AddNodeLink("myotherchild", c); err != nil {
233+
t.Fatal(err)
234+
}
235+
bk, err := dserv.Add(b)
236+
if err != nil {
237+
t.Fatal(err)
238+
}
239+
//t.Logf("B is %s", bk)
240+
241+
// Pin C recursively
242+
243+
if err := p.Pin(ctx, c, true); err != nil {
244+
t.Fatal(err)
245+
}
246+
247+
// Pin B recursively
248+
249+
if err := p.Pin(ctx, b, true); err != nil {
250+
t.Fatal(err)
251+
}
252+
253+
assertPinned(t, p, aKeys[0], "A0 should be pinned")
254+
assertPinned(t, p, aKeys[1], "A1 should be pinned")
255+
assertPinned(t, p, ck, "C should be pinned")
256+
assertPinned(t, p, bk, "B should be pinned")
257+
258+
// Unpin A5 recursively
259+
if err := p.Unpin(ctx, aKeys[5], true); err != nil {
260+
t.Fatal(err)
261+
}
262+
263+
assertPinned(t, p, aKeys[0], "A0 should still be pinned through B")
264+
assertUnpinned(t, p, aKeys[4], "A4 should be unpinned")
265+
266+
// Unpin B recursively
267+
if err := p.Unpin(ctx, bk, true); err != nil {
268+
t.Fatal(err)
269+
}
270+
assertUnpinned(t, p, bk, "B should be unpinned")
271+
assertUnpinned(t, p, aKeys[1], "A1 should be unpinned")
272+
assertPinned(t, p, aKeys[0], "A0 should still be pinned through C")
273+
}
274+
148275
func TestDuplicateSemantics(t *testing.T) {
149276
ctx := context.Background()
150277
dstore := dssync.MutexWrap(ds.NewMapDatastore())

0 commit comments

Comments
 (0)