From fb300c26ec10e6daff44ea7660842c72dccd467e Mon Sep 17 00:00:00 2001 From: Martin Acosta Date: Tue, 16 Jun 2020 13:07:02 -0300 Subject: [PATCH] Fixed crash on random concurrent requests --- index.js | 62 ++++++++++++++++++++++++++++------------------------ package.json | 6 ++--- test.js | 51 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 86 insertions(+), 33 deletions(-) diff --git a/index.js b/index.js index 57518b8..5b2edee 100644 --- a/index.js +++ b/index.js @@ -152,7 +152,7 @@ WriteRequest.prototype.onwrite = function (err, e) { } if (!err) { - this.file.updateSize(e.currentTarget.length, this.truncating) + this.file.updateSize(e.currentTarget.length) } if (this.truncating) { @@ -166,6 +166,7 @@ WriteRequest.prototype.onwrite = function (err, e) { WriteRequest.prototype.truncate = function () { this.truncating = true + this.file.truncate() this.writer.truncate(this.req.offset) } @@ -176,21 +177,23 @@ WriteRequest.prototype.lock = function () { } WriteRequest.prototype.run = function (req) { - var file = this.file + this.file.getWritableFile((err, file) => { + if (err) return req.callback(err) - this.req = req - if (!this.writer || this.writer.length !== file.size) return this.makeWriter() + this.req = req + if (!this.writer || this.writer.length !== file.size) return this.makeWriter() - const end = req.offset + req.size - if (end > file.size && !this.lock()) return + const end = req.offset + req.size + if (end > file.size && !this.lock()) return - if (req.offset > this.writer.length) { - if (req.offset > file.size) return this.truncate() - return this.makeWriter() - } + if (req.offset > this.writer.length) { + if (req.offset > file.size) return this.truncate() + return this.makeWriter() + } - this.writer.seek(req.offset) - this.writer.write(new Blob([req.data], TYPE)) + this.writer.seek(req.offset) + this.writer.write(new Blob([req.data], TYPE)) + }) } function Mutex () { @@ -246,12 +249,6 @@ ReadRequest.prototype.onread = function (err, buf) { const req = this.req if (err && this.retry) { - if (err.name === 'NotReadableError') { - this.file.clearFile() - this.run(req) - return - } - this.retry = false if (this.lock(this)) { this.file.clearFile() @@ -289,44 +286,53 @@ class EntryFile { this._lock = mutexify() this._file = null this._size = 0 - } - - get locked () { - return this._lock.locked + this._truncated = false } get size () { return this._size } - updateSize (size, truncating = false) { - if (truncating || size > this._size) { + updateSize (size) { + if (!this._truncated) { this._size = size } this.clearFile() } + truncate () { + this._truncated = true + } + clearFile () { this._file = null } get (cb) { - if (this._file) { - cb(null, this._file) - return + if (this._file && !this._truncated) { + return cb(null, this._file) } this._lock(release => { - if (this._file) { + if (this._file && !this._truncated) { return release(cb, null, this._file) } this._entry.file(file => { + this._truncated = false this._file = file this._size = file.size release(cb, null, file) }, err => release(cb, err)) }) } + + getWritableFile (cb) { + if (!this._truncated) { + return cb(null, this) + } + + this.get(cb) + } } diff --git a/package.json b/package.json index f4ed100..e95089d 100644 --- a/package.json +++ b/package.json @@ -8,17 +8,17 @@ "random-access-storage": "^1.3.0" }, "devDependencies": { - "@dxos/browser-runner": "1.0.0-beta.6", + "@dxos/browser-runner": "^1.0.0-beta.9", "random-access-test": "^1.0.0", "standard": "^11.0.1", "tap-finished": "0.0.1", - "tape": "^5.0.0" + "tape": "^5.0.1" }, "scripts": { "test": "browser-runner test.js", "posttest": "npm run lint", "lint": "standard", - "bench": "browser-runner bench.js" + "bench": "browser-runner bench.js --timeout 0" }, "repository": { "type": "git", diff --git a/test.js b/test.js index 04dabb7..f1d67b8 100644 --- a/test.js +++ b/test.js @@ -1,10 +1,57 @@ -const test = require('random-access-test') +const test = require('tape') +const randomAccessTest = require('random-access-test') const racf = require('./') const createStorage = (root) => (file, opts) => racf(`${root}/${file}`, opts) const storage = createStorage('tests-' + Math.random()) -test(function (name, options, callback) { +randomAccessTest(function (name, options, callback) { callback(storage(name, options)) }, {}) + +test('write/read concurrent requests', async t => { + const st = storage('random') + + const rand = (min, max) => Math.floor(Math.random() * max) + min + + const read = (...args) => new Promise((resolve, reject) => { + st.read(...args, (err) => { + if (err) return reject(err) + resolve() + }) + }) + + const write = (...args) => new Promise((resolve, reject) => { + st.write(...args, (err) => { + if (err) return reject(err) + resolve() + }) + }) + + try { + await new Promise(resolve => st.open(() => resolve())) + + const buf = Buffer.alloc(1) + + await Promise.all([...Array(1000).keys()].map(from => { + return write(from, buf) + })) + + await Promise.all([...Array(1000).keys()].map(() => { + const row = rand(0, 2) + const from = rand(0, 1000) + const to = 1 + + if (row === 0) { + return read(from, to) + } + return write(from, buf) + })) + + t.pass('should work ok with random concurrent request') + t.end() + } catch (err) { + t.end(err) + } +})