Skip to content

Commit e2fc7f2

Browse files
authored
pinner: change the interface to have async pin listing
The rational is that if the pin list get big, a synchronous call to get the complete list can delay handling unnecessarily. For example, when listing indirect pins, you can start walking the DAGs immediately with the first recursive pin instead of waiting for the full list. This matters even more on low power device, of if the pin list is stored remotely. * coreiface: allow to return an error not linked to a specific Cid * merkledag/test: add a DAG generator Rationale is that generating a test DAG is quite difficult, and anything that helps writing better tests is helpful.
1 parent 4c5c98b commit e2fc7f2

9 files changed

Lines changed: 312 additions & 97 deletions

File tree

coreiface/pin.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ type PinStatus interface {
2727

2828
// BadNodes returns any bad (usually missing) nodes from the pin
2929
BadNodes() []BadPinNode
30+
31+
// if not nil, an error happened. Everything else should be ignored.
32+
Err() error
3033
}
3134

3235
// BadPinNode is a node that has been marked as bad by Pin.Verify

coreiface/tests/pin.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ func (tp *TestSuite) TestPinRecursive(t *testing.T) {
198198
}
199199
n := 0
200200
for r := range res {
201+
if err := r.Err(); err != nil {
202+
t.Error(err)
203+
}
201204
if !r.Ok() {
202205
t.Error("expected pin to be ok")
203206
}
@@ -208,7 +211,7 @@ func (tp *TestSuite) TestPinRecursive(t *testing.T) {
208211
t.Errorf("unexpected verify result count: %d", n)
209212
}
210213

211-
//TODO: figure out a way to test verify without touching IpfsNode
214+
// TODO: figure out a way to test verify without touching IpfsNode
212215
/*
213216
err = api.Block().Rm(ctx, p0, opt.Block.Force(true))
214217
if err != nil {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package mdutils
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
blocks "github.com/ipfs/go-block-format"
8+
"github.com/ipfs/go-cid"
9+
format "github.com/ipfs/go-ipld-format"
10+
11+
"github.com/ipfs/boxo/ipld/merkledag"
12+
)
13+
14+
// NewDAGGenerator returns an object capable of
15+
// producing IPLD DAGs.
16+
func NewDAGGenerator() *DAGGenerator {
17+
return &DAGGenerator{}
18+
}
19+
20+
// DAGGenerator generates BasicBlocks on demand.
21+
// For each instance of DAGGenerator, each new DAG is different from the
22+
// previous, although two different instances will produce the same, given the
23+
// same parameters.
24+
type DAGGenerator struct {
25+
seq int
26+
}
27+
28+
// MakeDagBlock generate a balanced DAG with the given fanout and depth, and add the blocks to the adder.
29+
// This adder can be for example a blockstore.Put or a blockservice.AddBlock.
30+
func (dg *DAGGenerator) MakeDagBlock(adder func(ctx context.Context, block blocks.Block) error, fanout uint, depth uint) (c cid.Cid, allCids []cid.Cid, err error) {
31+
return dg.MakeDagNode(func(ctx context.Context, node format.Node) error {
32+
return adder(ctx, node.(blocks.Block))
33+
}, fanout, depth)
34+
}
35+
36+
// MakeDagNode generate a balanced DAG with the given fanout and depth, and add the blocks to the adder.
37+
// This adder can be for example a DAGService.Add.
38+
func (dg *DAGGenerator) MakeDagNode(adder func(ctx context.Context, node format.Node) error, fanout uint, depth uint) (c cid.Cid, allCids []cid.Cid, err error) {
39+
c, _, allCids, err = dg.generate(adder, fanout, depth)
40+
return c, allCids, err
41+
}
42+
43+
func (dg *DAGGenerator) generate(adder func(ctx context.Context, node format.Node) error, fanout uint, depth uint) (c cid.Cid, size uint64, allCids []cid.Cid, err error) {
44+
if depth == 0 {
45+
panic("depth should be at least 1")
46+
}
47+
if depth == 1 {
48+
c, size, err = dg.encodeBlock(adder)
49+
if err != nil {
50+
return cid.Undef, 0, nil, err
51+
}
52+
return c, size, []cid.Cid{c}, nil
53+
}
54+
links := make([]*format.Link, fanout)
55+
for i := uint(0); i < fanout; i++ {
56+
root, size, children, err := dg.generate(adder, fanout, depth-1)
57+
if err != nil {
58+
return cid.Undef, 0, nil, err
59+
}
60+
links[i] = &format.Link{Cid: root, Size: size}
61+
allCids = append(allCids, children...)
62+
}
63+
c, size, err = dg.encodeBlock(adder, links...)
64+
if err != nil {
65+
return cid.Undef, 0, nil, err
66+
}
67+
return c, size, append([]cid.Cid{c}, allCids...), nil
68+
}
69+
70+
func (dg *DAGGenerator) encodeBlock(adder func(ctx context.Context, node format.Node) error, links ...*format.Link) (cid.Cid, uint64, error) {
71+
dg.seq++
72+
nd := &merkledag.ProtoNode{}
73+
nd.SetData([]byte(fmt.Sprint(dg.seq)))
74+
for i, link := range links {
75+
err := nd.AddRawLink(fmt.Sprintf("link-%d", i), link)
76+
if err != nil {
77+
return cid.Undef, 0, err
78+
}
79+
}
80+
err := adder(context.Background(), nd)
81+
if err != nil {
82+
return cid.Undef, 0, err
83+
}
84+
size, err := nd.Size()
85+
return nd.Cid(), size, err
86+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package mdutils
2+
3+
import (
4+
"context"
5+
"sync"
6+
"testing"
7+
8+
"github.com/ipfs/go-cid"
9+
format "github.com/ipfs/go-ipld-format"
10+
)
11+
12+
type testDagServ struct {
13+
mu sync.Mutex
14+
nodes map[string]format.Node
15+
}
16+
17+
func newTestDagServ() *testDagServ {
18+
return &testDagServ{nodes: make(map[string]format.Node)}
19+
}
20+
21+
func (d *testDagServ) Get(_ context.Context, cid cid.Cid) (format.Node, error) {
22+
d.mu.Lock()
23+
defer d.mu.Unlock()
24+
if n, ok := d.nodes[cid.KeyString()]; ok {
25+
return n, nil
26+
}
27+
return nil, format.ErrNotFound{Cid: cid}
28+
}
29+
30+
func (d *testDagServ) Add(_ context.Context, node format.Node) error {
31+
d.mu.Lock()
32+
defer d.mu.Unlock()
33+
d.nodes[node.Cid().KeyString()] = node
34+
return nil
35+
}
36+
37+
func TestNodesAreDifferent(t *testing.T) {
38+
dserv := newTestDagServ()
39+
gen := NewDAGGenerator()
40+
41+
var allCids []cid.Cid
42+
var allNodes []format.Node
43+
44+
const nbDag = 5
45+
46+
for i := 0; i < nbDag; i++ {
47+
c, cids, err := gen.MakeDagNode(dserv.Add, 5, 3)
48+
if err != nil {
49+
t.Fatal(err)
50+
}
51+
52+
allCids = append(allCids, cids...)
53+
54+
// collect all nodes
55+
var getChildren func(n format.Node)
56+
getChildren = func(n format.Node) {
57+
for _, link := range n.Links() {
58+
n, err = dserv.Get(context.Background(), link.Cid)
59+
if err != nil {
60+
t.Fatal(err)
61+
}
62+
allNodes = append(allNodes, n)
63+
getChildren(n)
64+
}
65+
}
66+
n, err := dserv.Get(context.Background(), c)
67+
if err != nil {
68+
t.Fatal(err)
69+
}
70+
allNodes = append(allNodes, n)
71+
getChildren(n)
72+
73+
// make sure they are all different
74+
for i, node1 := range allNodes {
75+
for j, node2 := range allNodes {
76+
if i != j {
77+
if node1.Cid().String() == node2.Cid().String() {
78+
t.Error("Found duplicate node")
79+
}
80+
}
81+
}
82+
}
83+
}
84+
85+
// expected count
86+
if len(allNodes) != nbDag*31 {
87+
t.Error("expected nbDag*31 nodes (1+5+5*5)")
88+
}
89+
if len(allCids) != nbDag*31 {
90+
t.Error("expected nbDag*31 cids (1+5+5*5)")
91+
}
92+
}

pinning/pinner/dspinner/pin.go

Lines changed: 42 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import (
1010
"path"
1111
"sync"
1212

13-
"github.com/ipfs/boxo/ipld/merkledag"
14-
"github.com/ipfs/boxo/ipld/merkledag/dagutils"
1513
"github.com/ipfs/go-cid"
1614
ds "github.com/ipfs/go-datastore"
1715
"github.com/ipfs/go-datastore/query"
@@ -20,6 +18,8 @@ import (
2018
"github.com/polydawn/refmt/cbor"
2119
"github.com/polydawn/refmt/obj/atlas"
2220

21+
"github.com/ipfs/boxo/ipld/merkledag"
22+
"github.com/ipfs/boxo/ipld/merkledag/dagutils"
2323
ipfspinner "github.com/ipfs/boxo/pinning/pinner"
2424
"github.com/ipfs/boxo/pinning/pinner/dsindex"
2525
)
@@ -665,61 +665,56 @@ func (p *pinner) loadPin(ctx context.Context, pid string) (*pin, error) {
665665
}
666666

667667
// DirectKeys returns a slice containing the directly pinned keys
668-
func (p *pinner) DirectKeys(ctx context.Context) ([]cid.Cid, error) {
669-
p.lock.RLock()
670-
defer p.lock.RUnlock()
671-
672-
cidSet := cid.NewSet()
673-
var e error
674-
err := p.cidDIndex.ForEach(ctx, "", func(key, value string) bool {
675-
var c cid.Cid
676-
c, e = cid.Cast([]byte(key))
677-
if e != nil {
678-
return false
679-
}
680-
cidSet.Add(c)
681-
return true
682-
})
683-
if err != nil {
684-
return nil, err
685-
}
686-
if e != nil {
687-
return nil, e
688-
}
689-
690-
return cidSet.Keys(), nil
668+
func (p *pinner) DirectKeys(ctx context.Context) <-chan ipfspinner.StreamedCid {
669+
return p.streamIndex(ctx, p.cidDIndex)
691670
}
692671

693672
// RecursiveKeys returns a slice containing the recursively pinned keys
694-
func (p *pinner) RecursiveKeys(ctx context.Context) ([]cid.Cid, error) {
695-
p.lock.RLock()
696-
defer p.lock.RUnlock()
673+
func (p *pinner) RecursiveKeys(ctx context.Context) <-chan ipfspinner.StreamedCid {
674+
return p.streamIndex(ctx, p.cidRIndex)
675+
}
697676

698-
cidSet := cid.NewSet()
699-
var e error
700-
err := p.cidRIndex.ForEach(ctx, "", func(key, value string) bool {
701-
var c cid.Cid
702-
c, e = cid.Cast([]byte(key))
703-
if e != nil {
704-
return false
677+
func (p *pinner) streamIndex(ctx context.Context, index dsindex.Indexer) <-chan ipfspinner.StreamedCid {
678+
out := make(chan ipfspinner.StreamedCid)
679+
680+
go func() {
681+
defer close(out)
682+
683+
p.lock.RLock()
684+
defer p.lock.RUnlock()
685+
686+
cidSet := cid.NewSet()
687+
688+
err := index.ForEach(ctx, "", func(key, value string) bool {
689+
c, err := cid.Cast([]byte(key))
690+
if err != nil {
691+
out <- ipfspinner.StreamedCid{Err: err}
692+
return false
693+
}
694+
if !cidSet.Has(c) {
695+
select {
696+
case <-ctx.Done():
697+
return false
698+
case out <- ipfspinner.StreamedCid{C: c}:
699+
}
700+
cidSet.Add(c)
701+
}
702+
return true
703+
})
704+
if err != nil {
705+
out <- ipfspinner.StreamedCid{Err: err}
705706
}
706-
cidSet.Add(c)
707-
return true
708-
})
709-
if err != nil {
710-
return nil, err
711-
}
712-
if e != nil {
713-
return nil, e
714-
}
707+
}()
715708

716-
return cidSet.Keys(), nil
709+
return out
717710
}
718711

719712
// InternalPins returns all cids kept pinned for the internal state of the
720713
// pinner
721-
func (p *pinner) InternalPins(ctx context.Context) ([]cid.Cid, error) {
722-
return nil, nil
714+
func (p *pinner) InternalPins(ctx context.Context) <-chan ipfspinner.StreamedCid {
715+
c := make(chan ipfspinner.StreamedCid)
716+
close(c)
717+
return c
723718
}
724719

725720
// Update updates a recursive pin from one cid to another. This is equivalent

pinning/pinner/dspinner/pin_test.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,17 @@ import (
1212
bs "github.com/ipfs/boxo/blockservice"
1313
mdag "github.com/ipfs/boxo/ipld/merkledag"
1414

15-
blockstore "github.com/ipfs/boxo/blockstore"
16-
offline "github.com/ipfs/boxo/exchange/offline"
17-
util "github.com/ipfs/boxo/util"
1815
cid "github.com/ipfs/go-cid"
1916
ds "github.com/ipfs/go-datastore"
2017
"github.com/ipfs/go-datastore/query"
2118
dssync "github.com/ipfs/go-datastore/sync"
2219
ipld "github.com/ipfs/go-ipld-format"
2320
logging "github.com/ipfs/go-log"
2421

22+
blockstore "github.com/ipfs/boxo/blockstore"
23+
offline "github.com/ipfs/boxo/exchange/offline"
24+
util "github.com/ipfs/boxo/util"
25+
2526
ipfspin "github.com/ipfs/boxo/pinning/pinner"
2627
)
2728

@@ -198,10 +199,17 @@ func TestPinnerBasic(t *testing.T) {
198199
dk := d.Cid()
199200
assertPinned(t, p, dk, "pinned node not found.")
200201

201-
cids, err := p.RecursiveKeys(ctx)
202-
if err != nil {
203-
t.Fatal(err)
202+
allCids := func(ch <-chan ipfspin.StreamedCid) (cids []cid.Cid) {
203+
for val := range ch {
204+
if val.Err != nil {
205+
t.Fatal(val.Err)
206+
}
207+
cids = append(cids, val.C)
208+
}
209+
return cids
204210
}
211+
212+
cids := allCids(p.RecursiveKeys(ctx))
205213
if len(cids) != 2 {
206214
t.Error("expected 2 recursive pins")
207215
}
@@ -243,20 +251,17 @@ func TestPinnerBasic(t *testing.T) {
243251
}
244252
}
245253

246-
cids, err = p.DirectKeys(ctx)
247-
if err != nil {
248-
t.Fatal(err)
249-
}
254+
cids = allCids(p.DirectKeys(ctx))
250255
if len(cids) != 1 {
251256
t.Error("expected 1 direct pin")
252257
}
253258
if cids[0] != ak {
254259
t.Error("wrong direct pin")
255260
}
256261

257-
cids, _ = p.InternalPins(ctx)
262+
cids = allCids(p.InternalPins(ctx))
258263
if len(cids) != 0 {
259-
t.Error("shound not have internal keys")
264+
t.Error("should not have internal keys")
260265
}
261266

262267
err = p.Unpin(ctx, dk, false)

0 commit comments

Comments
 (0)