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

Commit 5aea24f

Browse files
committed
fix: handle concurrent writes on windows
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.
1 parent 45ec48b commit 5aea24f

File tree

2 files changed

+52
-1
lines changed

2 files changed

+52
-1
lines changed

src/index.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const setImmediate = require('async/setImmediate')
1010
const waterfall = require('async/series')
1111
const each = require('async/each')
1212
const mkdirp = require('mkdirp')
13-
const writeFile = require('fast-write-atomic')
13+
const writeAtomic = require('fast-write-atomic')
1414
const path = require('path')
1515

1616
const asyncFilter = require('interface-datastore').utils.asyncFilter
@@ -19,6 +19,32 @@ const IDatastore = require('interface-datastore')
1919
const Key = IDatastore.Key
2020
const Errors = IDatastore.Errors
2121

22+
function writeFile (path, contents, callback) {
23+
writeAtomic(path, contents, (err) => {
24+
if (err) {
25+
if (err.code === 'EPERM' && err.syscall === 'rename') {
26+
// fast-write-atomic writes a file to a temp location before renaming it.
27+
// On Windows, if the final file already exists this error is thrown.
28+
// No such error is thrown on Linux/Mac
29+
// Make sure we can read & write to this file
30+
return fs.access(path, fs.constants.F_OK | fs.constants.W_OK, (err) => {
31+
if (err) {
32+
return callback(err)
33+
}
34+
35+
// The file was created by another context - this means there were
36+
// attempts to write the same block by two different function calls
37+
return callback()
38+
})
39+
}
40+
41+
return callback(err)
42+
}
43+
44+
callback()
45+
})
46+
}
47+
2248
/* :: export type FsInputOptions = {
2349
createIfMissing?: bool,
2450
errorIfExists?: bool,

test/index.spec.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,29 @@ describe('FsDatastore', () => {
153153
}
154154
})
155155
})
156+
157+
it('can survive concurrent writes', (done) => {
158+
const dir = utils.tmpdir()
159+
const fstore = new FsStore(dir)
160+
const key = new Key('CIQGFTQ7FSI2COUXWWLOQ45VUM2GUZCGAXLWCTOKKPGTUWPXHBNIVOY')
161+
const value = Buffer.from('Hello world')
162+
163+
parallel(
164+
new Array(100).fill(0).map(() => {
165+
return (cb) => {
166+
fstore.put(key, value, cb)
167+
}
168+
}),
169+
(err) => {
170+
expect(err).to.not.exist()
171+
172+
fstore.get(key, (err, res) => {
173+
expect(err).to.not.exist()
174+
expect(res).to.deep.equal(value)
175+
176+
done()
177+
})
178+
}
179+
)
180+
})
156181
})

0 commit comments

Comments
 (0)