Rework DB transaction handling
Rollback callbacks weren't being properly called, and some other things were in the wrong place, particularly with nested transactions.
This commit is contained in:
parent
4cb5e7e4d5
commit
9e3e680be8
2 changed files with 158 additions and 67 deletions
|
@ -465,6 +465,15 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var result = yield Zotero.Promise.coroutine(func)();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
Zotero.debug("Rolled back nested async DB transaction", 5);
|
||||||
|
this._asyncTransactionNestingLevel = 0;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
Zotero.debug("Decreasing async DB transaction level to "
|
Zotero.debug("Decreasing async DB transaction level to "
|
||||||
+ --this._asyncTransactionNestingLevel, 5);
|
+ --this._asyncTransactionNestingLevel, 5);
|
||||||
return result;
|
return result;
|
||||||
|
@ -489,78 +498,77 @@ Zotero.DBConnection.prototype.executeTransaction = Zotero.Promise.coroutine(func
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var result = yield conn.executeTransaction(func);
|
var result = yield conn.executeTransaction(func);
|
||||||
try {
|
Zotero.debug("Committed async DB transaction", 5);
|
||||||
Zotero.debug("Committed async DB transaction", 5);
|
|
||||||
|
|
||||||
// Clear transaction time
|
// Clear transaction time
|
||||||
if (this._transactionDate) {
|
if (this._transactionDate) {
|
||||||
this._transactionDate = null;
|
this._transactionDate = null;
|
||||||
}
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
// Function to run once transaction has been committed but before any
|
|
||||||
// permanent callbacks
|
|
||||||
if (options.onCommit) {
|
|
||||||
this._callbacks.current.commit.push(options.onCommit);
|
|
||||||
}
|
|
||||||
this._callbacks.current.rollback = [];
|
|
||||||
|
|
||||||
if (options.vacuumOnCommit) {
|
|
||||||
Zotero.debug('Vacuuming database');
|
|
||||||
yield Zotero.DB.queryAsync('VACUUM');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run temporary commit callbacks
|
|
||||||
var f;
|
|
||||||
while (f = this._callbacks.current.commit.shift()) {
|
|
||||||
yield Zotero.Promise.resolve(f());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run commit callbacks
|
|
||||||
for (var i=0; i<this._callbacks.commit.length; i++) {
|
|
||||||
if (this._callbacks.commit[i]) {
|
|
||||||
yield this._callbacks.commit[i]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(resolve, 0);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
catch (e) {
|
|
||||||
Zotero.debug("Rolled back async DB transaction", 5);
|
|
||||||
Zotero.debug(e, 1);
|
|
||||||
|
|
||||||
try {
|
if (options) {
|
||||||
if (options) {
|
// Function to run once transaction has been committed but before any
|
||||||
// Function to run once transaction has been committed but before any
|
// permanent callbacks
|
||||||
// permanent callbacks
|
if (options.onCommit) {
|
||||||
if (options.onRollback) {
|
this._callbacks.current.commit.push(options.onCommit);
|
||||||
this._callbacks.current.rollback.push(options.onRollback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run temporary commit callbacks
|
|
||||||
var f;
|
|
||||||
while (f = this._callbacks.current.rollback.shift()) {
|
|
||||||
yield Zotero.Promise.resolve(f());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run rollback callbacks
|
|
||||||
for (var i=0; i<this._callbacks.rollback.length; i++) {
|
|
||||||
if (this._callbacks.rollback[i]) {
|
|
||||||
yield Zotero.Promise.resolve(this._callbacks.rollback[i]());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
setTimeout(reject, 0);
|
|
||||||
}
|
}
|
||||||
|
this._callbacks.current.rollback = [];
|
||||||
|
|
||||||
throw e;
|
if (options.vacuumOnCommit) {
|
||||||
|
Zotero.debug('Vacuuming database');
|
||||||
|
yield Zotero.DB.queryAsync('VACUUM');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run temporary commit callbacks
|
||||||
|
var f;
|
||||||
|
while (f = this._callbacks.current.commit.shift()) {
|
||||||
|
yield Zotero.Promise.resolve(f());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run commit callbacks
|
||||||
|
for (var i=0; i<this._callbacks.commit.length; i++) {
|
||||||
|
if (this._callbacks.commit[i]) {
|
||||||
|
yield this._callbacks.commit[i]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(resolve, 0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
Zotero.debug("Rolled back async DB transaction", 5);
|
||||||
|
Zotero.debug(e, 1);
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
// Function to run once transaction has been committed but before any
|
||||||
|
// permanent callbacks
|
||||||
|
if (options.onRollback) {
|
||||||
|
this._callbacks.current.rollback.push(options.onRollback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run temporary commit callbacks
|
||||||
|
var f;
|
||||||
|
while (f = this._callbacks.current.rollback.shift()) {
|
||||||
|
yield Zotero.Promise.resolve(f());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run rollback callbacks
|
||||||
|
for (var i=0; i<this._callbacks.rollback.length; i++) {
|
||||||
|
if (this._callbacks.rollback[i]) {
|
||||||
|
yield Zotero.Promise.resolve(this._callbacks.rollback[i]());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reject) {
|
||||||
|
setTimeout(function () {
|
||||||
|
reject(e);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
// Reset options back to their previous values
|
// Reset options back to their previous values
|
||||||
|
|
83
test/tests/dbTest.js
Normal file
83
test/tests/dbTest.js
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
describe("Zotero.DB", function() {
|
||||||
|
describe("#executeTransaction()", function () {
|
||||||
|
it("should roll back on error", function* () {
|
||||||
|
yield Zotero.DB.queryAsync("CREATE TABLE tmpRollbackOnError (foo INT)");
|
||||||
|
yield Zotero.DB.queryAsync("INSERT INTO tmpRollbackOnError VALUES (1)");
|
||||||
|
try {
|
||||||
|
yield Zotero.DB.executeTransaction(function* () {
|
||||||
|
yield Zotero.DB.queryAsync("INSERT INTO tmpRollbackOnError VALUES (2)");
|
||||||
|
throw 'Aborting transaction -- ignore';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (typeof e != 'string' || !e.startsWith('Aborting transaction')) throw e;
|
||||||
|
}
|
||||||
|
var count = yield Zotero.DB.valueQueryAsync("SELECT COUNT(*) FROM tmpRollbackOnError");
|
||||||
|
assert.equal(count, 1);
|
||||||
|
|
||||||
|
var conn = yield Zotero.DB._getConnectionAsync();
|
||||||
|
assert.isFalse(conn.transactionInProgress);
|
||||||
|
|
||||||
|
yield Zotero.DB.queryAsync("DROP TABLE tmpRollbackOnError");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should run onRollback callbacks", function* () {
|
||||||
|
var callbackRan = false;
|
||||||
|
yield Zotero.DB.queryAsync("CREATE TABLE tmpOnRollback (foo INT)");
|
||||||
|
try {
|
||||||
|
yield Zotero.DB.executeTransaction(
|
||||||
|
function* () {
|
||||||
|
yield Zotero.DB.queryAsync("INSERT INTO tmpOnRollback VALUES (1)");
|
||||||
|
throw 'Aborting transaction -- ignore';
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onRollback: function () {
|
||||||
|
callbackRan = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (typeof e != 'string' || !e.startsWith('Aborting transaction')) throw e;
|
||||||
|
}
|
||||||
|
assert.ok(callbackRan);
|
||||||
|
|
||||||
|
yield Zotero.DB.queryAsync("DROP TABLE tmpOnRollback");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should run onRollback callbacks for nested transactions", function* () {
|
||||||
|
var callback1Ran = false;
|
||||||
|
var callback2Ran = false;
|
||||||
|
yield Zotero.DB.queryAsync("CREATE TABLE tmpOnNestedRollback (foo INT)");
|
||||||
|
try {
|
||||||
|
yield Zotero.DB.executeTransaction(function* () {
|
||||||
|
yield Zotero.DB.queryAsync("INSERT INTO tmpOnNestedRollback VALUES (1)");
|
||||||
|
yield Zotero.DB.executeTransaction(
|
||||||
|
function* () {
|
||||||
|
yield Zotero.DB.queryAsync("INSERT INTO tmpOnNestedRollback VALUES (2)");
|
||||||
|
|
||||||
|
throw 'Aborting transaction -- ignore';
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onRollback: function () {
|
||||||
|
callback1Ran = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onRollback: function () {
|
||||||
|
callback2Ran = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (typeof e != 'string' || !e.startsWith('Aborting transaction')) throw e;
|
||||||
|
}
|
||||||
|
assert.ok(callback1Ran);
|
||||||
|
assert.ok(callback2Ran);
|
||||||
|
|
||||||
|
yield Zotero.DB.queryAsync("DROP TABLE tmpOnNestedRollback");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
Loading…
Reference in a new issue