mirror of https://github.com/PCSX2/pcsx2.git
linux: Fix and simplify stdout/stderr redirection
Use close() instead of some dodgy read unblocking and thread cancelling sequence - it fixes a race condition when closing PCSX2. Move most of the setup/cleanup into LinuxPipeThread. In addition to simplifying things, it should also mean that no messages to stdout/stderr are lost - in the previous code there was a small period of time where messages would disappear.
This commit is contained in:
parent
0bd7cb1ebe
commit
60426a5dec
pcsx2/Linux
|
@ -21,47 +21,100 @@
|
||||||
|
|
||||||
using namespace Threading;
|
using namespace Threading;
|
||||||
|
|
||||||
// Reads data sent to stdout/stderr and sends it to the PCSX2 console.
|
// Redirects and reads data sent to stdout/stderr and sends it to the PCSX2
|
||||||
|
// console.
|
||||||
class LinuxPipeThread : public pxThread
|
class LinuxPipeThread : public pxThread
|
||||||
{
|
{
|
||||||
typedef pxThread _parent;
|
typedef pxThread _parent;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const int& m_read_fd;
|
FILE* m_stdstream;
|
||||||
|
FILE* m_fp;
|
||||||
const ConsoleColors m_color;
|
const ConsoleColors m_color;
|
||||||
|
int m_pipe_fd[2];
|
||||||
|
|
||||||
void ExecuteTaskInThread();
|
void ExecuteTaskInThread();
|
||||||
public:
|
public:
|
||||||
LinuxPipeThread(const int& read_fd, ConsoleColors color);
|
LinuxPipeThread(FILE* stdstream);
|
||||||
virtual ~LinuxPipeThread() noexcept;
|
virtual ~LinuxPipeThread() noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
LinuxPipeThread::LinuxPipeThread(const int& read_fd, ConsoleColors color)
|
LinuxPipeThread::LinuxPipeThread(FILE* stdstream)
|
||||||
: pxThread(color == Color_Red ? L"Redirect_Stderr" : L"Redirect_Stdout")
|
: pxThread(stdstream == stderr? L"Redirect_Stderr" : L"Redirect_Stdout")
|
||||||
, m_read_fd(read_fd)
|
, m_stdstream(stdstream)
|
||||||
, m_color(color)
|
, m_fp(nullptr)
|
||||||
|
, m_color(stdstream == stderr? Color_Red : Color_Black)
|
||||||
|
, m_pipe_fd{-1, -1}
|
||||||
{
|
{
|
||||||
|
const wxChar* stream_name = stdstream == stderr? L"stderr" : L"stdout";
|
||||||
|
|
||||||
|
// Save the original stdout/stderr file descriptor
|
||||||
|
int dup_fd = dup(fileno(stdstream));
|
||||||
|
if (dup_fd == -1)
|
||||||
|
throw Exception::RuntimeError().SetDiagMsg(wxString::Format(
|
||||||
|
L"Redirect %s failed: dup: %s", stream_name, strerror(errno)));
|
||||||
|
m_fp = fdopen(dup_fd, "w");
|
||||||
|
if (m_fp == nullptr) {
|
||||||
|
int error = errno;
|
||||||
|
close(dup_fd);
|
||||||
|
throw Exception::RuntimeError().SetDiagMsg(wxString::Format(
|
||||||
|
L"Redirect %s failed: fdopen: %s", stream_name, strerror(error)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pipe(m_pipe_fd) == -1) {
|
||||||
|
int error = errno;
|
||||||
|
fclose(m_fp);
|
||||||
|
throw Exception::RuntimeError().SetDiagMsg(wxString::Format(
|
||||||
|
L"Redirect %s failed: pipe: %s", stream_name, strerror(error)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LinuxPipeThread::~LinuxPipeThread() noexcept
|
LinuxPipeThread::~LinuxPipeThread() noexcept
|
||||||
{
|
{
|
||||||
_parent::Cancel();
|
// Close write end of the pipe first so the redirection thread starts
|
||||||
|
// finishing up and restore the original stdout/stderr file descriptor so
|
||||||
|
// messages aren't lost.
|
||||||
|
dup2(fileno(m_fp), m_pipe_fd[1]);
|
||||||
|
|
||||||
|
if (m_stdstream == stdout)
|
||||||
|
Console_SetStdout(stdout);
|
||||||
|
fclose(m_fp);
|
||||||
|
|
||||||
|
// Read end of pipe should only be closed after the thread terminates to
|
||||||
|
// prevent messages being lost.
|
||||||
|
m_mtx_InThread.Wait();
|
||||||
|
close(m_pipe_fd[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinuxPipeThread::ExecuteTaskInThread()
|
void LinuxPipeThread::ExecuteTaskInThread()
|
||||||
{
|
{
|
||||||
char buffer[2049];
|
const wxChar* stream_name = m_stdstream == stderr? L"stderr" : L"stdout";
|
||||||
while (ssize_t bytes_read = read(m_read_fd, buffer, sizeof(buffer) - 1)) {
|
|
||||||
TestCancel();
|
|
||||||
|
|
||||||
|
// Redirect stdout/stderr
|
||||||
|
int stdstream_fd = fileno(m_stdstream);
|
||||||
|
if (dup2(m_pipe_fd[1], stdstream_fd) != stdstream_fd) {
|
||||||
|
Console.Error(wxString::Format(L"Redirect %s failed: dup2: %s",
|
||||||
|
stream_name, strerror(errno)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(m_pipe_fd[1]);
|
||||||
|
m_pipe_fd[1] = stdstream_fd;
|
||||||
|
|
||||||
|
// Send console output to the original stdout, otherwise there'll be an
|
||||||
|
// infinite loop.
|
||||||
|
if (m_stdstream == stdout)
|
||||||
|
Console_SetStdout(m_fp);
|
||||||
|
|
||||||
|
char buffer[2049];
|
||||||
|
while (ssize_t bytes_read = read(m_pipe_fd[0], buffer, sizeof(buffer) - 1)) {
|
||||||
if (bytes_read == -1) {
|
if (bytes_read == -1) {
|
||||||
if (errno == EINTR) {
|
if (errno == EINTR) {
|
||||||
continue;
|
continue;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// Should never happen.
|
// Should never happen.
|
||||||
Console.Error(wxString::Format(L"Redirect %s failed: read: %s",
|
Console.Error(wxString::Format(L"Redirect %s failed: read: %s",
|
||||||
m_color == Color_Red ? L"stderr" : L"stdout", strerror(errno)));
|
stream_name, strerror(errno)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,111 +125,37 @@ void LinuxPipeThread::ExecuteTaskInThread()
|
||||||
ConsoleColorScope cs(m_color);
|
ConsoleColorScope cs(m_color);
|
||||||
Console.WriteRaw(fromUTF8(buffer));
|
Console.WriteRaw(fromUTF8(buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
TestCancel();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirects data sent to stdout/stderr into a pipe, and sets up a thread to
|
|
||||||
// forward data to the console.
|
|
||||||
class LinuxPipeRedirection : public PipeRedirectionBase
|
class LinuxPipeRedirection : public PipeRedirectionBase
|
||||||
{
|
{
|
||||||
DeclareNoncopyableObject(LinuxPipeRedirection);
|
DeclareNoncopyableObject(LinuxPipeRedirection);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
FILE* m_stdstream;
|
|
||||||
FILE* m_fp;
|
|
||||||
int m_pipe_fd[2];
|
|
||||||
LinuxPipeThread m_thread;
|
LinuxPipeThread m_thread;
|
||||||
|
|
||||||
void Cleanup() noexcept;
|
|
||||||
public:
|
public:
|
||||||
LinuxPipeRedirection(FILE* stdstream);
|
LinuxPipeRedirection(FILE* stdstream);
|
||||||
virtual ~LinuxPipeRedirection() noexcept;
|
virtual ~LinuxPipeRedirection() noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
LinuxPipeRedirection::LinuxPipeRedirection(FILE* stdstream)
|
LinuxPipeRedirection::LinuxPipeRedirection(FILE* stdstream)
|
||||||
: m_stdstream(stdstream)
|
: m_thread(stdstream)
|
||||||
, m_fp(nullptr)
|
|
||||||
, m_pipe_fd{-1, -1}
|
|
||||||
, m_thread(m_pipe_fd[0], stdstream == stderr ? Color_Red : Color_Black)
|
|
||||||
{
|
{
|
||||||
pxAssert((stdstream == stderr) || (stdstream == stdout));
|
pxAssert((stdstream == stderr) || (stdstream == stdout));
|
||||||
|
|
||||||
const wxChar* stream_name = stdstream == stderr? L"stderr" : L"stdout";
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int stdstream_fd = fileno(stdstream);
|
|
||||||
|
|
||||||
// Save the original stdout/stderr file descriptor...
|
|
||||||
int dup_fd = dup(stdstream_fd);
|
|
||||||
if (dup_fd == -1)
|
|
||||||
throw Exception::RuntimeError().SetDiagMsg(wxString::Format(
|
|
||||||
L"Redirect %s failed: dup: %s", stream_name, strerror(errno)));
|
|
||||||
m_fp = fdopen(dup_fd, "w");
|
|
||||||
if (m_fp == nullptr)
|
|
||||||
throw Exception::RuntimeError().SetDiagMsg(wxString::Format(
|
|
||||||
L"Redirect %s failed: fdopen: %s", stream_name, strerror(errno)));
|
|
||||||
|
|
||||||
// and now redirect stdout/stderr.
|
|
||||||
if (pipe(m_pipe_fd) == -1)
|
|
||||||
throw Exception::RuntimeError().SetDiagMsg(wxString::Format(
|
|
||||||
L"Redirect %s failed: pipe: %s", stream_name, strerror(errno)));
|
|
||||||
if (dup2(m_pipe_fd[1], stdstream_fd) != stdstream_fd)
|
|
||||||
throw Exception::RuntimeError().SetDiagMsg(wxString::Format(
|
|
||||||
L"Redirect %s failed: dup2: %s", stream_name, strerror(errno)));
|
|
||||||
close(m_pipe_fd[1]);
|
|
||||||
m_pipe_fd[1] = stdstream_fd;
|
|
||||||
|
|
||||||
// And send the final console output goes to the original stdout,
|
|
||||||
// otherwise we'll have an infinite data loop.
|
|
||||||
if (stdstream == stdout)
|
|
||||||
Console_SetStdout(m_fp);
|
|
||||||
|
|
||||||
m_thread.Start();
|
m_thread.Start();
|
||||||
} catch (Exception::BaseThreadError& ex) {
|
} catch (Exception::BaseThreadError& ex) {
|
||||||
// thread object will become invalid because of scoping after we leave
|
// thread object will become invalid because of scoping after we leave
|
||||||
// the constructor, so re-pack a new exception:
|
// the constructor, so re-pack a new exception:
|
||||||
Cleanup();
|
|
||||||
throw Exception::RuntimeError().SetDiagMsg(ex.FormatDiagnosticMessage());
|
throw Exception::RuntimeError().SetDiagMsg(ex.FormatDiagnosticMessage());
|
||||||
} catch (...) {
|
|
||||||
Cleanup();
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LinuxPipeRedirection::~LinuxPipeRedirection() noexcept
|
LinuxPipeRedirection::~LinuxPipeRedirection() noexcept
|
||||||
{
|
{
|
||||||
Cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LinuxPipeRedirection::Cleanup() noexcept
|
|
||||||
{
|
|
||||||
// Restore stdout/stderr file descriptor - mostly useful if the thread
|
|
||||||
// fails to start, but then you have bigger issues to worry about.
|
|
||||||
if (m_pipe_fd[1] != -1) {
|
|
||||||
if (m_pipe_fd[1] == fileno(m_stdstream)) {
|
|
||||||
// FIXME: Use lock for better termination.
|
|
||||||
// The redirect thread is most likely waiting at read(). Send a
|
|
||||||
// newline so it returns and the thread begins to terminate.
|
|
||||||
fflush(m_stdstream);
|
|
||||||
m_thread.Cancel();
|
|
||||||
fputc('\n', m_stdstream);
|
|
||||||
fflush(m_stdstream);
|
|
||||||
dup2(fileno(m_fp), fileno(m_stdstream));
|
|
||||||
} else {
|
|
||||||
close(m_pipe_fd[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_fp != nullptr) {
|
|
||||||
if (m_stdstream == stdout)
|
|
||||||
Console_SetStdout(stdout);
|
|
||||||
fclose(m_fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_pipe_fd[0] != -1)
|
|
||||||
close(m_pipe_fd[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PipeRedirectionBase* NewPipeRedir(FILE* stdstream)
|
PipeRedirectionBase* NewPipeRedir(FILE* stdstream)
|
||||||
|
|
Loading…
Reference in New Issue