Skip to content

Commit 62494bf

Browse files
authored
fix: prevent OOM on very deep DAGs (#253)
Refactor exporting files to use a queue instead of recursion to walk the DAG. The queue exectutes on a new stack so we won't run out of memory when traversing very deep DAGs.
1 parent ba851f6 commit 62494bf

File tree

2 files changed

+24
-11
lines changed

2 files changed

+24
-11
lines changed

packages/ipfs-unixfs-exporter/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@
163163
"it-pipe": "^2.0.4",
164164
"it-pushable": "^3.1.0",
165165
"it-map": "^1.0.6",
166+
"p-queue": "^7.3.0",
166167
"multiformats": "^9.4.2",
167168
"uint8arrays": "^3.0.0"
168169
},

packages/ipfs-unixfs-exporter/src/resolvers/unixfs-v1/content/file.js

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { pushable } from 'it-pushable'
88
import parallel from 'it-parallel'
99
import { pipe } from 'it-pipe'
1010
import map from 'it-map'
11+
import PQueue from 'p-queue'
1112

1213
/**
1314
* @typedef {import('../../../types').ExporterOptions} ExporterOptions
@@ -19,14 +20,15 @@ import map from 'it-map'
1920
/**
2021
* @param {Blockstore} blockstore
2122
* @param {PBNode | Uint8Array} node
22-
* @param {import('it-pushable').Pushable<Uint8Array | undefined>} queue
23+
* @param {import('it-pushable').Pushable<Uint8Array>} queue
2324
* @param {number} streamPosition
2425
* @param {number} start
2526
* @param {number} end
27+
* @param {PQueue} walkQueue
2628
* @param {ExporterOptions} options
2729
* @returns {Promise<void>}
2830
*/
29-
async function walkDAG (blockstore, node, queue, streamPosition, start, end, options) {
31+
async function walkDAG (blockstore, node, queue, streamPosition, start, end, walkQueue, options) {
3032
// a `raw` node
3133
if (node instanceof Uint8Array) {
3234
queue.push(extractDataFromBlock(node, streamPosition, start, end))
@@ -100,19 +102,23 @@ async function walkDAG (blockstore, node, queue, streamPosition, start, end, opt
100102
}),
101103
async (source) => {
102104
for await (const { link, block, blockStart } of source) {
105+
/** @type {PBNode | Uint8Array} */
103106
let child
104107
switch (link.Hash.code) {
105108
case dagPb.code:
106-
child = await dagPb.decode(block)
109+
child = dagPb.decode(block)
107110
break
108111
case raw.code:
109112
child = block
110113
break
111114
default:
112-
throw errCode(new Error(`Unsupported codec: ${link.Hash.code}`), 'ERR_NOT_UNIXFS')
115+
queue.end(errCode(new Error(`Unsupported codec: ${link.Hash.code}`), 'ERR_NOT_UNIXFS'))
116+
return
113117
}
114118

115-
await walkDAG(blockstore, child, queue, blockStart, start, end, options)
119+
walkQueue.add(async () => {
120+
await walkDAG(blockstore, child, queue, blockStart, start, end, walkQueue, options)
121+
})
116122
}
117123
}
118124
)
@@ -141,14 +147,20 @@ const fileContent = (cid, node, unixfs, path, resolve, depth, blockstore) => {
141147
return
142148
}
143149

144-
const queue = pushable({
145-
objectMode: true
150+
// use a queue to walk the DAG instead of recursion to ensure very deep DAGs
151+
// don't overflow the stack
152+
const walkQueue = new PQueue({
153+
concurrency: 1
146154
})
155+
const queue = pushable()
147156

148-
walkDAG(blockstore, node, queue, 0, offset, offset + length, options)
149-
.catch(err => {
150-
queue.end(err)
151-
})
157+
walkQueue.add(async () => {
158+
await walkDAG(blockstore, node, queue, 0, offset, offset + length, walkQueue, options)
159+
})
160+
161+
walkQueue.on('error', error => {
162+
queue.end(error)
163+
})
152164

153165
let read = 0
154166

0 commit comments

Comments
 (0)