Skip to content

Commit 5632d9a

Browse files
committed
Fix #3783: Improve IsPinned() lookups for indirect pins
This avoids revisiting already-searched branches and cut down the IsPinned() check times considerably when recursive pins share big underlying DAGs. A test has been added which double-checks that pinned and unpinned items lookups respond as expected with shared branches. License: MIT Signed-off-by: Hector Sanjuan <[email protected]>
1 parent 7d02274 commit 5632d9a

File tree

2 files changed

+145
-9
lines changed

2 files changed

+145
-9
lines changed

pin/pin.go

+18-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,14 @@ 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(
526+
ds mdag.LinkService,
527+
root *cid.Cid,
528+
child *cid.Cid,
529+
visit func(*cid.Cid) bool) (bool, error) {
530+
523531
links, err := ds.GetLinks(context.Background(), root)
524532
if err != nil {
525533
return false, err
@@ -529,14 +537,15 @@ func hasChild(ds mdag.LinkService, root *cid.Cid, child *cid.Cid) (bool, error)
529537
if lnk.Cid.Equals(child) {
530538
return true, nil
531539
}
540+
if visit(c) {
541+
has, err := hasChild(ds, c, child, visit)
542+
if err != nil {
543+
return false, err
544+
}
532545

533-
has, err := hasChild(ds, c, child)
534-
if err != nil {
535-
return false, err
536-
}
537-
538-
if has {
539-
return has, nil
546+
if has {
547+
return has, nil
548+
}
540549
}
541550
}
542551
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

@@ -143,6 +154,122 @@ func TestPinnerBasic(t *testing.T) {
143154

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

148275
func TestDuplicateSemantics(t *testing.T) {

0 commit comments

Comments
 (0)