fixed IO redirection to work in wxExecute() in console applications (including the case when the child process outputs more than pipe buffer size) by using wxSelectDispatcher for multiplexing

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@52692 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2008-03-22 14:10:54 +00:00
parent a83ca5fbf5
commit 33343395da
3 changed files with 177 additions and 73 deletions

View File

@@ -41,6 +41,7 @@
#include "wx/wfstream.h"
#include "wx/private/selectdispatcher.h"
#include "wx/private/fdiodispatcher.h"
#include "wx/unix/execute.h"
#include "wx/unix/private.h"
@@ -650,19 +651,17 @@ long wxExecute(wxChar **argv, int flags, wxProcess *process)
// called bufOut and not bufIn
wxStreamTempInputBuffer bufOut,
bufErr;
#endif // HAS_PIPE_INPUT_STREAM
if ( process && process->IsRedirected() )
{
#if HAS_PIPE_INPUT_STREAM
wxOutputStream *inStream =
new wxFileOutputStream(pipeIn.Detach(wxPipe::Write));
wxPipeInputStream *outStream =
new wxPipeInputStream(pipeOut.Detach(wxPipe::Read));
const int fdOut = pipeOut.Detach(wxPipe::Read);
wxPipeInputStream *outStream = new wxPipeInputStream(fdOut);
wxPipeInputStream *errStream =
new wxPipeInputStream(pipeErr.Detach(wxPipe::Read));
const int fdErr = pipeErr.Detach(wxPipe::Read);
wxPipeInputStream *errStream = new wxPipeInputStream(fdErr);
process->SetPipeStreams(outStream, inStream, errStream);
@@ -671,8 +670,11 @@ long wxExecute(wxChar **argv, int flags, wxProcess *process)
execData.bufOut = &bufOut;
execData.bufErr = &bufErr;
#endif // HAS_PIPE_INPUT_STREAM
execData.fdOut = fdOut;
execData.fdErr = fdErr;
}
#endif // HAS_PIPE_INPUT_STREAM
if ( pipeIn.IsOk() )
{
@@ -1325,82 +1327,169 @@ bool wxAppTraits::CheckForRedirectedIO(wxExecuteData& execData)
#endif // HAS_PIPE_INPUT_STREAM/!HAS_PIPE_INPUT_STREAM
}
class wxReadFDIOHandler : public wxFDIOHandler
{
public:
wxReadFDIOHandler(wxFDIODispatcher& disp, int fd) : m_fd(fd)
{
if ( fd )
disp.RegisterFD(fd, this, wxFDIO_INPUT);
}
int GetFD() const { return m_fd; }
virtual void OnWriteWaiting() { wxFAIL_MSG("unreachable"); }
virtual void OnExceptionWaiting() { wxFAIL_MSG("unreachable"); }
private:
const int m_fd;
DECLARE_NO_COPY_CLASS(wxReadFDIOHandler)
};
class wxEndHandler : public wxReadFDIOHandler
{
public:
wxEndHandler(wxFDIODispatcher& disp, int fd)
: wxReadFDIOHandler(disp, fd)
{
m_terminated = false;
}
bool Terminated() const { return m_terminated; }
virtual void OnReadWaiting() { m_terminated = true; }
private:
bool m_terminated;
DECLARE_NO_COPY_CLASS(wxEndHandler)
};
#if wxUSE_STREAMS
class wxRedirectedIOHandler : public wxReadFDIOHandler
{
public:
wxRedirectedIOHandler(wxFDIODispatcher& disp,
int fd,
wxStreamTempInputBuffer *buf)
: wxReadFDIOHandler(disp, fd),
m_buf(buf)
{
}
virtual void OnReadWaiting()
{
m_buf->Update();
}
private:
wxStreamTempInputBuffer * const m_buf;
DECLARE_NO_COPY_CLASS(wxRedirectedIOHandler)
};
#endif // wxUSE_STREAMS
int wxAppTraits::WaitForChild(wxExecuteData& execData)
{
if ( execData.flags & wxEXEC_SYNC )
{
// just block waiting for the child to exit
int status = 0;
int result = waitpid(execData.pid, &status, 0);
#ifdef __DARWIN__
/* DE: waitpid manpage states that waitpid can fail with EINTR
if the call is interrupted by a caught signal. I suppose
that means that this ought to be a while loop.
The odd thing is that it seems to fail EVERY time. It fails
with a quickly exiting process (e.g. echo), and fails with a
slowly exiting process (e.g. sleep 2) but clearly after
having waited for the child to exit. Maybe it's a bug in
my particular version.
It works, however, from the CFSocket callback without this
trick but in that case it's used only after CFSocket calls
the callback and with the WNOHANG flag which would seem to
preclude it from being interrupted or at least make it much
less likely since it would not then be waiting.
If Darwin's man page is to be believed then this is definitely
necessary. It's just weird that I've never seen it before
and apparently no one else has either or you'd think they'd
have reported it by now. Perhaps blocking the GUI while
waiting for a child process to exit is simply not that common.
*/
if ( result == -1 && errno == EINTR )
{
result = waitpid(execData.pid, &status, 0);
}
#endif // __DARWIN__
if ( result == -1 )
{
wxLogLastError("waitpid");
}
else // child terminated
{
wxASSERT_MSG( result == execData.pid,
"unexpected waitpid() return value" );
if ( WIFEXITED(status) )
{
return WEXITSTATUS(status);
}
else // abnormal termination?
{
wxASSERT_MSG( WIFSIGNALED(status),
"unexpected child wait status" );
}
}
wxLogSysError(_("Waiting for subprocess termination failed"));
return -1;
}
else // asynchronous execution
if ( !(execData.flags & wxEXEC_SYNC) )
{
// asynchronous execution: just launch the process and return
wxEndProcessData *endProcData = new wxEndProcessData;
endProcData->process = execData.process;
endProcData->pid = execData.pid;
endProcData->tag = AddProcessCallback
(
endProcData,
execData.pipeEndProcDetect.Detach(wxPipe::Read)
execData.GetEndProcReadFD()
);
execData.pipeEndProcDetect.Close();
return execData.pid;
}
#if wxUSE_STREAMS
wxProcess * const process = execData.process;
if ( process && process->IsRedirected() )
{
// we can't simply block waiting for the child to terminate as we would
// dead lock if it writes more than the pipe buffer size (typically
// 4KB) bytes of output -- it would then block waiting for us to read
// the data while we'd block waiting for it to terminate
//
// so multiplex here waiting for any input from the child or closure of
// the pipe used to indicate its termination
wxSelectDispatcher disp;
wxEndHandler endHandler(disp, execData.GetEndProcReadFD());
wxRedirectedIOHandler outHandler(disp, execData.fdOut, execData.bufOut),
errHandler(disp, execData.fdErr, execData.bufErr);
while ( !endHandler.Terminated() )
{
disp.Dispatch();
}
}
//else: no IO redirection, just block waiting for the child to exit
#endif // wxUSE_STREAMS
int status = 0;
int result = waitpid(execData.pid, &status, 0);
#ifdef __DARWIN__
/* DE: waitpid manpage states that waitpid can fail with EINTR
if the call is interrupted by a caught signal. I suppose
that means that this ought to be a while loop.
The odd thing is that it seems to fail EVERY time. It fails
with a quickly exiting process (e.g. echo), and fails with a
slowly exiting process (e.g. sleep 2) but clearly after
having waited for the child to exit. Maybe it's a bug in
my particular version.
It works, however, from the CFSocket callback without this
trick but in that case it's used only after CFSocket calls
the callback and with the WNOHANG flag which would seem to
preclude it from being interrupted or at least make it much
less likely since it would not then be waiting.
If Darwin's man page is to be believed then this is definitely
necessary. It's just weird that I've never seen it before
and apparently no one else has either or you'd think they'd
have reported it by now. Perhaps blocking the GUI while
waiting for a child process to exit is simply not that common.
*/
if ( result == -1 && errno == EINTR )
{
result = waitpid(execData.pid, &status, 0);
}
#endif // __DARWIN__
if ( result == -1 )
{
wxLogLastError("waitpid");
}
else // child terminated
{
wxASSERT_MSG( result == execData.pid,
"unexpected waitpid() return value" );
if ( WIFEXITED(status) )
{
return WEXITSTATUS(status);
}
else // abnormal termination?
{
wxASSERT_MSG( WIFSIGNALED(status),
"unexpected child wait status" );
}
}
wxLogSysError(_("Waiting for subprocess termination failed"));
return -1;
}
void wxHandleProcessTermination(wxEndProcessData *proc_data)