Skip to content

Commit

Permalink
Update externals stubs atomically
Browse files Browse the repository at this point in the history
Since babel can run in many workers, and since we write the external stubs as a side-effect of discovering that they're needed from within babel, it's possible that a stub is written at the exact moment another worker is reading it. So we need to make sure our writes are atomic.
  • Loading branch information
ef4 committed Mar 3, 2022
1 parent be9de27 commit 62f6358
Showing 1 changed file with 24 additions and 4 deletions.
28 changes: 24 additions & 4 deletions packages/core/src/babel-plugin-adjust-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import type { NodePath } from '@babel/traverse';
import type * as Babel from '@babel/core';
import type { types as t } from '@babel/core';
import { PackageCache, Package, V2Package, explicitRelative } from '@embroider/shared-internals';
import { outputFileSync } from 'fs-extra';
import { Memoize } from 'typescript-memoize';
import { compile } from './js-handlebars';
import { handleImportDeclaration } from './mini-modules-polyfill';
import { ImportUtil } from 'babel-import-util';
import { randomBytes } from 'crypto';
import { outputFileSync, pathExistsSync } from 'fs-extra';
import { renameSync } from 'fs';

interface State {
adjustFile: AdjustFile;
Expand Down Expand Up @@ -332,8 +334,8 @@ function handleExternal(specifier: string, sourceFile: AdjustFile, opts: Options
}

function makeMissingModule(specifier: string, sourceFile: AdjustFile, opts: Options): string {
let target = join(opts.externalsDir, specifier + '.js');
outputFileSync(
let target = join(opts.externalsDir, 'missing', specifier + '.js');
atomicWrite(
target,
dynamicMissingModule({
moduleName: specifier,
Expand All @@ -344,7 +346,7 @@ function makeMissingModule(specifier: string, sourceFile: AdjustFile, opts: Opti

function makeExternal(specifier: string, sourceFile: AdjustFile, opts: Options): string {
let target = join(opts.externalsDir, specifier + '.js');
outputFileSync(
atomicWrite(
target,
externalTemplate({
runtimeName: specifier,
Expand All @@ -353,6 +355,24 @@ function makeExternal(specifier: string, sourceFile: AdjustFile, opts: Options):
return explicitRelative(dirname(sourceFile.name), target.slice(0, -3));
}

function atomicWrite(path: string, content: string) {
if (pathExistsSync(path)) {
return;
}
let suffix = randomBytes(8).toString('hex');
outputFileSync(path + suffix, content);
try {
renameSync(path + suffix, path);
} catch (err: any) {
// windows throws EPERM for concurrent access. For us it's not an error
// condition because the other thread is writing the exact same value we
// would have.
if (err.code !== 'EPERM') {
throw err;
}
}
}

export default function main(babel: typeof Babel) {
let t = babel.types;
return {
Expand Down

0 comments on commit 62f6358

Please sign in to comment.