ab86c9961e
`LaunchProcess()` on Linux works by calling `fork` then `execvpe`. `fork` is used to copy a running process, generating a new child process. The new child starts running from the location where the parent was running, from whatever thread from the parent called `fork`. The child process only gets one thread, however. If a different thread in the parent process had locked a mutex, that mutex is also locked in the child process. Since that separate thread is not present in the child, the mutex remains locked in the child, with no way to unlock it. So it is important that as little work as possible happens between the call to `fork` and to `execvpe`. Previously, this code was trying to report an error that may have occurred from calling `execvpe`. It was doing that by calling `AZ_TracePrintf`. That function does lots of things, including trying to make an EBus call, which looks up a variable in the `AZ::Environment` instance, which has a global mutex. If there was some other thread that had that mutex locked when the `fork` call was made, the subprocess would deadlock, and the parent process would also deadlock waiting for the child to finish. This solves that issue by removing the call to `AZ_TracePrintf` from the subprocess code path. Instead, the parent process sets up a pipe for the child process to write to in case the call to `execvpe` fails (the self-pipe trick). The parent then reads from that pipe. If it reads no data, `execvpe` worked and there's no error. If it does read data, the data to be read is the errno from the failed `execvpe` call made by the child. The parent can then use `strerror()` to report the error. Fixes #4702. Signed-off-by: Chris Burel <burelc@amazon.com>
106 lines
4.0 KiB
C++
106 lines
4.0 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
|
#include <AzCore/std/smart_ptr/scoped_ptr.h>
|
|
#include <AzCore/std/parallel/thread.h>
|
|
#include <AzFramework/StringFunc/StringFunc.h>
|
|
#include <AzFramework/Process/ProcessWatcher.h>
|
|
#include <AzFramework/Process/ProcessCommunicator.h>
|
|
|
|
namespace AzFramework
|
|
{
|
|
bool ProcessWatcher::LaunchProcessAndRetrieveOutput(const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, ProcessCommunicationType communicationType, AzFramework::ProcessOutput& outProcessOutput)
|
|
{
|
|
// launch the process
|
|
|
|
AZStd::scoped_ptr<ProcessWatcher> pWatcher(LaunchProcess(processLaunchInfo, communicationType));
|
|
if (!pWatcher)
|
|
{
|
|
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// get the communicator and ensure it is valid
|
|
ProcessCommunicator* pCommunicator = pWatcher->GetCommunicator();
|
|
if (!pCommunicator || !pCommunicator->IsValid())
|
|
{
|
|
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
pCommunicator->ReadIntoProcessOutput(outProcessOutput);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ProcessWatcher::SpawnProcess(const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, ProcessCommunicationType communicationType)
|
|
{
|
|
InitProcessData(communicationType == COMMUNICATOR_TYPE_STDINOUT);
|
|
|
|
if (communicationType == COMMUNICATOR_TYPE_STDINOUT)
|
|
{
|
|
StdProcessCommunicator* pStdCommunicator = CreateStdCommunicator();
|
|
if (pStdCommunicator->CreatePipesForProcess(m_pWatcherData.get()))
|
|
{
|
|
m_pCommunicator = pStdCommunicator;
|
|
}
|
|
else
|
|
{
|
|
// Communicator failure, just clean it up
|
|
delete pStdCommunicator;
|
|
}
|
|
}
|
|
else if (communicationType == COMMUNICATOR_TYPE_NONE)
|
|
{
|
|
//Implemented, but don't do anything.
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(false, "communicationType %d not implemented", communicationType);
|
|
}
|
|
|
|
return ProcessLauncher::LaunchProcess(processLaunchInfo, *m_pWatcherData);
|
|
}
|
|
|
|
class ProcessCommunicator* ProcessWatcher::GetCommunicator()
|
|
{
|
|
return m_pCommunicator;
|
|
}
|
|
|
|
AZStd::shared_ptr<ProcessCommunicatorForChildProcess> ProcessWatcher::GetCommunicatorForChildProcess(ProcessCommunicationType communicationType)
|
|
{
|
|
if (communicationType == COMMUNICATOR_TYPE_STDINOUT)
|
|
{
|
|
StdProcessCommunicatorForChildProcess* communicator = CreateStdCommunicatorForChildProcess();
|
|
if (!communicator->AttachToExistingPipes())
|
|
{
|
|
// Delete the communicator if attaching fails, it is useless
|
|
delete communicator;
|
|
communicator = nullptr;
|
|
}
|
|
return AZStd::shared_ptr<ProcessCommunicatorForChildProcess>{
|
|
communicator
|
|
};
|
|
}
|
|
else if (communicationType == COMMUNICATOR_TYPE_NONE)
|
|
{
|
|
AZ_Assert(false, "No communicator for communicationType %d", communicationType);
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(false, "communicationType %d not implemented", communicationType);
|
|
}
|
|
return AZStd::shared_ptr<ProcessCommunicatorForChildProcess>{};
|
|
}
|
|
} // AzFramework
|