diff --git a/netwerk/cache2/CachePurgeLock.cpp b/netwerk/cache2/CachePurgeLock.cpp index 6da0ddc0ddeb..6328891669ac 100644 --- a/netwerk/cache2/CachePurgeLock.cpp +++ b/netwerk/cache2/CachePurgeLock.cpp @@ -7,18 +7,16 @@ #include "nsIFile.h" #include "nsAppRunner.h" #include "mozilla/MultiInstanceLock.h" +#include "nsLocalFile.h" namespace mozilla::net { NS_IMPL_ISUPPORTS(CachePurgeLock, nsICachePurgeLock) -NS_IMETHODIMP -CachePurgeLock::Lock(const nsACString& profileName) { +static nsresult PrepareLockArguments(const nsACString& profileName, + nsCString& lockName, + nsString& appDirPath) { nsresult rv; - if (mLock != MULTI_INSTANCE_LOCK_HANDLE_ERROR) { - // Lock is already open. - return NS_OK; - } nsCOMPtr appFile = mozilla::GetNormalizedAppFile(nullptr); if (!appFile) { @@ -29,13 +27,54 @@ CachePurgeLock::Lock(const nsACString& profileName) { rv = appFile->GetParent(getter_AddRefs(appDirFile)); NS_ENSURE_SUCCESS(rv, rv); - nsAutoString appDirPath; rv = appDirFile->GetPath(appDirPath); NS_ENSURE_SUCCESS(rv, rv); - nsAutoCString lockName(profileName); + lockName = profileName; lockName.Append("-cachePurge"); + return NS_OK; +} + +NS_IMETHODIMP +CachePurgeLock::GetLockFile(const nsACString& profileName, nsIFile** aResult) { + nsresult rv; + nsCString lockName; + nsString appDirPath; + rv = PrepareLockArguments(profileName, lockName, appDirPath); + if (NS_FAILED(rv)) { + return rv; + } + + nsCString filePath; + if (!GetMultiInstanceLockFileName(lockName.get(), appDirPath.get(), + filePath)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr lockFile = new nsLocalFile(); + rv = lockFile->InitWithNativePath(filePath); + NS_ENSURE_SUCCESS(rv, rv); + + lockFile.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +CachePurgeLock::Lock(const nsACString& profileName) { + nsresult rv; + if (mLock != MULTI_INSTANCE_LOCK_HANDLE_ERROR) { + // Lock is already open. + return NS_OK; + } + + nsCString lockName; + nsString appDirPath; + rv = PrepareLockArguments(profileName, lockName, appDirPath); + if (NS_FAILED(rv)) { + return rv; + } + mLock = mozilla::OpenMultiInstanceLock(lockName.get(), appDirPath.get()); if (mLock == MULTI_INSTANCE_LOCK_HANDLE_ERROR) { return NS_ERROR_FAILURE; diff --git a/netwerk/cache2/nsICachePurgeLock.idl b/netwerk/cache2/nsICachePurgeLock.idl index fcd4b4afee4a..a0937a892248 100644 --- a/netwerk/cache2/nsICachePurgeLock.idl +++ b/netwerk/cache2/nsICachePurgeLock.idl @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsISupports.idl" +interface nsIFile; /** * This object is a wrapper of MultiInstanceLock. @@ -30,4 +31,10 @@ interface nsICachePurgeLock : nsISupports { * after unlocking. */ void unlock(); + + /** + * Returns the file used to guarantee single access to a resource. + * This method is used to remove the lock file when no longer necessary. + */ + nsIFile getLockFile(in AUTF8String profileName); }; diff --git a/netwerk/test/marionette/test_purge_http_cache_at_shutdown.py b/netwerk/test/marionette/test_purge_http_cache_at_shutdown.py index d7ade93f63a8..ee27e29e8d6c 100644 --- a/netwerk/test/marionette/test_purge_http_cache_at_shutdown.py +++ b/netwerk/test/marionette/test_purge_http_cache_at_shutdown.py @@ -2,6 +2,7 @@ # 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/. +import os from pathlib import Path from marionette_driver import Wait @@ -34,8 +35,28 @@ class PurgeHTTPCacheAtShutdownTestCase(MarionetteTestCase): child.name.endswith(".purge.bg_rm") for child in self.profile_path.iterdir() ) - def test_ensure_cache_purge_after_in_app_quit(self): - self.assertTrue(self.cacheDirExists(), "Cache directory must exist") + def initLockDir(self): + self.lock_dir = None + with self.marionette.using_context("chrome"): + path = self.marionette.execute_script( + """ + return Services.dirsvc.get("UpdRootD", Ci.nsIFile).parent.parent.path; + """ + ) + self.lock_dir = Path(path) + + def assertNoLocks(self): + locks = [ + x + for x in self.lock_dir.iterdir() + if x.is_file() and "-cachePurge" in x.name + ] + self.assertEqual(locks, [], "All locks should have been removed") + + # Tests are run in lexicographical order, so test_a* is run first to + # cleanup locks that may be there from previous runs. + def test_asetup(self): + self.initLockDir() self.marionette.quit() @@ -44,8 +65,33 @@ class PurgeHTTPCacheAtShutdownTestCase(MarionetteTestCase): message="Cache directory must be removed after orderly shutdown", ) + # delete locks from previous runs + locks = [ + x + for x in self.lock_dir.iterdir() + if x.is_file() and "-cachePurge" in x.name + ] + for lock in locks: + os.remove(lock) + # all locks should have been removed successfully. + self.assertNoLocks() + + def test_ensure_cache_purge_after_in_app_quit(self): + self.assertTrue(self.cacheDirExists(), "Cache directory must exist") + self.initLockDir() + + self.marionette.quit() + + Wait(self.marionette, timeout=60).until( + lambda _: not self.cacheDirExists() and not self.renamedDirExists(), + message="Cache directory must be removed after orderly shutdown", + ) + + self.assertNoLocks() + def test_longstanding_cache_purge_after_in_app_quit(self): self.assertTrue(self.cacheDirExists(), "Cache directory must exist") + self.initLockDir() self.marionette.set_pref( "toolkit.background_tasks.remove_directory.testing.sleep_ms", 5000 @@ -58,12 +104,15 @@ class PurgeHTTPCacheAtShutdownTestCase(MarionetteTestCase): message="Cache directory must be removed after orderly shutdown", ) + self.assertNoLocks() + def test_ensure_cache_purge_after_forced_restart(self): """ Doing forced restart here to prevent the shutdown phase purging and only allow startup phase one, via `CacheFileIOManager::OnDelayedStartupFinished`. """ self.profile_path.joinpath("foo.purge.bg_rm").mkdir() + self.initLockDir() self.marionette.restart(in_app=False) @@ -72,3 +121,5 @@ class PurgeHTTPCacheAtShutdownTestCase(MarionetteTestCase): message="Directories with .purge.bg_rm postfix must be removed at startup after" "disorderly shutdown", ) + + self.assertNoLocks() diff --git a/toolkit/components/backgroundtasks/BackgroundTask_removeDirectory.sys.mjs b/toolkit/components/backgroundtasks/BackgroundTask_removeDirectory.sys.mjs index f67a393e3703..e49f925145eb 100644 --- a/toolkit/components/backgroundtasks/BackgroundTask_removeDirectory.sys.mjs +++ b/toolkit/components/backgroundtasks/BackgroundTask_removeDirectory.sys.mjs @@ -88,6 +88,14 @@ function tryRemoveDir(aFile, countObj) { const FILE_CHECK_ITERATION_TIMEOUT_MS = 1000; +function cleanupDirLockFile(aLock, aProfileName) { + let lockFile = aLock.getLockFile(aProfileName); + try { + // Try to clean up the lock file + lockFile.remove(false); + } catch (ex) {} +} + async function deleteChildDirectory( parentDirPath, childDirName, @@ -178,6 +186,7 @@ async function deleteChildDirectory( if (locked) { dirLock.unlock(); locked = false; + cleanupDirLockFile(dirLock, childDirName); } } } @@ -243,6 +252,7 @@ async function cleanupOtherDirectories( `Deletion of folder ${entry.leafName} - success=${removedDir}` ); dirLock.unlock(); + cleanupDirLockFile(dirLock, entry.leafName); } } diff --git a/toolkit/xre/MultiInstanceLock.cpp b/toolkit/xre/MultiInstanceLock.cpp index 6ffa704704e8..eb4db6367a53 100644 --- a/toolkit/xre/MultiInstanceLock.cpp +++ b/toolkit/xre/MultiInstanceLock.cpp @@ -31,8 +31,9 @@ namespace mozilla { -static bool GetLockFileName(const char* nameToken, const char16_t* installPath, - nsCString& filePath) { +bool GetMultiInstanceLockFileName(const char* nameToken, + const char16_t* installPath, + nsCString& filePath) { #ifdef XP_WIN // On Windows, the lock file is placed at the path // [updateDirectory]\[nameToken]-[pathHash], so first we need to get the @@ -108,7 +109,7 @@ static bool GetLockFileName(const char* nameToken, const char16_t* installPath, MultiInstLockHandle OpenMultiInstanceLock(const char* nameToken, const char16_t* installPath) { nsCString filePath; - if (!GetLockFileName(nameToken, installPath, filePath)) { + if (!GetMultiInstanceLockFileName(nameToken, installPath, filePath)) { return MULTI_INSTANCE_LOCK_HANDLE_ERROR; } diff --git a/toolkit/xre/MultiInstanceLock.h b/toolkit/xre/MultiInstanceLock.h index 332470ed2ab6..1d6a93e7a8b4 100644 --- a/toolkit/xre/MultiInstanceLock.h +++ b/toolkit/xre/MultiInstanceLock.h @@ -88,6 +88,12 @@ bool IsOtherInstanceRunning(MultiInstLockHandle lock, bool* aResult); // this function ensures the file path is properly normalized. already_AddRefed GetNormalizedAppFile(nsIFile* aAppFile); +// Computes the file path of multi instance lock +// Returns true when successful - false otherwise +bool GetMultiInstanceLockFileName(const char* nameToken, + const char16_t* installPath, + nsCString& filePath); + }; // namespace mozilla #endif // MULTIINSTANCELOCK_H