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:
@@ -40,9 +40,22 @@ struct wxExecuteData
|
|||||||
#if wxUSE_STREAMS
|
#if wxUSE_STREAMS
|
||||||
bufOut =
|
bufOut =
|
||||||
bufErr = NULL;
|
bufErr = NULL;
|
||||||
|
|
||||||
|
fdOut =
|
||||||
|
fdErr = wxPipe::INVALID_FD;
|
||||||
#endif // wxUSE_STREAMS
|
#endif // wxUSE_STREAMS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the FD corresponding to the read end of the process end detection
|
||||||
|
// pipe and close the write one
|
||||||
|
int GetEndProcReadFD()
|
||||||
|
{
|
||||||
|
const int fd = pipeEndProcDetect.Detach(wxPipe::Read);
|
||||||
|
pipeEndProcDetect.Close();
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// wxExecute() flags
|
// wxExecute() flags
|
||||||
int flags;
|
int flags;
|
||||||
|
|
||||||
@@ -60,6 +73,10 @@ struct wxExecuteData
|
|||||||
// called bufOut and not bufIn
|
// called bufOut and not bufIn
|
||||||
wxStreamTempInputBuffer *bufOut,
|
wxStreamTempInputBuffer *bufOut,
|
||||||
*bufErr;
|
*bufErr;
|
||||||
|
|
||||||
|
// the corresponding FDs, -1 if not redirected
|
||||||
|
int fdOut,
|
||||||
|
fdErr;
|
||||||
#endif // wxUSE_STREAMS
|
#endif // wxUSE_STREAMS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -58,11 +58,9 @@ int wxGUIAppTraits::WaitForChild(wxExecuteData& execData)
|
|||||||
endProcData.tag = AddProcessCallback
|
endProcData.tag = AddProcessCallback
|
||||||
(
|
(
|
||||||
&endProcData,
|
&endProcData,
|
||||||
execData.pipeEndProcDetect.Detach(wxPipe::Read)
|
execData.GetEndProcReadFD()
|
||||||
);
|
);
|
||||||
|
|
||||||
execData.pipeEndProcDetect.Close();
|
|
||||||
|
|
||||||
|
|
||||||
// prepare to wait for the child termination: show to the user that we're
|
// prepare to wait for the child termination: show to the user that we're
|
||||||
// busy and refuse all input unless explicitly told otherwise
|
// busy and refuse all input unless explicitly told otherwise
|
||||||
|
@@ -41,6 +41,7 @@
|
|||||||
|
|
||||||
#include "wx/wfstream.h"
|
#include "wx/wfstream.h"
|
||||||
|
|
||||||
|
#include "wx/private/selectdispatcher.h"
|
||||||
#include "wx/private/fdiodispatcher.h"
|
#include "wx/private/fdiodispatcher.h"
|
||||||
#include "wx/unix/execute.h"
|
#include "wx/unix/execute.h"
|
||||||
#include "wx/unix/private.h"
|
#include "wx/unix/private.h"
|
||||||
@@ -650,19 +651,17 @@ long wxExecute(wxChar **argv, int flags, wxProcess *process)
|
|||||||
// called bufOut and not bufIn
|
// called bufOut and not bufIn
|
||||||
wxStreamTempInputBuffer bufOut,
|
wxStreamTempInputBuffer bufOut,
|
||||||
bufErr;
|
bufErr;
|
||||||
#endif // HAS_PIPE_INPUT_STREAM
|
|
||||||
|
|
||||||
if ( process && process->IsRedirected() )
|
if ( process && process->IsRedirected() )
|
||||||
{
|
{
|
||||||
#if HAS_PIPE_INPUT_STREAM
|
|
||||||
wxOutputStream *inStream =
|
wxOutputStream *inStream =
|
||||||
new wxFileOutputStream(pipeIn.Detach(wxPipe::Write));
|
new wxFileOutputStream(pipeIn.Detach(wxPipe::Write));
|
||||||
|
|
||||||
wxPipeInputStream *outStream =
|
const int fdOut = pipeOut.Detach(wxPipe::Read);
|
||||||
new wxPipeInputStream(pipeOut.Detach(wxPipe::Read));
|
wxPipeInputStream *outStream = new wxPipeInputStream(fdOut);
|
||||||
|
|
||||||
wxPipeInputStream *errStream =
|
const int fdErr = pipeErr.Detach(wxPipe::Read);
|
||||||
new wxPipeInputStream(pipeErr.Detach(wxPipe::Read));
|
wxPipeInputStream *errStream = new wxPipeInputStream(fdErr);
|
||||||
|
|
||||||
process->SetPipeStreams(outStream, inStream, errStream);
|
process->SetPipeStreams(outStream, inStream, errStream);
|
||||||
|
|
||||||
@@ -671,8 +670,11 @@ long wxExecute(wxChar **argv, int flags, wxProcess *process)
|
|||||||
|
|
||||||
execData.bufOut = &bufOut;
|
execData.bufOut = &bufOut;
|
||||||
execData.bufErr = &bufErr;
|
execData.bufErr = &bufErr;
|
||||||
#endif // HAS_PIPE_INPUT_STREAM
|
|
||||||
|
execData.fdOut = fdOut;
|
||||||
|
execData.fdErr = fdErr;
|
||||||
}
|
}
|
||||||
|
#endif // HAS_PIPE_INPUT_STREAM
|
||||||
|
|
||||||
if ( pipeIn.IsOk() )
|
if ( pipeIn.IsOk() )
|
||||||
{
|
{
|
||||||
@@ -1325,82 +1327,169 @@ bool wxAppTraits::CheckForRedirectedIO(wxExecuteData& execData)
|
|||||||
#endif // HAS_PIPE_INPUT_STREAM/!HAS_PIPE_INPUT_STREAM
|
#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)
|
int wxAppTraits::WaitForChild(wxExecuteData& execData)
|
||||||
{
|
{
|
||||||
if ( execData.flags & wxEXEC_SYNC )
|
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
|
|
||||||
{
|
{
|
||||||
|
// asynchronous execution: just launch the process and return
|
||||||
wxEndProcessData *endProcData = new wxEndProcessData;
|
wxEndProcessData *endProcData = new wxEndProcessData;
|
||||||
endProcData->process = execData.process;
|
endProcData->process = execData.process;
|
||||||
endProcData->pid = execData.pid;
|
endProcData->pid = execData.pid;
|
||||||
endProcData->tag = AddProcessCallback
|
endProcData->tag = AddProcessCallback
|
||||||
(
|
(
|
||||||
endProcData,
|
endProcData,
|
||||||
execData.pipeEndProcDetect.Detach(wxPipe::Read)
|
execData.GetEndProcReadFD()
|
||||||
);
|
);
|
||||||
|
|
||||||
execData.pipeEndProcDetect.Close();
|
|
||||||
return execData.pid;
|
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)
|
void wxHandleProcessTermination(wxEndProcessData *proc_data)
|
||||||
|
Reference in New Issue
Block a user