Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 6b6c4da

Browse files
committed
Bug 1696772 - Don't use FILE_FLAG_DELETE_ON_CLOSE for multi-instance locks. r=nalexander,application-update-reviewers.
FILE_FLAG_DELETE_ON_CLOSE had the wrong semantics, rendering the lock file unusable after it had been closed once. Delete the lock file in the uninstaller as a simple alternative (given that the lock file is not in a temporary location on Windows). For a test I returned to the older form of test_backgroundtask_update_sync_manager which initially exposed the issue: It expects the background task to be able to detect the xpcshell instance after running resetLock, which failed before this fix. I also extended the original updateSyncManager test to run the second copy twice, which also catches the issue. Differential Revision: https://phabricator.services.mozilla.com/D109565
1 parent 5a8c4f1 commit 6b6c4da

4 files changed

Lines changed: 64 additions & 54 deletions

File tree

browser/installer/windows/nsis/uninstaller.nsi

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,20 @@ Function un.OpenRefreshHelpURL
327327
ExecShell "open" "${URLProfileRefreshHelp}"
328328
FunctionEnd
329329

330+
; Returns the common directory (typically "C:\ProgramData\Mozilla") on the stack.
331+
Function un.GetCommonDirectory
332+
Push $0 ; Save $0
333+
334+
; This gets C:\ProgramData or the equivalent.
335+
; 0x23 is CSIDL_COMMON_APPDATA, see CreateUpdateDir in common.nsh.
336+
System::Call "Shell32::SHGetSpecialFolderPathW(p 0, t.r0, i 0x23, i 0)"
337+
; Add our subdirectory, this is hardcoded as grandparent of the update directory in
338+
; several other places.
339+
StrCpy $0 "$0\Mozilla"
340+
341+
Exch $0 ; Restore original $0 and put our $0 on the stack.
342+
FunctionEnd
343+
330344
Function un.SendUninstallPing
331345
${If} $AppUserModelID == ""
332346
Return
@@ -340,12 +354,8 @@ Function un.SendUninstallPing
340354
Push $5 ; $5 = URL, POST result
341355
Push $6 ; $6 = Full path to the ping file
342356

343-
; This gets C:\ProgramData or the equivalent.
344-
; 0x23 is CSIDL_COMMON_APPDATA, see CreateUpdateDir in common.nsh.
345-
System::Call "Shell32::SHGetSpecialFolderPathW(p 0, t.r2, i 0x23, i 0)"
346-
; Add our subdirectory, this is hardcoded as grandparent of the update directory in
347-
; several other places.
348-
StrCpy $2 "$2\Mozilla"
357+
Call un.GetCommonDirectory
358+
Pop $2
349359

350360
; The ping ID is in the file name, so that we can get it for the submission URL
351361
; without having to parse the ping. Since we don't know the exact name, use FindFirst
@@ -441,6 +451,13 @@ Section "Uninstall"
441451
ApplicationID::UninstallJumpLists "$AppUserModelID"
442452
${EndIf}
443453

454+
; Remove the update sync manager's multi-instance lock file
455+
${If} "$AppUserModelID" != ""
456+
Call un.GetCommonDirectory
457+
Pop $0
458+
Delete /REBOOTOK "$0\UpdateLock-$AppUserModelID"
459+
${EndIf}
460+
444461
; Remove the updates directory
445462
${un.CleanUpdateDirectories} "Mozilla\Firefox" "Mozilla\updates"
446463

toolkit/components/backgroundtasks/tests/xpcshell/test_backgroundtask_update_sync_manager.js

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,15 @@ add_task(async function test_backgroundtask_update_sync_manager() {
1414
// This can also be achieved by overriding directory providers, but
1515
// that's not particularly robust in the face of parallel testing.
1616
// Doing it this way exercises `resetLock` with a path.
17-
let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
18-
Ci.nsIUpdateSyncManager
19-
);
20-
21-
let file = do_get_profile();
22-
file.append("customExePath1");
23-
file.append("customExe");
24-
25-
// The background task will see our process.
26-
syncManager.resetLock(file);
27-
2817
let exitCode = await do_backgroundtask("update_sync_manager", {
29-
extraArgs: [file.path],
18+
extraArgs: [Services.dirsvc.get("XREExeF", Ci.nsIFile).path],
3019
});
3120
Assert.equal(80, exitCode, "Another instance is running");
3221

3322
// If we tell the backgroundtask to use a unique appPath, the
3423
// background task won't see any other instances running.
35-
file = do_get_profile();
36-
file.append("customExePath2");
24+
let file = do_get_profile();
25+
file.append("customExePath");
3726
file.append("customExe");
3827

3928
exitCode = await do_backgroundtask("update_sync_manager", {

toolkit/mozapps/update/tests/unit_aus_update/updateSyncManager.js

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -61,39 +61,45 @@ add_task(async function() {
6161
getTestDirFile("syncManagerTestChild.js").path,
6262
];
6363

64-
// Now we can actually invoke the process.
65-
debugDump(`launching child process at ${thisBinary.path} with args ${args}`);
66-
Subprocess.call({
67-
command: thisBinary.path,
68-
arguments: args,
69-
stderr: "stdout",
70-
});
71-
72-
// It will take the new xpcshell a little time to start up, but we should see
73-
// the effect on the lock within at most a few seconds.
74-
await TestUtils.waitForCondition(
75-
() => syncManager.isOtherInstanceRunning(),
76-
"waiting for child process to take the lock"
77-
).catch(e => {
78-
// Rather than throwing out of waitForCondition(), catch and log the failure
79-
// manually so that we get output that's a bit more readable.
80-
Assert.ok(
81-
syncManager.isOtherInstanceRunning(),
82-
"child process has the lock"
64+
// Run the second copy two times, to show the lock is usable after having
65+
// been closed.
66+
for (let runs = 0; runs < 2; runs++) {
67+
// Now we can actually invoke the process.
68+
debugDump(
69+
`launching child process at ${thisBinary.path} with args ${args}`
8370
);
84-
});
71+
Subprocess.call({
72+
command: thisBinary.path,
73+
arguments: args,
74+
stderr: "stdout",
75+
});
8576

86-
// The lock should have been closed when the process exited, but we'll allow
87-
// a little time for the OS to clean up the handle.
88-
await TestUtils.waitForCondition(
89-
() => !syncManager.isOtherInstanceRunning(),
90-
"waiting for child process to release the lock"
91-
).catch(e => {
92-
Assert.ok(
93-
!syncManager.isOtherInstanceRunning(),
94-
"child process has released the lock"
95-
);
96-
});
77+
// It will take the new xpcshell a little time to start up, but we should see
78+
// the effect on the lock within at most a few seconds.
79+
await TestUtils.waitForCondition(
80+
() => syncManager.isOtherInstanceRunning(),
81+
"waiting for child process to take the lock"
82+
).catch(e => {
83+
// Rather than throwing out of waitForCondition(), catch and log the failure
84+
// manually so that we get output that's a bit more readable.
85+
Assert.ok(
86+
syncManager.isOtherInstanceRunning(),
87+
"child process has the lock"
88+
);
89+
});
90+
91+
// The lock should have been closed when the process exited, but we'll allow
92+
// a little time for the OS to clean up the handle.
93+
await TestUtils.waitForCondition(
94+
() => !syncManager.isOtherInstanceRunning(),
95+
"waiting for child process to release the lock"
96+
).catch(e => {
97+
Assert.ok(
98+
!syncManager.isOtherInstanceRunning(),
99+
"child process has released the lock"
100+
);
101+
});
102+
}
97103

98104
doTestFinish();
99105
});

toolkit/xre/MultiInstanceLock.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ MultiInstLockHandle OpenMultiInstanceLock(const char* nameToken,
6565
::CreateFileW(PromiseFlatString(NS_ConvertUTF8toUTF16(filePath)).get(),
6666
GENERIC_READ | GENERIC_WRITE,
6767
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
68-
nullptr, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, nullptr);
68+
nullptr, OPEN_ALWAYS, 0, nullptr);
6969
if (h != INVALID_HANDLE_VALUE) {
7070
// The LockFileEx functions always require an OVERLAPPED structure even
7171
// though we did not open the lock file for overlapped I/O.
@@ -108,8 +108,6 @@ void ReleaseMultiInstanceLock(MultiInstLockHandle lock) {
108108
#ifdef XP_WIN
109109
OVERLAPPED o = {0};
110110
::UnlockFileEx(lock, 0, 1, 0, &o);
111-
// We've used FILE_FLAG_DELETE_ON_CLOSE, so if we are the last instance
112-
// with a handle on the lock file, closing it here will delete it.
113111
::CloseHandle(lock);
114112

115113
#else

0 commit comments

Comments
 (0)