diff --git a/src/index.js b/src/index.js index d5b1826..8c1355a 100644 --- a/src/index.js +++ b/src/index.js @@ -10,7 +10,7 @@ const setImmediate = require('async/setImmediate') const waterfall = require('async/series') const each = require('async/each') const mkdirp = require('mkdirp') -const writeFile = require('fast-write-atomic') +const writeAtomic = require('fast-write-atomic') const path = require('path') const asyncFilter = require('interface-datastore').utils.asyncFilter @@ -19,6 +19,32 @@ const IDatastore = require('interface-datastore') const Key = IDatastore.Key const Errors = IDatastore.Errors +function writeFile (path, contents, callback) { + writeAtomic(path, contents, (err) => { + if (err) { + if (err.code === 'EPERM' && err.syscall === 'rename') { + // fast-write-atomic writes a file to a temp location before renaming it. + // On Windows, if the final file already exists this error is thrown. + // No such error is thrown on Linux/Mac + // Make sure we can read & write to this file + return fs.access(path, fs.constants.F_OK | fs.constants.W_OK, (err) => { + if (err) { + return callback(err) + } + + // The file was created by another context - this means there were + // attempts to write the same block by two different function calls + return callback() + }) + } + + return callback(err) + } + + callback() + }) +} + /* :: export type FsInputOptions = { createIfMissing?: bool, errorIfExists?: bool, diff --git a/test/index.spec.js b/test/index.spec.js index 9e7e275..2b2d06b 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -153,4 +153,29 @@ describe('FsDatastore', () => { } }) }) + + it('can survive concurrent writes', (done) => { + const dir = utils.tmpdir() + const fstore = new FsStore(dir) + const key = new Key('CIQGFTQ7FSI2COUXWWLOQ45VUM2GUZCGAXLWCTOKKPGTUWPXHBNIVOY') + const value = Buffer.from('Hello world') + + parallel( + new Array(100).fill(0).map(() => { + return (cb) => { + fstore.put(key, value, cb) + } + }), + (err) => { + expect(err).to.not.exist() + + fstore.get(key, (err, res) => { + expect(err).to.not.exist() + expect(res).to.deep.equal(value) + + done() + }) + } + ) + }) })