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:
Molly Howell 2020-12-03 21:52:17 +00:00
Родитель 6e429cc674
Коммит 99a6151dc3
5 изменённых файлов: 231 добавлений и 0 удалений

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

@ -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__