Rewrite wxExecute() implementation under Unix.

This commit changes wxExecute() to handle SIGCHLD to be notified about the
child process termination instead of detecting when the file descriptor
corresponding to the other end of a pipe opened in the parent process was
closed in the child as this was not reliable and could (and did) result in not
detecting the termination of the child processes that closed all their file
descriptors before exiting.

This commit also removes a lot of platform-specific code duplicating the
generic event loop sources support and reuses it for wxExecute() purposes too.

Final big change is that wxEndProcessData was merged into wxExecuteData and we
don't have two similar but quite different classes any more but just one,
which is used both to pass the information from wxExecute() to wxAppTraits
methods and to store this information until the child termination.

Closes #10258.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@74350 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2013-07-03 00:32:16 +00:00
parent 76015a6bb7
commit 821d856a61
22 changed files with 497 additions and 554 deletions

View File

@@ -47,7 +47,7 @@ public:
// Register the signal wake up pipe with the given dispatcher.
//
// This is not used anywhere yet but will be soon.
// This is used by wxExecute(wxEXEC_NOEVENTS) implementation only.
//
// The pointer to the handler used for processing events on this descriptor
// is returned so that it can be deleted when we no longer needed it.

View File

@@ -12,8 +12,10 @@
#ifndef _WX_UNIX_APPTBASE_H_
#define _WX_UNIX_APPTBASE_H_
struct wxEndProcessData;
struct wxExecuteData;
#include "wx/evtloop.h"
#include "wx/evtloopsrc.h"
class wxExecuteData;
class wxFDIOManager;
class wxEventLoopSourcesManagerBase;
@@ -27,23 +29,13 @@ public:
// wxExecute() support methods
// ---------------------------
// wait for the process termination, return whatever wxExecute() must
// return
// Wait for the process termination and return its exit code or -1 on error.
//
// base class implementation handles all cases except wxEXEC_SYNC without
// wxEXEC_NOEVENTS one which is implemented at the GUI level
// Notice that this is only used when execData.flags contains wxEXEC_SYNC
// and does not contain wxEXEC_NOEVENTS, i.e. when we need to really wait
// until the child process exit and dispatch the events while doing it.
virtual int WaitForChild(wxExecuteData& execData);
// integrate the monitoring of the given fd with the port-specific event
// loop: when this fd, which corresponds to a dummy pipe opened between the
// parent and child processes, is closed by the child, the parent is
// notified about this via a call to wxHandleProcessTermination() function
//
// the default implementation uses wxFDIODispatcher and so is suitable for
// the console applications or ports which don't have any specific event
// loop
virtual int AddProcessCallback(wxEndProcessData *data, int fd);
#if wxUSE_SOCKETS
// return a pointer to the object which should be used to integrate
// monitoring of the file descriptors to the event loop (currently this is
@@ -62,10 +54,12 @@ public:
virtual wxEventLoopSourcesManagerBase* GetEventLoopSourcesManager();
protected:
// a helper for the implementation of WaitForChild() in wxGUIAppTraits:
// checks the streams used for redirected IO in execData and returns true
// if there is any activity in them
bool CheckForRedirectedIO(wxExecuteData& execData);
// Wait for the process termination by running the given event loop until
// this happens.
//
// This is used by the public WaitForChild() after creating the event loop
// of the appropriate kind.
int RunLoopUntilChildExit(wxExecuteData& execData, wxEventLoopBase& loop);
};
#endif // _WX_UNIX_APPTBASE_H_

View File

@@ -51,9 +51,6 @@ class WXDLLIMPEXP_CORE wxGUIAppTraits : public wxGUIAppTraitsBase
public:
virtual wxEventLoopBase *CreateEventLoop();
virtual int WaitForChild(wxExecuteData& execData);
#ifdef wxHAS_GUI_PROCESS_CALLBACKS
virtual int AddProcessCallback(wxEndProcessData *data, int fd);
#endif
#if wxUSE_TIMER
virtual wxTimerImpl *CreateTimerImpl(wxTimer *timer);
#endif

View File

@@ -4,63 +4,53 @@
// Author: Vadim Zeitlin
// Id: $Id$
// Copyright: (c) 1998 Robert Roebling, Julian Smart, Vadim Zeitlin
// (c) 2013 Vadim Zeitlin
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
#ifndef _WX_UNIX_EXECUTE_H
#define _WX_UNIX_EXECUTE_H
#include "wx/app.h"
#include "wx/hashmap.h"
#include "wx/process.h"
#include "wx/unix/pipe.h"
#include "wx/private/streamtempinput.h"
class WXDLLIMPEXP_FWD_BASE wxProcess;
class wxStreamTempInputBuffer;
class wxEventLoopBase;
struct wxEndProcessData
{
wxEndProcessData()
{
pid =
tag =
exitcode = -1;
process = NULL;
async = false;
}
int pid; // pid of the process
int tag; // port dependent value
wxProcess *process; // if !NULL: notified on process termination
int exitcode; // the exit code
bool async; // if true, delete us on process termination
};
// struct in which information is passed from wxExecute() to wxAppTraits
// methods
struct wxExecuteData
// Information associated with a running child process.
class wxExecuteData
{
public:
wxExecuteData()
{
flags =
pid = 0;
exitcode = -1;
process = NULL;
#if wxUSE_STREAMS
bufOut =
bufErr = NULL;
syncEventLoop = NULL;
#if wxUSE_STREAMS
fdOut =
fdErr = wxPipe::INVALID_FD;
#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;
}
// This must be called in the parent process as soon as fork() returns to
// update us with the effective child PID. It also ensures that we handle
// SIGCHLD to be able to detect when this PID exits, so wxTheApp must be
// available.
void OnStart(int pid);
// Called when the child process exits.
void OnExit(int exitcode);
// Return true if we should (or already did) redirect the child IO.
bool IsRedirected() const { return process && process->IsRedirected(); }
// wxExecute() flags
@@ -69,26 +59,43 @@ struct wxExecuteData
// the pid of the child process
int pid;
// The exit code of the process, set once the child terminates.
int exitcode;
// the associated process object or NULL
wxProcess *process;
// pipe used for end process detection
wxPipe pipeEndProcDetect;
// Local event loop used to wait for the child process termination in
// synchronous execution case. We can't create it ourselves as its exact
// type depends on the application kind (console/GUI), so we rely on
// wxAppTraits setting up this pointer to point to the appropriate object.
wxEventLoopBase *syncEventLoop;
#if wxUSE_STREAMS
// the input buffer bufOut is connected to stdout, this is why it is
// called bufOut and not bufIn
wxStreamTempInputBuffer *bufOut,
*bufErr;
wxStreamTempInputBuffer bufOut,
bufErr;
// the corresponding FDs, -1 if not redirected
int fdOut,
fdErr;
#endif // wxUSE_STREAMS
private:
// SIGCHLD signal handler that checks whether any of the currently running
// children have exited.
static void OnSomeChildExited(int sig);
// All currently running child processes indexed by their PID.
//
// Notice that the container doesn't own its elements.
WX_DECLARE_HASH_MAP(int, wxExecuteData*, wxIntegerHash, wxIntegerEqual,
ChildProcessesData);
static ChildProcessesData ms_childProcesses;
wxDECLARE_NO_COPY_CLASS(wxExecuteData);
};
// this function is called when the process terminates from port specific
// callback function and is common to all ports (src/unix/utilsunx.cpp)
extern WXDLLIMPEXP_BASE void wxHandleProcessTermination(wxEndProcessData *proc_data);
#endif // _WX_UNIX_EXECUTE_H

View File

@@ -0,0 +1,137 @@
///////////////////////////////////////////////////////////////////////////////
// Name: wx/unix/private/executeiohandler.h
// Purpose: IO handler class for the FD used by wxExecute() under Unix
// Author: Rob Bresalier, Vadim Zeitlin
// Created: 2013-01-06
// RCS-ID: $Id$
// Copyright: (c) 2013 Rob Bresalier, Vadim Zeitlin
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#ifndef _WX_UNIX_PRIVATE_EXECUTEIOHANDLER_H_
#define _WX_UNIX_PRIVATE_EXECUTEIOHANDLER_H_
#include "wx/private/streamtempinput.h"
// This class handles IO events on the pipe FD connected to the child process
// stdout/stderr and is used by wxExecute().
//
// Currently it can derive from either wxEventLoopSourceHandler or
// wxFDIOHandler depending on the kind of dispatcher/event loop it is used
// with. In the future, when we get rid of wxFDIOHandler entirely, it will
// derive from wxEventLoopSourceHandler only.
template <class T>
class wxExecuteIOHandlerBase : public T
{
public:
wxExecuteIOHandlerBase(int fd, wxStreamTempInputBuffer& buf)
: m_fd(fd),
m_buf(buf)
{
m_callbackDisabled = false;
}
// Called when the associated descriptor is available for reading.
virtual void OnReadWaiting()
{
// Sync process, process all data coming at us from the pipe so that
// the pipe does not get full and cause a deadlock situation.
m_buf.Update();
if ( m_buf.Eof() )
DisableCallback();
}
// These methods are never called as we only monitor the associated FD for
// reading, but we still must implement them as they're pure virtual in the
// base class.
virtual void OnWriteWaiting() { }
virtual void OnExceptionWaiting() { }
// Disable any future calls to our OnReadWaiting(), can be called when
// we're sure that no more input is forthcoming.
void DisableCallback()
{
if ( !m_callbackDisabled )
{
m_callbackDisabled = true;
DoDisable();
}
}
protected:
const int m_fd;
private:
virtual void DoDisable() = 0;
wxStreamTempInputBuffer& m_buf;
// If true, DisableCallback() had been already called.
bool m_callbackDisabled;
wxDECLARE_NO_COPY_CLASS(wxExecuteIOHandlerBase);
};
// This is the version used with wxFDIODispatcher, which must be passed to the
// ctor in order to register this handler with it.
class wxExecuteFDIOHandler : public wxExecuteIOHandlerBase<wxFDIOHandler>
{
public:
wxExecuteFDIOHandler(wxFDIODispatcher& dispatcher,
int fd,
wxStreamTempInputBuffer& buf)
: wxExecuteIOHandlerBase<wxFDIOHandler>(fd, buf),
m_dispatcher(dispatcher)
{
dispatcher.RegisterFD(fd, this, wxFDIO_INPUT);
}
virtual ~wxExecuteFDIOHandler()
{
DisableCallback();
}
private:
virtual void DoDisable()
{
m_dispatcher.UnregisterFD(m_fd);
}
wxFDIODispatcher& m_dispatcher;
wxDECLARE_NO_COPY_CLASS(wxExecuteFDIOHandler);
};
// And this is the version used with an event loop. As AddSourceForFD() is
// static, we don't require passing the event loop to the ctor but an event
// loop must be running to handle our events.
class wxExecuteEventLoopSourceHandler
: public wxExecuteIOHandlerBase<wxEventLoopSourceHandler>
{
public:
wxExecuteEventLoopSourceHandler(int fd, wxStreamTempInputBuffer& buf)
: wxExecuteIOHandlerBase<wxEventLoopSourceHandler>(fd, buf)
{
m_source = wxEventLoop::AddSourceForFD(fd, this, wxEVENT_SOURCE_INPUT);
}
virtual ~wxExecuteEventLoopSourceHandler()
{
DisableCallback();
}
private:
virtual void DoDisable()
{
delete m_source;
m_source = NULL;
}
wxEventLoopSource* m_source;
wxDECLARE_NO_COPY_CLASS(wxExecuteEventLoopSourceHandler);
};
#endif // _WX_UNIX_PRIVATE_EXECUTEIOHANDLER_H_