Bug 1213169 - requestsync assumes all in-memory mozAlarms will never be purged (and these alarms get persisted anyways), but they are purged on timezone/clock changes, r=asuth

This commit is contained in:
Andrea Marchesini 2015-10-27 09:48:00 -04:00
Родитель 5731d2c32f
Коммит 8d4a4c315a
2 изменённых файлов: 134 добавлений и 66 удалений

Просмотреть файл

@ -49,6 +49,8 @@ XPCOMUtils.defineLazyGetter(this, "powerManagerService", function() {
*/
this.AlarmService = {
lastChromeId: 0,
init: function init() {
debug("init()");
@ -205,6 +207,12 @@ this.AlarmService = {
};
}
// Is this a chrome alarm?
if (aId < 0) {
aRemoveSuccessCb();
return;
}
this._db.remove(aId, aManifestURL, aRemoveSuccessCb,
function removeErrorCb(aErrorMsg) {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
@ -241,11 +249,19 @@ this.AlarmService = {
_notifyAlarmObserver: function _notifyAlarmObserver(aAlarm) {
debug("_notifyAlarmObserver()");
if (aAlarm.manifestURL) {
this._fireSystemMessage(aAlarm);
} else if (typeof aAlarm.alarmFiredCb === "function") {
aAlarm.alarmFiredCb(this._publicAlarm(aAlarm));
}
let wakeLock = powerManagerService.newWakeLock("cpu");
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(() => {
debug("_notifyAlarmObserver - timeout()");
if (aAlarm.manifestURL) {
this._fireSystemMessage(aAlarm);
} else if (typeof aAlarm.alarmFiredCb === "function") {
aAlarm.alarmFiredCb(this._publicAlarm(aAlarm));
}
wakeLock.unlock();
}, 0, Ci.nsITimer.TYPE_ONE_SHOT);
},
_onAlarmFired: function _onAlarmFired() {
@ -265,8 +281,12 @@ this.AlarmService = {
}
this._removeAlarmFromDb(this._currentAlarm.id, null);
this._notifyAlarmObserver(this._currentAlarm);
// We need to clear the current alarm before notifying because chrome
// alarms may add a new alarm during their callback, and we do not want
// to clobber it.
let firingAlarm = this._currentAlarm;
this._currentAlarm = null;
this._notifyAlarmObserver(firingAlarm);
}
// Reset the next alarm from the queue.
@ -309,15 +329,25 @@ this.AlarmService = {
debug("Callback after getting alarms from database: " +
JSON.stringify(aAlarms));
// Clear any alarms set or queued in the cache.
// Clear any alarms set or queued in the cache if coming from db.
let alarmQueue = this._alarmQueue;
alarmQueue.length = 0;
this._currentAlarm = null;
if (this._currentAlarm) {
alarmQueue.unshift(this._currentAlarm);
this._currentAlarm = null;
}
for (let i = 0; i < alarmQueue.length;) {
if (alarmQueue[i]['id'] < 0) {
++i;
continue;
}
alarmQueue.splice(i, 1);
}
// Only restore the alarm that's not yet expired; otherwise, remove it
// from the database and notify the observer.
aAlarms.forEach(function addAlarm(aAlarm) {
if (this._getAlarmTime(aAlarm) > Date.now()) {
if ("manifestURL" in aAlarm && aAlarm.manifestURL &&
this._getAlarmTime(aAlarm) > Date.now()) {
alarmQueue.push(aAlarm);
} else {
this._removeAlarmFromDb(aAlarm.id, null);
@ -416,55 +446,65 @@ this.AlarmService = {
aNewAlarm['timezoneOffset'] = this._currentTimezoneOffset;
this._db.add(aNewAlarm,
function addSuccessCb(aNewId) {
debug("Callback after adding alarm in database.");
if ("manifestURL" in aNewAlarm) {
this._db.add(aNewAlarm,
function addSuccessCb(aNewId) {
debug("Callback after adding alarm in database.");
this.processNewAlarm(aNewAlarm, aNewId, aAlarmFiredCb, aSuccessCb);
}.bind(this),
function addErrorCb(aErrorMsg) {
aErrorCb(aErrorMsg);
}.bind(this));
} else {
// alarms without manifests are managed by chrome code. For them we use
// negative IDs.
this.processNewAlarm(aNewAlarm, --this.lastChromeId, aAlarmFiredCb,
aSuccessCb);
}
},
aNewAlarm['id'] = aNewId;
processNewAlarm: function(aNewAlarm, aNewId, aAlarmFiredCb, aSuccessCb) {
aNewAlarm['id'] = aNewId;
// Now that the alarm has been added to the database, we can tack on
// the non-serializable callback to the in-memory object.
aNewAlarm['alarmFiredCb'] = aAlarmFiredCb;
// Now that the alarm has been added to the database, we can tack on
// the non-serializable callback to the in-memory object.
aNewAlarm['alarmFiredCb'] = aAlarmFiredCb;
// If the new alarm already expired at this moment, we directly
// notify this alarm
let aNewAlarmTime = this._getAlarmTime(aNewAlarm);
if (aNewAlarmTime < Date.now()) {
aSuccessCb(aNewId);
this._removeAlarmFromDb(aNewAlarm.id, null);
this._notifyAlarmObserver(aNewAlarm);
return;
}
// If the new alarm already expired at this moment, we directly
// notify this alarm
let newAlarmTime = this._getAlarmTime(aNewAlarm);
if (newAlarmTime < Date.now()) {
aSuccessCb(aNewId);
this._removeAlarmFromDb(aNewAlarm.id, null);
this._notifyAlarmObserver(aNewAlarm);
return;
}
// If there is no alarm being set in system, set the new alarm.
if (this._currentAlarm == null) {
this._currentAlarm = aNewAlarm;
this._debugCurrentAlarm();
aSuccessCb(aNewId);
return;
}
// If there is no alarm being set in system, set the new alarm.
if (this._currentAlarm == null) {
this._currentAlarm = aNewAlarm;
this._debugCurrentAlarm();
aSuccessCb(aNewId);
return;
}
// If the new alarm is earlier than the current alarm, swap them and
// push the previous alarm back to the queue.
let alarmQueue = this._alarmQueue;
let currentAlarmTime = this._getAlarmTime(this._currentAlarm);
if (aNewAlarmTime < currentAlarmTime) {
alarmQueue.unshift(this._currentAlarm);
this._currentAlarm = aNewAlarm;
this._debugCurrentAlarm();
aSuccessCb(aNewId);
return;
}
// If the new alarm is earlier than the current alarm, swap them and
// push the previous alarm back to the queue.
let alarmQueue = this._alarmQueue;
let currentAlarmTime = this._getAlarmTime(this._currentAlarm);
if (newAlarmTime < currentAlarmTime) {
alarmQueue.unshift(this._currentAlarm);
this._currentAlarm = aNewAlarm;
this._debugCurrentAlarm();
aSuccessCb(aNewId);
return;
}
// Push the new alarm in the queue.
alarmQueue.push(aNewAlarm);
alarmQueue.sort(this._sortAlarmByTimeStamps.bind(this));
this._debugCurrentAlarm();
aSuccessCb(aNewId);
}.bind(this),
function addErrorCb(aErrorMsg) {
aErrorCb(aErrorMsg);
}.bind(this));
// Push the new alarm in the queue.
alarmQueue.push(aNewAlarm);
alarmQueue.sort(this._sortAlarmByTimeStamps.bind(this));
this._debugCurrentAlarm();
aSuccessCb(aNewId);
},
/*

Просмотреть файл

@ -46,6 +46,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "secMan",
"@mozilla.org/scriptsecuritymanager;1",
"nsIScriptSecurityManager");
XPCOMUtils.defineLazyServiceGetter(this, "powerManagerService",
"@mozilla.org/power/powermanagerservice;1",
"nsIPowerManagerService");
XPCOMUtils.defineLazyModuleGetter(this, "AlarmService",
"resource://gre/modules/AlarmService.jsm");
@ -595,7 +599,7 @@ this.RequestSyncService = {
// Storing the requestID into the task for the callback.
this.storePendingRequest(task, aTarget, aData.requestID);
this.timeout(task);
this.timeout(task, null);
},
// We cannot expose the full internal object to content but just a subset.
@ -681,7 +685,7 @@ this.RequestSyncService = {
this._afterSchedulingTasks.push(aCb);
},
timeout: function(aObj) {
timeout: function(aObj, aWakeLock) {
debug("timeout");
if (this._activeTask) {
@ -690,6 +694,7 @@ this.RequestSyncService = {
if (this._queuedTasks.indexOf(aObj) == -1) {
this._queuedTasks.push(aObj);
}
this.maybeReleaseWakeLock(aWakeLock);
return;
}
@ -697,7 +702,9 @@ this.RequestSyncService = {
if (!app) {
dump("ERROR!! RequestSyncService - Failed to retrieve app data from a principal.\n");
aObj.active = false;
this.updateObjectInDB(aObj);
this.updateObjectInDB(aObj, () => {
this.maybeReleaseWakeLock(aWakeLock);
});
return;
}
@ -706,20 +713,25 @@ this.RequestSyncService = {
// Maybe need to be rescheduled?
if (this.hasPendingMessages('request-sync', manifestURL, pageURL)) {
this.scheduleTimer(aObj);
this.scheduleTimer(aObj, () => {
this.maybeReleaseWakeLock(aWakeLock);
});
return;
}
this.removeTimer(aObj);
this._activeTask = aObj;
if (!manifestURL || !pageURL) {
dump("ERROR!! RequestSyncService - Failed to create URI for the page or the manifest\n");
aObj.active = false;
this.updateObjectInDB(aObj);
this.updateObjectInDB(aObj, () => {
this.maybeReleaseWakeLock(aWakeLock);
});
return;
}
this._activeTask = aObj;
// We don't want to run more than 1 task at the same time. We do this using
// the promise created by sendMessage(). But if the task takes more than
// RSYNC_OPERATION_TIMEOUT millisecs, we have to ignore the promise and
@ -727,6 +739,14 @@ this.RequestSyncService = {
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
// We need a wakelock to keep the device alive and we want to release it
// only when all the steps are fully completely. This can involve calling
// timeout() again if we have something in _queuedTasks. In this scenario
// we want to reuse the same wakelock and we receive it as param.
// The Wakelock is passed to operationCompleted() because we want to wait
// until the data is written into IDB and maybe until all the pending next
// tasks are executed too.
let wakeLock = aWakeLock ? aWakeLock : powerManagerService.newWakeLock("cpu");
let done = false;
let self = this;
function taskCompleted() {
@ -734,7 +754,7 @@ this.RequestSyncService = {
if (!done) {
done = true;
self.operationCompleted();
self.operationCompleted(wakeLock);
}
timer.cancel();
@ -768,11 +788,12 @@ this.RequestSyncService = {
});
},
operationCompleted: function() {
operationCompleted: function(aWakeLock) {
debug("operationCompleted");
if (!this._activeTask) {
dump("ERROR!! RequestSyncService - OperationCompleted called without an active task\n");
aWakeLock.unlock();
return;
}
@ -790,25 +811,26 @@ this.RequestSyncService = {
this.updateObjectInDB(this._activeTask, function() {
if (!this._activeTask.data.oneShot) {
this.scheduleTimer(this._activeTask, function() {
this.processNextTask();
this.processNextTask(aWakeLock);
}.bind(this));
} else {
this.processNextTask();
this.processNextTask(aWakeLock);
}
}.bind(this));
},
processNextTask: function() {
processNextTask: function(aWakeLock) {
debug("processNextTask");
this._activeTask = null;
if (this._queuedTasks.length == 0) {
aWakeLock.unlock();
return;
}
let task = this._queuedTasks.shift();
this.timeout(task);
this.timeout(task, aWakeLock);
},
hasPendingMessages: function(aMessageName, aManifestURL, aPageURL) {
@ -932,7 +954,7 @@ this.RequestSyncService = {
AlarmService.add(
{ date: new Date(Date.now() + interval * 1000),
ignoreTimezone: false },
() => this.timeout(aObj),
() => this.timeout(aObj, null),
function(aTimerId) {
this._timers[aObj.dbKey] = aTimerId;
aCb();
@ -968,6 +990,12 @@ this.RequestSyncService = {
let requests = this._pendingRequests[aObj.dbKey];
delete this._pendingRequests[aObj.dbKey];
return requests;
},
maybeReleaseWakeLock: function(aWakeLock) {
if (aWakeLock) {
aWakeLock.unlock();
}
}
}