Skip to content

add emitAtomic option (emit to tmp, delete old and rename) #38690

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
5 tasks done
nahuel opened this issue May 20, 2020 · 5 comments
Closed
5 tasks done

add emitAtomic option (emit to tmp, delete old and rename) #38690

nahuel opened this issue May 20, 2020 · 5 comments
Labels
Out of Scope This idea sits outside of the TypeScript language design constraints Suggestion An idea for TypeScript

Comments

@nahuel
Copy link

nahuel commented May 20, 2020

It will be nice to add an emitAtomic option, so tsc when emitting will first generate a temporary file (x.js.tmp), then remove the x.js from a previous build (if exists), and then atomically rename the x.js.tmp file to x.js.

This is needed for some workflows were tsc --watch is running emitting *.js files, and another process B is watching the build directory to hot reload these files when changed. Currently process B is seeing and trying to load *.js files partially written. I think defaulting emitAtomic to true is a sane option.

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@nahuel nahuel changed the title atomic file swap: add emitAtomic option add emitAtomic option (emit to tmp, delete old and rename) May 20, 2020
@DanielRosenwasser
Copy link
Member

How would a bulk rename occur atomically? Are there clear APIs in Node to do this?

@nahuel
Copy link
Author

nahuel commented May 20, 2020

@DanielRosenwasser I proposed a per-file atomic rename by just writting to the .js.tmp file and then doing fs.rename. That's the most atomic you can get with current (non multi-file transactional) filesystems. Another option is to make tsc create a build.tmp directory and switch when the entire build completes, but I think is overkill and may generate problems.

@RyanCavanaugh RyanCavanaugh added Out of Scope This idea sits outside of the TypeScript language design constraints Suggestion An idea for TypeScript labels May 29, 2020
@RyanCavanaugh
Copy link
Member

I don't think this is in scope for us. The file writing is already as atomic as a non-rename write can be (we build up an entire string, then write that string to disk in a single operation from our side through fs.writeFileSync). File watchers without at least a few milliseconds of debouncing are going to encounter problems with all kinds of tools.

@nahuel
Copy link
Author

nahuel commented May 31, 2020

I think is in scope, because it can only be done by modifying tsc. Debouncing works but is not optimal.

@dko-slapdash
Copy link

@RyanCavanaugh
I think what @nahuel asked about is not a real atomicity; they asked to apply all the tsc writes as close to each other in time as possible, i.e. not spreaded in time. AFAIK currently, tsc writes the compiled files output as soon as it's ready; thus, if the project is large, and the compilation takes, say, 15-20 seconds, then all the writes are spreaded over these 20 seconds. If there is some other watch-mode tool which listens to the tsc output directory (like webpack), then having debouncing there doesn't help: debouncing is typically set to 300-500 ms, whilst tsc may spread all writes through 20 s.

I've just implemented a work-around for this topic using the following pipeline for a monorepo with large number of cross-dependent packages:

  1. tsc-watch(src) -> dist.tsc/
  2. chokidar(dist.tsc/) -> rsync(dist.tsc/ to dist/)
  3. webpack-watch(dist/) -> public/

Rsync is run via -c --delay-updates --delete-after flags where "delay-updates" works the following way: rsync copies the files to tmp locations, and then, after all is collected (and thanks to -c which additionally compares the files contents), applies a batch of renames as close to each other as possible (aka "atomic", but not fully atomic, just within several milliseconds from each other).

I mean, this feature (emitAtomic or emitAlmostAtomic) is very missing here. And rsync guys implemented a special option to have it covered in their usecases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Out of Scope This idea sits outside of the TypeScript language design constraints Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants