forked from parcel-bundler/parcel
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Vendor fs write stream atomic (parcel-bundler#4685)
* Vendor * Update package.json
- Loading branch information
Showing
20 changed files
with
749 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,21 @@ | ||
node_modules/ | ||
.DS_Store | ||
package-lock.json | ||
coverage | ||
.nyc_output | ||
.cache | ||
.parcel-cache | ||
dist | ||
lib | ||
coverage/ | ||
.nyc_output/ | ||
.cache/ | ||
.parcel-cache/ | ||
dist/ | ||
lib/ | ||
.vscode/ | ||
.idea/ | ||
*.min.js | ||
# Logs | ||
logs | ||
logs/ | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
tmp | ||
parcel-bundle-reports | ||
.verdaccio_storage | ||
parcel-bundle-reports/ | ||
.verdaccio_storage/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
language: node_js | ||
sudo: false | ||
before_install: | ||
- "npm -g install npm" | ||
node_js: | ||
- "10" | ||
- "8" | ||
- "6" | ||
- "11" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
The ISC License | ||
|
||
Copyright (c) Isaac Z. Schlueter and Contributors | ||
|
||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted, provided that the above | ||
copyright notice and this permission notice appear in all copies. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR | ||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# fs-write-stream-atomic | ||
|
||
Like `fs.createWriteStream(...)`, but atomic. | ||
|
||
Writes to a tmp file and does an atomic `fs.rename` to move it into | ||
place when it's done. | ||
|
||
First rule of debugging: **It's always a race condition.** | ||
|
||
## USAGE | ||
|
||
```javascript | ||
var fsWriteStreamAtomic = require('fs-write-stream-atomic'); | ||
// options are optional. | ||
var write = fsWriteStreamAtomic('output.txt', options); | ||
var read = fs.createReadStream('input.txt'); | ||
read.pipe(write); | ||
|
||
// When the write stream emits a 'finish' or 'close' event, | ||
// you can be sure that it is moved into place, and contains | ||
// all the bytes that were written to it, even if something else | ||
// was writing to `output.txt` at the same time. | ||
``` | ||
|
||
### `fsWriteStreamAtomic(filename, [options])` | ||
|
||
- `filename` {String} The file we want to write to | ||
- `options` {Object} | ||
- `chown` {Object} User and group to set ownership after write | ||
- `uid` {Number} | ||
- `gid` {Number} | ||
- `encoding` {String} default = 'utf8' | ||
- `mode` {Number} default = `0666` | ||
- `flags` {String} default = `'w'` |
3 changes: 3 additions & 0 deletions
3
packages/utils/fs-write-stream-atomic/fixtures/thread-id-test-worker.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
'use strict'; | ||
|
||
require('worker_threads').parentPort.postMessage(require('../thread-id')); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
var fs = require('graceful-fs'); | ||
var Writable = require('readable-stream').Writable; | ||
var util = require('util'); | ||
var MurmurHash3 = require('imurmurhash'); | ||
var iferr = require('iferr'); | ||
var crypto = require('crypto'); | ||
var threadId = require('./thread-id'); | ||
|
||
function murmurhex() { | ||
var hash = MurmurHash3(''); | ||
for (var ii = 0; ii < arguments.length; ++ii) { | ||
hash.hash('' + arguments[ii]); | ||
} | ||
return hash.result(); | ||
} | ||
|
||
var invocations = 0; | ||
function getTmpname(filename) { | ||
return ( | ||
filename + '.' + murmurhex(__filename, process.pid, threadId, ++invocations) | ||
); | ||
} | ||
|
||
var setImmediate = global.setImmediate || setTimeout; | ||
|
||
module.exports = WriteStreamAtomic; | ||
|
||
// Requirements: | ||
// 1. Write everything written to the stream to a temp file. | ||
// 2. If there are no errors: | ||
// a. moves the temp file into its final destination | ||
// b. emits `finish` & `closed` ONLY after the file is | ||
// fully flushed and renamed. | ||
// 3. If there's an error, removes the temp file. | ||
|
||
util.inherits(WriteStreamAtomic, Writable); | ||
function WriteStreamAtomic(path, options) { | ||
if (!(this instanceof WriteStreamAtomic)) { | ||
return new WriteStreamAtomic(path, options); | ||
} | ||
Writable.call(this, options); | ||
|
||
this.__isWin = | ||
options && options.hasOwnProperty('isWin') | ||
? options.isWin | ||
: process.platform === 'win32'; | ||
|
||
this.__atomicTarget = path; | ||
this.__atomicTmp = getTmpname(path); | ||
|
||
this.__atomicChown = options && options.chown; | ||
|
||
this.__atomicClosed = false; | ||
|
||
this.__atomicStream = fs.WriteStream(this.__atomicTmp, options); | ||
|
||
this.__atomicStream.once('open', handleOpen(this)); | ||
this.__atomicStream.once('close', handleClose(this)); | ||
this.__atomicStream.once('error', handleError(this)); | ||
} | ||
|
||
// We have to suppress default finish emitting, because ordinarily it | ||
// would happen as soon as `end` is called on us and all of the | ||
// data has been written to our target stream. So we suppress | ||
// finish from being emitted here, and only emit it after our | ||
// target stream is closed and we've moved everything around. | ||
WriteStreamAtomic.prototype.emit = function(event) { | ||
if (event === 'finish') return this.__atomicStream.end(); | ||
return Writable.prototype.emit.apply(this, arguments); | ||
}; | ||
|
||
WriteStreamAtomic.prototype._write = function(buffer, encoding, cb) { | ||
var flushed = this.__atomicStream.write(buffer, encoding); | ||
if (flushed) return cb(); | ||
this.__atomicStream.once('drain', cb); | ||
}; | ||
|
||
function handleOpen(writeStream) { | ||
return function(fd) { | ||
writeStream.emit('open', fd); | ||
}; | ||
} | ||
|
||
function handleClose(writeStream) { | ||
return function() { | ||
if (writeStream.__atomicClosed) return; | ||
writeStream.__atomicClosed = true; | ||
if (writeStream.__atomicChown) { | ||
var uid = writeStream.__atomicChown.uid; | ||
var gid = writeStream.__atomicChown.gid; | ||
return fs.chown( | ||
writeStream.__atomicTmp, | ||
uid, | ||
gid, | ||
iferr(cleanup, moveIntoPlace), | ||
); | ||
} else { | ||
moveIntoPlace(); | ||
} | ||
}; | ||
|
||
function moveIntoPlace() { | ||
fs.rename( | ||
writeStream.__atomicTmp, | ||
writeStream.__atomicTarget, | ||
iferr(trapWindowsEPERM, end), | ||
); | ||
} | ||
|
||
function trapWindowsEPERM(err) { | ||
if ( | ||
writeStream.__isWin && | ||
err.syscall && | ||
err.syscall === 'rename' && | ||
err.code && | ||
err.code === 'EPERM' | ||
) { | ||
checkFileHashes(err); | ||
} else { | ||
cleanup(err); | ||
} | ||
} | ||
|
||
function checkFileHashes(eperm) { | ||
var inprocess = 2; | ||
var tmpFileHash = crypto.createHash('sha512'); | ||
var targetFileHash = crypto.createHash('sha512'); | ||
|
||
fs.createReadStream(writeStream.__atomicTmp) | ||
.on('data', function(data, enc) { | ||
tmpFileHash.update(data, enc); | ||
}) | ||
.on('error', fileHashError) | ||
.on('end', fileHashComplete); | ||
fs.createReadStream(writeStream.__atomicTarget) | ||
.on('data', function(data, enc) { | ||
targetFileHash.update(data, enc); | ||
}) | ||
.on('error', fileHashError) | ||
.on('end', fileHashComplete); | ||
|
||
function fileHashError() { | ||
if (inprocess === 0) return; | ||
inprocess = 0; | ||
cleanup(eperm); | ||
} | ||
|
||
function fileHashComplete() { | ||
if (inprocess === 0) return; | ||
if (--inprocess) return; | ||
if (tmpFileHash.digest('hex') === targetFileHash.digest('hex')) { | ||
return cleanup(); | ||
} else { | ||
return cleanup(eperm); | ||
} | ||
} | ||
} | ||
|
||
function cleanup(err) { | ||
fs.unlink(writeStream.__atomicTmp, function() { | ||
if (err) { | ||
writeStream.emit('error', err); | ||
writeStream.emit('close'); | ||
} else { | ||
end(); | ||
} | ||
}); | ||
} | ||
|
||
function end() { | ||
// We have to use our parent class directly because we suppress `finish` | ||
// events fired via our own emit method. | ||
Writable.prototype.emit.call(writeStream, 'finish'); | ||
|
||
// Delay the close to provide the same temporal separation a physical | ||
// file operation would have– that is, the close event is emitted only | ||
// after the async close operation completes. | ||
setImmediate(function() { | ||
writeStream.emit('close'); | ||
}); | ||
} | ||
} | ||
|
||
function handleError(writeStream) { | ||
return function(er) { | ||
cleanupSync(); | ||
writeStream.emit('error', er); | ||
writeStream.__atomicClosed = true; | ||
writeStream.emit('close'); | ||
}; | ||
function cleanupSync() { | ||
try { | ||
fs.unlinkSync(writeStream.__atomicTmp); | ||
} finally { | ||
return; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"name": "@parcel/fs-write-stream-atomic", | ||
"version": "2.0.0-alpha.3.1", | ||
"description": "Like `fs.createWriteStream(...)`, but atomic.", | ||
"main": "index.js", | ||
"directories": { | ||
"test": "test" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"dependencies": { | ||
"graceful-fs": "^4.1.2", | ||
"iferr": "^1.0.2", | ||
"imurmurhash": "^0.1.4", | ||
"readable-stream": "1 || 2" | ||
}, | ||
"author": "Isaac Z. Schlueter <[email protected]> (http://blog.izs.me/)", | ||
"license": "ISC" | ||
} |
Oops, something went wrong.