Skip to content

Commit

Permalink
Fix bug where half-completed wait methods could block future method e…
Browse files Browse the repository at this point in the history
…xecution.
  • Loading branch information
glasser committed Nov 17, 2012
1 parent 1eec537 commit c1c1fd9
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 9 deletions.
28 changes: 19 additions & 9 deletions packages/livedata/livedata_connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,13 @@ Meteor._LivedataConnection = function (url, options) {
// (in either direction) since we were disconnected (TCP being
// sloppy about that.)

// If the current block of methods all got their results (but didn't all get
// their data visible), discard the empty block now.
if (! _.isEmpty(self._outstandingMethodBlocks) &&
_.isEmpty(self._outstandingMethodBlocks[0].methods)) {
self._outstandingMethodBlocks.shift();
}

// If an `onReconnect` handler is set, call it first. Go through
// some hoops to ensure that methods that are called from within
// `onReconnect` get executed _before_ ones that were originally
Expand Down Expand Up @@ -1055,15 +1062,18 @@ _.extend(Meteor._LivedataConnection.prototype, {
return;

// No methods are outstanding. This should mean that the first block of
// methods is empty.
var firstBlock = self._outstandingMethodBlocks.shift();
if (! _.isEmpty(firstBlock.methods))
throw new Error("No methods outstanding but nonempty block: " +
JSON.stringify(firstBlock));

// Send the outstanding methods now in the first block.
if (!_.isEmpty(self._outstandingMethodBlocks))
self._sendOutstandingMethods();
// methods is empty. (Or it might not exist, if this was a method that
// half-finished before disconnect/reconnect.)
if (! _.isEmpty(self._outstandingMethodBlocks)) {
var firstBlock = self._outstandingMethodBlocks.shift();
if (! _.isEmpty(firstBlock.methods))
throw new Error("No methods outstanding but nonempty block: " +
JSON.stringify(firstBlock));

// Send the outstanding methods now in the first block.
if (!_.isEmpty(self._outstandingMethodBlocks))
self._sendOutstandingMethods();
}

// Maybe accept a hot code push.
self._maybeMigrate();
Expand Down
64 changes: 64 additions & 0 deletions packages/livedata/livedata_connection_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,70 @@ Tinytest.add("livedata connection - onReconnect prepends messages correctly with
]);
});

Tinytest.add("livedata stub - reconnect double wait method", function (test) {
var stream = new Meteor._StubStream;
var conn = newConnection(stream);
startAndConnect(test, stream);

var output = [];
conn.onReconnect = function () {
conn.apply('reconnectMethod', [], {wait: true}, function (err, result) {
output.push('reconnect');
});
};

conn.apply('halfwayMethod', [], {wait: true}, function (err, result) {
output.push('halfway');
});

test.equal(output, []);
// Method sent.
var halfwayId = testGotMessage(
test, stream, {msg: 'method', method: 'halfwayMethod',
params: [], id: '*'});
test.equal(stream.sent.length, 0);

// Get the result. This means it will not be resent.
stream.receive({msg: 'result', id: halfwayId, result: 'bla'});
// Callback not called.
test.equal(output, []);

// Reset stream. halfwayMethod does NOT get resent, but reconnectMethod does!
// Reconnect quiescence happens when reconnectMethod is done.
stream.reset();
testGotMessage(test, stream, {msg: 'connect', session: SESSION_ID});
var reconnectId = testGotMessage(
test, stream, {msg: 'method', method: 'reconnectMethod',
params: [], id: '*'});
test.length(stream.sent, 0);
// Still holding out hope for session resumption, so no callbacks yet.
test.equal(output, []);

// Receive 'connected', but reconnect quiescence is blocking on
// reconnectMethod.
stream.receive({msg: 'connected', session: SESSION_ID + 1});
test.equal(output, []);

// Data-done for reconnectMethod. This gets us to reconnect quiescence, so
// halfwayMethod's callback fires. reconnectMethod's is still waiting on its
// result.
stream.receive({msg: 'data', methods: [reconnectId]});
test.equal(output.shift(), 'halfway');
test.equal(output, []);

// Get result of reconnectMethod. Its callback fires.
stream.receive({msg: 'result', id: reconnectId, result: 'foo'});
test.equal(output.shift(), 'reconnect');
test.equal(output, []);

// Call another method. It should be delivered immediately. This is a
// regression test for a case where it never got delivered because there was
// an empty block in _outstandingMethodBlocks blocking it from being sent.
conn.call('lastMethod');
testGotMessage(test, stream,
{msg: 'method', method: 'lastMethod', params: [], id: '*'});
});

// XXX also test:
// - reconnect, with session resume.
// - restart on update flag
Expand Down

0 comments on commit c1c1fd9

Please sign in to comment.