Skip to content
This repository has been archived by the owner on Jan 10, 2023. It is now read-only.

Commit

Permalink
Fixing data inconsistency problem with lovefield/indexeddb. There is …
Browse files Browse the repository at this point in the history
…currently the possibility for a bad transaction when you are trying to replace a row, in a single row object store.

The problem manifests as follows
- have a single row in an object store
- do a transaction that deletes the row, and then inserts a new row
- observe that in the end, no rows exist.

The way this happens is that the lovefield journal flushing code allows its delete and put calls to race (i.e. not promise chained). Delete has a special optimization where it counts the number of rows in the table, and if the number of IDS to delete == the number of rows that exist, then it just clears the table. For the case of single row delete/insert, the following sequence can happen with the race.
1. delete code counts number of rows that exist (returns 1)
2. put happens (now number of rows == 2)
3. chained from 1, objectStore.clear() is called (removes 2 instead of 1)

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=171225366
  • Loading branch information
freshp86 committed Nov 14, 2017
1 parent 07af979 commit cd01937
Show file tree
Hide file tree
Showing 9 changed files with 469 additions and 385 deletions.
330 changes: 165 additions & 165 deletions dist/lovefield.es6.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/lovefield.es6.js.map

Large diffs are not rendered by default.

69 changes: 38 additions & 31 deletions dist/lovefield.js
Original file line number Diff line number Diff line change
Expand Up @@ -503,10 +503,14 @@ goog.globalEval = function(script) {
} else {
if (goog.global.eval) {
if (null == goog.evalWorksForGlobals_) {
if (goog.global.eval("var _evalTest_ = 1;"), "undefined" != typeof goog.global._evalTest_) {
try {
goog.global.eval("var _evalTest_ = 1;");
} catch (ignore) {
}
if ("undefined" != typeof goog.global._evalTest_) {
try {
delete goog.global._evalTest_;
} catch (ignore) {
} catch (ignore$0) {
}
goog.evalWorksForGlobals_ = !0;
} else {
Expand All @@ -520,8 +524,8 @@ goog.globalEval = function(script) {
scriptElt.type = "text/javascript";
scriptElt.defer = !1;
scriptElt.appendChild(doc.createTextNode(script));
doc.body.appendChild(scriptElt);
doc.body.removeChild(scriptElt);
doc.head.appendChild(scriptElt);
doc.head.removeChild(scriptElt);
}
} else {
throw Error("goog.globalEval not available");
Expand Down Expand Up @@ -1194,6 +1198,10 @@ goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) {
!goog.asserts.ENABLE_ASSERTS || value instanceof type || goog.asserts.doAssertFailure_("Expected instanceof %s but got %s.", [goog.asserts.getType_(type), goog.asserts.getType_(value)], opt_message, Array.prototype.slice.call(arguments, 3));
return value;
};
goog.asserts.assertFinite = function(value, opt_message, var_args) {
!goog.asserts.ENABLE_ASSERTS || "number" == typeof value && isFinite(value) || goog.asserts.doAssertFailure_("Expected %s to be a finite number but it is not.", [value], opt_message, Array.prototype.slice.call(arguments, 2));
return value;
};
goog.asserts.assertObjectPrototypeIsIntact = function() {
for (var key in Object.prototype) {
goog.asserts.fail(key + " should not be enumerable in Object.prototype.");
Expand Down Expand Up @@ -3072,9 +3080,9 @@ goog.iter.forEach = function(iterable, f, opt_obj) {
for (;;) {
f.call(opt_obj, iterable.next(), void 0, iterable);
}
} catch (ex$0) {
if (ex$0 !== goog.iter.StopIteration) {
throw ex$0;
} catch (ex$1) {
if (ex$1 !== goog.iter.StopIteration) {
throw ex$1;
}
}
}
Expand Down Expand Up @@ -3786,10 +3794,10 @@ goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
return Number(goog.userAgent.DOCUMENT_MODE) >= documentMode;
};
goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher;
var JSCompiler_inline_result$jscomp$1;
var doc$jscomp$inline_3 = goog.global.document, mode$jscomp$inline_4 = goog.userAgent.getDocumentMode_();
JSCompiler_inline_result$jscomp$1 = doc$jscomp$inline_3 && goog.userAgent.IE ? mode$jscomp$inline_4 || ("CSS1Compat" == doc$jscomp$inline_3.compatMode ? parseInt(goog.userAgent.VERSION, 10) : 5) : void 0;
goog.userAgent.DOCUMENT_MODE = JSCompiler_inline_result$jscomp$1;
var JSCompiler_inline_result$jscomp$2;
var doc$jscomp$inline_4 = goog.global.document, mode$jscomp$inline_5 = goog.userAgent.getDocumentMode_();
JSCompiler_inline_result$jscomp$2 = doc$jscomp$inline_4 && goog.userAgent.IE ? mode$jscomp$inline_5 || ("CSS1Compat" == doc$jscomp$inline_4.compatMode ? parseInt(goog.userAgent.VERSION, 10) : 5) : void 0;
goog.userAgent.DOCUMENT_MODE = JSCompiler_inline_result$jscomp$2;
goog.userAgent.platform = {};
goog.userAgent.platform.determineVersion_ = function() {
if (goog.userAgent.WINDOWS) {
Expand Down Expand Up @@ -4069,11 +4077,10 @@ lf.backstore.BaseTx.prototype.mergeTableChanges_ = function() {
diff.forEach(function(tableDiff, tableName) {
var tableSchema = this.journal_.getScope().get(tableName), table = this.getTable(tableSchema.getName(), tableSchema.deserializeRow.bind(tableSchema), lf.backstore.TableType.DATA), toDeleteRowIds = lf.structs.map.values(tableDiff.deleted_).map(function(row) {
return row.id();
});
0 < toDeleteRowIds.length && table.remove(toDeleteRowIds).thenCatch(this.handleError_, this);
var toPut = lf.structs.map.values(tableDiff.modified_).map(function(modification) {
}), toPut = lf.structs.map.values(tableDiff.modified_).map(function(modification) {
return modification[1];
}).concat(lf.structs.map.values(tableDiff.added_));
}).concat(lf.structs.map.values(tableDiff.added_)), shouldDisableClearTableOptimization = 0 < toPut.length;
0 < toDeleteRowIds.length && table.remove(toDeleteRowIds, shouldDisableClearTableOptimization).thenCatch(this.handleError_, this);
table.put(toPut).thenCatch(this.handleError_, this);
}, this);
};
Expand Down Expand Up @@ -6557,24 +6564,24 @@ lf.backstore.ObjectStore.prototype.put = function(rows) {
}, this);
return goog.Promise.all(promises);
};
lf.backstore.ObjectStore.prototype.remove = function(ids) {
return new goog.Promise(function(resolve, reject) {
var request = this.store_.count();
lf.backstore.ObjectStore.prototype.remove = function(ids, disableClearTableOptimization) {
var $jscomp$this = this, deleteByIdsFn = function() {
var promises = ids.map(function(id) {
return $jscomp$this.performWriteOp_(function() {
return $jscomp$this.store_.delete(id);
});
});
return goog.Promise.all(promises);
};
return disableClearTableOptimization ? deleteByIdsFn() : new goog.Promise(function(resolve, reject) {
var request = $jscomp$this.store_.count();
request.onsuccess = function(ev) {
if (0 == ids.length || ev.target.result == ids.length) {
return this.performWriteOp_(function() {
return this.store_.clear();
}.bind(this)).then(resolve, reject);
}
var promises = ids.map(function(id) {
return this.performWriteOp_(function() {
return this.store_.delete(id);
}.bind(this));
}, this);
goog.Promise.all(promises).then(resolve, reject);
}.bind(this);
0 == ids.length || ev.target.result == ids.length ? $jscomp$this.performWriteOp_(function() {
return $jscomp$this.store_.clear();
}).then(resolve, reject) : deleteByIdsFn().then(resolve, reject);
};
request.onerror = reject;
}, this);
});
};
lf.backstore.IndexedDBTx = function(global, transaction, txType, bundleMode, opt_journal) {
lf.backstore.BaseTx.call(this, txType, opt_journal);
Expand Down
Loading

0 comments on commit cd01937

Please sign in to comment.