Skip to content
This repository was archived by the owner on Apr 2, 2024. It is now read-only.

Commit 84a70af

Browse files
author
Alec Gibson
committed
Update hard rollback callback logic
We previously followed the following logic for callbacks in a hard rollback: - check there's an inflight op, and that it has a callback - if this isn't the case, then emit the error This logic doesn't work very well in the situation where we don't have an inflight op. This can happen in the following case: - I submit an invalid op - the op is added to the pending ops queue - we attempt to apply the op to the local document before flushing - `type.apply` throws, so the op is never flushed from pending to inflight - we perform a hard rollback - we now find we have no inflight op, but we do have a pending op In this case, since we have no inflight op, we'd emit the error, but also call the pending op callback, causing both a callback call _and_ an error emission, which is a bit surprising. This change updates our logic, so that: - we check that there are some ops to callback - and that all of those ops have callbacks If there are no callbacks, or if any of the ops don't handle the error, then we emit, so that we can be sure of never swallowing an error. We also avoid treating the inflight op any differently to pending ops, so that we avoid behaviour that's dependent on where in its lifecycle an op throws. However, if the client handles all of their op submission errors, then we never emit.
1 parent d41e034 commit 84a70af

File tree

1 file changed

+19
-7
lines changed

1 file changed

+19
-7
lines changed

lib/client/doc.js

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -887,22 +887,34 @@ Doc.prototype._rollback = function(err) {
887887
};
888888

889889
Doc.prototype._hardRollback = function(err) {
890+
// Store pending ops so that we can notify their callbacks of the error.
891+
// We combine the inflight op and the pending ops, because it's possible
892+
// to hit a condition where we have no inflight op, but we do have pending
893+
// ops. This can happen when an invalid op is submitted, which causes us
894+
// to hard rollback before the pending op was flushed.
895+
var pendingOps = [];
896+
if (this.inflightOp) pendingOps.push(this.inflightOp);
897+
pendingOps = pendingOps.concat(this.pendingOps);
898+
890899
// Cancel all pending ops and reset if we can't invert
891-
var op = this.inflightOp;
892-
var pending = this.pendingOps;
893900
this._setType(null);
894901
this.version = null;
895902
this.inflightOp = null;
896903
this.pendingOps = [];
897904

898-
// Fetch the latest from the server to get us back into a working state
905+
// Fetch the latest version from the server to get us back into a working state
899906
var doc = this;
900907
this.fetch(function() {
901-
var called = op && callEach(op.callbacks, err);
902-
for (var i = 0; i < pending.length; i++) {
903-
called = callEach(pending[i].callbacks, err) || called;
908+
// We want to check that no errors are swallowed, so we check that:
909+
// - there are callbacks to call, and
910+
// - that every single pending op called a callback
911+
// If there are no ops queued, or one of them didn't handle the error,
912+
// then we emit the error.
913+
var allOpsHadCallbacks = !!pendingOps.length;
914+
for (var i = 0; i < pendingOps.length; i++) {
915+
allOpsHadCallbacks = callEach(pendingOps[i].callbacks, err) && allOpsHadCallbacks;
904916
}
905-
if (err && !called) return doc.emit('error', err);
917+
if (err && !allOpsHadCallbacks) return doc.emit('error', err);
906918
});
907919
};
908920

0 commit comments

Comments
 (0)