Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions docs/client/full-api/api/methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ it will re-call the method when it reconnects. This means that a client may
call a method multiple times when it only means to call it once. If this
behavior is problematic for your method, consider attaching a unique ID
to each method call on the client, and checking on the server whether a call
with this ID has already been made.
with this ID has already been made. Alternatively, you can use [`Meteor.apply`](#meteor_apply) with the noRetry option set to true.

{{> autoApiBox "DDPCommon.MethodInvocation#userId"}}

Expand Down Expand Up @@ -178,7 +178,10 @@ even if the method's writes are not available yet, you can specify an

`Meteor.apply` is just like `Meteor.call`, except that the method arguments are
passed as an array rather than directly as arguments, and you can specify
options about how the client executes the method.
options about how the client executes the method. Options permitted are:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to list these options here, right? They should be in the API box above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, missed that the api box was there, glad it's not needed.

- `wait` (Client only): If true, don't send this method until all previous method calls have completed, and don't send any subsequent method calls until this one is completed.
- `onResultReceived` (Client only): This callback is invoked with the error or result of the method (just like `asyncCallback`) as soon as the error or result is available. The local cache may not yet reflect the writes performed by the method.
- `noRetry` (Client only): if true, don't send this method again on reload, simply call the callback with an error (will be `Meteor.Error(409)`)

<h2 id="ddpratelimiter"><span>DDPRateLimiter</span></h2>

Expand Down
44 changes: 40 additions & 4 deletions packages/ddp-client/livedata_connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,11 +274,44 @@ var Connection = function (url, options) {
msg.support = self._supportedDDPVersions;
self._send(msg);

//Mark non-retry calls as failed This has to be done early as getting these methods out of the current block is
// pretty important to making sure that quiescence is properly calculated, as well as possibly moving on
// to another useful block.

// Only bother testing if there is an outstandingMethodBlock (there might not be, especially if we are connecting
// for the first time.
if (self._outstandingMethodBlocks.length > 0) {
// If there is an outstanding method block, we only care about the first one as that is the one
// that could have already sent messages with no response, that are not allowed to retry.
_.each(self._outstandingMethodBlocks[0].methods, function(methodInvoker) {
// if the message wasn't sent or it's allowed to retry, do nothing.
if (methodInvoker.sentMessage && methodInvoker.noRetry) {
// The next loop serves to get the index in the current method block of this method.
var currentMethodBlock = self._outstandingMethodBlocks[0].methods;
var loopMethod;
for (var i = 0; i < currentMethodBlock.length; i++) {
loopMethod = currentMethodBlock[i];
if (loopMethod.methodId === methodInvoker.methodId) {
break;
}
}
// Remove from current method block. This may leave the block empty, but we
// don't move on to the next block until the callback has been delivered, in
// _outstandingMethodFinished.
currentMethodBlock.splice(i, 1);
// make sure that the method is told that it failed.
methodInvoker.receiveResult(Meteor.Error(409, 'Method is non-idempotent but attempted to call a second time',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change this to an error string - for example "invocation-failed". Error numbers in DDP are deprecated and I'd like to get rid of them if possible. Also, including the word "idempotent" in the message might be confusing for developers who don't know what that means.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was actually going to change that just as soon as I got home after re-reading the style guide today. If 'invocation-failed' is used, the test should also be updated to expect that.

'Method result is unknown due to dropped connection. This method request was marked to not retry'), undefined);
}
});
}

// Now, to minimize setup latency, go ahead and blast out all of
// our pending methods ands subscriptions before we've even taken
// the necessary RTT to know if we successfully reconnected. (1)
// They're supposed to be idempotent; (2) even if we did
// reconnect, we're not sure what messages might have gotten lost
// They're supposed to be idempotent, and where they are not,
// they can block retry in apply; (2) even if we did reconnect,
// we're not sure what messages might have gotten lost
// (in either direction) since we were disconnected (TCP being
// sloppy about that.)

Expand Down Expand Up @@ -352,6 +385,7 @@ var MethodInvoker = function (options) {
self._message = options.message;
self._onResultReceived = options.onResultReceived || function () {};
self._wait = options.wait;
self.noRetry = options.noRetry;
self._methodResult = null;
self._dataVisible = false;

Expand All @@ -369,10 +403,10 @@ _.extend(MethodInvoker.prototype, {
if (self.gotResult())
throw new Error("sendingMethod is called on method with result");


// If we're re-sending it, it doesn't matter if data was written the first
// time.
self._dataVisible = false;

self.sentMessage = true;

// If this is a wait method, make all data messages be buffered until it is
Expand Down Expand Up @@ -703,6 +737,7 @@ _.extend(Connection.prototype, {
* @param {Object} [options]
* @param {Boolean} options.wait (Client only) If true, don't send this method until all previous method calls have completed, and don't send any subsequent method calls until this one is completed.
* @param {Function} options.onResultReceived (Client only) This callback is invoked with the error or result of the method (just like `asyncCallback`) as soon as the error or result is available. The local cache may not yet reflect the writes performed by the method.
* @param (Boolean) options.noRetry (Client only) if true, don't send this method again on reload, simply call the callback with the error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation is generated from here, see how to update it in this hackpad: https://meteor.hackpad.com/Automatically-Generating-API-Docs-using-JSDoc-EpPmd2iuFEH

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I see now that I somehow did parenthesis instead of braces, otherwise the documentation will update as needed.

* @param {Function} [asyncCallback] Optional callback; same semantics as in [`Meteor.call`](#meteor_call).
*/
apply: function (name, args, options, callback) {
Expand Down Expand Up @@ -885,7 +920,8 @@ _.extend(Connection.prototype, {
connection: self,
onResultReceived: options.onResultReceived,
wait: !!options.wait,
message: message
message: message,
noRetry: !!options.noRetry
});

if (options.wait) {
Expand Down
43 changes: 42 additions & 1 deletion packages/ddp-client/livedata_connection_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ var makeConnectMessage = function (session) {
if (session)
msg.session = session;
return msg;
}
};

// Tests that stream got a message that matches expected.
// Expected is normally an object, and allows a wildcard value of '*',
Expand Down Expand Up @@ -742,6 +742,47 @@ Tinytest.add("livedata stub - reconnect", function (test) {
o.stop();
});

if (Meteor.isClient) {
Tinytest.add("livedata stub - reconnect non-idempotent method", function(test) {
// This test is for https://github.com/meteor/meteor/issues/6108
var stream = new StubStream();
var conn = newConnection(stream);

startAndConnect(test, stream);

var methodCallbackFired = false;
var methodCallbackErrored = false;
// call with noRetry true so that the method should fail to retry on reconnect.
conn.apply('do_something', [], {noRetry: true}, function(error) {
methodCallbackFired = true;
// failure on reconnect should trigger an error.
if (error && error.error === 409) {
methodCallbackErrored = true;
}
});

//The method has not succeeded yet
test.isFalse(methodCallbackFired);
// reconnect.
stream.sent.shift();
// "receive the message"
stream.reset();

// verify that a reconnect message was sent.
testGotMessage(test, stream, makeConnectMessage(SESSION_ID));

// Make sure that the stream triggers connection.
stream.receive({msg: 'connected', session: SESSION_ID + 1});

//The method callback should fire even though the stream has not sent a response.
//the callback should have been fired with an error.
test.isTrue(methodCallbackFired);
test.isTrue(methodCallbackErrored);

// verify that the method message was not sent.
test.isUndefined(stream.sent.shift());
});
}

if (Meteor.isClient) {
Tinytest.add("livedata stub - reconnect method which only got result", function (test) {
Expand Down