diff --git a/src/filesystem/interface.js b/src/filesystem/interface.js index b894afd..4c3b5a4 100644 --- a/src/filesystem/interface.js +++ b/src/filesystem/interface.js @@ -223,8 +223,8 @@ function FileSystem(options, callback) { fs.readyState = FS_ERROR; } else { fs.readyState = FS_READY; - runQueued(); } + runQueued(); callback(error, fs); } @@ -301,6 +301,13 @@ FileSystem.providers = providers; var error = fs.queueOrRun(function() { var context = fs.provider.openReadWriteContext(); + // Fail early if the filesystem is in an error state (e.g., + // provider failed to open. + if(FS_ERROR === fs.readyState) { + var err = new Errors.EFILESYSTEMERROR('filesystem unavailable, operation canceled'); + return callback.call(fs, err); + } + // Wrap the callback so we can explicitly close the context function complete() { context.close(); diff --git a/tests/bugs/issue258.js b/tests/bugs/issue258.js new file mode 100644 index 0000000..a17a169 --- /dev/null +++ b/tests/bugs/issue258.js @@ -0,0 +1,89 @@ +var Filer = require('../..'); +var util = require('../lib/test-utils.js'); +var expect = require('chai').expect; +var setImmediate = require('../../lib/async.js').setImmediate; + +describe('Queued operations should error when fs is in error state, issue 258', function() { + var provider; + + // Provider that does nothing but fail on open. + function FailingProviderContext(){} + FailingProviderContext.prototype.clear = function(callback) { + this.failCallback(callback); + }; + FailingProviderContext.prototype.getObject = + FailingProviderContext.prototype.getBuffer = function(key, callback) { + this.failCallback(callback); + }; + FailingProviderContext.prototype.putObject = + FailingProviderContext.prototype.putBuffer = function(key, value, callback) { + this.failCallback(callback); + }; + FailingProviderContext.prototype.delete = function(key, callback) { + this.failCallback(callback); + }; + + function FailingProvider() { + var self = this; + self.name = 'failure'; + self.open = function(callback) { + // Wait until caller tells us to fail + self.failNow = function() { + self.failCallback(callback); + }; + }; + self.failCallback = function(callback) { + setImmediate(function() { + callback(new Error); + }); + }; + } + FailingProvider.prototype.getReadWriteContext = + FailingProvider.prototype.getReadWriteContext = function() { + return new FailingProviderContext(); + }; + + beforeEach(function() { + provider = new FailingProvider(); + }); + + afterEach(function() { + provider = null; + }); + + it('should get EFILESYSTEMERROR errors on callbacks to queued operations on provider error', function(done) { + var errCount = 0; + var fs = new Filer.FileSystem({provider: provider}); + + function maybeDone(err) { + expect(err).to.exist; + expect(err.code).to.equal('EFILESYSTEMERROR'); + errCount++; + + if(errCount === 2) { + done(); + } + } + + // Queue some fs operations, and expect them to fail + fs.mkdir('/tmp', maybeDone); + fs.writeFile('/file', 'data', maybeDone); + + // Operations are queued, tell the provider to fail now. + provider.failNow(); + }); + + it('should get EFILESYSTEMERROR errors on callbacks to queued operations after ready callback', function(done) { + var fs = new Filer.FileSystem({provider: provider}, function(err) { + expect(err).to.exist; + + // Queue is drained, but new operations should also fail + fs.mkdir('/tmp', function(err) { + expect(err).to.exist; + expect(err.code).to.equal('EFILESYSTEMERROR'); + done(); + }); + }); + provider.failNow(); + }); +}); diff --git a/tests/index.js b/tests/index.js index c0684ff..823f698 100644 --- a/tests/index.js +++ b/tests/index.js @@ -72,4 +72,4 @@ require("./bugs/issue249"); require("./bugs/ls-depth-bug"); require("./bugs/issue247.js"); require("./bugs/issue254.js"); - +require("./bugs/issue258.js");