Redis AOF-style fsync

pull/2/head
Louis Chatriot 9 years ago
parent 37336c270e
commit 6884f83df8
  1. 43
      lib/storage.js
  2. 15
      test/db.test.js
  3. 169
      test/persistence.test.js

@ -10,6 +10,7 @@
var fs = require('fs') var fs = require('fs')
, mkdirp = require('mkdirp') , mkdirp = require('mkdirp')
, async = require('async') , async = require('async')
, path = require('path')
, storage = {} , storage = {}
; ;
@ -35,15 +36,26 @@ storage.ensureFileDoesntExist = function (file, callback) {
/** /**
* Flush data in OS buffer to storage if corresponding option is set * Flush data in OS buffer to storage if corresponding option is set
* @param {String} options.filename
* @param {Boolean} options.isDir Optional, defaults to false
* If options is a string, it is assumed that the flush of the file (not dir) called options was requested
*/ */
storage.flushToStorage = function (filename, callback) { storage.flushToStorage = function (options, callback) {
fs.open(filename, 'w', function (err, fd) { var filename, flags;
if (typeof options === 'string') {
filename = options;
flags = 'r+';
} else {
filename = options.filename;
flags = options.isDir ? 'r' : 'r+';
}
fs.open(filename, flags, function (err, fd) {
if (err) { return callback(err); } if (err) { return callback(err); }
fs.fsync(fd, function (err) { fs.fsync(fd, function (err) {
return callback(err); return callback(err);
if (err) { return callback(err); } if (err) { return callback(err); }
fs.close(fd, function (err) { return callback(err); }); fs.close(fd, function (err) { return callback(err); });
}); });
@ -59,17 +71,14 @@ storage.flushToStorage = function (filename, callback) {
*/ */
storage.crashSafeWriteFile = function (filename, data, cb) { storage.crashSafeWriteFile = function (filename, data, cb) {
var callback = cb || function () {} var callback = cb || function () {}
, tempFilename = filename + '~' , tempFilename = filename + '~';
, oldFilename = filename + '~~'
;
async.waterfall([ async.waterfall([
async.apply(storage.ensureFileDoesntExist, tempFilename) async.apply(storage.flushToStorage, { filename: path.dirname(filename), isDir: true })
, async.apply(storage.ensureFileDoesntExist, oldFilename)
, function (cb) { , function (cb) {
storage.exists(filename, function (exists) { storage.exists(filename, function (exists) {
if (exists) { if (exists) {
storage.rename(filename, oldFilename, function (err) { return cb(err); }); storage.flushToStorage(filename, function (err) { return cb(err); });
} else { } else {
return cb(); return cb();
} }
@ -78,12 +87,12 @@ storage.crashSafeWriteFile = function (filename, data, cb) {
, function (cb) { , function (cb) {
storage.writeFile(tempFilename, data, function (err) { return cb(err); }); storage.writeFile(tempFilename, data, function (err) { return cb(err); });
} }
//, async.apply(storage.flushToStorage, tempFilename) , async.apply(storage.flushToStorage, tempFilename)
, function (cb) { , function (cb) {
storage.rename(tempFilename, filename, function (err) { return cb(err); }); storage.rename(tempFilename, filename, function (err) { return cb(err); });
} }
//, async.apply(storage.flushToStorage, filename) , async.apply(storage.flushToStorage, { filename: path.dirname(filename), isDir: true })
, async.apply(storage.ensureFileDoesntExist, oldFilename) , async.apply(storage.ensureFileDoesntExist, tempFilename)
], function (err) { return callback(err); }) ], function (err) { return callback(err); })
}; };
@ -94,22 +103,20 @@ storage.crashSafeWriteFile = function (filename, data, cb) {
* @param {Function} callback signature: err * @param {Function} callback signature: err
*/ */
storage.ensureDatafileIntegrity = function (filename, callback) { storage.ensureDatafileIntegrity = function (filename, callback) {
var tempFilename = filename + '~' var tempFilename = filename + '~';
, oldFilename = filename + '~~'
;
storage.exists(filename, function (filenameExists) { storage.exists(filename, function (filenameExists) {
// Write was successful // Write was successful
if (filenameExists) { return callback(null); } if (filenameExists) { return callback(null); }
storage.exists(oldFilename, function (oldFilenameExists) { storage.exists(tempFilename, function (oldFilenameExists) {
// New database // New database
if (!oldFilenameExists) { if (!oldFilenameExists) {
return storage.writeFile(filename, '', 'utf8', function (err) { callback(err); }); return storage.writeFile(filename, '', 'utf8', function (err) { callback(err); });
} }
// Write failed, use old version // Write failed, use old version
storage.rename(oldFilename, filename, function (err) { return callback(err); }); storage.rename(tempFilename, filename, function (err) { return callback(err); });
}); });
}); });
}; };

@ -8,6 +8,7 @@ var should = require('chai').should()
, model = require('../lib/model') , model = require('../lib/model')
, Datastore = require('../lib/datastore') , Datastore = require('../lib/datastore')
, Persistence = require('../lib/persistence') , Persistence = require('../lib/persistence')
, reloadTimeUpperBound = 60; // In ms, an upper bound for the reload time used to check createdAt and updatedAt
; ;
@ -285,7 +286,7 @@ describe('Database', function () {
insertedDoc.createdAt.should.equal(insertedDoc.updatedAt); insertedDoc.createdAt.should.equal(insertedDoc.updatedAt);
assert.isDefined(insertedDoc._id); assert.isDefined(insertedDoc._id);
Object.keys(insertedDoc).length.should.equal(4); Object.keys(insertedDoc).length.should.equal(4);
assert.isBelow(Math.abs(insertedDoc.createdAt.getTime() - beginning), 30); // No more than 30ms should have elapsed (worst case, if there is a flush) assert.isBelow(Math.abs(insertedDoc.createdAt.getTime() - beginning), reloadTimeUpperBound); // No more than 30ms should have elapsed (worst case, if there is a flush)
// Modifying results of insert doesn't change the cache // Modifying results of insert doesn't change the cache
insertedDoc.bloup = "another"; insertedDoc.bloup = "another";
@ -332,7 +333,7 @@ describe('Database', function () {
d.insert(newDoc, function (err, insertedDoc) { d.insert(newDoc, function (err, insertedDoc) {
Object.keys(insertedDoc).length.should.equal(4); Object.keys(insertedDoc).length.should.equal(4);
insertedDoc.createdAt.getTime().should.equal(234); // Not modified insertedDoc.createdAt.getTime().should.equal(234); // Not modified
assert.isBelow(insertedDoc.updatedAt.getTime() - beginning, 30); // Created assert.isBelow(insertedDoc.updatedAt.getTime() - beginning, reloadTimeUpperBound); // Created
d.find({}, function (err, docs) { d.find({}, function (err, docs) {
assert.deepEqual(insertedDoc, docs[0]); assert.deepEqual(insertedDoc, docs[0]);
@ -354,7 +355,7 @@ describe('Database', function () {
d.insert(newDoc, function (err, insertedDoc) { d.insert(newDoc, function (err, insertedDoc) {
Object.keys(insertedDoc).length.should.equal(4); Object.keys(insertedDoc).length.should.equal(4);
insertedDoc.updatedAt.getTime().should.equal(234); // Not modified insertedDoc.updatedAt.getTime().should.equal(234); // Not modified
assert.isBelow(insertedDoc.createdAt.getTime() - beginning, 30); // Created assert.isBelow(insertedDoc.createdAt.getTime() - beginning, reloadTimeUpperBound); // Created
d.find({}, function (err, docs) { d.find({}, function (err, docs) {
assert.deepEqual(insertedDoc, docs[0]); assert.deepEqual(insertedDoc, docs[0]);
@ -961,8 +962,8 @@ describe('Database', function () {
var beginning = Date.now(); var beginning = Date.now();
d = new Datastore({ filename: testDb, autoload: true, timestampData: true }); d = new Datastore({ filename: testDb, autoload: true, timestampData: true });
d.insert({ hello: 'world' }, function (err, insertedDoc) { d.insert({ hello: 'world' }, function (err, insertedDoc) {
assert.isBelow(insertedDoc.updatedAt.getTime() - beginning, 30); assert.isBelow(insertedDoc.updatedAt.getTime() - beginning, reloadTimeUpperBound);
assert.isBelow(insertedDoc.createdAt.getTime() - beginning, 30); assert.isBelow(insertedDoc.createdAt.getTime() - beginning, reloadTimeUpperBound);
Object.keys(insertedDoc).length.should.equal(4); Object.keys(insertedDoc).length.should.equal(4);
// Wait 100ms before performing the update // Wait 100ms before performing the update
@ -976,7 +977,7 @@ describe('Database', function () {
docs[0].createdAt.should.equal(insertedDoc.createdAt); docs[0].createdAt.should.equal(insertedDoc.createdAt);
docs[0].hello.should.equal('mars'); docs[0].hello.should.equal('mars');
assert.isAbove(docs[0].updatedAt.getTime() - beginning, 99); // updatedAt modified assert.isAbove(docs[0].updatedAt.getTime() - beginning, 99); // updatedAt modified
assert.isBelow(docs[0].updatedAt.getTime() - step1, 30); // updatedAt modified assert.isBelow(docs[0].updatedAt.getTime() - step1, reloadTimeUpperBound); // updatedAt modified
done(); done();
}); });
@ -2396,7 +2397,7 @@ describe('Database', function () {
describe('Persisting indexes', function () { describe('Persisting indexes', function () {
it.only('Indexes are persisted to a separate file and recreated upon reload', function (done) { it('Indexes are persisted to a separate file and recreated upon reload', function (done) {
var persDb = "workspace/persistIndexes.db" var persDb = "workspace/persistIndexes.db"
, db , db
; ;

@ -554,16 +554,16 @@ describe('Persistence', function () {
var p = new Persistence({ db: { inMemoryOnly: false, filename: 'workspace/it.db' } }); var p = new Persistence({ db: { inMemoryOnly: false, filename: 'workspace/it.db' } });
if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); } if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); }
if (fs.existsSync('workspace/it.db~~')) { fs.unlinkSync('workspace/it.db~~'); } if (fs.existsSync('workspace/it.db~')) { fs.unlinkSync('workspace/it.db~'); }
fs.existsSync('workspace/it.db').should.equal(false); fs.existsSync('workspace/it.db').should.equal(false);
fs.existsSync('workspace/it.db~~').should.equal(false); fs.existsSync('workspace/it.db~').should.equal(false);
storage.ensureDatafileIntegrity(p.filename, function (err) { storage.ensureDatafileIntegrity(p.filename, function (err) {
assert.isNull(err); assert.isNull(err);
fs.existsSync('workspace/it.db').should.equal(true); fs.existsSync('workspace/it.db').should.equal(true);
fs.existsSync('workspace/it.db~~').should.equal(false); fs.existsSync('workspace/it.db~').should.equal(false);
fs.readFileSync('workspace/it.db', 'utf8').should.equal(''); fs.readFileSync('workspace/it.db', 'utf8').should.equal('');
@ -575,18 +575,18 @@ describe('Persistence', function () {
var p = new Persistence({ db: { inMemoryOnly: false, filename: 'workspace/it.db' } }); var p = new Persistence({ db: { inMemoryOnly: false, filename: 'workspace/it.db' } });
if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); } if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); }
if (fs.existsSync('workspace/it.db~~')) { fs.unlinkSync('workspace/it.db~~'); } if (fs.existsSync('workspace/it.db~')) { fs.unlinkSync('workspace/it.db~'); }
fs.writeFileSync('workspace/it.db', 'something', 'utf8'); fs.writeFileSync('workspace/it.db', 'something', 'utf8');
fs.existsSync('workspace/it.db').should.equal(true); fs.existsSync('workspace/it.db').should.equal(true);
fs.existsSync('workspace/it.db~~').should.equal(false); fs.existsSync('workspace/it.db~').should.equal(false);
storage.ensureDatafileIntegrity(p.filename, function (err) { storage.ensureDatafileIntegrity(p.filename, function (err) {
assert.isNull(err); assert.isNull(err);
fs.existsSync('workspace/it.db').should.equal(true); fs.existsSync('workspace/it.db').should.equal(true);
fs.existsSync('workspace/it.db~~').should.equal(false); fs.existsSync('workspace/it.db~').should.equal(false);
fs.readFileSync('workspace/it.db', 'utf8').should.equal('something'); fs.readFileSync('workspace/it.db', 'utf8').should.equal('something');
@ -594,22 +594,22 @@ describe('Persistence', function () {
}); });
}); });
it('If old datafile exists and datafile doesnt, ensureDatafileIntegrity will use it', function (done) { it('If temp datafile exists and datafile doesnt, ensureDatafileIntegrity will use it (cannot happen except upon first use)', function (done) {
var p = new Persistence({ db: { inMemoryOnly: false, filename: 'workspace/it.db' } }); var p = new Persistence({ db: { inMemoryOnly: false, filename: 'workspace/it.db' } });
if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); } if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); }
if (fs.existsSync('workspace/it.db~~')) { fs.unlinkSync('workspace/it.db~~'); } if (fs.existsSync('workspace/it.db~')) { fs.unlinkSync('workspace/it.db~~'); }
fs.writeFileSync('workspace/it.db~~', 'something', 'utf8'); fs.writeFileSync('workspace/it.db~', 'something', 'utf8');
fs.existsSync('workspace/it.db').should.equal(false); fs.existsSync('workspace/it.db').should.equal(false);
fs.existsSync('workspace/it.db~~').should.equal(true); fs.existsSync('workspace/it.db~').should.equal(true);
storage.ensureDatafileIntegrity(p.filename, function (err) { storage.ensureDatafileIntegrity(p.filename, function (err) {
assert.isNull(err); assert.isNull(err);
fs.existsSync('workspace/it.db').should.equal(true); fs.existsSync('workspace/it.db').should.equal(true);
fs.existsSync('workspace/it.db~~').should.equal(false); fs.existsSync('workspace/it.db~').should.equal(false);
fs.readFileSync('workspace/it.db', 'utf8').should.equal('something'); fs.readFileSync('workspace/it.db', 'utf8').should.equal('something');
@ -617,23 +617,24 @@ describe('Persistence', function () {
}); });
}); });
it('If both old and current datafiles exist, ensureDatafileIntegrity will use the datafile, it means step 4 of persistence failed', function (done) { // Technically it could also mean the write was successful but the rename wasn't, but there is in any case no guarantee that the data in the temp file is whole so we have to discard the whole file
it('If both temp and current datafiles exist, ensureDatafileIntegrity will use the datafile, as it means that the write of the temp file failed', function (done) {
var theDb = new Datastore({ filename: 'workspace/it.db' }); var theDb = new Datastore({ filename: 'workspace/it.db' });
if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); } if (fs.existsSync('workspace/it.db')) { fs.unlinkSync('workspace/it.db'); }
if (fs.existsSync('workspace/it.db~~')) { fs.unlinkSync('workspace/it.db~~'); } if (fs.existsSync('workspace/it.db~')) { fs.unlinkSync('workspace/it.db~'); }
fs.writeFileSync('workspace/it.db', '{"_id":"0","hello":"world"}', 'utf8'); fs.writeFileSync('workspace/it.db', '{"_id":"0","hello":"world"}', 'utf8');
fs.writeFileSync('workspace/it.db~~', '{"_id":"0","hello":"other"}', 'utf8'); fs.writeFileSync('workspace/it.db~', '{"_id":"0","hello":"other"}', 'utf8');
fs.existsSync('workspace/it.db').should.equal(true); fs.existsSync('workspace/it.db').should.equal(true);
fs.existsSync('workspace/it.db~~').should.equal(true); fs.existsSync('workspace/it.db~').should.equal(true);
storage.ensureDatafileIntegrity(theDb.persistence.filename, function (err) { storage.ensureDatafileIntegrity(theDb.persistence.filename, function (err) {
assert.isNull(err); assert.isNull(err);
fs.existsSync('workspace/it.db').should.equal(true); fs.existsSync('workspace/it.db').should.equal(true);
fs.existsSync('workspace/it.db~~').should.equal(true); fs.existsSync('workspace/it.db~').should.equal(true);
fs.readFileSync('workspace/it.db', 'utf8').should.equal('{"_id":"0","hello":"world"}'); fs.readFileSync('workspace/it.db', 'utf8').should.equal('{"_id":"0","hello":"world"}');
@ -643,6 +644,8 @@ describe('Persistence', function () {
assert.isNull(err); assert.isNull(err);
docs.length.should.equal(1); docs.length.should.equal(1);
docs[0].hello.should.equal("world"); docs[0].hello.should.equal("world");
fs.existsSync('workspace/it.db').should.equal(true);
fs.existsSync('workspace/it.db~').should.equal(false);
done(); done();
}); });
}); });
@ -656,20 +659,16 @@ describe('Persistence', function () {
if (fs.existsSync(testDb)) { fs.unlinkSync(testDb); } if (fs.existsSync(testDb)) { fs.unlinkSync(testDb); }
if (fs.existsSync(testDb + '~')) { fs.unlinkSync(testDb + '~'); } if (fs.existsSync(testDb + '~')) { fs.unlinkSync(testDb + '~'); }
if (fs.existsSync(testDb + '~~')) { fs.unlinkSync(testDb + '~~'); }
fs.existsSync(testDb).should.equal(false); fs.existsSync(testDb).should.equal(false);
fs.writeFileSync(testDb + '~', 'something', 'utf8'); fs.writeFileSync(testDb + '~', 'something', 'utf8');
fs.writeFileSync(testDb + '~~', 'something else', 'utf8');
fs.existsSync(testDb + '~').should.equal(true); fs.existsSync(testDb + '~').should.equal(true);
fs.existsSync(testDb + '~~').should.equal(true);
d.persistence.persistCachedDatabase(function (err) { d.persistence.persistCachedDatabase(function (err) {
var contents = fs.readFileSync(testDb, 'utf8'); var contents = fs.readFileSync(testDb, 'utf8');
assert.isNull(err); assert.isNull(err);
fs.existsSync(testDb).should.equal(true); fs.existsSync(testDb).should.equal(true);
fs.existsSync(testDb + '~').should.equal(false); fs.existsSync(testDb + '~').should.equal(false);
fs.existsSync(testDb + '~~').should.equal(false);
if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) { if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) {
throw "Datafile contents not as expected"; throw "Datafile contents not as expected";
} }
@ -686,47 +685,41 @@ describe('Persistence', function () {
if (fs.existsSync(testDb)) { fs.unlinkSync(testDb); } if (fs.existsSync(testDb)) { fs.unlinkSync(testDb); }
if (fs.existsSync(testDb + '~')) { fs.unlinkSync(testDb + '~'); } if (fs.existsSync(testDb + '~')) { fs.unlinkSync(testDb + '~'); }
if (fs.existsSync(testDb + '~~')) { fs.unlinkSync(testDb + '~~'); }
fs.existsSync(testDb).should.equal(false); fs.existsSync(testDb).should.equal(false);
fs.existsSync(testDb + '~').should.equal(false);
fs.writeFileSync(testDb + '~', 'bloup', 'utf8'); fs.writeFileSync(testDb + '~', 'bloup', 'utf8');
fs.writeFileSync(testDb + '~~', 'blap', 'utf8');
fs.existsSync(testDb + '~').should.equal(true); fs.existsSync(testDb + '~').should.equal(true);
fs.existsSync(testDb + '~~').should.equal(true);
d.persistence.persistCachedDatabase(function (err) { d.persistence.persistCachedDatabase(function (err) {
var contents = fs.readFileSync(testDb, 'utf8'); var contents = fs.readFileSync(testDb, 'utf8');
assert.isNull(err); assert.isNull(err);
fs.existsSync(testDb).should.equal(true); fs.existsSync(testDb).should.equal(true);
fs.existsSync(testDb + '~').should.equal(false); fs.existsSync(testDb + '~').should.equal(false);
fs.existsSync(testDb + '~~').should.equal(false);
if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) { if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) {
throw "Datafile contents not as expected"; throw "Datafile contents not as expected";
} }
done(); done();
}); });
}); });
}); });
}); });
it('persistCachedDatabase should update the contents of the datafile and leave a clean state even if there is a temp or old datafile', function (done) { it('persistCachedDatabase should update the contents of the datafile and leave a clean state even if there is a temp datafile', function (done) {
d.insert({ hello: 'world' }, function () { d.insert({ hello: 'world' }, function () {
d.find({}, function (err, docs) { d.find({}, function (err, docs) {
docs.length.should.equal(1); docs.length.should.equal(1);
if (fs.existsSync(testDb)) { fs.unlinkSync(testDb); } if (fs.existsSync(testDb)) { fs.unlinkSync(testDb); }
fs.writeFileSync(testDb + '~', 'blabla', 'utf8'); fs.writeFileSync(testDb + '~', 'blabla', 'utf8');
fs.writeFileSync(testDb + '~~', 'bloblo', 'utf8');
fs.existsSync(testDb).should.equal(false); fs.existsSync(testDb).should.equal(false);
fs.existsSync(testDb + '~').should.equal(true); fs.existsSync(testDb + '~').should.equal(true);
fs.existsSync(testDb + '~~').should.equal(true);
d.persistence.persistCachedDatabase(function (err) { d.persistence.persistCachedDatabase(function (err) {
var contents = fs.readFileSync(testDb, 'utf8'); var contents = fs.readFileSync(testDb, 'utf8');
assert.isNull(err); assert.isNull(err);
fs.existsSync(testDb).should.equal(true); fs.existsSync(testDb).should.equal(true);
fs.existsSync(testDb + '~').should.equal(false); fs.existsSync(testDb + '~').should.equal(false);
fs.existsSync(testDb + '~~').should.equal(false);
if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) { if (!contents.match(/^{"hello":"world","_id":"[0-9a-zA-Z]{16}"}\n$/)) {
throw "Datafile contents not as expected"; throw "Datafile contents not as expected";
} }
@ -736,12 +729,11 @@ describe('Persistence', function () {
}); });
}); });
it('persistCachedDatabase should update the contents of the datafile and leave a clean state even if there is a temp or old datafile', function (done) { it('persistCachedDatabase should update the contents of the datafile and leave a clean state even if there is a temp datafile', function (done) {
var dbFile = 'workspace/test2.db', theDb; var dbFile = 'workspace/test2.db', theDb;
if (fs.existsSync(dbFile)) { fs.unlinkSync(dbFile); } if (fs.existsSync(dbFile)) { fs.unlinkSync(dbFile); }
if (fs.existsSync(dbFile + '~')) { fs.unlinkSync(dbFile + '~'); } if (fs.existsSync(dbFile + '~')) { fs.unlinkSync(dbFile + '~'); }
if (fs.existsSync(dbFile + '~~')) { fs.unlinkSync(dbFile + '~~'); }
theDb = new Datastore({ filename: dbFile }); theDb = new Datastore({ filename: dbFile });
@ -750,7 +742,6 @@ describe('Persistence', function () {
assert.isNull(err); assert.isNull(err);
fs.existsSync(dbFile).should.equal(true); fs.existsSync(dbFile).should.equal(true);
fs.existsSync(dbFile + '~').should.equal(false); fs.existsSync(dbFile + '~').should.equal(false);
fs.existsSync(dbFile + '~~').should.equal(false);
if (contents != "") { if (contents != "") {
throw "Datafile contents not as expected"; throw "Datafile contents not as expected";
} }
@ -762,85 +753,79 @@ describe('Persistence', function () {
var dbFile = 'workspace/test2.db', theDb, theDb2, doc1, doc2; var dbFile = 'workspace/test2.db', theDb, theDb2, doc1, doc2;
async.waterfall([ async.waterfall([
async.apply(storage.ensureFileDoesntExist, dbFile) async.apply(storage.ensureFileDoesntExist, dbFile)
, async.apply(storage.ensureFileDoesntExist, dbFile + '~') , async.apply(storage.ensureFileDoesntExist, dbFile + '~')
, async.apply(storage.ensureFileDoesntExist, dbFile + '~~')
, function (cb) { , function (cb) {
theDb = new Datastore({ filename: dbFile }); theDb = new Datastore({ filename: dbFile });
theDb.loadDatabase(cb); theDb.loadDatabase(cb);
} }
, function (cb) { , function (cb) {
theDb.find({}, function (err, docs) { theDb.find({}, function (err, docs) {
assert.isNull(err); assert.isNull(err);
docs.length.should.equal(0); docs.length.should.equal(0);
return cb(); return cb();
}); });
} }
, function (cb) { , function (cb) {
theDb.insert({ a: 'hello' }, function (err, _doc1) { theDb.insert({ a: 'hello' }, function (err, _doc1) {
assert.isNull(err);
doc1 = _doc1;
theDb.insert({ a: 'world' }, function (err, _doc2) {
assert.isNull(err); assert.isNull(err);
doc2 = _doc2; doc1 = _doc1;
return cb(); theDb.insert({ a: 'world' }, function (err, _doc2) {
}); assert.isNull(err);
}); doc2 = _doc2;
return cb();
});
});
} }
, function (cb) { , function (cb) {
theDb.find({}, function (err, docs) { theDb.find({}, function (err, docs) {
assert.isNull(err); assert.isNull(err);
docs.length.should.equal(2); docs.length.should.equal(2);
_.find(docs, function (item) { return item._id === doc1._id }).a.should.equal('hello'); _.find(docs, function (item) { return item._id === doc1._id }).a.should.equal('hello');
_.find(docs, function (item) { return item._id === doc2._id }).a.should.equal('world'); _.find(docs, function (item) { return item._id === doc2._id }).a.should.equal('world');
return cb(); return cb();
}); });
} }
, function (cb) { , function (cb) {
theDb.loadDatabase(cb); theDb.loadDatabase(cb);
} }
, function (cb) { // No change , function (cb) { // No change
theDb.find({}, function (err, docs) { theDb.find({}, function (err, docs) {
assert.isNull(err); assert.isNull(err);
docs.length.should.equal(2); docs.length.should.equal(2);
_.find(docs, function (item) { return item._id === doc1._id }).a.should.equal('hello'); _.find(docs, function (item) { return item._id === doc1._id }).a.should.equal('hello');
_.find(docs, function (item) { return item._id === doc2._id }).a.should.equal('world'); _.find(docs, function (item) { return item._id === doc2._id }).a.should.equal('world');
return cb(); return cb();
}); });
} }
, function (cb) { , function (cb) {
fs.existsSync(dbFile).should.equal(true); fs.existsSync(dbFile).should.equal(true);
fs.existsSync(dbFile + '~').should.equal(false); fs.existsSync(dbFile + '~').should.equal(false);
fs.existsSync(dbFile + '~~').should.equal(false); return cb();
return cb();
} }
, function (cb) { , function (cb) {
theDb2 = new Datastore({ filename: dbFile }); theDb2 = new Datastore({ filename: dbFile });
theDb2.loadDatabase(cb); theDb2.loadDatabase(cb);
} }
, function (cb) { // No change in second db , function (cb) { // No change in second db
theDb2.find({}, function (err, docs) { theDb2.find({}, function (err, docs) {
assert.isNull(err); assert.isNull(err);
docs.length.should.equal(2); docs.length.should.equal(2);
_.find(docs, function (item) { return item._id === doc1._id }).a.should.equal('hello'); _.find(docs, function (item) { return item._id === doc1._id }).a.should.equal('hello');
_.find(docs, function (item) { return item._id === doc2._id }).a.should.equal('world'); _.find(docs, function (item) { return item._id === doc2._id }).a.should.equal('world');
return cb(); return cb();
}); });
} }
, function (cb) { , function (cb) {
fs.existsSync(dbFile).should.equal(true); fs.existsSync(dbFile).should.equal(true);
fs.existsSync(dbFile + '~').should.equal(false); fs.existsSync(dbFile + '~').should.equal(false);
fs.existsSync(dbFile + '~~').should.equal(false); return cb();
return cb(); }
}
], done); ], done);
}); });
// This test needs to be rewritten. The goal is to check what happens when the system crashes during a writeFile, so this test
// This test is a bit complicated since it depends on the time I/O actions take to execute // must rewrite a custom and buggy writeFile that will be used by the child process and crash in the midst of writing the file
// That depends on the machine and the load on the machine when the tests are run
// It is timed for my machine with nothing else running but may not work as expected on others (it will not fail but may not be a proof)
// Every new version of NeDB passes it on my machine before rtelease
it('If system crashes during a loadDatabase, the former version is not lost', function (done) { it('If system crashes during a loadDatabase, the former version is not lost', function (done) {
var cp, N = 150000, toWrite = "", i; var cp, N = 150000, toWrite = "", i;
@ -851,7 +836,7 @@ describe('Persistence', function () {
// Creating a db file with 150k records (a bit long to load) // Creating a db file with 150k records (a bit long to load)
for (i = 0; i < N; i += 1) { for (i = 0; i < N; i += 1) {
toWrite += model.serialize({ _id: customUtils.uid(16), hello: 'world' }) + '\n'; toWrite += model.serialize({ _id: customUtils.uid(16), hello: 'world' }) + '\n';
} }
fs.writeFileSync('workspace/lac.db', toWrite, 'utf8'); fs.writeFileSync('workspace/lac.db', toWrite, 'utf8');
// Loading it in a separate process that we will crash before finishing the loadDatabase // Loading it in a separate process that we will crash before finishing the loadDatabase

Loading…
Cancel
Save