зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1553982 Part 2 - Use a global semaphore to implement an update sync manager XPCOM component. r=bytesized,nalexander
Differential Revision: https://phabricator.services.mozilla.com/D95627
This commit is contained in:
Родитель
6e429cc674
Коммит
99a6151dc3
|
@ -169,6 +169,16 @@ 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'):
|
||||
|
|
|
@ -425,6 +425,35 @@ 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.
|
||||
|
|
|
@ -169,6 +169,7 @@ if CONFIG["MOZ_UPDATER"]:
|
|||
if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android":
|
||||
UNIFIED_SOURCES += [
|
||||
"nsUpdateDriver.cpp",
|
||||
"nsUpdateSyncManager.cpp",
|
||||
]
|
||||
|
||||
if CONFIG["MOZ_PDF_PRINTING"]:
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
/* -*- 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();
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/* -*- 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__
|
Загрузка…
Ссылка в новой задаче