Skip to content

Commit

Permalink
CP
Browse files Browse the repository at this point in the history
  • Loading branch information
Dawidpol committed Oct 3, 2023
1 parent 62e4ec5 commit 4ae9c50
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 25 deletions.
21 changes: 15 additions & 6 deletions lib/client/doc.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,10 +329,6 @@ Doc.prototype._handleSubscribe = function(error, snapshot) {
Doc.prototype._handleOp = function(err, message) {
if (err) {
if (this.inflightOp) {
// The server has rejected submission of the current operation. If we get
// an "Op submit rejected" error, this was done intentionally
// and we should roll back but not return an error to the user.
if (err.code === ERROR_CODE.ERR_OP_SUBMIT_REJECTED) err = null;
return this._rollback(err);
}
return this.emit('error', err);
Expand Down Expand Up @@ -1014,7 +1010,12 @@ Doc.prototype._rollback = function(err) {
return this._hardRollback(error);
}

this._clearInflightOp(err);
// The server has rejected submission of the current operation. If we get
// an "Op submit rejected" error, this was done intentionally
// and we should roll back but not return an error to the user.
if (err.code !== ERROR_CODE.ERR_OP_SUBMIT_REJECTED) {
this._clearInflightOp(err);
}
};

Doc.prototype._hardRollback = function(err) {
Expand All @@ -1035,12 +1036,20 @@ Doc.prototype._hardRollback = function(err) {

// Fetch the latest version from the server to get us back into a working state
var doc = this;
this._fetch({force: true}, function() {
this._fetch({force: true}, function(fetchError) {
// We want to check that no errors are swallowed, so we check that:
// - there are callbacks to call, and
// - that every single pending op called a callback
// If there are no ops queued, or one of them didn't handle the error,
// then we emit the error.

if (fetchError) {
// This is critical error as it means that our doc is not in usable state
// anymore, we should doc and throw doc error.
logger.error('Hard rollback doc fetch failed.', fetchError, doc);
doc.emit('error', fetchError);
}

var allOpsHadCallbacks = !!pendingOps.length;
for (var i = 0; i < pendingOps.length; i++) {
allOpsHadCallbacks = util.callEach(pendingOps[i].callbacks, err) && allOpsHadCallbacks;
Expand Down
33 changes: 33 additions & 0 deletions test/client/doc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var richText = require('rich-text').type;
var ShareDBError = require('../../lib/error');
var errorHandler = require('../util').errorHandler;
var sinon = require('sinon');
var types = require('../../lib/types');

describe('Doc', function() {
beforeEach(function() {
Expand Down Expand Up @@ -514,6 +515,38 @@ describe('Doc', function() {
], done);
});

it('throws an error when hard rollback fetch failed', function(done) {
var backend = this.backend;
doc = this.connection.get('dogs', 'scrappy');
types.register(richText);

async.series([
doc.create.bind(doc, {ops: [{insert: 'Scrappy'}]}, 'rich-text'),
function(next) {
backend.use('reply', function(replyContext, cb) {
if (replyContext.request.a !== 'f') return cb();
cb({code: 'FETCH_ERROR'});
});
backend.use('submit', function(_context, cb) {
cb(new ShareDBError('SUBMIT_ERROR'));
});
var nonInvertibleOp = [{insert: 'e'}];

var count = 0;
function expectError(code) {
count++;
return function(error) {
expect(error.code).to.equal(code);
count--;
if (!count) next();
};
}

doc.on('error', expectError('FETCH_ERROR'));
doc.submitOp(nonInvertibleOp, expectError('SUBMIT_ERROR'));
}
], done);
});

it('rescues an irreversible op collision', function(done) {
// This test case attempts to reconstruct the following corner case, with
Expand Down
16 changes: 14 additions & 2 deletions test/client/presence/doc-presence.js
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,13 @@ describe('DocPresence', function() {
});
});

doc1.submitOp({index: 5, value: 'ern'}, errorHandler(done));
doc1.submitOp({index: 5, value: 'ern'}, errorHandler(function(error) {
if (!error) {
return done('should throw an error');
}

expect(error.code).to.be.equal('ERR_OP_SUBMIT_REJECTED');
}));
}
], done);
});
Expand Down Expand Up @@ -741,7 +747,13 @@ describe('DocPresence', function() {
});
});

doc1.submitOp({index: 5, value: 'ern'}, errorHandler(done));
doc1.submitOp({index: 5, value: 'ern'}, errorHandler(function(error) {
if (!error) {
return done('should throw an error');
}

expect(error.code).to.be.equal('ERR_OP_SUBMIT_REJECTED');
}));
}
], done);
});
Expand Down
37 changes: 20 additions & 17 deletions test/client/submit.js
Original file line number Diff line number Diff line change
Expand Up @@ -919,32 +919,34 @@ module.exports = function() {
], done);
});

it('request.rejectedError() soft rejects a create', function(done) {
it('request.rejectedError() rejects a create', function(done) {
this.backend.use('submit', function(request, next) {
next(request.rejectedError());
});
var doc = this.backend.connect().get('dogs', 'fido');
doc.create({age: 3}, function(err) {
if (err) return done(err);
if (!err) {
return done('Should emit error');
}
expect(doc.version).equal(0);
expect(doc.data).equal(undefined);
expect(err.code).to.be.equal('ERR_OP_SUBMIT_REJECTED');
done();
});
expect(doc.version).equal(null);
expect(doc.data).eql({age: 3});
});

it('request.rejectedError() soft rejects a create without callback', function(done) {
it('request.rejectedError() rejects a create without callback', function(done) {
this.backend.use('submit', function(request, next) {
next(request.rejectedError());
});
var doc = this.backend.connect().get('dogs', 'fido');
doc.create({age: 3});
expect(doc.version).equal(null);
expect(doc.data).eql({age: 3});
doc.whenNothingPending(function() {
expect(doc.version).equal(0);
expect(doc.data).equal(undefined);
doc.on('error', function(err) {
expect(err.code).to.be.equal('ERR_OP_SUBMIT_REJECTED');
done();
});
});
Expand Down Expand Up @@ -1024,7 +1026,7 @@ module.exports = function() {
});
});

it('request.rejectedError() soft rejects an op', function(done) {
it('request.rejectedError() rejects an op', function(done) {
this.backend.use('submit', function(request, next) {
if ('op' in request.op) return next(request.rejectedError());
next();
Expand All @@ -1033,32 +1035,33 @@ module.exports = function() {
doc.create({age: 3}, function(err) {
if (err) return done(err);
doc.submitOp({p: ['age'], na: 1}, function(err) {
if (err) return done(err);
expect(doc.version).equal(1);
expect(doc.data).eql({age: 3});
if (!err) {
return done('Should throw an error');
}

expect(err.code).to.be.equal('ERR_OP_SUBMIT_REJECTED');
done();
});
expect(doc.version).equal(1);
expect(doc.data).eql({age: 4});
});
});

it('request.rejectedError() soft rejects an op without callback', function(done) {
it('request.rejectedError() rejects an op without callback', function(done) {
this.backend.use('submit', function(request, next) {
if ('op' in request.op) return next(request.rejectedError());
next();
});
var doc = this.backend.connect().get('dogs', 'fido');
doc.create({age: 3}, function(err) {
if (err) return done(err);
doc.submitOp({p: ['age'], na: 1});
expect(doc.version).equal(1);
expect(doc.data).eql({age: 4});
doc.whenNothingPending(function() {
expect(doc.version).equal(1);
expect(doc.data).eql({age: 3});
doc.on('error', function(err) {
if (!err) return done('Should throw an error');

expect(err.code).to.be.equal('ERR_OP_SUBMIT_REJECTED');
done();
});
doc.submitOp({p: ['age'], na: 1});
});
});

Expand Down

0 comments on commit 4ae9c50

Please sign in to comment.