Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit f14c20d

Browse files
author
Alan Shaw
authored
fix: over eager preload (#1693)
TLDR; Preload was sending preload requests for EVERY dag node of an added file. This is unnecessary as the preload request will recursively slurp down the entire graph. This PR fixes that behaviour. --- Adding file(s) causes the preload module to preload any **root** nodes for the added content. It sends a HTTP request to preload for each root CID because the API on the preload nodes will fetch any children automatically. This greatly reduces the number of HTTP requests we have make when adding large files that are chunked into multiple nodes. However, the issue was that the tests were checking that a CID had been requested for preload, not that it had been requested only once. I was inspecting the debug output for preload because of the recent [CORS issues we've been having](ipfs/infra#447) and noticed that multiple preload requests for the same CID were being sent. Worse still, when adding large files, the child nodes were also being requested 😱 The issues are: 1. `ipfs.add` causes [`object.get`](https://github.com/ipfs/js-ipfs/blob/99fb64e72c1752b3c227537dc6419d8bb837cd5f/src/core/components/files.js#L39) to be called for every file added. The issue with that is that `object.get` will also attempt to preload the CID you pass it. 2. `ipfs.add` causes `pin.add` to be called for each root CID. This in turn causes `dag.get` to be called for _every_ node in the graph. The issue with that is that `dag.get` will also attempt to preload the CID you pass it. The solution in both cases is to tell `object.get` and `pin.add` in the context of `ipfs.add` to not preload the CIDs that they are passed. I've augmented the tests to ensure that only one of the required CIDs is requested from the preload nodes and with that change and the fixes to the code the tests are now passing. License: MIT Signed-off-by: Alan Shaw <[email protected]>
1 parent 99fb64e commit f14c20d

File tree

6 files changed

+41
-16
lines changed

6 files changed

+41
-16
lines changed

src/core/components/dag.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,14 @@ module.exports = function dag (self) {
5050

5151
if (typeof options === 'function') {
5252
callback = options
53-
options = {}
53+
54+
// Allow options in path position
55+
if (typeof path !== 'string') {
56+
options = path
57+
path = null
58+
} else {
59+
options = {}
60+
}
5461
}
5562

5663
options = options || {}
@@ -156,7 +163,7 @@ module.exports = function dag (self) {
156163
if (err) { return callback(err) }
157164

158165
mapAsync(res.value.links, (link, cb) => {
159-
self.dag._getRecursive(link.multihash, cb)
166+
self.dag._getRecursive(link.multihash, options, cb)
160167
}, (err, nodes) => {
161168
// console.log('nodes:', nodes)
162169
if (err) return callback(err)

src/core/components/files.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ function prepareFile (self, opts, file, callback) {
3636
waterfall([
3737
(cb) => opts.onlyHash
3838
? cb(null, file)
39-
: self.object.get(file.multihash, opts, cb),
39+
: self.object.get(file.multihash, Object.assign({}, opts, { preload: false }), cb),
4040
(node, cb) => {
4141
const b58Hash = cid.toBaseEncodedString()
4242

@@ -118,7 +118,7 @@ function pinFile (self, opts, file, cb) {
118118
const isRootDir = !file.path.includes('/')
119119
const shouldPin = pin && isRootDir && !opts.onlyHash && !opts.hashAlg
120120
if (shouldPin) {
121-
return self.pin.add(file.hash, err => cb(err, file))
121+
return self.pin.add(file.hash, { preload: false }, err => cb(err, file))
122122
} else {
123123
cb(null, file)
124124
}

src/core/components/object.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,7 @@ module.exports = function object (self) {
206206
return callback(err)
207207
}
208208

209-
if (options.preload !== false) {
210-
self._preload(cid)
211-
}
212-
213-
self.object.get(node.multihash, callback)
209+
self.object.get(node.multihash, { preload: options.preload }, callback)
214210
})
215211
}
216212
}),

src/core/components/pin.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,11 @@ module.exports = (self) => {
119119
add: promisify((paths, options, callback) => {
120120
if (typeof options === 'function') {
121121
callback = options
122-
options = null
122+
options = {}
123123
}
124-
const recursive = options ? options.recursive : true
124+
options = options || {}
125+
126+
const recursive = options.recursive == null ? true : options.recursive
125127

126128
resolvePath(self.object, paths, (err, mhs) => {
127129
if (err) { return callback(err) }
@@ -137,7 +139,7 @@ module.exports = (self) => {
137139

138140
// entire graph of nested links should be pinned,
139141
// so make sure we have all the objects
140-
dag._getRecursive(key, (err) => {
142+
dag._getRecursive(key, { preload: options.preload }, (err) => {
141143
if (err) { return cb(err) }
142144
// found all objects, we can add the pin
143145
return cb(null, key)
@@ -153,7 +155,7 @@ module.exports = (self) => {
153155
}
154156

155157
// make sure we have the object
156-
dag.get(new CID(multihash), (err) => {
158+
dag.get(new CID(multihash), { preload: options.preload }, (err) => {
157159
if (err) { return cb(err) }
158160
// found the object, we can add the pin
159161
return cb(null, key)

test/core/preload.spec.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,14 @@ describe('preload', () => {
141141
const wrappingDir = res.find(file => file.path === '')
142142
expect(wrappingDir).to.exist()
143143

144-
ipfs.ls(wrappingDir.hash, (err) => {
144+
// Adding these files with have preloaded wrappingDir.hash, clear it out
145+
MockPreloadNode.clearPreloadCids((err) => {
145146
expect(err).to.not.exist()
146-
MockPreloadNode.waitForCids(wrappingDir.hash, done)
147+
148+
ipfs.ls(wrappingDir.hash, (err) => {
149+
expect(err).to.not.exist()
150+
MockPreloadNode.waitForCids(wrappingDir.hash, done)
151+
})
147152
})
148153
})
149154
})

test/utils/mock-preload-node.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,22 @@ module.exports.waitForCids = (cids, opts, cb) => {
142142
getPreloadCids(opts.addr, (err, preloadCids) => {
143143
if (err) return cb(err)
144144

145-
if (cids.every(cid => preloadCids.includes(cid))) {
145+
// See if our cached preloadCids includes all the cids we're looking for.
146+
const { missing, duplicates } = cids.reduce((results, cid) => {
147+
const count = preloadCids.filter(preloadedCid => preloadedCid === cid).length
148+
if (count === 0) {
149+
results.missing.push(cid)
150+
} else if (count > 1) {
151+
results.duplicates.push(cid)
152+
}
153+
return results
154+
}, { missing: [], duplicates: [] })
155+
156+
if (duplicates.length) {
157+
return cb(errCode(new Error(`Multiple occurances of ${duplicates} found`), 'ERR_DUPLICATE'))
158+
}
159+
160+
if (missing.length === 0) {
146161
return cb()
147162
}
148163

0 commit comments

Comments
 (0)