Skip to content
This repository has been archived by the owner on Mar 23, 2023. It is now read-only.

Commit

Permalink
fix: handle concurrent writes on windows
Browse files Browse the repository at this point in the history
Windows returns 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.
  • Loading branch information
achingbrain committed Sep 7, 2019
1 parent 45ec48b commit 5aea24f
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 1 deletion.
28 changes: 27 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down
25 changes: 25 additions & 0 deletions test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
}
)
})
})

0 comments on commit 5aea24f

Please sign in to comment.