Skip to content

Commit 4e01be7

Browse files
Initial setup for concurrent workers (#46)
* Initial setup for concurrent workers * Add logs for timer * Prettier lint * Fix worker script issues and await in api * Lint * Fix worker in dev mode * Remove unneeded awaits * Lint and format * Ignore worker.js file in eslint * remove class from worker for readability --------- Co-authored-by: florianbgt <[email protected]>
1 parent 0cc7191 commit 4e01be7

File tree

9 files changed

+163
-97
lines changed

9 files changed

+163
-97
lines changed

eslint.config.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// @ts-check
22

33
import eslint from "@eslint/js";
4-
import tseslint from "typescript-eslint";
54
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
5+
import tseslint from "typescript-eslint";
66

77
export default tseslint.config(
88
eslint.configs.recommended,
@@ -16,6 +16,7 @@ export default tseslint.config(
1616
"examples/**",
1717
"packages/app/dist",
1818
"packages/cli/dist",
19+
"packages/cli/src/**/worker.js",
1920
],
2021
},
2122
);

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/src/api/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { json, Router } from "express";
2+
import { z } from "zod";
3+
import { napiConfigSchema } from "../config";
4+
import { scanSchema, splitSchema, syncSchema } from "./helpers/validation";
25
import { scan } from "./scan";
36
import { split } from "./split";
47
import { sync } from "./sync";
5-
import { scanSchema, splitSchema, syncSchema } from "./helpers/validation";
6-
import { napiConfigSchema } from "../config";
7-
import { z } from "zod";
88

99
export function getApi(napiConfig: z.infer<typeof napiConfigSchema>) {
1010
const api = Router();
@@ -42,13 +42,13 @@ export function getApi(napiConfig: z.infer<typeof napiConfigSchema>) {
4242
res.status(200).json({ success: true });
4343
});
4444

45-
api.post("/api/split", (req, res) => {
45+
api.post("/api/split", async (req, res) => {
4646
const result = splitSchema.safeParse(req.body);
4747
if (!result.success) {
4848
res.status(400).json(result.error.issues);
4949
return;
5050
}
51-
const splitResult = split(result.data);
51+
const splitResult = await split(result.data);
5252
res.status(200).json(splitResult);
5353
});
5454

packages/cli/src/api/split.ts

Lines changed: 16 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
import fs from "fs";
21
import path from "path";
2+
import { z } from "zod";
33
import DependencyTreeManager from "../dependencyManager/dependencyManager";
44
import { cleanupOutputDir, createOutputDir } from "../helper/file";
5-
import SplitRunner from "../splitRunner/splitRunner";
5+
import { runWithWorker, writeSplitsToDisk } from "../splitRunner/splitRunner";
66
import { splitSchema } from "./helpers/validation";
7-
import { z } from "zod";
8-
import { Group } from "../dependencyManager/types";
97

10-
export function split(payload: z.infer<typeof splitSchema>) {
8+
export async function split(payload: z.infer<typeof splitSchema>) {
119
console.time("split command");
12-
const groupMap: Record<number, Group> = {};
1310

1411
// Get the dependency tree
1512
const dependencyTreeManager = new DependencyTreeManager(
@@ -26,30 +23,19 @@ export function split(payload: z.infer<typeof splitSchema>) {
2623
const groups = dependencyTreeManager.getGroups();
2724

2825
// Process each group for splitting
29-
groups.forEach((group, index) => {
30-
const splitRunner = new SplitRunner(dependencyTreeManager, group);
31-
const files = splitRunner.run();
32-
33-
const targetDir = path.dirname(payload.entrypointPath);
34-
const annotationDirectory = path.join(outputDir, index.toString());
35-
36-
files.forEach((file) => {
37-
const relativeFileNamePath = path.relative(targetDir, file.path);
38-
const destinationPath = path.join(
39-
annotationDirectory,
40-
relativeFileNamePath,
41-
);
42-
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
43-
fs.writeFileSync(destinationPath, file.sourceCode, "utf8");
44-
});
45-
});
46-
47-
// Store the processed annotations in the output directory
48-
groups.forEach((group, index) => {
49-
groupMap[index] = group;
50-
});
51-
const annotationFilePath = path.join(outputDir, "annotations.json");
52-
fs.writeFileSync(annotationFilePath, JSON.stringify(groupMap, null, 2));
26+
const splits = groups.map((group, index) =>
27+
runWithWorker(
28+
index,
29+
group,
30+
dependencyTreeManager.entryPointPath,
31+
dependencyTreeManager.getFiles(),
32+
),
33+
);
34+
35+
// Wait for all splits to be processed
36+
const processedSplits = await Promise.all(splits.map(async (split) => split));
37+
38+
writeSplitsToDisk(outputDir, payload.entrypointPath, processedSplits);
5339

5440
console.timeEnd("split command");
5541
return { groups, success: true };

packages/cli/src/commands/split.ts

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
import path from "path";
2-
import fs from "fs";
31
import DependencyTreeManager from "../dependencyManager/dependencyManager";
42
import { cleanupOutputDir, createOutputDir } from "../helper/file";
5-
import SplitRunner from "../splitRunner/splitRunner";
6-
import { Group } from "../dependencyManager/types";
3+
import { runWithWorker, writeSplitsToDisk } from "../splitRunner/splitRunner";
74

8-
export default function splitCommandHandler(
5+
export default async function splitCommandHandler(
96
entrypointPath: string, // Path to the entrypoint file
107
outputDir: string, // Path to the output directory
118
) {
12-
const groupMap: Record<number, Group> = {};
13-
9+
console.time("split command");
1410
const dependencyTreeManager = new DependencyTreeManager(entrypointPath);
1511

1612
cleanupOutputDir(outputDir);
@@ -20,28 +16,19 @@ export default function splitCommandHandler(
2016
const groups = dependencyTreeManager.getGroups();
2117

2218
// Process each group for splitting
23-
groups.forEach((group, index) => {
24-
const splitRunner = new SplitRunner(dependencyTreeManager, group);
25-
const files = splitRunner.run();
19+
const splits = groups.map((group, index) =>
20+
runWithWorker(
21+
index,
22+
group,
23+
dependencyTreeManager.entryPointPath,
24+
dependencyTreeManager.getFiles(),
25+
),
26+
);
2627

27-
const targetDir = path.dirname(entrypointPath);
28-
const annotationDirectory = path.join(outputDir, index.toString());
28+
// Wait for all splits to be processed
29+
const processedSplits = await Promise.all(splits.map(async (split) => split));
2930

30-
files.forEach((file) => {
31-
const relativeFileNamePath = path.relative(targetDir, file.path);
32-
const destinationPath = path.join(
33-
annotationDirectory,
34-
relativeFileNamePath,
35-
);
36-
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
37-
fs.writeFileSync(destinationPath, file.sourceCode, "utf8");
38-
});
39-
});
31+
writeSplitsToDisk(outputDir, entrypointPath, processedSplits);
4032

41-
// Store the processed annotations in the output directory
42-
groups.forEach((group, index) => {
43-
groupMap[index] = group;
44-
});
45-
const annotationFilePath = path.join(outputDir, "annotations.json");
46-
fs.writeFileSync(annotationFilePath, JSON.stringify(groupMap, null, 2));
33+
console.timeEnd("split command");
4734
}

packages/cli/src/languagesPlugins/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Group } from "../dependencyManager/types";
21
import Parser from "tree-sitter";
2+
import { Group } from "../dependencyManager/types";
33

44
export interface DepImportIdentifier {
55
// Specific to each programing languages. Used by the language plugins.

packages/cli/src/splitRunner/splitRunner.ts

Lines changed: 97 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
1+
import path from "path";
2+
import { Worker } from "worker_threads";
13
import { Group } from "../dependencyManager/types";
2-
import { removeIndexesFromSourceCode } from "../helper/file";
3-
import DependencyTreeManager from "../dependencyManager/dependencyManager";
44
import { File } from "./types";
5-
import Parser from "tree-sitter";
6-
import assert from "assert";
75
import { getLanguagePlugin } from "../languagesPlugins";
86
import { DepExport } from "../languagesPlugins/types";
7+
import { removeIndexesFromSourceCode } from "../helper/file";
8+
import assert from "assert";
9+
import Parser from "tree-sitter";
10+
import fs from "fs";
911

10-
class SplitRunner {
11-
private dependencyTreeManager: DependencyTreeManager;
12-
private entrypointPath: string;
12+
export class SplitRunner {
13+
private index: number;
1314
private group: Group;
15+
private entrypointPath: string;
1416
private files: File[];
1517

16-
constructor(dependencyTreeManager: DependencyTreeManager, group: Group) {
17-
this.dependencyTreeManager = dependencyTreeManager;
18-
this.entrypointPath = dependencyTreeManager.dependencyTree.path;
18+
constructor(
19+
index: number,
20+
group: Group,
21+
entrypointPath: string,
22+
files: File[],
23+
) {
24+
this.index = index;
25+
this.entrypointPath = entrypointPath;
1926
this.group = group;
20-
this.files = dependencyTreeManager.getFiles();
27+
this.files = files;
2128
}
2229

2330
#removeAnnotationFromOtherGroups() {
@@ -83,7 +90,7 @@ class SplitRunner {
8390
// We always want to keep the entrypoint file.
8491
// It will never be imported anywhere, so we add it now.
8592
const filesToKeep = new Set<string>();
86-
filesToKeep.add(this.dependencyTreeManager.dependencyTree.path);
93+
filesToKeep.add(this.entrypointPath);
8794

8895
this.files.forEach((file) => {
8996
const languagePlugin = getLanguagePlugin(
@@ -191,41 +198,100 @@ class SplitRunner {
191198
}
192199

193200
run() {
194-
console.info("\n");
195-
console.time("Splitting");
201+
console.time(`Splitting-${this.index}`);
196202

197-
console.time("remove annotation from other groups");
203+
console.time(`remove annotation from other groups-${this.index}`);
198204
this.#removeAnnotationFromOtherGroups();
199-
console.timeEnd("remove annotation from other groups");
205+
console.timeEnd(`remove annotation from other groups-${this.index}`);
200206

201-
console.time("Get export map");
207+
console.time(`Get export map-${this.index}`);
202208
const exportMap = this.#getExportMap();
203-
console.timeEnd("Get export map");
209+
console.timeEnd(`Get export map-${this.index}`);
204210

205-
console.time("Remove invalid imports and usages");
211+
console.time(`Remove invalid imports and usages-${this.index}`);
206212
this.#removeInvalidImportsAndUsages(exportMap);
207-
console.timeEnd("Remove invalid imports and usages");
213+
console.timeEnd(`Remove invalid imports and usages-${this.index}`);
208214

209-
console.time("Remove unused imports");
215+
console.time(`Remove unused imports-${this.index}`);
210216
this.#removeUnusedImports();
211-
console.timeEnd("Remove unused imports");
217+
console.timeEnd(`Remove unused imports-${this.index}`);
212218

213-
console.time("Remove unused files");
219+
console.time(`Remove unused files-${this.index}`);
214220
this.#removeUnusedFiles();
215-
console.timeEnd("Remove unused files");
221+
console.timeEnd(`Remove unused files-${this.index}`);
216222

217-
console.time("Remove unused exports");
223+
console.time(`Remove unused exports-${this.index}`);
218224
this.#removeUnusedExports(exportMap);
219-
console.timeEnd("Remove unused exports");
225+
console.timeEnd(`Remove unused exports-${this.index}`);
220226

221-
console.time("Remove errors");
227+
console.time(`Remove errors-${this.index}`);
222228
this.#removeErrors();
223-
console.timeEnd("Remove errors");
229+
console.timeEnd(`Remove errors-${this.index}`);
224230

225-
console.timeEnd("Splitting");
231+
console.timeEnd(`Splitting-${this.index}`);
226232

227-
return this.files;
233+
return { index: this.index, group: this.group, files: this.files };
228234
}
229235
}
230236

231-
export default SplitRunner;
237+
export function runWithWorker(
238+
index: number,
239+
group: Group,
240+
entryPointPath: string,
241+
files: File[],
242+
) {
243+
const worker = new Worker(path.resolve(__dirname, "worker"), {
244+
workerData: {
245+
index,
246+
group,
247+
entryPointPath,
248+
files,
249+
},
250+
});
251+
252+
return new Promise<{ index: number; group: Group; files: File[] }>(
253+
(resolve, reject) => {
254+
worker.on(
255+
"message",
256+
(split: { index: number; group: Group; files: File[] }) => {
257+
resolve(split);
258+
},
259+
);
260+
261+
worker.on("error", reject);
262+
worker.on("exit", (code) => {
263+
if (code !== 0) {
264+
reject(new Error(`Worker stopped with exit code ${code}`));
265+
}
266+
});
267+
},
268+
);
269+
}
270+
271+
export function writeSplitsToDisk(
272+
outputDir: string,
273+
entrypointPath: string,
274+
splits: { index: number; group: Group; files: File[] }[],
275+
) {
276+
const targetDir = path.dirname(entrypointPath);
277+
const groupMap: Record<number, Group> = {};
278+
279+
splits.forEach((split) => {
280+
const annotationDirectory = path.join(outputDir, split.index.toString());
281+
282+
split.files.forEach((file) => {
283+
const relativeFileNamePath = path.relative(targetDir, file.path);
284+
const destinationPath = path.join(
285+
annotationDirectory,
286+
relativeFileNamePath,
287+
);
288+
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
289+
fs.writeFileSync(destinationPath, file.sourceCode, "utf8");
290+
});
291+
292+
groupMap[split.index] = split.group;
293+
});
294+
295+
const annotationFilePath = path.join(outputDir, "annotations.json");
296+
fs.writeFileSync(annotationFilePath, JSON.stringify(groupMap, null, 2));
297+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const path = require("path");
2+
require("ts-node").register();
3+
require(path.resolve(__dirname, "worker.ts"));
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { parentPort, workerData } from "worker_threads";
2+
import { SplitRunner } from "./splitRunner";
3+
import { Group } from "../dependencyManager/types";
4+
import { File } from "./types";
5+
6+
const {
7+
index,
8+
group,
9+
entryPointPath,
10+
files,
11+
}: {
12+
index: number;
13+
group: Group;
14+
entryPointPath: string;
15+
files: File[];
16+
} = workerData;
17+
18+
(() => {
19+
const splitRunner = new SplitRunner(index, group, entryPointPath, files);
20+
const updatedFiled = splitRunner.run();
21+
// Send updated files back to the parent
22+
parentPort?.postMessage(updatedFiled);
23+
})();

0 commit comments

Comments
 (0)