зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to autoland a=merge
This commit is contained in:
Коммит
6b41a63046
|
@ -154,20 +154,6 @@
|
|||
</popupnotificationcontent>
|
||||
</popupnotification>
|
||||
|
||||
<popupnotification id="appMenu-update-other-instance-notification"
|
||||
popupid="update-other-instance"
|
||||
data-lazy-l10n-id="appmenu-update-other-instance"
|
||||
data-l10n-attrs="buttonlabel, buttonaccesskey, secondarybuttonlabel, secondarybuttonaccesskey"
|
||||
closebuttonhidden="true"
|
||||
dropmarkerhidden="true"
|
||||
checkboxhidden="true"
|
||||
buttonhighlight="true"
|
||||
hidden="true">
|
||||
<popupnotificationcontent id="update-other-instance-notification-content" orient="vertical">
|
||||
<description id="update-other-instance-description" data-lazy-l10n-id="appmenu-update-other-instance-message"></description>
|
||||
</popupnotificationcontent>
|
||||
</popupnotification>
|
||||
|
||||
<popupnotification id="appMenu-addon-installed-notification"
|
||||
popupid="addon-installed"
|
||||
closebuttonhidden="true"
|
||||
|
|
|
@ -55,61 +55,6 @@ add_task(async function init() {
|
|||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Override our binary path so that the update semaphore doesn't think more
|
||||
* than one instance of this test is running.
|
||||
* This is a heavily pared down copy of the function in xpcshellUtilsAUS.js.
|
||||
*/
|
||||
function adjustGeneralPaths() {
|
||||
let dirProvider = {
|
||||
getFile(aProp, aPersistent) {
|
||||
// Set the value of persistent to false so when this directory provider is
|
||||
// unregistered it will revert back to the original provider.
|
||||
aPersistent.value = false;
|
||||
// The semaphore only needs XRE_EXECUTABLE_FILE, so that's all we need to
|
||||
// override, we won't bother handling anything else.
|
||||
if (aProp == XRE_EXECUTABLE_FILE) {
|
||||
// The temp directory that the mochitest runner creates is unique per
|
||||
// test, so its path can serve to provide the unique key that the
|
||||
// update semaphore requires (it doesn't need for this to be the actual
|
||||
// path to any real file, it's only used as an opaque string).
|
||||
let tempPath = gEnv.get("MOZ_PROCESS_LOG");
|
||||
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
file.initWithPath(tempPath);
|
||||
return file;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIDirectoryServiceProvider"]),
|
||||
};
|
||||
|
||||
let ds = Services.dirsvc.QueryInterface(Ci.nsIDirectoryService);
|
||||
try {
|
||||
ds.QueryInterface(Ci.nsIProperties).undefine(XRE_EXECUTABLE_FILE);
|
||||
} catch (_ex) {
|
||||
// We only override one property, so we have nothing to do if that fails.
|
||||
return;
|
||||
}
|
||||
ds.registerProvider(dirProvider);
|
||||
registerCleanupFunction(() => {
|
||||
ds.unregisterProvider(dirProvider);
|
||||
// Reset the update semaphore once again so that we know the semaphore we're
|
||||
// interested in here will be closed properly (normally that happens during
|
||||
// XPCOM shutdown, but that isn't consistent during tests).
|
||||
let syncManager = Cc[
|
||||
"@mozilla.org/updates/update-sync-manager;1"
|
||||
].getService(Ci.nsIUpdateSyncManager);
|
||||
syncManager.resetSemaphore();
|
||||
});
|
||||
|
||||
// Now that we've overridden the directory provider, the name of the update
|
||||
// semaphore needs to be changed to match the overridden path.
|
||||
let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
|
||||
Ci.nsIUpdateSyncManager
|
||||
);
|
||||
syncManager.resetSemaphore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a mock app update. Adapted from runAboutDialogUpdateTest:
|
||||
* https://searchfox.org/mozilla-central/source/toolkit/mozapps/update/tests/browser/head.js
|
||||
|
@ -126,7 +71,6 @@ async function initUpdate(params) {
|
|||
],
|
||||
});
|
||||
|
||||
adjustGeneralPaths();
|
||||
await setupTestUpdater();
|
||||
|
||||
let queryString = params.queryString ? params.queryString : "";
|
||||
|
|
|
@ -32,14 +32,6 @@ appmenu-update-restart =
|
|||
.secondarybuttonlabel = Not Now
|
||||
.secondarybuttonaccesskey = N
|
||||
appmenu-update-restart-message = After a quick restart, { -brand-shorter-name } will restore all your open tabs and windows that are not in Private Browsing mode.
|
||||
appmenu-update-other-instance =
|
||||
.label = { -brand-shorter-name } is unable to automatically update to the latest version.
|
||||
.buttonlabel = Update { -brand-shorter-name } anyway
|
||||
.buttonaccesskey = U
|
||||
.secondarybuttonlabel = Not Now
|
||||
.secondarybuttonaccesskey = N
|
||||
appmenu-update-other-instance-message = A new { -brand-shorter-name } update is available, but it can’t be installed because another copy of { -brand-shorter-name } is running. Close it to continue the update, or choose to update anyway (the other copy may not work correctly until you restart it).
|
||||
|
||||
appmenu-addon-private-browsing-installed =
|
||||
.buttonlabel = Okay, Got It
|
||||
.buttonaccesskey = O
|
||||
|
|
|
@ -330,18 +330,7 @@ class AppUpdater {
|
|||
case Cr.NS_OK:
|
||||
this.aus.removeDownloadListener(this);
|
||||
if (this.updateStagingEnabled) {
|
||||
// It could be that another instance was started during the download,
|
||||
// and if that happened, then we actually should not advance to the
|
||||
// STAGING status because the staging process isn't really happening
|
||||
// until that instance exits (or we time out waiting).
|
||||
if (this.aus.isOtherInstanceHandlingUpdates) {
|
||||
this._setStatus(AppUpdater.OTHER_INSTANCE_HANDLING_UPDATES);
|
||||
} else {
|
||||
this._setStatus(AppUpdater.STATUS.STAGING);
|
||||
}
|
||||
// But we should register the staging observer in either case, because
|
||||
// if we do time out waiting for the other instance to exit, then
|
||||
// staging really will start at that point.
|
||||
this._setStatus(AppUpdater.STATUS.STAGING);
|
||||
this._awaitStagingComplete();
|
||||
} else {
|
||||
this._awaitDownloadComplete();
|
||||
|
|
|
@ -69,7 +69,6 @@
|
|||
#PanelUI-menu-button[badge-status="update-available"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
|
||||
#PanelUI-menu-button[badge-status="update-downloading"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
|
||||
#PanelUI-menu-button[badge-status="update-manual"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
|
||||
#PanelUI-menu-button[badge-status="update-other-instance"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
|
||||
#PanelUI-menu-button[badge-status="update-restart"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
|
||||
#PanelUI-menu-button[badge-status="update-unsupported"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
|
||||
border-radius: 50%;
|
||||
|
@ -84,7 +83,6 @@
|
|||
#PanelUI-menu-button[badge-status="update-available"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
|
||||
#PanelUI-menu-button[badge-status="update-downloading"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
|
||||
#PanelUI-menu-button[badge-status="update-manual"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
|
||||
#PanelUI-menu-button[badge-status="update-other-instance"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
|
||||
#PanelUI-menu-button[badge-status="update-restart"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
|
||||
background: #74BF43 url(chrome://browser/skin/update-badge.svg) no-repeat center;
|
||||
}
|
||||
|
@ -96,7 +94,6 @@
|
|||
.panel-banner-item[notificationid="update-available"]::after,
|
||||
.panel-banner-item[notificationid="update-downloading"]::after,
|
||||
.panel-banner-item[notificationid="update-manual"]::after,
|
||||
.panel-banner-item[notificationid="update-other-instance"]::after,
|
||||
.panel-banner-item[notificationid="update-restart"]::after {
|
||||
background: #74BF43 url(chrome://browser/skin/update-badge.svg) no-repeat center;
|
||||
border-radius: 50%;
|
||||
|
|
|
@ -415,7 +415,6 @@ html|*#webRTC-previewVideo {
|
|||
.popup-notification-icon[popupid="update-available"],
|
||||
.popup-notification-icon[popupid="update-downloading"],
|
||||
.popup-notification-icon[popupid="update-manual"],
|
||||
.popup-notification-icon[popupid="update-other-instance"],
|
||||
.popup-notification-icon[popupid="update-restart"] {
|
||||
background: #74BF43 url(chrome://browser/skin/notification-icons/update.svg) no-repeat center;
|
||||
border-radius: 50%;
|
||||
|
|
|
@ -302,7 +302,6 @@ toolbar[brighttext] {
|
|||
#PanelUI-menu-button[badge-status="update-available"],
|
||||
#PanelUI-menu-button[badge-status="update-downloading"],
|
||||
#PanelUI-menu-button[badge-status="update-manual"],
|
||||
#PanelUI-menu-button[badge-status="update-other-instance"],
|
||||
#PanelUI-menu-button[badge-status="update-restart"] {
|
||||
list-style-image: url("chrome://browser/skin/menu-badged.svg");
|
||||
}
|
||||
|
|
|
@ -169,16 +169,6 @@ if defined('MOZ_UPDATER') and not IS_ANDROID:
|
|||
'type': 'nsUpdateProcessor',
|
||||
'headers': ['/toolkit/xre/nsUpdateDriver.h'],
|
||||
},
|
||||
{
|
||||
'cid': '{cf4c4487-66d9-4e18-a2e9-39002245332f}',
|
||||
'contract_ids': ['@mozilla.org/updates/update-sync-manager;1'],
|
||||
'type': 'nsUpdateSyncManager',
|
||||
'singleton': True,
|
||||
'headers': ['/toolkit/xre/nsUpdateSyncManager.h'],
|
||||
'constructor': 'nsUpdateSyncManager::GetSingleton',
|
||||
'processes': ProcessSelector.MAIN_PROCESS_ONLY,
|
||||
'categories': {'xpcom-startup': 'nsUpdateSyncManager'},
|
||||
},
|
||||
]
|
||||
|
||||
if not defined('MOZ_DISABLE_PARENTAL_CONTROLS'):
|
||||
|
|
|
@ -6778,10 +6778,10 @@
|
|||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com", "bytesized@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "categorical",
|
||||
"bug_numbers": [893505, 1521427, 1553982],
|
||||
"bug_numbers": [893505, 1521427],
|
||||
"releaseChannelCollection": "opt-out",
|
||||
"description": "Update: the application update doorhanger type that was displayed.",
|
||||
"labels": ["restart", "available", "manual", "unsupported", "otherinstance"]
|
||||
"labels": ["restart", "available", "manual", "unsupported"]
|
||||
},
|
||||
"UPDATE_NOTIFICATION_BADGE_SHOWN": {
|
||||
"record_in_processes": ["main"],
|
||||
|
@ -6789,10 +6789,10 @@
|
|||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com", "bytesized@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "categorical",
|
||||
"bug_numbers": [893505, 1365204, 1521427, 1553982],
|
||||
"bug_numbers": [893505, 1365204, 1521427],
|
||||
"releaseChannelCollection": "opt-out",
|
||||
"description": "Update: the application update badge type that was displayed.",
|
||||
"labels": ["restart", "available", "manual", "unsupported", "otherinstance"]
|
||||
"labels": ["restart", "available", "manual", "unsupported"]
|
||||
},
|
||||
"UPDATE_NOTIFICATION_DISMISSED": {
|
||||
"record_in_processes": ["main"],
|
||||
|
@ -6800,10 +6800,10 @@
|
|||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com", "bytesized@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "categorical",
|
||||
"bug_numbers": [893505, 1521427, 1553982],
|
||||
"bug_numbers": [893505, 1521427],
|
||||
"releaseChannelCollection": "opt-out",
|
||||
"description": "Update: the dismiss action was executed for this application update doorhanger type.",
|
||||
"labels": ["restart", "available", "manual", "unsupported", "otherinstance"]
|
||||
"labels": ["restart", "available", "manual", "unsupported"]
|
||||
},
|
||||
"UPDATE_NOTIFICATION_MAIN_ACTION_DOORHANGER": {
|
||||
"record_in_processes": ["main"],
|
||||
|
@ -6811,10 +6811,10 @@
|
|||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com", "bytesized@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "categorical",
|
||||
"bug_numbers": [893505, 1521427, 1553982],
|
||||
"bug_numbers": [893505, 1521427],
|
||||
"releaseChannelCollection": "opt-out",
|
||||
"description": "Update: the main update action was initiated for this application update doorhanger type.",
|
||||
"labels": ["restart", "available", "manual", "unsupported", "otherinstance"]
|
||||
"labels": ["restart", "available", "manual", "unsupported"]
|
||||
},
|
||||
"UPDATE_NOTIFICATION_MAIN_ACTION_MENU": {
|
||||
"record_in_processes": ["main"],
|
||||
|
@ -6822,10 +6822,10 @@
|
|||
"alert_emails": ["application-update-telemetry-alerts@mozilla.com", "bytesized@mozilla.com"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "categorical",
|
||||
"bug_numbers": [893505, 1521427, 1553982],
|
||||
"bug_numbers": [893505, 1521427],
|
||||
"releaseChannelCollection": "opt-out",
|
||||
"description": "Update: the update action was initiated from the PanelUI application update menu item.",
|
||||
"labels": ["restart", "available", "manual", "unsupported", "otherinstance"]
|
||||
"labels": ["restart", "available", "manual", "unsupported"]
|
||||
},
|
||||
"UPDATE_CAN_USE_BITS_EXTERNAL": {
|
||||
"record_in_processes": ["main"],
|
||||
|
|
|
@ -10,9 +10,6 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|||
const { clearTimeout, setTimeout } = ChromeUtils.import(
|
||||
"resource://gre/modules/Timer.jsm"
|
||||
);
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
|
@ -20,13 +17,6 @@ ChromeUtils.defineModuleGetter(
|
|||
"resource://gre/modules/AppMenuNotifications.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(
|
||||
this,
|
||||
"AppUpdateService",
|
||||
"@mozilla.org/updates/update-service;1",
|
||||
"nsIApplicationUpdateService"
|
||||
);
|
||||
|
||||
const PREF_APP_UPDATE_UNSUPPORTED_URL = "app.update.unsupported.url";
|
||||
|
||||
// Setup the hamburger button badges for updates.
|
||||
|
@ -39,7 +29,7 @@ var UpdateListener = {
|
|||
|
||||
init() {
|
||||
// Persist the unsupported notification across sessions. If at some point an
|
||||
// update is found this pref is cleared and the notification won't be shown.
|
||||
// update is found this pref is cleared and the notifcation won't be shown.
|
||||
let url = Services.prefs.getCharPref(PREF_APP_UPDATE_UNSUPPORTED_URL, null);
|
||||
if (url) {
|
||||
this.showUpdateNotification("unsupported", true, true, win =>
|
||||
|
@ -126,9 +116,7 @@ var UpdateListener = {
|
|||
const addTelemetry = id => {
|
||||
// No telemetry for the "downloading" state.
|
||||
if (type !== "downloading") {
|
||||
// Histogram category labels can't have dashes in them.
|
||||
let telemetryType = type.replaceAll("-", "");
|
||||
Services.telemetry.getHistogramById(id).add(telemetryType);
|
||||
Services.telemetry.getHistogramById(id).add(type);
|
||||
}
|
||||
};
|
||||
let action = {
|
||||
|
@ -162,11 +150,8 @@ var UpdateListener = {
|
|||
}
|
||||
},
|
||||
|
||||
showRestartNotification(update, dismissed) {
|
||||
let notification = AppUpdateService.isOtherInstanceHandlingUpdates
|
||||
? "other-instance"
|
||||
: "restart";
|
||||
this.showUpdateNotification(notification, true, dismissed, () =>
|
||||
showRestartNotification(dismissed) {
|
||||
this.showUpdateNotification("restart", true, dismissed, () =>
|
||||
this.requestRestart()
|
||||
);
|
||||
},
|
||||
|
@ -177,7 +162,10 @@ var UpdateListener = {
|
|||
false,
|
||||
dismissed,
|
||||
() => {
|
||||
AppUpdateService.downloadUpdate(update, true);
|
||||
let updateService = Cc[
|
||||
"@mozilla.org/updates/update-service;1"
|
||||
].getService(Ci.nsIApplicationUpdateService);
|
||||
updateService.downloadUpdate(update, true);
|
||||
},
|
||||
doc => this.replaceReleaseNotes(doc, update, "updateAvailableWhatsNew")
|
||||
);
|
||||
|
@ -265,22 +253,19 @@ var UpdateListener = {
|
|||
|
||||
if (badgeWaitTimeMs < doorhangerWaitTimeMs) {
|
||||
this.addTimeout(badgeWaitTimeMs, () => {
|
||||
// Skip the badge if we're waiting for another instance.
|
||||
if (!AppUpdateService.isOtherInstanceHandlingUpdates) {
|
||||
this.showRestartNotification(update, true);
|
||||
}
|
||||
this.showRestartNotification(true);
|
||||
|
||||
// doorhangerWaitTimeMs is relative to when we initially received
|
||||
// the event. Since we've already waited badgeWaitTimeMs, subtract
|
||||
// that from doorhangerWaitTimeMs.
|
||||
let remainingTime = doorhangerWaitTimeMs - badgeWaitTimeMs;
|
||||
this.addTimeout(remainingTime, () => {
|
||||
this.showRestartNotification(update, false);
|
||||
this.showRestartNotification(false);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.addTimeout(doorhangerWaitTimeMs, () => {
|
||||
this.showRestartNotification(update, false);
|
||||
this.showRestartNotification(false);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -52,12 +52,6 @@ const PREF_APP_UPDATE_BITS_ENABLED = "app.update.BITS.enabled";
|
|||
const PREF_APP_UPDATE_CANCELATIONS = "app.update.cancelations";
|
||||
const PREF_APP_UPDATE_CANCELATIONS_OSX = "app.update.cancelations.osx";
|
||||
const PREF_APP_UPDATE_CANCELATIONS_OSX_MAX = "app.update.cancelations.osx.max";
|
||||
const PREF_APP_UPDATE_CHECK_ONLY_INSTANCE_ENABLED =
|
||||
"app.update.checkOnlyInstance.enabled";
|
||||
const PREF_APP_UPDATE_CHECK_ONLY_INSTANCE_INTERVAL =
|
||||
"app.update.checkOnlyInstance.interval";
|
||||
const PREF_APP_UPDATE_CHECK_ONLY_INSTANCE_TIMEOUT =
|
||||
"app.update.checkOnlyInstance.timeout";
|
||||
const PREF_APP_UPDATE_DISABLEDFORTESTING = "app.update.disabledForTesting";
|
||||
const PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS = "app.update.download.attempts";
|
||||
const PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS = "app.update.download.maxAttempts";
|
||||
|
@ -251,18 +245,6 @@ const XML_SAVER_INTERVAL_MS = 200;
|
|||
// update before proceeding anyway.
|
||||
const LANGPACK_UPDATE_DEFAULT_TIMEOUT = 300000;
|
||||
|
||||
// Interval between rechecks for other instances after the initial check finds
|
||||
// at least one other instance.
|
||||
const ONLY_INSTANCE_CHECK_DEFAULT_POLL_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
// Wait this long after detecting that another instance is running (having been
|
||||
// polling that entire time) before giving up and applying the update anyway.
|
||||
const ONLY_INSTANCE_CHECK_DEFAULT_TIMEOUT_MS = 6 * 60 * 60 * 1000; // 6 hours
|
||||
|
||||
// The other instance check timeout can be overridden via a pref, but we limit
|
||||
// that value to this so that the pref can't effectively disable the feature.
|
||||
const ONLY_INSTANCE_CHECK_MAX_TIMEOUT_MS = 2 * 24 * 60 * 60 * 1000; // 2 days
|
||||
|
||||
// Object to keep track of the current phase of the update and whether there
|
||||
// has been a write failure for the phase so only one telemetry ping is made
|
||||
// for the phase.
|
||||
|
@ -334,106 +316,6 @@ function unwrap(obj) {
|
|||
*/
|
||||
const LangPackUpdates = new WeakMap();
|
||||
|
||||
/**
|
||||
* When we're polling to see if other running instances of the application have
|
||||
* exited, there's no need to ever start polling again in parallel. To prevent
|
||||
* doing that, we keep track of the promise that resolves when polling completes
|
||||
* and return that if a second simultaneous poll is requested, so that the
|
||||
* multiple callers end up waiting for the same promise to resolve.
|
||||
*/
|
||||
let gOtherInstancePollPromise;
|
||||
|
||||
/**
|
||||
* Query the update sync manager to see if another instance of this same
|
||||
* installation of this application is currently running, under the context of
|
||||
* any operating system user (not just the current one).
|
||||
* This function immediately returns the current, instantaneous status of any
|
||||
* other instances.
|
||||
*
|
||||
* @return true if at least one other instance is running, false if not
|
||||
*/
|
||||
function isOtherInstanceRunning(callback) {
|
||||
const checkEnabled = Services.prefs.getBoolPref(
|
||||
PREF_APP_UPDATE_CHECK_ONLY_INSTANCE_ENABLED,
|
||||
true
|
||||
);
|
||||
if (!checkEnabled) {
|
||||
LOG("isOtherInstanceRunning - disabled by pref, skipping check");
|
||||
return false;
|
||||
}
|
||||
|
||||
let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
|
||||
Ci.nsIUpdateSyncManager
|
||||
);
|
||||
return syncManager.isOtherInstanceRunning();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the update sync manager to see if another instance of this same
|
||||
* installation of this application is currently running, under the context of
|
||||
* any operating system user (not just the one running this instance).
|
||||
* This function polls for the status of other instances continually
|
||||
* (asynchronously) until either none exist or a timeout expires.
|
||||
*
|
||||
* @return a Promise that resolves with false if at any point during polling no
|
||||
* other instances can be found, or resolves with true if the timeout
|
||||
* expires when other instances are still running
|
||||
*/
|
||||
function waitForOtherInstances() {
|
||||
// If we're already in the middle of a poll, reuse it rather than start again.
|
||||
if (gOtherInstancePollPromise) {
|
||||
return gOtherInstancePollPromise;
|
||||
}
|
||||
|
||||
let timeout = Services.prefs.getIntPref(
|
||||
PREF_APP_UPDATE_CHECK_ONLY_INSTANCE_TIMEOUT,
|
||||
ONLY_INSTANCE_CHECK_DEFAULT_TIMEOUT_MS
|
||||
);
|
||||
// Don't allow the pref to set a super high timeout and break this feature.
|
||||
if (timeout > ONLY_INSTANCE_CHECK_MAX_TIMEOUT_MS) {
|
||||
timeout = ONLY_INSTANCE_CHECK_MAX_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
let interval = Services.prefs.getIntPref(
|
||||
PREF_APP_UPDATE_CHECK_ONLY_INSTANCE_INTERVAL,
|
||||
ONLY_INSTANCE_CHECK_DEFAULT_POLL_INTERVAL_MS
|
||||
);
|
||||
// Don't allow an interval longer than the timeout.
|
||||
interval = Math.min(interval, timeout);
|
||||
|
||||
let iterations = 0;
|
||||
const maxIterations = Math.ceil(timeout / interval);
|
||||
|
||||
gOtherInstancePollPromise = new Promise(function(resolve, reject) {
|
||||
let poll = function() {
|
||||
iterations++;
|
||||
if (!isOtherInstanceRunning()) {
|
||||
LOG("waitForOtherInstances - no other instances found, exiting");
|
||||
resolve(false);
|
||||
gOtherInstancePollPromise = undefined;
|
||||
} else if (iterations >= maxIterations) {
|
||||
LOG(
|
||||
"waitForOtherInstances - timeout expired while other instances " +
|
||||
"are still running"
|
||||
);
|
||||
resolve(true);
|
||||
gOtherInstancePollPromise = undefined;
|
||||
} else if (iterations + 1 == maxIterations && timeout % interval != 0) {
|
||||
// In case timeout isn't a multiple of interval, set the next timeout
|
||||
// for the remainder of the time rather than for the usual interval.
|
||||
setTimeout(poll, timeout % interval);
|
||||
} else {
|
||||
setTimeout(poll, interval);
|
||||
}
|
||||
};
|
||||
|
||||
LOG("waitForOtherInstances - beginning polling");
|
||||
poll();
|
||||
});
|
||||
|
||||
return gOtherInstancePollPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests to make sure that we can write to a given directory.
|
||||
*
|
||||
|
@ -2464,7 +2346,7 @@ UpdateService.prototype = {
|
|||
"UpdateService:_postUpdateProcessing - unable to apply " +
|
||||
"updates... returning early"
|
||||
);
|
||||
if (hasUpdateMutex()) {
|
||||
if (!this.isOtherInstanceHandlingUpdates) {
|
||||
// If the update is present in the update directory somehow,
|
||||
// it would prevent us from notifying the user of further updates.
|
||||
cleanupUpdate();
|
||||
|
@ -3012,8 +2894,6 @@ UpdateService.prototype = {
|
|||
);
|
||||
} else if (!hasUpdateMutex()) {
|
||||
AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_NO_MUTEX);
|
||||
} else if (isOtherInstanceRunning()) {
|
||||
AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_OTHER_INSTANCE);
|
||||
} else if (!this.canCheckForUpdates) {
|
||||
AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_UNABLE_TO_CHECK);
|
||||
}
|
||||
|
@ -3339,15 +3219,6 @@ UpdateService.prototype = {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (isOtherInstanceRunning()) {
|
||||
// This doesn't block update checks, but we will have to wait until either
|
||||
// the other instance is gone or we time out waiting for it.
|
||||
LOG(
|
||||
"UpdateService.canCheckForUpdates - another instance is holding the " +
|
||||
"semaphore, will need to wait for it prior to checking for updates"
|
||||
);
|
||||
}
|
||||
|
||||
LOG("UpdateService.canCheckForUpdates - able to check for updates");
|
||||
return true;
|
||||
},
|
||||
|
@ -3363,9 +3234,7 @@ UpdateService.prototype = {
|
|||
* See nsIUpdateService.idl
|
||||
*/
|
||||
get canApplyUpdates() {
|
||||
return (
|
||||
getCanApplyUpdates() && hasUpdateMutex() && !isOtherInstanceRunning()
|
||||
);
|
||||
return getCanApplyUpdates() && hasUpdateMutex();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -3379,7 +3248,7 @@ UpdateService.prototype = {
|
|||
* See nsIUpdateService.idl
|
||||
*/
|
||||
get isOtherInstanceHandlingUpdates() {
|
||||
return !hasUpdateMutex() || isOtherInstanceRunning();
|
||||
return !hasUpdateMutex();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -3551,7 +3420,7 @@ UpdateService.prototype = {
|
|||
this.canStageUpdates;
|
||||
LOG("Elevation required: " + this.elevationRequired);
|
||||
LOG(
|
||||
"Other instance of the application currently running: " +
|
||||
"Update being handled by other instance: " +
|
||||
this.isOtherInstanceHandlingUpdates
|
||||
);
|
||||
LOG("Downloading: " + !!this.isDownloading);
|
||||
|
@ -3994,7 +3863,7 @@ UpdateManager.prototype = {
|
|||
/**
|
||||
* See nsIUpdateService.idl
|
||||
*/
|
||||
refreshUpdateStatus: async function UM_refreshUpdateStatus() {
|
||||
refreshUpdateStatus: function UM_refreshUpdateStatus() {
|
||||
var update = this._readyUpdate;
|
||||
if (!update) {
|
||||
return;
|
||||
|
@ -4231,62 +4100,47 @@ Checker.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
waitForOtherInstances()
|
||||
.then(() => this.getUpdateURL(force))
|
||||
.then(url => {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
this.getUpdateURL(force).then(url => {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
// It's possible that another check was kicked off and that request sent
|
||||
// while we were waiting for other instances to exit here; if the other
|
||||
// instances were closed and also the other check was started during the
|
||||
// same interval between polls, then here we could now be about to start
|
||||
// a second overlapping check, which should not happen. So make sure we
|
||||
// don't have a request already active before we start a new one.
|
||||
if (this._request) {
|
||||
LOG(
|
||||
"Checker: checkForUpdates: check request already active, aborting"
|
||||
);
|
||||
return;
|
||||
}
|
||||
this._request = new XMLHttpRequest();
|
||||
this._request.open("GET", url, true);
|
||||
this._request.channel.notificationCallbacks = new CertUtils.BadCertHandler(
|
||||
false
|
||||
);
|
||||
// Prevent the request from reading from the cache.
|
||||
this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
|
||||
// Prevent the request from writing to the cache.
|
||||
this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
|
||||
// Disable cutting edge features, like TLS 1.3, where middleboxes might brick us
|
||||
this._request.channel.QueryInterface(
|
||||
Ci.nsIHttpChannelInternal
|
||||
).beConservative = true;
|
||||
|
||||
this._request = new XMLHttpRequest();
|
||||
this._request.open("GET", url, true);
|
||||
this._request.channel.notificationCallbacks = new CertUtils.BadCertHandler(
|
||||
false
|
||||
);
|
||||
// Prevent the request from reading from the cache.
|
||||
this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
|
||||
// Prevent the request from writing to the cache.
|
||||
this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
|
||||
// Disable cutting edge features, like TLS 1.3, where middleboxes might brick us
|
||||
this._request.channel.QueryInterface(
|
||||
Ci.nsIHttpChannelInternal
|
||||
).beConservative = true;
|
||||
this._request.overrideMimeType("text/xml");
|
||||
// The Cache-Control header is only interpreted by proxies and the
|
||||
// final destination. It does not help if a resource is already
|
||||
// cached locally.
|
||||
this._request.setRequestHeader("Cache-Control", "no-cache");
|
||||
// HTTP/1.0 servers might not implement Cache-Control and
|
||||
// might only implement Pragma: no-cache
|
||||
this._request.setRequestHeader("Pragma", "no-cache");
|
||||
|
||||
this._request.overrideMimeType("text/xml");
|
||||
// The Cache-Control header is only interpreted by proxies and the
|
||||
// final destination. It does not help if a resource is already
|
||||
// cached locally.
|
||||
this._request.setRequestHeader("Cache-Control", "no-cache");
|
||||
// HTTP/1.0 servers might not implement Cache-Control and
|
||||
// might only implement Pragma: no-cache
|
||||
this._request.setRequestHeader("Pragma", "no-cache");
|
||||
|
||||
var self = this;
|
||||
this._request.addEventListener("error", function(event) {
|
||||
self.onError(event);
|
||||
});
|
||||
this._request.addEventListener("load", function(event) {
|
||||
self.onLoad(event);
|
||||
});
|
||||
|
||||
LOG("Checker:checkForUpdates - sending request to: " + url);
|
||||
this._request.send(null);
|
||||
|
||||
this._callback = listener;
|
||||
var self = this;
|
||||
this._request.addEventListener("error", function(event) {
|
||||
self.onError(event);
|
||||
});
|
||||
this._request.addEventListener("load", function(event) {
|
||||
self.onLoad(event);
|
||||
});
|
||||
|
||||
LOG("Checker:checkForUpdates - sending request to: " + url);
|
||||
this._request.send(null);
|
||||
|
||||
this._callback = listener;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -5693,7 +5547,7 @@ Downloader.prototype = {
|
|||
let update = this._update;
|
||||
promiseLangPacksUpdated(update).then(() => {
|
||||
LOG(
|
||||
"Downloader:onStopRequest - Notifying observers that " +
|
||||
"UpdateManager:refreshUpdateStatus - Notifying observers that " +
|
||||
"an update was downloaded. topic: update-downloaded, status: " +
|
||||
update.state
|
||||
);
|
||||
|
|
|
@ -92,9 +92,6 @@ var AUSTLMY = {
|
|||
CHK_DISABLED_BY_POLICY: 37,
|
||||
// Update check failed due to write error
|
||||
CHK_ERR_WRITE_FAILURE: 38,
|
||||
// Update check was delayed because another instance of the application is
|
||||
// currently running
|
||||
CHK_OTHER_INSTANCE: 39,
|
||||
|
||||
/**
|
||||
* Submit a telemetry ping for the update check result code or a telemetry
|
||||
|
|
|
@ -425,35 +425,6 @@ interface nsIUpdateProcessor : nsISupports
|
|||
void fixUpdateDirectoryPerms(in boolean useServiceOnFailure);
|
||||
};
|
||||
|
||||
/**
|
||||
* Upon creation, which should happen early during startup, the sync manager
|
||||
* opens and locks a named semaphore. All other running instances of the same
|
||||
* installation of the app also open the same semaphore, so we can use it to
|
||||
* determine whether any other instance is running. If so, we'll temporarily
|
||||
* hold off on performing update tasks until there are no other instances or
|
||||
* until a timeout expires, whichever comes first. That way we can avoid
|
||||
* updating behind the back of copies that are still running, so we don't force
|
||||
* all running instances to restart (see bug 1366808, where an error was added
|
||||
* informing the user of the need to restart any running instances that have
|
||||
* been updated).
|
||||
*/
|
||||
[scriptable, uuid(cf4c4487-66d9-4e18-a2e9-39002245332f)]
|
||||
interface nsIUpdateSyncManager : nsISupports
|
||||
{
|
||||
/**
|
||||
* Returns whether another instance of this application is running.
|
||||
* @returns true if another instance has the semaphore open, false if not
|
||||
*/
|
||||
bool isOtherInstanceRunning();
|
||||
|
||||
/**
|
||||
* Should only be used for testing.
|
||||
* Closes and reopens the semaphore, possibly under a different name if the
|
||||
* path hash has changed (which should only happen if a test is forcing it).
|
||||
*/
|
||||
void resetSemaphore();
|
||||
};
|
||||
|
||||
/**
|
||||
* An interface describing a global application service that maintains a list
|
||||
* of updates previously performed as well as the current active update.
|
||||
|
|
|
@ -81,44 +81,6 @@ add_task(async function setupTestCommon() {
|
|||
],
|
||||
});
|
||||
|
||||
// We need to keep the update semaphore from thinking two instances are
|
||||
// running because of the mochitest parent instance, which means we need to
|
||||
// override the directory service with a fake executable path and then reset
|
||||
// the semaphore. But leaving the directory service overridden causes problems
|
||||
// for these tests, so we need to restore the real service immediately after.
|
||||
// To form the path, we'll use the real executable path with a token appended
|
||||
// (the path needs to be absolute, but not to point to a real file).
|
||||
// This block is loosely copied from adjustGeneralPaths() in another update
|
||||
// test file, xpcshellUtilsAUS.js, but this is a much more limited version;
|
||||
// it's been copied here both because the full function is overkill and also
|
||||
// because making it general enough to run in both xpcshell and mochitest
|
||||
// would have been unreasonably difficult.
|
||||
let exePath = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile);
|
||||
let dirProvider = {
|
||||
getFile: function AGP_DP_getFile(aProp, aPersistent) {
|
||||
// Set the value of persistent to false so when this directory provider is
|
||||
// unregistered it will revert back to the original provider.
|
||||
aPersistent.value = false;
|
||||
switch (aProp) {
|
||||
case XRE_EXECUTABLE_FILE:
|
||||
exePath.append("browser-test");
|
||||
return exePath;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIDirectoryServiceProvider"]),
|
||||
};
|
||||
let ds = Services.dirsvc.QueryInterface(Ci.nsIDirectoryService);
|
||||
ds.QueryInterface(Ci.nsIProperties).undefine(XRE_EXECUTABLE_FILE);
|
||||
ds.registerProvider(dirProvider);
|
||||
|
||||
let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
|
||||
Ci.nsIUpdateSyncManager
|
||||
);
|
||||
syncManager.resetSemaphore();
|
||||
|
||||
ds.unregisterProvider(dirProvider);
|
||||
|
||||
setUpdateTimerPrefs();
|
||||
reloadUpdateManagerData(true);
|
||||
removeUpdateFiles(true);
|
||||
|
@ -145,13 +107,6 @@ registerCleanupFunction(async () => {
|
|||
// Always try to restore the original updater files. If none of the updater
|
||||
// backup files are present then this is just a no-op.
|
||||
await finishTestRestoreUpdaterBackup();
|
||||
// Reset the update semaphore once again so that we know the semaphore we're
|
||||
// interested in here will be closed properly (normally that happens during
|
||||
// XPCOM shutdown, but that isn't consistent during tests).
|
||||
let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
|
||||
Ci.nsIUpdateSyncManager
|
||||
);
|
||||
syncManager.resetSemaphore();
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// This is the script that runs in the child xpcshell process for the test
|
||||
// unit_aus_update/updateSemaphore.js.
|
||||
// The main thing this script does is override the child's directory service
|
||||
// so that it ends up with the same fake
|
||||
// binary path that the parent test runner has opened its semaphore with.
|
||||
// This requires that we have already been passed a constant on our command
|
||||
// line which contains the relevant fake binary path, which is called:
|
||||
/* global customExePath */
|
||||
// We also need this builtin function from xpcshell itself:
|
||||
/* global simulateNoScriptActivity */
|
||||
|
||||
print("child process is running");
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// This function is copied from xpcshellUtilsAUS.js so that we can have our
|
||||
// xpcshell subprocess call it without having to load that whole file, because
|
||||
// it turns out that needs a bunch of infrastructure that normally the testing
|
||||
// framework would provide, and that also requires a bunch of setup, and it's
|
||||
// just not worth all that. This is a cut down version that only includes the
|
||||
// directory provider functionality that the subprocess really needs.
|
||||
function adjustGeneralPaths() {
|
||||
let dirProvider = {
|
||||
getFile: function AGP_DP_getFile(aProp, aPersistent) {
|
||||
// Set the value of persistent to false so when this directory provider is
|
||||
// unregistered it will revert back to the original provider.
|
||||
aPersistent.value = false;
|
||||
// The semaphore only needs XREExeF, so that's all we provide.
|
||||
if (aProp == "XREExeF") {
|
||||
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
file.initWithPath(customExePath);
|
||||
return file;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIDirectoryServiceProvider"]),
|
||||
};
|
||||
let ds = Services.dirsvc.QueryInterface(Ci.nsIDirectoryService);
|
||||
ds.QueryInterface(Ci.nsIProperties).undefine("XREExeF");
|
||||
ds.registerProvider(dirProvider);
|
||||
|
||||
// Now that we've overridden the directory provider, the name of the update
|
||||
// semaphore needs to be changed to match the overridden path.
|
||||
let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
|
||||
Ci.nsIUpdateSyncManager
|
||||
);
|
||||
syncManager.resetSemaphore();
|
||||
}
|
||||
|
||||
adjustGeneralPaths();
|
||||
|
||||
// Wait a few seconds for the parent to do what it needs to do, then exit.
|
||||
print("child process should now have the semaphore; will exit in 5 seconds");
|
||||
simulateNoScriptActivity(5);
|
||||
print("child process exiting now");
|
|
@ -1619,15 +1619,7 @@ function getSpecialFolderDir(aCSIDL) {
|
|||
);
|
||||
|
||||
let aryPath = ctypes.char16_t.array()(260);
|
||||
let rv = SHGetSpecialFolderPath(0, aryPath, aCSIDL, false);
|
||||
if (!rv) {
|
||||
do_throw(
|
||||
"SHGetSpecialFolderPath failed to retrieve " +
|
||||
aCSIDL +
|
||||
" with Win32 error " +
|
||||
ctypes.winLastError
|
||||
);
|
||||
}
|
||||
SHGetSpecialFolderPath(0, aryPath, aCSIDL, false);
|
||||
lib.close();
|
||||
|
||||
let path = aryPath.readString(); // Convert the c-string to js-string
|
||||
|
@ -4354,7 +4346,6 @@ function adjustGeneralPaths() {
|
|||
let ds = Services.dirsvc.QueryInterface(Ci.nsIDirectoryService);
|
||||
ds.QueryInterface(Ci.nsIProperties).undefine(NS_GRE_DIR);
|
||||
ds.QueryInterface(Ci.nsIProperties).undefine(NS_GRE_BIN_DIR);
|
||||
ds.QueryInterface(Ci.nsIProperties).undefine(XRE_EXECUTABLE_FILE);
|
||||
ds.registerProvider(dirProvider);
|
||||
registerCleanupFunction(function AGP_cleanup() {
|
||||
debugDump("start - unregistering directory provider");
|
||||
|
@ -4412,25 +4403,8 @@ function adjustGeneralPaths() {
|
|||
ds.unregisterProvider(dirProvider);
|
||||
cleanupTestCommon();
|
||||
|
||||
// Now that our provided is unregistered, reset the semaphore a second time
|
||||
// so that we know the semaphore we're interested in gets unlocked (xpcshell
|
||||
// doesn't always run a proper XPCOM shutdown sequence, which is where that
|
||||
// would normally be happening).
|
||||
let syncManager = Cc[
|
||||
"@mozilla.org/updates/update-sync-manager;1"
|
||||
].getService(Ci.nsIUpdateSyncManager);
|
||||
syncManager.resetSemaphore();
|
||||
|
||||
debugDump("finish - unregistering directory provider");
|
||||
});
|
||||
|
||||
// Now that we've overridden the directory provider, the name of the update
|
||||
// semaphore needs to be changed to match the overridden path.
|
||||
debugDump("resetting update semaphore");
|
||||
let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
|
||||
Ci.nsIUpdateSyncManager
|
||||
);
|
||||
syncManager.resetSemaphore();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -94,7 +94,6 @@ FINAL_TARGET_FILES += [
|
|||
"data/partial_removed-files_mac",
|
||||
"data/partial_update_manifest",
|
||||
"data/replace_log_success",
|
||||
"data/semaphoreTestChildScript.js",
|
||||
"data/simple.mar",
|
||||
"TestAUSReadStrings1.ini",
|
||||
"TestAUSReadStrings2.ini",
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// This test verifies that the update semaphore is working correctly by
|
||||
// a) making sure we're the only one that's opened it to begin with, and then
|
||||
// b) starting a second copy of the same binary and making sure we can tell we
|
||||
// are no longer the only one that's opened it.
|
||||
|
||||
const { Subprocess } = ChromeUtils.import(
|
||||
"resource://gre/modules/Subprocess.jsm"
|
||||
);
|
||||
|
||||
// Save off the real GRE directory and binary path before we register our
|
||||
// mock directory service which overrides them both.
|
||||
const thisBinary = Services.dirsvc.get("XREExeF", Ci.nsIFile);
|
||||
const greDir = Services.dirsvc.get("GreD", Ci.nsIFile);
|
||||
|
||||
add_task(async function() {
|
||||
setupTestCommon();
|
||||
|
||||
// First check that we believe we exclusively hold the semaphore.
|
||||
let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
|
||||
Ci.nsIUpdateSyncManager
|
||||
);
|
||||
Assert.ok(
|
||||
!syncManager.isOtherInstanceRunning(),
|
||||
"no other instance is running yet"
|
||||
);
|
||||
|
||||
// Now start a second copy of this xpcshell binary so that something else
|
||||
// takes the same semaphore. First we'll define its command line.
|
||||
// Most of the child's code is in a separate script file, so all the command
|
||||
// line has to do is set up a few required path strings we need to pass
|
||||
// through to the child, and then include the script file.
|
||||
const args = [
|
||||
"-g",
|
||||
greDir.path,
|
||||
"-e",
|
||||
`
|
||||
const customGreDirPath = "${getApplyDirFile(
|
||||
DIR_RESOURCES
|
||||
).path.replaceAll("\\", "\\\\")}";
|
||||
const customGreBinDirPath = "${getApplyDirFile(DIR_MACOS).path.replaceAll(
|
||||
"\\",
|
||||
"\\\\"
|
||||
)}";
|
||||
const customExePath = "${getApplyDirFile(
|
||||
DIR_MACOS + FILE_APP_BIN
|
||||
).path.replaceAll("\\", "\\\\")}";
|
||||
const customUpdDirPath = "${getMockUpdRootD().path.replaceAll(
|
||||
"\\",
|
||||
"\\\\"
|
||||
)}";
|
||||
const customOldUpdDirPath = "${getMockUpdRootD(true).path.replaceAll(
|
||||
"\\",
|
||||
"\\\\"
|
||||
)}";
|
||||
`,
|
||||
"-f",
|
||||
getTestDirFile("semaphoreTestChildScript.js").path,
|
||||
];
|
||||
|
||||
// Now we can actually invoke the process.
|
||||
debugDump(`launching child process at ${thisBinary.path} with args ${args}`);
|
||||
Subprocess.call({
|
||||
command: thisBinary.path,
|
||||
arguments: args,
|
||||
stderr: "stdout",
|
||||
});
|
||||
|
||||
// It will take the new xpcshell a little time to start up, but we should see
|
||||
// the effect on the semaphore within at most a few seconds.
|
||||
await TestUtils.waitForCondition(
|
||||
() => syncManager.isOtherInstanceRunning(),
|
||||
"waiting for child process to take the semaphore"
|
||||
).catch(e => {
|
||||
// Rather than throwing out of waitForCondition(), catch and log the failure
|
||||
// manually so that we get output that's a bit more readable.
|
||||
Assert.ok(
|
||||
syncManager.isOtherInstanceRunning(),
|
||||
"child process has the semaphore"
|
||||
);
|
||||
});
|
||||
|
||||
// The semaphore lock should have been closed when the process exited, but
|
||||
// we'll allow a little time for the OS to clean up the handle.
|
||||
await TestUtils.waitForCondition(
|
||||
() => !syncManager.isOtherInstanceRunning(),
|
||||
"waiting for child process to give back the semaphore"
|
||||
).catch(e => {
|
||||
Assert.ok(
|
||||
!syncManager.isOtherInstanceRunning(),
|
||||
"child process has given back the semaphore"
|
||||
);
|
||||
});
|
||||
|
||||
doTestFinish();
|
||||
});
|
|
@ -36,6 +36,3 @@ reason = Update pref migration is currently Windows only
|
|||
[updateDirectoryMigrate.js]
|
||||
skip-if = os != 'win'
|
||||
reason = Update directory migration is currently Windows only
|
||||
[updateSemaphore.js]
|
||||
skip-if = os == 'mac'
|
||||
reason = The semaphore isn't implemented for Mac
|
||||
|
|
|
@ -1,196 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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/. */
|
||||
|
||||
#include "GlobalSemaphore.h"
|
||||
|
||||
#include "commonupdatedir.h" // for GetInstallHash
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "updatedefines.h" // for NS_t* definitions
|
||||
|
||||
#ifndef XP_WIN
|
||||
# include <fcntl.h> // for O_CREAT
|
||||
# include <sys/stat.h> // for mode constants
|
||||
#else
|
||||
# include <limits.h> // for _XOPEN_PATH_MAX / _POSIX_PATH_MAX
|
||||
#endif
|
||||
|
||||
#ifdef XP_WIN
|
||||
# define SEMAPHORE_NAME_PREFIX "Global\\"
|
||||
#else
|
||||
# define SEMAPHORE_NAME_PREFIX "/"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// Prevent this function failing the build for being unused on Mac.
|
||||
#ifndef XP_MACOSX
|
||||
|
||||
static bool GetSemaphoreName(const char* nameToken, const char16_t* installPath,
|
||||
mozilla::UniquePtr<NS_tchar[]>& semName) {
|
||||
mozilla::UniquePtr<NS_tchar[]> pathHash;
|
||||
if (!GetInstallHash(installPath, MOZ_APP_VENDOR, pathHash)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t semNameLen = strlen(SEMAPHORE_NAME_PREFIX) + strlen(nameToken) +
|
||||
NS_tstrlen(pathHash.get()) + 1;
|
||||
semName = mozilla::MakeUnique<NS_tchar[]>(semNameLen + 1);
|
||||
if (!semName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// On Windows, we need to convert the token string to UTF-16.
|
||||
// printf can do that for us, but we need to tell it to by changing the type
|
||||
// indicator in the format string.
|
||||
// We also need different prefixes to the semaphore name, because it's a
|
||||
// path-like string and because Windows has the "global" concept.
|
||||
# ifdef XP_WIN
|
||||
const NS_tchar* kSemaphoreNameFormat = NS_T(SEMAPHORE_NAME_PREFIX "%S-%s");
|
||||
# else
|
||||
const NS_tchar* kSemaphoreNameFormat = NS_T(SEMAPHORE_NAME_PREFIX "%s-%s");
|
||||
# endif
|
||||
|
||||
NS_tsnprintf(semName.get(), semNameLen + 1, kSemaphoreNameFormat, nameToken,
|
||||
pathHash.get());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef XP_WIN
|
||||
|
||||
GlobalSemHandle OpenGlobalSemaphore(const char* nameToken,
|
||||
const char16_t* installPath) {
|
||||
mozilla::UniquePtr<NS_tchar[]> semName;
|
||||
if (!GetSemaphoreName(nameToken, installPath, semName)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Initialize the semaphore to LONG_MAX because we're not actually using it
|
||||
// to control access to any limited resource, we just want to know how many
|
||||
// times the semaphore is taken. And taking a semaphore decrements it, so we
|
||||
// have to start at the top of the range instead of at zero.
|
||||
GlobalSemHandle sem =
|
||||
::CreateSemaphoreW(nullptr, LONG_MAX, LONG_MAX, semName.get());
|
||||
if (sem) {
|
||||
// Claim a reference to the semaphore. After this point, the semaphore count
|
||||
// should always be LONG_MAX - [number of running instances].
|
||||
if (::WaitForSingleObject(sem, 0) != WAIT_OBJECT_0) {
|
||||
// Either there are LONG_MAX instances running or something is wrong and
|
||||
// this semaphore is not usable.
|
||||
::CloseHandle(sem);
|
||||
sem = nullptr;
|
||||
}
|
||||
}
|
||||
return sem;
|
||||
}
|
||||
|
||||
void ReleaseGlobalSemaphore(GlobalSemHandle sem) {
|
||||
if (sem) {
|
||||
::ReleaseSemaphore(sem, 1, nullptr);
|
||||
::CloseHandle(sem);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsOtherInstanceRunning(GlobalSemHandle sem, bool* aResult) {
|
||||
// There's no documented way to get a semaphore's current count except to
|
||||
// wait on it (which decrements it) and then release it (which increments it
|
||||
// back and returns the pre-incremented count), so that's what we'll do. There
|
||||
// is also NtQuerySemaphore, but using the native API doesn't seem worth the
|
||||
// trouble here.
|
||||
if (sem && ::WaitForSingleObject(sem, 0) == WAIT_OBJECT_0) {
|
||||
LONG count = 0;
|
||||
if (::ReleaseSemaphore(sem, 1, &count)) {
|
||||
// At rest, the count is (LONG_MAX - [number of running instances]), but
|
||||
// the count we read had been
|
||||
// decremented once more by the Wait call here, so we need to compare
|
||||
// against one less than that to see if there's exactly one instance
|
||||
// running.
|
||||
*aResult = (count != (LONG_MAX - 2));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#elif defined(XP_MACOSX)
|
||||
|
||||
// We don't actually have Mac support here, because sem_getvalue isn't supported
|
||||
// there (the header calls it deprecated, but actually it's just unimplemented),
|
||||
// and there is no other way to get the value of a POSIX-semaphore. We'll have
|
||||
// to do something platform-specific.
|
||||
|
||||
GlobalSemHandle OpenGlobalSemaphore(const char* /* unused */,
|
||||
const char16_t* /* unused */) {
|
||||
// Return something that isn't a valid pointer, but that the manager won't
|
||||
// think represents a failure.
|
||||
return (GlobalSemHandle)1;
|
||||
}
|
||||
|
||||
void ReleaseGlobalSemaphore(GlobalSemHandle /* unused */) {}
|
||||
|
||||
bool IsOtherInstanceRunning(GlobalSemHandle /* unused */, bool* aResult) {
|
||||
*aResult = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
#else // Neither Windows nor Mac
|
||||
|
||||
GlobalSemHandle OpenGlobalSemaphore(const char* nameToken,
|
||||
const char16_t* installPath) {
|
||||
mozilla::UniquePtr<NS_tchar[]> semName;
|
||||
if (!GetSemaphoreName(nameToken, installPath, semName)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Initialize the semaphore to the maximum value because we don't actually
|
||||
// want to limit the number of instances, and assign all permissions because
|
||||
// we also don't want to lock out any other users. The update service has
|
||||
// protections to prevent problems resulting from this semaphore
|
||||
// being messed with by an attacker.
|
||||
GlobalSemHandle sem = sem_open(semName.get(), O_CREAT,
|
||||
S_IRWXU | S_IRWXG | S_IRWXO, SEM_VALUE_MAX);
|
||||
if (sem == SEM_FAILED) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Decrement the semaphore whether we created it or opened it. This way the
|
||||
// count is always SEM_VALUE_MAX - [number of running instances].
|
||||
if (sem_trywait(sem)) {
|
||||
// Either there are SEM_VALUE_MAX instances running or something is wrong
|
||||
// and this semaphore is not usable.
|
||||
sem_close(sem);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return sem;
|
||||
}
|
||||
|
||||
void ReleaseGlobalSemaphore(GlobalSemHandle sem) {
|
||||
if (sem) {
|
||||
sem_post(sem);
|
||||
sem_close(sem);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsOtherInstanceRunning(GlobalSemHandle sem, bool* aResult) {
|
||||
int value = 0;
|
||||
if (sem && !sem_getvalue(sem, &value)) {
|
||||
// Zero and negative values are all error states.
|
||||
if (value <= 0) {
|
||||
return false;
|
||||
}
|
||||
*aResult = (value != (SEM_VALUE_MAX - 1));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}; // namespace mozilla
|
|
@ -1,45 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef GLOBALSEMAPHORE_H
|
||||
#define GLOBALSEMAPHORE_H
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#ifdef XP_WIN
|
||||
# include <windows.h>
|
||||
using GlobalSemHandle = HANDLE;
|
||||
#else
|
||||
# include <semaphore.h>
|
||||
using GlobalSemHandle = sem_t*;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* nameToken should be a string very briefly naming the semaphore you are
|
||||
* creating, and it should be unique systemwide except for across multiple
|
||||
* instances of the same application.
|
||||
* installPath should be the path to the directory containing the application.
|
||||
*
|
||||
* Taken together, those two parameters will be used to form a string which is
|
||||
* unique to this copy of this application.
|
||||
* Creating the semaphore will fail if the final string (the token plus the path
|
||||
* hash) is longer than the platform's maximum. On Windows that's MAX_PATH, on
|
||||
* POSIX systems it's either _POSIX_PATH_MAX or perhaps SEM_NAME_LEN.
|
||||
*
|
||||
* Returns nullptr upon failure, on all platforms.
|
||||
*/
|
||||
GlobalSemHandle OpenGlobalSemaphore(const char* nameToken,
|
||||
const char16_t* installPath);
|
||||
|
||||
void ReleaseGlobalSemaphore(GlobalSemHandle sem);
|
||||
|
||||
// aResult will be set to true if another instance *was* found, false if not.
|
||||
// Return value is true on success, false on error (and aResult won't be set).
|
||||
bool IsOtherInstanceRunning(GlobalSemHandle sem, bool* aResult);
|
||||
|
||||
}; // namespace mozilla
|
||||
|
||||
#endif // GLOBALEMAPHORE_H
|
|
@ -38,7 +38,6 @@ EXPORTS.mozilla += [
|
|||
"AutoSQLiteLifetime.h",
|
||||
"Bootstrap.h",
|
||||
"CmdLineAndEnvUtils.h",
|
||||
"GlobalSemaphore.h",
|
||||
"SafeMode.h",
|
||||
"UntrustedModulesData.h",
|
||||
]
|
||||
|
@ -126,11 +125,6 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
|
|||
"nsAndroidStartup.cpp",
|
||||
]
|
||||
|
||||
if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android":
|
||||
UNIFIED_SOURCES += [
|
||||
"GlobalSemaphore.cpp",
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
"/toolkit/mozapps/update/common/commonupdatedir.cpp",
|
||||
"AutoSQLiteLifetime.cpp",
|
||||
|
@ -169,7 +163,6 @@ if CONFIG["MOZ_UPDATER"]:
|
|||
if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android":
|
||||
UNIFIED_SOURCES += [
|
||||
"nsUpdateDriver.cpp",
|
||||
"nsUpdateSyncManager.cpp",
|
||||
]
|
||||
|
||||
if CONFIG["MOZ_PDF_PRINTING"]:
|
||||
|
|
|
@ -3,18 +3,14 @@
|
|||
* 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/. */
|
||||
|
||||
#include "nsAppStartupNotifier.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsString.h"
|
||||
#include "nsICategoryManager.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsXPCOM.h"
|
||||
#include "nsAppStartupNotifier.h"
|
||||
#include "mozilla/SimpleEnumerator.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
/* static */
|
||||
nsresult nsAppStartupNotifier::NotifyObservers(const char* aCategory) {
|
||||
NS_ENSURE_ARG(aCategory);
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#define nsAppStartupNotifier_h___
|
||||
|
||||
#include "nsIAppStartupNotifier.h"
|
||||
#include "nsError.h"
|
||||
|
||||
class nsAppStartupNotifier final {
|
||||
public:
|
||||
|
|
|
@ -55,10 +55,6 @@
|
|||
using namespace mozilla;
|
||||
|
||||
static LazyLogModule sUpdateLog("updatedriver");
|
||||
// Some other file in our unified batch might have defined LOG already.
|
||||
#ifdef LOG
|
||||
# undef LOG
|
||||
#endif
|
||||
#define LOG(args) MOZ_LOG(sUpdateLog, mozilla::LogLevel::Debug, args)
|
||||
|
||||
#ifdef XP_WIN
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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/. */
|
||||
|
||||
#include "nsUpdateSyncManager.h"
|
||||
|
||||
#include "mozilla/Unused.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsCRT.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIProperties.h"
|
||||
#include "nsString.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
|
||||
#define UPDATE_SEMAPHORE_NAME_TOKEN "MozillaUpdateSemaphore"
|
||||
|
||||
nsUpdateSyncManager* gUpdateSyncManager = nullptr;
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsUpdateSyncManager, nsIUpdateSyncManager, nsIObserver)
|
||||
|
||||
nsUpdateSyncManager::nsUpdateSyncManager() { OpenSemaphore(); }
|
||||
|
||||
nsUpdateSyncManager::~nsUpdateSyncManager() {
|
||||
ReleaseSemaphore();
|
||||
gUpdateSyncManager = nullptr;
|
||||
}
|
||||
|
||||
already_AddRefed<nsUpdateSyncManager> nsUpdateSyncManager::GetSingleton() {
|
||||
if (!gUpdateSyncManager) {
|
||||
gUpdateSyncManager = new nsUpdateSyncManager();
|
||||
}
|
||||
return do_AddRef(gUpdateSyncManager);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsUpdateSyncManager::Observe(nsISupports* aSubject,
|
||||
const char* aTopic,
|
||||
const char16_t* aData) {
|
||||
mozilla::Unused << aSubject;
|
||||
mozilla::Unused << aData;
|
||||
|
||||
// We want to hold the semaphore for as much of the lifetime of the app
|
||||
// as we can, so we observe xpcom-startup so we get constructed as early as
|
||||
// possible, which triggers constructing the singleton.
|
||||
if (!nsCRT::strcmp(aTopic, NS_XPCOM_STARTUP_OBSERVER_ID)) {
|
||||
nsCOMPtr<nsIObserverService> observerService =
|
||||
mozilla::services::GetObserverService();
|
||||
if (observerService) {
|
||||
return observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
|
||||
false);
|
||||
}
|
||||
return NS_ERROR_SERVICE_NOT_AVAILABLE;
|
||||
}
|
||||
if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
|
||||
ReleaseSemaphore();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult nsUpdateSyncManager::OpenSemaphore() {
|
||||
if (mSemaphore) {
|
||||
// Semaphore is already open.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Only open the semaphore from the browser process.
|
||||
// Our component registration should already have made sure of this.
|
||||
if (NS_WARN_IF(XRE_GetProcessType() != GeckoProcessType_Default)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIProperties> dirSvc =
|
||||
do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
|
||||
NS_ENSURE_TRUE(dirSvc, NS_ERROR_SERVICE_NOT_AVAILABLE);
|
||||
|
||||
nsCOMPtr<nsIFile> appFile;
|
||||
nsresult rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
|
||||
getter_AddRefs(appFile));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIFile> appDirFile;
|
||||
rv = appFile->GetParent(getter_AddRefs(appDirFile));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsAutoString appDirPath;
|
||||
rv = appDirFile->GetPath(appDirPath);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mSemaphore = mozilla::OpenGlobalSemaphore(
|
||||
UPDATE_SEMAPHORE_NAME_TOKEN, PromiseFlatString(appDirPath).get());
|
||||
NS_ENSURE_TRUE(mSemaphore, NS_ERROR_FAILURE);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void nsUpdateSyncManager::ReleaseSemaphore() {
|
||||
if (!mSemaphore) {
|
||||
// Semaphore is already released.
|
||||
return;
|
||||
}
|
||||
|
||||
mozilla::ReleaseGlobalSemaphore(mSemaphore);
|
||||
mSemaphore = nullptr;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsUpdateSyncManager::IsOtherInstanceRunning(bool* aResult) {
|
||||
if (NS_WARN_IF(XRE_GetProcessType() != GeckoProcessType_Default)) {
|
||||
return NS_ERROR_SERVICE_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
if (!mSemaphore) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
bool rv = mozilla::IsOtherInstanceRunning(mSemaphore, aResult);
|
||||
NS_ENSURE_TRUE(rv, NS_ERROR_FAILURE);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsUpdateSyncManager::ResetSemaphore() {
|
||||
ReleaseSemaphore();
|
||||
return OpenSemaphore();
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef nsUpdateSyncManager_h__
|
||||
#define nsUpdateSyncManager_h__
|
||||
|
||||
#include "mozilla/AlreadyAddRefed.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIUpdateService.h"
|
||||
#include "GlobalSemaphore.h"
|
||||
|
||||
// The update sync manager is responsible for making sure that only one
|
||||
// instance of the application is running at the time we want to start updating
|
||||
// it. It does this by taking a semaphore very early during the application's
|
||||
// startup process. Then, when app update tasks are ready to run, the update
|
||||
// service asks us whether anything else has also taken the semaphore, which,
|
||||
// if true, would mean another instance of the application is currently running
|
||||
// and performing update tasks should be avoided (the update service also runs
|
||||
// a timeout and eventually goes ahead with the update in order to prevent an
|
||||
// external program from effectively disabling updates).
|
||||
// The immediately obvious tool for this job would be a mutex and not a
|
||||
// semaphore, since only one instance can be applying updates at a time, but at
|
||||
// it turns out that wouldn't quite meet the requirements. Consider this
|
||||
// scenario: an application instance we'll call instance A runs and takes the
|
||||
// mutex. It doesn't check for updates right away. A second instance called B
|
||||
// then starts and cannot get the mutex during its startup because instance A
|
||||
// still holds it. A third instance C is started and has the same problem. Now,
|
||||
// what if instance A exits? It returns the mutex, so if either B or C decide to
|
||||
// check for updates they'll be able to take it. But neither is aware of the
|
||||
// other's existence, so whichever one wins that race will be able to apply an
|
||||
// update behind the other one's back, which is the exact thing this component
|
||||
// is intended to prevent. By using a semaphore instead, every instance is
|
||||
// always aware of how many other instances are running by checking the
|
||||
// semaphore's count, and this problem is avoided.
|
||||
class nsUpdateSyncManager final : public nsIUpdateSyncManager,
|
||||
public nsIObserver {
|
||||
public:
|
||||
nsUpdateSyncManager();
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIUPDATESYNCMANAGER
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
static already_AddRefed<nsUpdateSyncManager> GetSingleton();
|
||||
|
||||
private:
|
||||
~nsUpdateSyncManager();
|
||||
|
||||
nsUpdateSyncManager(nsUpdateSyncManager&) = delete;
|
||||
nsUpdateSyncManager(nsUpdateSyncManager&&) = delete;
|
||||
nsUpdateSyncManager& operator=(nsUpdateSyncManager&) = delete;
|
||||
nsUpdateSyncManager& operator=(nsUpdateSyncManager&&) = delete;
|
||||
|
||||
nsresult OpenSemaphore();
|
||||
void ReleaseSemaphore();
|
||||
|
||||
mozilla::GlobalSemHandle mSemaphore = nullptr;
|
||||
};
|
||||
|
||||
#endif // nsUpdateSyncManager_h__
|
Загрузка…
Ссылка в новой задаче