From 6a2c952cd56df2df85c843e8ee92b062589b3625 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Wed, 4 Dec 2013 21:52:45 -0800 Subject: [PATCH] Use an object instead of an array to store connection callbacks. This way we can ensure a callback is never called after its stop handle is called. --- packages/livedata/livedata_server.js | 20 +++++++++----- packages/livedata/livedata_server_tests.js | 32 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index b740b3fc8c..036e426a26 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -995,9 +995,12 @@ _.extend(Subscription.prototype, { Server = function () { var self = this; - // List of callbacks to call when a new connection comes in to the - // server and completes DDP version negotiation. - self.connectionCallbacks = []; + // Map of callbacks to call when a new connection comes in to the + // server and completes DDP version negotiation. Use an object instead + // of an array so we can safely remove one from the list while + // iterating over it. + self.connectionCallbacks = {}; + self.nextConnectionCallbackId = 0; self.publish_handlers = {}; self.universal_publish_handlers = []; @@ -1073,11 +1076,12 @@ _.extend(Server.prototype, { fn = Meteor.bindEnvironment(fn, "onConnection callback"); - self.connectionCallbacks.push(fn); + var id = self.nextConnectionCallbackId++; + self.connectionCallbacks[id] = fn; return { stop: function () { - self.connectionCallbacks = _.without(self.connectionCallbacks, fn); + delete self.connectionCallbacks[id]; } }; }, @@ -1092,9 +1096,11 @@ _.extend(Server.prototype, { // Creating a new session socket._meteorSession = new Session(self, version, socket); self.sessions[socket._meteorSession.id] = socket._meteorSession; - _.each(self.connectionCallbacks, function (callback) { - if (socket._meteorSession) + _.each(_.keys(self.connectionCallbacks), function (id) { + if (_.has(self.connectionCallbacks, id) && socket._meteorSession) { + var callback = self.connectionCallbacks[id]; callback(socket._meteorSession.connectionHandle); + } }); } else if (!msg.version) { // connect message without a version. This means an old (pre-pre1) diff --git a/packages/livedata/livedata_server_tests.js b/packages/livedata/livedata_server_tests.js index 8ee882414e..4fe5dec901 100644 --- a/packages/livedata/livedata_server_tests.js +++ b/packages/livedata/livedata_server_tests.js @@ -52,6 +52,38 @@ Tinytest.addAsync( ); +testAsyncMulti( + "livedata server - onConnection doesn't get callback after stop.", + [function (test, expect) { + var afterStop = false; + var expectStop1 = expect(); + var stopHandle1 = Meteor.onConnection(function (conn) { + stopHandle2.stop(); + stopHandle1.stop(); + afterStop = true; + // yield to the event loop for a moment to see that no other calls + // to listener2 are called. + Meteor.setTimeout(expectStop1, 10); + }); + var stopHandle2 = Meteor.onConnection(function (conn) { + test.isFalse(afterStop); + }); + + // trigger a connection + var expectConnection = expect(); + makeTestConnection( + test, + function (clientConn, serverConn) { + // Close the connection from the client. + clientConn.disconnect(); + expectConnection(); + }, + expectConnection + ); + }] +); + + Meteor.methods({ livedata_server_test_inner: function () { return this.connection.id;