From a375ae1df01693ca7508254157fce3a2a71a978c Mon Sep 17 00:00:00 2001 From: Young Hahn Date: Tue, 30 Sep 2014 11:52:04 -0400 Subject: [PATCH] Add locking to mbtiles commits -- sqlite3 doesn't allow concurrent transactions. --- lib/mbtiles.js | 28 ++++++++++++++++++++++------ test/commit.test.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 test/commit.test.js diff --git a/lib/mbtiles.js b/lib/mbtiles.js index f521828..ab221c0 100644 --- a/lib/mbtiles.js +++ b/lib/mbtiles.js @@ -368,11 +368,23 @@ MBTiles.prototype.write = function(table, id, row, callback) { // - @param {Function(err)} callback MBTiles.prototype._commit = function(callback) { var mbtiles = this; + + // If no pending commits our work's done. + if (!mbtiles._pending) return callback(); + + // If already committing wait in line. + if (mbtiles._committing) return mbtiles.once('commit', function() { + mbtiles._commit(callback); + }); + + var writes = mbtiles._writes; + mbtiles._clearCaches(); + mbtiles._committing = true; mbtiles._db.serialize(function() { mbtiles._db.run('BEGIN'); var statements = {}; - Object.keys(mbtiles._writes).forEach(function(table) { + Object.keys(writes).forEach(function(table) { switch (table) { case 'map': // Insert map table. This has to be so complicate due to a design flaw @@ -384,8 +396,8 @@ MBTiles.prototype._commit = function(callback) { COALESCE(?, (SELECT tile_id FROM map WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?)),\ COALESCE(?, (SELECT grid_id FROM map WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?)))'; statements['map'] = mbtiles._db.prepare(sql); - for (var id in mbtiles._writes[table]) { - var row = mbtiles._writes[table][id]; + for (var id in writes[table]) { + var row = writes[table][id]; statements['map'].run( row.zoom_level, row.tile_column, row.tile_row, row.tile_id, row.zoom_level, row.tile_column, row.tile_row, @@ -397,8 +409,8 @@ MBTiles.prototype._commit = function(callback) { var rows = []; var args = []; var fields = []; - for (var id in mbtiles._writes[table]) { - var record = mbtiles._writes[table][id]; + for (var id in writes[table]) { + var record = writes[table][id]; var row = []; for (var field in record) { row.push(record[field]); @@ -417,7 +429,11 @@ MBTiles.prototype._commit = function(callback) { } }); - mbtiles._db.run('COMMIT', callback); + mbtiles._db.run('COMMIT', function(err) { + mbtiles._committing = false; + mbtiles.emit('commit'); + callback(err); + }); mbtiles._clearCaches(); }); }; diff --git a/test/commit.test.js b/test/commit.test.js new file mode 100644 index 0000000..38c2dfb --- /dev/null +++ b/test/commit.test.js @@ -0,0 +1,32 @@ +require('sqlite3').verbose(); + +var fs = require('fs'); +var assert = require('assert'); +var MBTiles = require('..'); +var fixtureDir = __dirname + '/fixtures/output'; +var image = fs.readFileSync(__dirname + '/fixtures/images/plain_1_0_0_0.png'); + +describe('write', function() { + before(function(done) { + // Recreate output directory to remove previous tests. + try { fs.unlinkSync(fixtureDir + '/commit_1.mbtiles'); } catch(err) {} + try { fs.mkdirSync(fixtureDir, 0755); } catch(err) {} + done(); + }); + it('test mbtiles commit lock', function(done) { + var remaining = 10; + new MBTiles('mbtiles://' + fixtureDir + '/commit_1.mbtiles?batch=1', function(err, mbtiles) { + assert.ifError(err); + mbtiles.startWriting(function(err) { + assert.ifError(err); + for (var i = 0; i < remaining; i++) mbtiles.putTile(0,0,0,image,putcb); + assert.equal(mbtiles._committing, true); + assert.equal(mbtiles._events.commit.length, 19); + }); + }); + function putcb(err) { + assert.ifError(err); + if (!--remaining) done(); + } + }); +});