gecko/dom/settings/SettingsService.js
Alexandre Lissy b72fa97718 Bug 1107329 - Add locks tasks queue vivacity checking. r=qdot
To help diagnosing further issues with the Settings API getting blocked,
we add some tracking of the tasks and locks queue vivacity. We ensure to
keep track of the last lock id at the head of the queue, and we verify
whether it does not stays on top of it for too long.
2014-12-24 01:30:00 -05:00

319 lines
11 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import('resource://gre/modules/SettingsRequestManager.jsm');
/* static functions */
let DEBUG = false;
let VERBOSE = false;
try {
DEBUG =
Services.prefs.getBoolPref("dom.mozSettings.SettingsService.debug.enabled");
VERBOSE =
Services.prefs.getBoolPref("dom.mozSettings.SettingsService.verbose.enabled");
} catch (ex) { }
function debug(s) {
dump("-*- SettingsService: " + s + "\n");
}
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
XPCOMUtils.defineLazyServiceGetter(this, "mrm",
"@mozilla.org/memory-reporter-manager;1",
"nsIMemoryReporterManager");
const nsIClassInfo = Ci.nsIClassInfo;
const kXpcomShutdownObserverTopic = "xpcom-shutdown";
const SETTINGSSERVICELOCK_CONTRACTID = "@mozilla.org/settingsServiceLock;1";
const SETTINGSSERVICELOCK_CID = Components.ID("{d7a395a0-e292-11e1-834e-1761d57f5f99}");
const nsISettingsServiceLock = Ci.nsISettingsServiceLock;
function makeSettingsServiceRequest(aCallback, aName, aValue) {
return {
callback: aCallback,
name: aName,
value: aValue
};
};
function SettingsServiceLock(aSettingsService, aTransactionCallback) {
if (VERBOSE) debug("settingsServiceLock constr!");
this._open = true;
this._settingsService = aSettingsService;
this._id = uuidgen.generateUUID().toString();
this._transactionCallback = aTransactionCallback;
this._requests = {};
let closeHelper = function() {
if (VERBOSE) debug("closing lock " + this._id);
this._open = false;
this.runOrFinalizeQueries();
}.bind(this);
let msgs = ["Settings:Get:OK", "Settings:Get:KO",
"Settings:Clear:OK", "Settings:Clear:KO",
"Settings:Set:OK", "Settings:Set:KO",
"Settings:Finalize:OK", "Settings:Finalize:KO"];
for (let msg in msgs) {
cpmm.addMessageListener(msgs[msg], this);
}
let createLockPayload = {
lockID: this._id,
isServiceLock: true,
windowID: undefined,
lockStack: (new Error).stack
};
cpmm.sendAsyncMessage("Settings:CreateLock",
createLockPayload,
undefined,
Services.scriptSecurityManager.getSystemPrincipal());
Services.tm.currentThread.dispatch(closeHelper, Ci.nsIThread.DISPATCH_NORMAL);
}
SettingsServiceLock.prototype = {
get closed() {
return !this._open;
},
runOrFinalizeQueries: function() {
if (!this._requests || Object.keys(this._requests).length == 0) {
this._settingsService.unregisterLock(this._id);
cpmm.sendAsyncMessage("Settings:Finalize", {lockID: this._id}, undefined, Services.scriptSecurityManager.getSystemPrincipal());
} else {
cpmm.sendAsyncMessage("Settings:Run", {lockID: this._id}, undefined, Services.scriptSecurityManager.getSystemPrincipal());
}
},
receiveMessage: function(aMessage) {
let msg = aMessage.data;
// SettingsRequestManager broadcasts changes to all locks in the child. If
// our lock isn't being addressed, just return.
if(msg.lockID != this._id) {
return;
}
if (VERBOSE) debug("receiveMessage (" + this._id + "): " + aMessage.name);
// Finalizing a transaction does not return a request ID since we are
// supposed to fire callbacks.
if (!msg.requestID) {
switch (aMessage.name) {
case "Settings:Finalize:OK":
if (VERBOSE) debug("Lock finalize ok!");
this.callTransactionHandle();
break;
case "Settings:Finalize:KO":
if (DEBUG) debug("Lock finalize failed!");
this.callAbort();
break;
default:
if (DEBUG) debug("Message type " + aMessage.name + " is missing a requestID");
}
return;
}
let req = this._requests[msg.requestID];
if (!req) {
if (DEBUG) debug("Matching request not found.");
return;
}
delete this._requests[msg.requestID];
switch (aMessage.name) {
case "Settings:Get:OK":
this._open = true;
let settings_names = Object.keys(msg.settings);
if (settings_names.length > 0) {
let name = settings_names[0];
if (DEBUG && settings_names.length > 1) {
debug("Warning: overloaded setting:" + name);
}
let result = msg.settings[name];
this.callHandle(req.callback, name, result);
} else {
this.callHandle(req.callback, req.name, null);
}
this._open = false;
break;
case "Settings:Set:OK":
this._open = true;
// We don't pass values back from sets in SettingsManager...
this.callHandle(req.callback, req.name, req.value);
this._open = false;
break;
case "Settings:Get:KO":
case "Settings:Set:KO":
if (DEBUG) debug("error:" + msg.errorMsg);
this.callError(req.callback, msg.error);
break;
default:
if (DEBUG) debug("Wrong message: " + aMessage.name);
}
this.runOrFinalizeQueries();
},
get: function get(aName, aCallback) {
if (VERBOSE) debug("get (" + this._id + "): " + aName);
if (!this._open) {
if (DEBUG) debug("Settings lock not open!\n");
throw Components.results.NS_ERROR_ABORT;
}
let reqID = uuidgen.generateUUID().toString();
this._requests[reqID] = makeSettingsServiceRequest(aCallback, aName);
cpmm.sendAsyncMessage("Settings:Get", {requestID: reqID,
lockID: this._id,
name: aName},
undefined,
Services.scriptSecurityManager.getSystemPrincipal());
},
set: function set(aName, aValue, aCallback) {
if (VERBOSE) debug("set: " + aName + " " + aValue);
if (!this._open) {
throw "Settings lock not open";
}
let reqID = uuidgen.generateUUID().toString();
this._requests[reqID] = makeSettingsServiceRequest(aCallback, aName, aValue);
let settings = {};
settings[aName] = aValue;
cpmm.sendAsyncMessage("Settings:Set", {requestID: reqID,
lockID: this._id,
settings: settings},
undefined,
Services.scriptSecurityManager.getSystemPrincipal());
},
callHandle: function callHandle(aCallback, aName, aValue) {
try {
aCallback && aCallback.handle ? aCallback.handle(aName, aValue) : null;
} catch (e) {
if (DEBUG) debug("settings 'handle' callback threw an exception, dropping: " + e + "\n");
}
},
callAbort: function callAbort(aCallback, aMessage) {
try {
aCallback && aCallback.handleAbort ? aCallback.handleAbort(aMessage) : null;
} catch (e) {
if (DEBUG) debug("settings 'abort' callback threw an exception, dropping: " + e + "\n");
}
},
callError: function callError(aCallback, aMessage) {
try {
aCallback && aCallback.handleError ? aCallback.handleError(aMessage) : null;
} catch (e) {
if (DEBUG) debug("settings 'error' callback threw an exception, dropping: " + e + "\n");
}
},
callTransactionHandle: function callTransactionHandle() {
try {
this._transactionCallback && this._transactionCallback.handle ? this._transactionCallback.handle() : null;
} catch (e) {
if (DEBUG) debug("settings 'Transaction handle' callback threw an exception, dropping: " + e + "\n");
}
},
classID : SETTINGSSERVICELOCK_CID,
QueryInterface : XPCOMUtils.generateQI([nsISettingsServiceLock])
};
const SETTINGSSERVICE_CID = Components.ID("{f656f0c0-f776-11e1-a21f-0800200c9a66}");
function SettingsService()
{
if (VERBOSE) debug("settingsService Constructor");
this._locks = [];
this._createdLocks = 0;
this._unregisteredLocks = 0;
this.init();
}
SettingsService.prototype = {
init: function() {
Services.obs.addObserver(this, kXpcomShutdownObserverTopic, false);
mrm.registerStrongReporter(this);
},
uninit: function() {
Services.obs.removeObserver(this, kXpcomShutdownObserverTopic);
mrm.unregisterStrongReporter(this);
},
observe: function(aSubject, aTopic, aData) {
if (VERBOSE) debug("observe: " + aTopic);
if (aTopic === kXpcomShutdownObserverTopic) {
this.uninit();
}
},
createLock: function createLock(aCallback) {
var lock = new SettingsServiceLock(this, aCallback);
this.registerLock(lock._id);
return lock;
},
registerLock: function(aLockID) {
this._locks.push(aLockID);
this._createdLocks++;
},
unregisterLock: function(aLockID) {
let lock_index = this._locks.indexOf(aLockID);
if (lock_index != -1) {
if (VERBOSE) debug("Unregistering lock " + aLockID);
this._locks.splice(lock_index, 1);
this._unregisteredLocks++;
}
},
collectReports: function(aCallback, aData, aAnonymize) {
aCallback.callback("",
"settings-service-locks/alive",
Ci.nsIMemoryReporter.KIND_OTHER,
Ci.nsIMemoryReporter.UNITS_COUNT,
this._locks.length,
"The number of service locks that are currently alives.",
aData);
aCallback.callback("",
"settings-service-locks/created",
Ci.nsIMemoryReporter.KIND_OTHER,
Ci.nsIMemoryReporter.UNITS_COUNT,
this._createdLocks,
"The number of service locks that were created.",
aData);
aCallback.callback("",
"settings-service-locks/deleted",
Ci.nsIMemoryReporter.KIND_OTHER,
Ci.nsIMemoryReporter.UNITS_COUNT,
this._unregisteredLocks,
"The number of service locks that were deleted.",
aData);
},
classID : SETTINGSSERVICE_CID,
QueryInterface : XPCOMUtils.generateQI([Ci.nsISettingsService,
Ci.nsIObserver,
Ci.nsIMemoryReporter])
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SettingsService, SettingsServiceLock]);