From ddfbf236ce747606ac72cea3816bb4a315be0709 Mon Sep 17 00:00:00 2001 From: Geoff Schmidt Date: Thu, 1 Mar 2012 17:24:13 -0800 Subject: [PATCH] allow packages to block migration until they're ready --- packages/livedata/livedata_connection.js | 6 +- packages/reload/reload.js | 79 +++++++++++++++--------- packages/session/session.js | 4 +- packages/stream/stream_client.js | 22 +++---- 4 files changed, 66 insertions(+), 45 deletions(-) diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index 281e82afc5..cba4ce2dad 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -97,14 +97,14 @@ Meteor._LivedataConnection = function (url) { self.outstanding_methods = reload_data.outstanding_methods; // pending messages will be transmitted on initial stream 'reset' } - Meteor._reload.on_migrate(reload_key, function () { + Meteor._reload.on_migrate(reload_key, function (result) { var methods = _.map(self.outstanding_methods, function (m) { // filter out callback return {msg: m.msg}; }); - return { next_method_id: self.next_method_id, - outstanding_methods: methods }; + result({ next_method_id: self.next_method_id, + outstanding_methods: methods }); }); // Setup stream diff --git a/packages/reload/reload.js b/packages/reload/reload.js index 60f855c151..ccc1f33968 100644 --- a/packages/reload/reload.js +++ b/packages/reload/reload.js @@ -68,16 +68,18 @@ } - var save_callbacks = {}; + var providers = []; ////////// External API ////////// // Called by packages when they start up. // Registers a callback for when we want to save data. - // Before a reload, callback is called, and should return - // a JSONifyable object. + // Before a reload, callback is called. It takes one argument, a + // function. The package should wait until it is ready to migrate, + // and then call the function. If it has migration data, it should + // pass it to the function as a single JSON-compatible argument. Meteor._reload.on_migrate = function (name, callback) { - save_callbacks[name] = callback; + providers.push({name: name, callback: callback}); }; // Called by packages when they start up. @@ -86,36 +88,55 @@ return old_data[name]; }; - // Trigger a reload. Calls all the callbacks, saves all the values, - // then blows up the world. + // Trigger a reload. Starts a process that asynchronously calls all + // the callbacks, saves all the values, and then terminates this VM + // and starts a new one. + var reloading = false; Meteor._reload.reload = function () { - // Meteor._debug("Beginning hot reload."); + if (reloading) + return; + reloading = true; - // ask everyone for stuff to save - var new_data = {}; - _.each(save_callbacks, function (callback, name) { - new_data[name] = callback(); - }); + // ask everyone for stuff to save, asynchronously + var migration_data = {}; + var remaining = _.clone(providers); + var requestNext = function () { + var next = remaining.shift(); + if (!next) + saveAndRestart(); + else + next.callback(function (value) { + if (value !== undefined) + migration_data[next.name] = value; + requestNext(); + }); + }; - var new_json; - try { - new_json = JSON.stringify({ - time: (new Date()).getTime(), data: new_data, reload: true - }); - } catch (err) { - Meteor._debug("Asked to persist non-JSONable data. Ignoring."); - new_json = '{}'; - } + // then persist the migration data and restart + var saveAndRestart = function () { + var json; + try { + json = JSON.stringify({ + time: (new Date()).getTime(), data: migration_data, reload: true + }); + } catch (err) { + Meteor._debug("Asked to persist non-JSONable data. Ignoring."); + json = '{}'; + } - // save it - if (typeof sessionStorage !== "undefined") { - sessionStorage.setItem(KEY_NAME, new_json); - } else { - Meteor._debug("Browser does not support sessionStorage. Not saving reload state."); - } + // save it + if (typeof sessionStorage !== "undefined") { + sessionStorage.setItem(KEY_NAME, json); + } else { + Meteor._debug("Browser does not support sessionStorage. Not saving reload state."); + } - // blow up the world! - window.location.reload(); + // Restart with the new code. + window.location.reload(); + }; + + // kick off asynchronous process + requestNext(); }; })(); diff --git a/packages/session/session.js b/packages/session/session.js index 6b6c961de1..b34149803d 100644 --- a/packages/session/session.js +++ b/packages/session/session.js @@ -105,9 +105,9 @@ Session = _.extend({}, { if (Meteor._reload) { - Meteor._reload.on_migrate('session', function () { + Meteor._reload.on_migrate('session', function (result) { // XXX sanitize and make sure it's JSONible? - return { keys: Session.keys }; + result({keys: Session.keys}); }); (function () { diff --git a/packages/stream/stream_client.js b/packages/stream/stream_client.js index 9b9864ef32..991610ed46 100644 --- a/packages/stream/stream_client.js +++ b/packages/stream/stream_client.js @@ -130,21 +130,21 @@ _.extend(Meteor._Stream.prototype, { // inspect the welcome data and decide if we have to reload try { var welcome_data = JSON.parse(welcome_message); - if (welcome_data && welcome_data.server_id) { - if (self.server_id && self.server_id !== welcome_data.server_id) { - Meteor._reload.reload(); - // world's about to end, just leave the connection 'connecting' - // until it does. - return; - } - self.server_id = welcome_data.server_id; - } else { - Meteor._debug("DEBUG: invalid welcome packet", welcome_data); - } } catch (err) { Meteor._debug("DEBUG: malformed welcome packet", welcome_message); } + if (welcome_data && welcome_data.server_id) { + if (self.server_id && self.server_id !== welcome_data.server_id) { + Meteor._reload.reload(); + // world's about to end, just leave the connection 'connecting' + // until it does. + return; + } + self.server_id = welcome_data.server_id; + } else + Meteor._debug("DEBUG: invalid welcome packet", welcome_data); + // update status self.current_status.status = "connected"; self.current_status.connected = true;