From d5c8e4f0e04e5129f94ef1a5cedc6da1f3633b33 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Sun, 8 Sep 2019 07:58:34 +0100 Subject: [PATCH] fix: handle concurrent writes on windows Windows can return EPERM errors when trying to rename temp files to files that already exist. In our case a file with a given name will always have the same content so if it's created while we are trying to also create it, we can reasonably assume it's ok to use. If we want to be more thorough we could hash the contents of the new file. --- src/index.js | 22 +++++++++++++++++++++- test/index.spec.js | 15 +++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 7e995e9..9c70324 100644 --- a/src/index.js +++ b/src/index.js @@ -4,7 +4,7 @@ const fs = require('fs') const glob = require('glob') const mkdirp = require('mkdirp') const promisify = require('util').promisify -const writeFile = promisify(require('fast-write-atomic')) +const writeAtomic = promisify(require('fast-write-atomic')) const path = require('path') const filter = require('interface-datastore').utils.filter @@ -22,6 +22,26 @@ const fsUnlink = promisify(fs.unlink || noop) const Key = IDatastore.Key const Errors = IDatastore.Errors +async function writeFile (path, contents) { + try { + await writeAtomic(path, contents) + } catch (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 + await fsAccess(path, fs.constants.F_OK | fs.constants.W_OK) + + // The file was created by another context - this means there were + // attempts to write the same block by two different function calls + return + } + + throw err + } +} + /** * A datastore backed by the file system. * diff --git a/test/index.spec.js b/test/index.spec.js index 172d79e..125adbe 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -172,4 +172,19 @@ describe('FsDatastore', () => { } }) }) + + it('can survive concurrent writes', async () => { + const dir = utils.tmpdir() + const fstore = new FsStore(dir) + const key = new Key('CIQGFTQ7FSI2COUXWWLOQ45VUM2GUZCGAXLWCTOKKPGTUWPXHBNIVOY') + const value = Buffer.from('Hello world') + + await Promise.all( + new Array(100).fill(0).map(() => fstore.put(key, value)) + ) + + const res = await fstore.get(key) + + expect(res).to.deep.equal(value) + }) })