`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>
AZ_TracePrintf("Process Watcher","ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'",processLaunchInfo.m_processExecutableString.c_str(),processLaunchInfo.m_commandlineParameters.c_str());
AZ_TracePrintf("Process Watcher","ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'\n",processLaunchInfo.m_processExecutableString.c_str(),processLaunchInfo.m_commandlineParameters.c_str());
AZ_TracePrintf("Process Watcher","ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!",processLaunchInfo.m_processExecutableString.c_str(),processLaunchInfo.m_commandlineParameters.c_str());
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());
AZ_TracePrintf("Process Watcher","ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to change the launched process' directory to '%s'.",processLaunchInfo.m_workingDirectory.c_str());
write(errorPipe[1],&errno,sizeof(int));
// We *have* to _exit as we are the child process and simply
// returning at this point would mean we would start running
// the code from our parent process and that will just wreck