master
go 333 lines 8.58 KB
Raw
1 // Package gc provides garbage collection for go-ipfs.
2 package gc
3
4 import (
5 "context"
6 "errors"
7 "fmt"
8 "strings"
9
10 bserv "github.com/ipfs/boxo/blockservice"
11 bstore "github.com/ipfs/boxo/blockstore"
12 offline "github.com/ipfs/boxo/exchange/offline"
13 dag "github.com/ipfs/boxo/ipld/merkledag"
14 pin "github.com/ipfs/boxo/pinning/pinner"
15 "github.com/ipfs/boxo/verifcid"
16 cid "github.com/ipfs/go-cid"
17 dstore "github.com/ipfs/go-datastore"
18 ipld "github.com/ipfs/go-ipld-format"
19 logging "github.com/ipfs/go-log/v2"
20 )
21
22 var log = logging.Logger("gc")
23
24 // Result represents an incremental output from a garbage collection
25 // run. It contains either an error, or the cid of a removed object.
26 type Result struct {
27 KeyRemoved cid.Cid
28 Error error
29 }
30
31 // converts a set of CIDs with different codecs to a set of CIDs with the raw codec.
32 func toRawCids(set *cid.Set) (*cid.Set, error) {
33 newSet := cid.NewSet()
34 err := set.ForEach(func(c cid.Cid) error {
35 newSet.Add(cid.NewCidV1(cid.Raw, c.Hash()))
36 return nil
37 })
38 return newSet, err
39 }
40
41 // GC performs a mark and sweep garbage collection of the blocks in the blockstore
42 // first, it creates a 'marked' set and adds to it the following:
43 // - all recursively pinned blocks, plus all of their descendants (recursively)
44 // - bestEffortRoots, plus all of its descendants (recursively)
45 // - all directly pinned blocks
46 // - all blocks utilized internally by the pinner
47 //
48 // The routine then iterates over every block in the blockstore and
49 // deletes any block that is not found in the marked set.
50 func GC(ctx context.Context, bs bstore.GCBlockstore, dstor dstore.Datastore, pn pin.Pinner, bestEffortRoots []cid.Cid) <-chan Result {
51 ctx, cancel := context.WithCancel(ctx)
52
53 unlocker := bs.GCLock(ctx)
54
55 bsrv := bserv.New(bs, offline.Exchange(bs))
56 ds := dag.NewDAGService(bsrv)
57
58 output := make(chan Result, 128)
59
60 go func() {
61 defer cancel()
62 defer close(output)
63 defer unlocker.Unlock(ctx)
64
65 gcs, err := ColoredSet(ctx, pn, ds, bestEffortRoots, output)
66 if err != nil {
67 select {
68 case output <- Result{Error: err}:
69 case <-ctx.Done():
70 }
71 return
72 }
73
74 // The blockstore reports raw blocks. We need to remove the codecs from the CIDs.
75 gcs, err = toRawCids(gcs)
76 if err != nil {
77 select {
78 case output <- Result{Error: err}:
79 case <-ctx.Done():
80 }
81 return
82 }
83
84 keychain, err := bs.AllKeysChan(ctx)
85 if err != nil {
86 select {
87 case output <- Result{Error: err}:
88 case <-ctx.Done():
89 }
90 return
91 }
92
93 errors := false
94 var removed uint64
95
96 loop:
97 for ctx.Err() == nil { // select may not notice that we're "done".
98 select {
99 case k, ok := <-keychain:
100 if !ok {
101 break loop
102 }
103 // NOTE: assumes that all CIDs returned by the keychain are _raw_ CIDv1 CIDs.
104 // This means we keep the block as long as we want it somewhere (CIDv1, CIDv0, Raw, other...).
105 if !gcs.Has(k) {
106 err := bs.DeleteBlock(ctx, k)
107 removed++
108 if err != nil {
109 errors = true
110 select {
111 case output <- Result{Error: &CannotDeleteBlockError{k, err}}:
112 case <-ctx.Done():
113 break loop
114 }
115 // continue as error is non-fatal
116 continue loop
117 }
118 select {
119 case output <- Result{KeyRemoved: k}:
120 case <-ctx.Done():
121 break loop
122 }
123 }
124 case <-ctx.Done():
125 break loop
126 }
127 }
128 if errors {
129 select {
130 case output <- Result{Error: ErrCannotDeleteSomeBlocks}:
131 case <-ctx.Done():
132 return
133 }
134 }
135
136 gds, ok := dstor.(dstore.GCDatastore)
137 if !ok {
138 return
139 }
140
141 err = gds.CollectGarbage(ctx)
142 if err != nil {
143 select {
144 case output <- Result{Error: err}:
145 case <-ctx.Done():
146 }
147 return
148 }
149 }()
150
151 return output
152 }
153
154 // Descendants recursively finds all the descendants of the given roots and
155 // adds them to the given cid.Set, using the provided dag.GetLinks function
156 // to walk the tree.
157 func Descendants(ctx context.Context, getLinks dag.GetLinks, set *cid.Set, roots <-chan pin.StreamedPin) error {
158 verifyGetLinks := func(ctx context.Context, c cid.Cid) ([]*ipld.Link, error) {
159 err := verifcid.ValidateCid(verifcid.DefaultAllowlist, c)
160 if err != nil {
161 return nil, err
162 }
163
164 return getLinks(ctx, c)
165 }
166
167 verboseCidError := func(err error) error {
168 if strings.Contains(err.Error(), verifcid.ErrDigestTooSmall.Error()) ||
169 strings.Contains(err.Error(), verifcid.ErrPossiblyInsecureHashFunction.Error()) {
170 err = fmt.Errorf("\"%s\"\nPlease run 'ipfs pin verify'"+ // nolint
171 " to list insecure hashes. If you want to read them,"+
172 " please downgrade your go-ipfs to 0.4.13\n", err)
173 log.Error(err)
174 }
175 return err
176 }
177
178 for {
179 select {
180 case <-ctx.Done():
181 return ctx.Err()
182 case wrapper, ok := <-roots:
183 if !ok {
184 return nil
185 }
186 if wrapper.Err != nil {
187 return wrapper.Err
188 }
189
190 // Walk recursively walks the dag and adds the keys to the given set
191 err := dag.Walk(ctx, verifyGetLinks, wrapper.Pin.Key, func(k cid.Cid) bool {
192 return set.Visit(toCidV1(k))
193 }, dag.Concurrent())
194 if err != nil {
195 err = verboseCidError(err)
196 return err
197 }
198 }
199 }
200 }
201
202 // toCidV1 converts any CIDv0s to CIDv1s.
203 func toCidV1(c cid.Cid) cid.Cid {
204 if c.Version() == 0 {
205 return cid.NewCidV1(c.Type(), c.Hash())
206 }
207 return c
208 }
209
210 // ColoredSet computes the set of nodes in the graph that are pinned by the
211 // pins in the given pinner.
212 func ColoredSet(ctx context.Context, pn pin.Pinner, ng ipld.NodeGetter, bestEffortRoots []cid.Cid, output chan<- Result) (*cid.Set, error) {
213 // KeySet currently implemented in memory, in the future, may be bloom filter or
214 // disk backed to conserve memory.
215 errors := false
216 gcs := cid.NewSet()
217 getLinks := func(ctx context.Context, cid cid.Cid) ([]*ipld.Link, error) {
218 links, err := ipld.GetLinks(ctx, ng, cid)
219 if err != nil {
220 errors = true
221 select {
222 case output <- Result{Error: &CannotFetchLinksError{cid, err}}:
223 case <-ctx.Done():
224 return nil, ctx.Err()
225 }
226 }
227 return links, nil
228 }
229 rkeys := pn.RecursiveKeys(ctx, false)
230 err := Descendants(ctx, getLinks, gcs, rkeys)
231 if err != nil {
232 errors = true
233 select {
234 case output <- Result{Error: err}:
235 case <-ctx.Done():
236 return nil, ctx.Err()
237 }
238 }
239
240 bestEffortGetLinks := func(ctx context.Context, cid cid.Cid) ([]*ipld.Link, error) {
241 links, err := ipld.GetLinks(ctx, ng, cid)
242 if err != nil && !ipld.IsNotFound(err) {
243 errors = true
244 select {
245 case output <- Result{Error: &CannotFetchLinksError{cid, err}}:
246 case <-ctx.Done():
247 return nil, ctx.Err()
248 }
249 }
250 return links, nil
251 }
252 bestEffortRootsChan := make(chan pin.StreamedPin)
253 go func() {
254 defer close(bestEffortRootsChan)
255 for _, root := range bestEffortRoots {
256 select {
257 case <-ctx.Done():
258 return
259 case bestEffortRootsChan <- pin.StreamedPin{Pin: pin.Pinned{Key: root}}:
260 }
261 }
262 }()
263 err = Descendants(ctx, bestEffortGetLinks, gcs, bestEffortRootsChan)
264 if err != nil {
265 errors = true
266 select {
267 case output <- Result{Error: err}:
268 case <-ctx.Done():
269 return nil, ctx.Err()
270 }
271 }
272
273 dkeys := pn.DirectKeys(ctx, false)
274 for k := range dkeys {
275 if k.Err != nil {
276 return nil, k.Err
277 }
278 gcs.Add(toCidV1(k.Pin.Key))
279 }
280
281 ikeys := pn.InternalPins(ctx, false)
282 err = Descendants(ctx, getLinks, gcs, ikeys)
283 if err != nil {
284 errors = true
285 select {
286 case output <- Result{Error: err}:
287 case <-ctx.Done():
288 return nil, ctx.Err()
289 }
290 }
291
292 if errors {
293 return nil, ErrCannotFetchAllLinks
294 }
295
296 return gcs, nil
297 }
298
299 // ErrCannotFetchAllLinks is returned as the last Result in the GC output
300 // channel when there was an error creating the marked set because of a
301 // problem when finding descendants.
302 var ErrCannotFetchAllLinks = errors.New("garbage collection aborted: could not retrieve some links")
303
304 // ErrCannotDeleteSomeBlocks is returned when removing blocks marked for
305 // deletion fails as the last Result in GC output channel.
306 var ErrCannotDeleteSomeBlocks = errors.New("garbage collection incomplete: could not delete some blocks")
307
308 // CannotFetchLinksError provides detailed information about which links
309 // could not be fetched and can appear as a Result in the GC output channel.
310 type CannotFetchLinksError struct {
311 Key cid.Cid
312 Err error
313 }
314
315 // Error implements the error interface for this type with a useful
316 // message.
317 func (e *CannotFetchLinksError) Error() string {
318 return fmt.Sprintf("could not retrieve links for %s: %s", e.Key, e.Err)
319 }
320
321 // CannotDeleteBlockError provides detailed information about which
322 // blocks could not be deleted and can appear as a Result in the GC output
323 // channel.
324 type CannotDeleteBlockError struct {
325 Key cid.Cid
326 Err error
327 }
328
329 // Error implements the error interface for this type with a
330 // useful message.
331 func (e *CannotDeleteBlockError) Error() string {
332 return fmt.Sprintf("could not remove %s: %s", e.Key, e.Err)
333 }