Back out changeset d4f14f6dd401 (bug 863732) on a CLOSED TREE for being half of the problem causing all debug builds to leak as a result of merging mozilla-central and mozilla-inbound.

This commit is contained in:
L. David Baron 2013-05-11 20:22:37 -07:00
parent c3ad278e44
commit 5304e2aebf
2 changed files with 62 additions and 148 deletions

View File

@ -394,13 +394,12 @@ pref("services.push.enabled", true);
// serverURL to be assigned by services team
pref("services.push.serverURL", "");
pref("services.push.userAgentID", "");
// Exponential back-off start is 5 seconds like in HTTP/1.1.
// Maximum back-off is pingInterval.
// exponential back-off start is 5 seconds like in HTTP/1.1
pref("services.push.retryBaseInterval", 5000);
// Interval at which to ping PushServer to check connection status. In
// milliseconds. If no reply is received within requestTimeout, the connection
// is considered closed.
pref("services.push.pingInterval", 1800000); // 30 minutes
// WebSocket level ping transmit interval in seconds.
pref("services.push.websocketPingInterval", 55);
// exponential back-off end is 20 minutes
pref("services.push.maxRetryInterval", 1200000);
// How long before a DOMRequest errors as timeout
pref("services.push.requestTimeout", 10000);
// enable udp wakeup support

View File

@ -19,7 +19,6 @@ Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
Cu.import("resource://gre/modules/AlarmService.jsm");
this.EXPORTED_SYMBOLS = ["PushService"];
@ -88,8 +87,7 @@ this.PushDB.prototype = {
function txnCb(aTxn, aStore) {
debug("Going to put " + aChannelRecord.channelID);
aStore.put(aChannelRecord).onsuccess = function setTxnResult(aEvent) {
debug("Request successful. Updated record ID: " +
aEvent.target.result);
debug("Request successful. Updated record ID: " + aEvent.target.result);
};
},
aSuccessCb,
@ -269,12 +267,12 @@ this.PushWebSocketListener.prototype = {
// websocket states
// websocket is off
const STATE_SHUT_DOWN = 0;
// Websocket has been opened on client side, waiting for successful open.
// websocket has been opened on client side, waiting for successful open
// (_wsOnStart)
const STATE_WAITING_FOR_WS_START = 1;
// Websocket opened, hello sent, waiting for server reply (_handleHelloReply).
// websocket opened, hello sent, waiting for server reply (_handleHelloReply)
const STATE_WAITING_FOR_HELLO = 2;
// Websocket operational, handshake completed, begin protocol messaging.
// websocket operational, handshake completed, begin protocol messaging
const STATE_READY = 3;
/**
@ -335,6 +333,9 @@ this.PushService = {
}
}
}
else if (aSubject == this._retryTimeoutTimer) {
this._beginWSSetup();
}
break;
case "webapps-uninstall":
debug("webapps-uninstall");
@ -350,8 +351,7 @@ this.PushService = {
debug("Got " + records.length);
for (var i = 0; i < records.length; i++) {
this._db.delete(records[i].channelID, null, function() {
debug("app uninstall: " + app.manifestURL +
" Could not delete entry " + records[i].channelID);
debug("app uninstall: " + app.manifestURL + " Could not delete entry " + records[i].channelID);
});
// courtesy, but don't establish a connection
// just for it
@ -389,6 +389,25 @@ this.PushService = {
_currentState: STATE_SHUT_DOWN,
_requestTimeout: 0,
_requestTimeoutTimer: null,
/**
* How retries work: The goal is to ensure websocket is always up on
* networks not supporting UDP. So the websocket should only be shutdown if
* onServerClose indicates UDP wakeup. If WS is closed due to socket error,
* _socketError() is called. The retry timer is started and when it times
* out, beginWSSetup() is called again.
*
* On a successful connection, the timer is cancelled if it is running and
* the values are reset to defaults.
*
* If we are in the middle of a timeout (i.e. waiting), but
* a register/unregister is called, we don't want to wait around anymore.
* _sendRequest will automatically call beginWSSetup(), which will cancel the
* timer. In addition since the state will have changed, even if a pending
* timer event comes in (because the timer fired the event before it was
* cancelled), so the connection won't be reset.
*/
_retryTimeoutTimer: null,
_retryFailCount: 0,
/**
@ -421,8 +440,6 @@ this.PushService = {
ppmm.addMessageListener(msgName, this);
}.bind(this));
this._alarmID = null;
this._requestTimeout = prefs.get("requestTimeout");
this._udpPort = prefs.get("udp.port");
@ -449,16 +466,12 @@ this.PushService = {
debug("shutdownWS()");
this._currentState = STATE_SHUT_DOWN;
this._willBeWokenUpByUDP = false;
if (this._wsListener)
this._wsListener._pushService = null;
try {
this._ws.close(0, null);
} catch (e) {}
this._ws = null;
this._waitingForPong = false;
this._stopAlarm();
},
_shutdown: function() {
@ -485,7 +498,8 @@ this.PushService = {
// At this point, profile-change-net-teardown has already fired, so the
// WebSocket has been closed with NS_ERROR_ABORT (if it was up) and will
// try to reconnect. Stop the timer.
this._stopAlarm();
if (this._retryTimeoutTimer)
this._retryTimeoutTimer.cancel();
if (this._requestTimeoutTimer)
this._requestTimeoutTimer.cancel();
@ -493,35 +507,31 @@ this.PushService = {
debug("shutdown complete!");
},
/**
* How retries work: The goal is to ensure websocket is always up on
* networks not supporting UDP. So the websocket should only be shutdown if
* onServerClose indicates UDP wakeup. If WS is closed due to socket error,
* _reconnectAfterBackoff() is called. The retry alarm is started and when
* it times out, beginWSSetup() is called again.
*
* On a successful connection, the alarm is cancelled in
* wsOnMessageAvailable() when the ping alarm is started.
*
* If we are in the middle of a timeout (i.e. waiting), but
* a register/unregister is called, we don't want to wait around anymore.
* _sendRequest will automatically call beginWSSetup(), which will cancel the
* timer. In addition since the state will have changed, even if a pending
* timer event comes in (because the timer fired the event before it was
* cancelled), so the connection won't be reset.
*/
_reconnectAfterBackoff: function() {
debug("reconnectAfterBackoff()");
// aStatusCode is an NS error from Components.results
_socketError: function(aStatusCode) {
debug("socketError()");
// Calculate new timeout, but cap it to pingInterval.
// Calculate new timeout, but cap it to
var retryTimeout = prefs.get("retryBaseInterval") *
Math.pow(2, this._retryFailCount);
retryTimeout = Math.min(retryTimeout, prefs.get("pingInterval"));
// It is easier to express the max interval as a pref in milliseconds,
// rather than have it as a number and make people do the calculation of
// retryBaseInterval * 2^maxRetryFailCount.
retryTimeout = Math.min(retryTimeout, prefs.get("maxRetryInterval"));
this._retryFailCount++;
debug("Retry in " + retryTimeout + " Try number " + this._retryFailCount);
this._setAlarm(retryTimeout);
if (!this._retryTimeoutTimer) {
this._retryTimeoutTimer = Cc["@mozilla.org/timer;1"]
.createInstance(Ci.nsITimer);
}
this._retryTimeoutTimer.init(this,
retryTimeout,
Ci.nsITimer.TYPE_ONE_SHOT);
},
_beginWSSetup: function() {
@ -532,9 +542,6 @@ this.PushService = {
return;
}
// Stop any pending reconnects scheduled for the near future.
this._stopAlarm();
var serverURL = prefs.get("serverURL");
if (!serverURL) {
debug("No services.push.serverURL found!");
@ -563,101 +570,14 @@ this.PushService = {
return;
}
debug("serverURL: " + uri.spec);
this._wsListener = new PushWebSocketListener(this);
this._ws.protocol = "push-notification";
this._ws.pingInterval = prefs.get("websocketPingInterval");
this._ws.asyncOpen(uri, serverURL, this._wsListener, null);
this._currentState = STATE_WAITING_FOR_WS_START;
},
/** |delay| should be in milliseconds. */
_setAlarm: function(delay) {
// Stop any existing alarm.
this._stopAlarm();
AlarmService.add(
{
date: new Date(Date.now() + delay),
ignoreTimezone: true
},
this._onAlarmFired.bind(this),
function onSuccess(alarmID) {
this._alarmID = alarmID;
debug("Set alarm " + delay + " in the future " + this._alarmID);
}.bind(this)
)
},
_stopAlarm: function() {
if (this._alarmID !== null) {
debug("Stopped existing alarm " + this._alarmID);
AlarmService.remove(this._alarmID);
this._alarmID = null;
}
},
/**
* There is only one alarm active at any time. This alarm has 3 intervals
* corresponding to 3 tasks.
*
* 1) Reconnect on ping timeout.
* If we haven't received any messages from the server by the time this
* alarm fires, the connection is closed and PushService tries to
* reconnect, repurposing the alarm for (3).
*
* 2) Send a ping.
* The protocol sends a ping ({}) on the wire every pingInterval ms. Once
* it sends the ping, the alarm goes to task (1) which is waiting for
* a pong. If data is received after the ping is sent,
* _wsOnMessageAvailable() will reset the ping alarm (which cancels
* waiting for the pong). So as long as the connection is fine, pong alarm
* never fires.
*
* 3) Reconnect after backoff.
* The alarm is set by _reconnectAfterBackoff() and increases in duration
* every time we try and fail to connect. When it triggers, websocket
* setup begins again. On successful socket setup, the socket starts
* receiving messages. The alarm now goes to (2) where it monitors the
* WebSocket by sending a ping. Since incoming data is a sign of the
* connection being up, the ping alarm is reset every time data is
* received.
*/
_onAlarmFired: function() {
// Conditions are arranged in decreasing specificity.
// i.e. when _waitingForPong is true, other conditions are also true.
if (this._waitingForPong) {
debug("Did not receive pong in time. Reconnecting WebSocket.");
this._shutdownWS();
this._reconnectAfterBackoff();
}
else if (this._currentState == STATE_READY) {
// Send a ping.
// Bypass the queue; we don't want this to be kept pending.
this._ws.sendMsg('{}');
debug("Sent ping.");
this._waitingForPong = true;
this._setAlarm(prefs.get("requestTimeout"));
}
else if (this._alarmID !== null) {
debug("reconnect alarm fired.");
// Reconnect after back-off.
// The check for a non-null _alarmID prevents a situation where the alarm
// fires, but _shutdownWS() is called from another code-path (e.g.
// network state change) and we don't want to reconnect.
//
// It also handles the case where _beginWSSetup() is called from another
// code-path.
//
// alarmID will be non-null only when no shutdown/connect is
// called between _reconnectAfterBackoff() setting the alarm and the
// alarm firing.
// Websocket is shut down. Backoff interval expired, try to connect.
this._beginWSSetup();
}
},
/**
* Protocol handler invoked by server message.
*/
@ -1208,6 +1128,9 @@ this.PushService = {
return;
}
if (this._retryTimeoutTimer)
this._retryTimeoutTimer.cancel();
// Since we've had a successful connection reset the retry fail count.
this._retryFailCount = 0;
@ -1254,25 +1177,17 @@ this.PushService = {
_wsOnStop: function(context, statusCode) {
debug("wsOnStop()");
this._shutdownWS();
if (statusCode != Cr.NS_OK &&
!(statusCode == Cr.NS_BASE_STREAM_CLOSED && this._willBeWokenUpByUDP)) {
debug("Socket error " + statusCode);
this._reconnectAfterBackoff();
this._socketError(statusCode);
}
this._shutdownWS();
},
_wsOnMessageAvailable: function(context, message) {
debug("wsOnMessageAvailable() " + message);
this._waitingForPong = false;
// Reset the ping timer. Note: This path is executed at every step of the
// handshake, so this alarm does not need to be set explicitly at startup.
this._setAlarm(prefs.get("pingInterval"));
var reply = undefined;
try {
reply = JSON.parse(message);
@ -1314,7 +1229,7 @@ this.PushService = {
/**
* The websocket should never be closed. Since we don't call ws.close(),
* _wsOnStop() receives error code NS_BASE_STREAM_CLOSED (see comment in that
* function), which calls reconnect and re-establishes the WebSocket
* function), which calls socketError and re-establishes the WebSocket
* connection.
*
* If the server said it'll use UDP for wakeup, we set _willBeWokenUpByUDP
@ -1378,9 +1293,9 @@ this.PushService = {
},
/**
* Get mobile network information to decide if the client is capable of being
* woken up by UDP (which currently just means having an mcc and mnc along
* with an IP).
* Get mobile network information to decide if the client is capable of being woken
* up by UDP (which currently just means having an mcc and mnc along with an
* IP).
*/
_getNetworkState: function() {
debug("getNetworkState()");