refactored common code from XXX_EndProcessDetect in wxGTK[12] and wxMac/wxCocoa into wxHandleProcessTermination itself; also removed code dealing with negative pids as we don't use them any more

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@52696 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2008-03-22 14:50:44 +00:00
parent 1282ddbb4d
commit b827d64707
4 changed files with 86 additions and 205 deletions

View File

@@ -1234,47 +1234,11 @@ int wxAppTraits::AddProcessCallback(wxEndProcessData *data, int fd)
{
wxEndProcessFDIOHandler(wxEndProcessData *data, int fd)
: m_data(data), m_fd(fd)
{}
{
}
virtual void OnReadWaiting()
{ wxFAIL_MSG("this isn't supposed to happen"); }
virtual void OnWriteWaiting()
{ wxFAIL_MSG("this isn't supposed to happen"); }
virtual void OnExceptionWaiting()
{
const int pid = m_data->pid > 0 ? m_data->pid : -(m_data->pid);
int status = 0;
// has the process really terminated?
int rc = waitpid(pid, &status, WNOHANG);
if ( rc == 0 )
{
// This can only happen if the child application closes our
// dummy pipe that is used to monitor its lifetime; in that
// case, our best bet is to pretend the process did terminate,
// because otherwise wxExecute() would hang indefinitely
// (OnExceptionWaiting() won't be called again, the descriptor
// is closed now).
wxLogDebug("Child process (PID %d) still alive, "
"even though pipe was closed.", pid);
}
else if ( rc == -1 )
{
// As above, if waitpid() fails, the best we can do is to log the
// error and pretend the child terminated:
wxLogSysError(_("Failed to check child process' status"));
}
// set exit code to -1 if something bad happened
m_data->exitcode = rc > 0 && WIFEXITED(status) ? WEXITSTATUS(status)
: -1;
wxLogTrace("exec",
"Child process (PID %d) terminated with exit code %d",
pid, m_data->exitcode);
// child exited, end waiting
wxFDIODispatcher::Get()->UnregisterFD(m_fd);
close(m_fd);
@@ -1283,6 +1247,9 @@ int wxAppTraits::AddProcessCallback(wxEndProcessData *data, int fd)
delete this;
}
virtual void OnWriteWaiting() { wxFAIL_MSG("unreachable"); }
virtual void OnExceptionWaiting() { wxFAIL_MSG("unreachable"); }
wxEndProcessData * const m_data;
const int m_fd;
};
@@ -1291,7 +1258,7 @@ int wxAppTraits::AddProcessCallback(wxEndProcessData *data, int fd)
(
fd,
new wxEndProcessFDIOHandler(data, fd),
wxFDIO_EXCEPTION
wxFDIO_INPUT
);
return fd; // unused, but return something unique for the tag
}
@@ -1378,6 +1345,59 @@ private:
#endif // wxUSE_STREAMS
// helper function which calls waitpid() and analyzes the result
namespace
{
int DoWaitForChild(int pid, int flags = 0)
{
wxASSERT_MSG( pid > 0, "invalid PID" );
int status, rc;
// loop while we're getting EINTR
for ( ;; )
{
rc = waitpid(pid, &status, flags);
if ( rc != -1 || errno != EINTR )
break;
}
if ( rc == 0 )
{
// This can only happen if the child application closes our dummy pipe
// that is used to monitor its lifetime; in that case, our best bet is
// to pretend the process did terminate, because otherwise wxExecute()
// would hang indefinitely (OnReadWaiting() won't be called again, the
// descriptor is closed now).
wxLogDebug("Child process (PID %d) still alive but pipe closed so "
"generating a close notification", pid);
}
else if ( rc == -1 )
{
wxLogLastError(wxString::Format("waitpid(%d)", pid));
}
else // child did terminate
{
wxASSERT_MSG( rc == pid, "unexpected waitpid() return value" );
if ( WIFEXITED(status) )
return WEXITSTATUS(status);
else if ( WIFSIGNALED(status) )
return -WTERMSIG(status);
else
{
wxLogError("Child process (PID %d) exited for unknown reason, "
"status = %d", pid, status);
}
}
return -1;
}
} // anonymous namespace
int wxAppTraits::WaitForChild(wxExecuteData& execData)
{
if ( !(execData.flags & wxEXEC_SYNC) )
@@ -1421,81 +1441,22 @@ int wxAppTraits::WaitForChild(wxExecuteData& execData)
//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;
return DoWaitForChild(execData.pid);
}
void wxHandleProcessTermination(wxEndProcessData *proc_data)
void wxHandleProcessTermination(wxEndProcessData *data)
{
data->exitcode = DoWaitForChild(data->pid, WNOHANG);
// notify user about termination if required
if ( proc_data->process )
if ( data->process )
{
proc_data->process->OnTerminate(proc_data->pid, proc_data->exitcode);
data->process->OnTerminate(data->pid, data->exitcode);
}
// clean up
if ( proc_data->pid > 0 )
{
// async execution
delete proc_data;
}
else // sync execution
{
// let wxExecute() know that the process has terminated
proc_data->pid = 0;
}
// this function is only called for asynchronously executing children, now
// that we have received the notification about their termination there is
// no need to keep it around any longer
delete data;
}