Bug 1272614 - Avoid blocking where possible while waiting for the updater to stage; r=spohl

MozReview-Commit-ID: 1aAU2wxQvMm

--HG--
extra : rebase_source : c965bd526d99560203da5b082a396c1102fd22dd
This commit is contained in:
Matt Howell 2016-08-04 12:54:01 -07:00
parent 2c2d766102
commit b29fee8676
2 changed files with 61 additions and 50 deletions

View File

@ -44,34 +44,11 @@
# define getpid() GetCurrentProcessId()
#elif defined(XP_UNIX)
# include <unistd.h>
# include <sys/wait.h>
#endif
using namespace mozilla;
//
// We use execv to spawn the updater process on all UNIX systems except Mac OSX
// since it is known to cause problems on the Mac. Windows has execv, but it
// is a faked implementation that doesn't really replace the current process.
// Instead it spawns a new process, so we gain nothing from using execv on
// Windows.
//
// On platforms where we are not calling execv, we may need to make the
// updater executable wait for the calling process to exit. Otherwise, the
// updater may have trouble modifying our executable image (because it might
// still be in use). This is accomplished by passing our PID to the updater so
// that it can wait for us to exit. This is not perfect as there is a race
// condition that could bite us. It's possible that the calling process could
// exit before the updater waits on the specified PID, and in the meantime a
// new process with the same PID could be created. This situation is unlikely,
// however, given the way most operating systems recycle PIDs. We'll take our
// chances ;-)
//
// A similar #define lives in updater.cpp and should be kept in sync with this.
//
#if defined(XP_UNIX) && !defined(XP_MACOSX)
#define USE_EXECV
#endif
static PRLogModuleInfo *
GetUpdateLog()
{
@ -204,7 +181,7 @@ static bool
GetFile(nsIFile *dir, const nsCSubstring &name, nsCOMPtr<nsIFile> &result)
{
nsresult rv;
nsCOMPtr<nsIFile> file;
rv = dir->Clone(getter_AddRefs(file));
if (NS_FAILED(rv))
@ -401,7 +378,7 @@ CopyUpdaterIntoUpdateDir(nsIFile *greDir, nsIFile *appDir, nsIFile *updateDir,
return false;
#endif
rv = updater->AppendNative(NS_LITERAL_CSTRING(UPDATER_BIN));
return NS_SUCCEEDED(rv);
return NS_SUCCEEDED(rv);
}
/**
@ -593,9 +570,10 @@ SwitchToUpdatedApp(nsIFile *greDir, nsIFile *updateDir,
if (NS_FAILED(rv))
return;
// Construct the PID argument for this process. If we are using execv, then
// we pass "0" which is then ignored by the updater.
#if defined(USE_EXECV)
// Construct the PID argument for this process. We start the updater using
// execv on all Unix platforms except Mac, so on those platforms we pass 0
// instead of a good PID to signal the updater not to try and wait for us.
#if defined(XP_UNIX) & !defined(XP_MACOSX)
nsAutoCString pid("0");
#else
nsAutoCString pid;
@ -642,14 +620,14 @@ SwitchToUpdatedApp(nsIFile *greDir, nsIFile *updateDir,
LOG(("spawning updater process for replacing [%s]\n", updaterPath.get()));
#if defined(USE_EXECV)
#if defined(XP_UNIX) & !defined(XP_MACOSX)
# if defined(MOZ_WIDGET_GONK)
// In Gonk, we preload libmozglue, which the updater process doesn't need.
// Since the updater will move and delete libmozglue.so, this can actually
// stop the /system mount from correctly being remounted as read-only.
unsetenv("LD_PRELOAD");
# endif
execv(updaterPath.get(), argv);
exit(execv(updaterPath.get(), argv));
#elif defined(XP_WIN)
// Switch the application using updater.exe
if (!WinLaunchChild(updaterPathW.get(), argc, argv)) {
@ -789,7 +767,7 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
rv = appFile->GetNativePath(appFilePath);
if (NS_FAILED(rv))
return;
nsAutoCString updaterPath;
rv = updater->GetNativePath(updaterPath);
if (NS_FAILED(rv))
@ -868,6 +846,16 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
// in the Windows case. This change was made for all platforms so
// that it stays consistent across all OS.
// On platforms where we are not calling execv, we may need to make the
// updater executable wait for the calling process to exit. Otherwise, the
// updater may have trouble modifying our executable image (because it might
// still be in use). This is accomplished by passing our PID to the updater so
// that it can wait for us to exit. This is not perfect as there is a race
// condition that could bite us. It's possible that the calling process could
// exit before the updater waits on the specified PID, and in the meantime a
// new process with the same PID could be created. This situation is unlikely,
// however, given the way most operating systems recycle PIDs. We'll take our
// chances ;-)
// Construct the PID argument for this process. If we are using execv, then
// we pass "0" which is then ignored by the updater.
nsAutoCString pid;
@ -875,7 +863,7 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
// Signal the updater application that it should stage the update.
pid.AssignASCII("-1");
} else {
#if defined(USE_EXECV)
#if defined(XP_UNIX) & !defined(XP_MACOSX)
pid.AssignASCII("0");
#else
pid.AppendInt((int32_t) getpid());
@ -942,12 +930,20 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
LOG(("spawning updater process [%s]\n", updaterPath.get()));
#if defined(USE_EXECV)
// Don't use execv when staging updates.
#if defined(XP_UNIX) && !defined(XP_MACOSX)
// We use execv to spawn the updater process on all UNIX systems except Mac OSX
// since it is known to cause problems on the Mac. Windows has execv, but it
// is a faked implementation that doesn't really replace the current process.
// Instead it spawns a new process, so we gain nothing from using execv on
// Windows.
if (restart) {
execv(updaterPath.get(), argv);
} else {
*outpid = PR_CreateProcess(updaterPath.get(), argv, nullptr, nullptr);
exit(execv(updaterPath.get(), argv));
}
*outpid = fork();
if (*outpid == -1) {
return;
} else if (*outpid == 0) {
exit(execv(updaterPath.get(), argv));
}
#elif defined(XP_WIN)
// Launch the update using updater.exe
@ -985,22 +981,35 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
}
/**
* Wait for a process until it terminates. This call is blocking.
* Wait briefly to see if a process terminates, then return true if it has.
*/
static void
WaitForProcess(ProcessType pt)
static bool
ProcessHasTerminated(ProcessType pt)
{
#if defined(XP_WIN)
WaitForSingleObject(pt, INFINITE);
if (WaitForSingleObject(pt, 1000)) {
return false;
}
CloseHandle(pt);
#elif defined(XP_MACOSX)
waitpid(pt, 0, 0);
return true;
#elif defined(XP_UNIX)
int exitStatus;
bool exited = waitpid(pt, &exitStatus, WNOHANG) > 0;
if (!exited) {
sleep(1);
} else if (WIFEXITED(exitStatus) && (WEXITSTATUS(exitStatus) != 0)) {
LOG(("Error while running the updater process, check update.log"));
}
return exited;
#else
// No way to have a non-blocking implementation on these platforms,
// because we're using NSPR and it only provides a blocking wait.
int32_t exitCode;
PR_WaitProcess(pt, &exitCode);
if (exitCode != 0) {
LOG(("Error while running the updater process, check update.log"));
}
return true;
#endif
}
@ -1024,7 +1033,7 @@ ProcessUpdates(nsIFile *greDir, nsIFile *appDir, nsIFile *updRootDir,
rv = updatesDir->AppendNative(NS_LITERAL_CSTRING("0"));
if (NS_FAILED(rv))
return rv;
nsCOMPtr<nsIFile> statusFile;
UpdateStatus status = GetUpdateStatus(updatesDir, statusFile);
switch (status) {
@ -1270,8 +1279,11 @@ void
nsUpdateProcessor::WaitForProcess()
{
MOZ_ASSERT(!NS_IsMainThread(), "main thread");
::WaitForProcess(mUpdaterPID);
NS_DispatchToMainThread(NewRunnableMethod(this, &nsUpdateProcessor::UpdateDone));
if (ProcessHasTerminated(mUpdaterPID)) {
NS_DispatchToMainThread(NewRunnableMethod(this, &nsUpdateProcessor::UpdateDone));
} else {
NS_DispatchToCurrentThread(NewRunnableMethod(this, &nsUpdateProcessor::WaitForProcess));
}
}
void
@ -1287,4 +1299,3 @@ nsUpdateProcessor::UpdateDone()
ShutdownWatcherThread();
}

View File

@ -19,7 +19,7 @@ class nsIFile;
#if defined(XP_WIN)
#include <windows.h>
typedef HANDLE ProcessType;
#elif defined(XP_MACOSX)
#elif defined(XP_UNIX)
typedef pid_t ProcessType;
#else
#include "prproces.h"
@ -33,7 +33,7 @@ class nsIFile;
* Two directories are passed to this function: greDir (where the actual
* binary resides) and appDir (which contains application.ini for XULRunner
* apps). If this is not a XULRunner app then appDir is identical to greDir.
*
*
* The argc and argv passed to this function should be what is needed to
* relaunch the current process.
*