Skip to content

Commit fc88a2e

Browse files
committed
Write files to disk atomically so they are never empty
This fixes a bug where if multiple parallel worker threads in a wds project start around the same time, one will trigger a build of the whole project and another will soon after trigger a build of just one file. Both builds are processed in parallel for speed, but if the timing is just right, both try to write to the same destination file around the same time. If one of the project threads is unlucky enough to read the file at around when it is being written, it can read an empty or half written file. Whoops! `write-file-atomic` solves this problem by using a tmpfile and serializing writes made from within the same process. COMPUTERS [no-changelog-required]
1 parent e5bee2a commit fc88a2e

File tree

5 files changed

+45
-17
lines changed

5 files changed

+45
-17
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"globby": "^11.1.0",
4646
"lodash": "^4.17.20",
4747
"pkg-dir": "^5.0.0",
48+
"write-file-atomic": "^6.0.0",
4849
"yargs": "^16.2.0"
4950
},
5051
"devDependencies": {
@@ -55,6 +56,7 @@
5556
"@types/jest": "^27.4.0",
5657
"@types/lodash": "^4.14.194",
5758
"@types/node": "^18.11.9",
59+
"@types/write-file-atomic": "^4.0.3",
5860
"@types/yargs": "^15.0.14",
5961
"eslint": "^8.40.0",
6062
"eslint-plugin-jest": "^27.2.1",

pnpm-lock.yaml

Lines changed: 36 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/SwcCompiler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Config, Options } from "@swc/core";
2+
import writeFileAtomic from "write-file-atomic";
23
import { transformFile } from "@swc/core";
34
import findRoot from "find-root";
45
import * as fs from "fs/promises";
@@ -163,7 +164,7 @@ export class SwcCompiler implements Compiler {
163164

164165
const destination = path.join(this.outDir, filename).replace(this.workspaceRoot, "");
165166
await fs.mkdir(path.dirname(destination), { recursive: true });
166-
await fs.writeFile(destination, output.code);
167+
await writeFileAtomic(destination, output.code);
167168
const file = { filename, root, destination, config };
168169

169170
this.compiledFiles.addFile(file);

src/SyncWorker.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// have to use a module import like this so we can re-access imported properties as they might change, see https://github.com/nodejs/node/issues/36531
22
import { promises as fs } from "fs";
33
import type { MessagePort } from "worker_threads";
4-
import workerThreads, { MessageChannel, receiveMessageOnPort, Worker } from "worker_threads";
4+
import workerThreads, { MessageChannel, receiveMessageOnPort, threadId, Worker } from "worker_threads";
55
import { log } from "./utils";
66

77
log.debug("syncworker file boot", { isMainThread: workerThreads.isMainThread, hasWorkerData: !!workerThreads.workerData });
@@ -52,7 +52,7 @@ export class SyncWorker {
5252
transferList: [port2],
5353
});
5454

55-
log.debug("booted syncworker worker", { filename: __filename, scriptPath, threadId: this.worker.threadId });
55+
log.debug("booted syncworker worker", { filename: __filename, scriptPath, childWorkerThreadId: this.worker.threadId });
5656

5757
this.worker.on("error", (error) => {
5858
log.error("[wds] Internal error", error);
@@ -82,7 +82,7 @@ export class SyncWorker {
8282

8383
const sharedBufferView = new Int32Array(call.sharedBuffer);
8484

85-
log.debug("calling syncworker", call);
85+
log.debug("calling syncworker", { thisThreadId: threadId, childWorkerThreadId: this.worker.threadId, call });
8686
this.port.postMessage(call);
8787

8888
// synchronously wait for worker thread to get back to us

src/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { promises as fs } from "fs";
22
import { defaults } from "lodash";
33
import path from "path";
44
import type { ProjectConfig } from "./Options";
5+
import { threadId } from "worker_threads";
56

6-
const logPrefix = `[wds pid=${process.pid}]`;
7+
const logPrefix = `[wds pid=${process.pid} thread=${threadId}]`;
78
export const log = {
89
debug: (...args: any[]) => process.env["WDS_DEBUG"] && console.warn(logPrefix, ...args),
910
info: (...args: any[]) => console.warn(logPrefix, ...args),

0 commit comments

Comments
 (0)