Skip to content

Commit 56a66e3

Browse files
authored
Merge pull request #73 from snyk/feat/memoization-in-graph-to-dep-tree
feat: add memoization when converting a graph into a tree
2 parents 099f36e + 113746f commit 56a66e3

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

src/legacy/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,12 @@ async function buildSubtree(
285285
depGraph: types.DepGraphInternal,
286286
nodeId: string,
287287
eventLoopSpinner: EventLoopSpinner,
288-
maybeDeduplicationSet: Set<string> | null | false = null, // false = disabled; null = not in deduplication scope yet
288+
maybeDeduplicationSet: Set<string> | null | false = false, // false = disabled; null = not in deduplication scope yet
289+
memoizationMap: Map<string, DepTree> = new Map(),
289290
): Promise<DepTree> {
291+
if (!maybeDeduplicationSet && memoizationMap.has(nodeId)) {
292+
return memoizationMap.get(nodeId)!;
293+
}
290294
const isRoot = nodeId === depGraph.rootNodeId;
291295
const nodePkg = depGraph.getNodePkg(nodeId);
292296
const nodeInfo = depGraph.getNode(nodeId);
@@ -302,6 +306,7 @@ async function buildSubtree(
302306

303307
const depInstanceIds = depGraph.getNodeDepsNodeIds(nodeId);
304308
if (!depInstanceIds || depInstanceIds.length === 0) {
309+
memoizationMap.set(nodeId, depTree);
305310
return depTree;
306311
}
307312

@@ -326,6 +331,7 @@ async function buildSubtree(
326331
depInstId,
327332
eventLoopSpinner,
328333
maybeDeduplicationSet,
334+
memoizationMap,
329335
);
330336
if (!subtree) {
331337
continue;
@@ -341,6 +347,7 @@ async function buildSubtree(
341347
if (eventLoopSpinner.isStarving()) {
342348
await eventLoopSpinner.spin();
343349
}
350+
memoizationMap.set(nodeId, depTree);
344351
return depTree;
345352
}
346353

test/legacy/stress.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as depGraphLib from '../../src';
2+
import { graphToDepTree } from '../../src/legacy';
3+
4+
const dependencyName = 'needle';
5+
6+
async function generateLargeGraph(width: number) {
7+
const builder = new depGraphLib.DepGraphBuilder(
8+
{ name: 'npm' },
9+
{ name: 'root', version: '1.2.3' },
10+
);
11+
const rootNodeId = 'root-node';
12+
13+
const deepDependency = { name: dependencyName, version: '1.2.3' };
14+
const deepDependency2 = { name: dependencyName + 2, version: '1.2.3' };
15+
16+
builder.addPkgNode(deepDependency, dependencyName);
17+
builder.addPkgNode(deepDependency2, deepDependency2.name);
18+
builder.connectDep(rootNodeId, dependencyName);
19+
builder.connectDep(deepDependency.name, deepDependency2.name);
20+
21+
for (let j = 0; j < width / 2; j++) {
22+
const shallowName = `id-${j}`;
23+
const shallowDependency = { name: shallowName, version: '1.2.3' };
24+
25+
builder.addPkgNode(shallowDependency, shallowName);
26+
builder.connectDep(rootNodeId, shallowName);
27+
builder.connectDep(shallowName, dependencyName);
28+
}
29+
30+
for (let j = 0; j < width / 2; j++) {
31+
const shallowName = `second-${j}`;
32+
const shallowDependency = { name: shallowName, version: '1.2.3' };
33+
34+
builder.addPkgNode(shallowDependency, shallowName);
35+
builder.connectDep(deepDependency2.name, shallowName);
36+
}
37+
38+
return builder.build();
39+
}
40+
41+
describe('stress tests', () => {
42+
test('graphToDepTree() with memoization (without deduplicateWithinTopLevelDeps) succeed for large dep-graphs', async () => {
43+
const graph = await generateLargeGraph(125000);
44+
45+
const depTree = await graphToDepTree(graph, 'gomodules', {
46+
deduplicateWithinTopLevelDeps: false,
47+
});
48+
expect(depTree).toBeDefined();
49+
});
50+
});

0 commit comments

Comments
 (0)