wxProcess::Redirect shutdown problem Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
Peer Sommerlund
Knows some wx things
Knows some wx things
Posts: 43
Joined: Tue Jun 13, 2006 7:21 am
Location: Denmark
Contact:

wxProcess::Redirect shutdown problem

Post by Peer Sommerlund »

Hi.

I have a GUI application that launches a commandline application and captures its stdio using wxProcess::Redirect()

I cannot see a way to wait for input from the child process, at the same time as I'm waiting for input from the GUI. I'm searching for something similar to select() where I can wait on multiple data, or wait with a timeout.

I have tried to solve it by using a wxThread that reads from the redirected stdout. This frees my GUI thread, but I still have a problem when the client process does not send any output - the thread can only wait for one "signal" at a time, so it is impossible to interrupt it while it is blocking on wxInputStream::GetC()

Are there a cross-platform solution to this, or do I have to write platform specific code with a Windows WaitForMultipleObjects and a Unix select?

Regards
DavidHart
Site Admin
Site Admin
Posts: 4252
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Post by DavidHart »

Hi,

I quite likely don't adequately understand the question; if so I apologise in advance. But the 'exec' sample seems to cope with a similar situation by using idle time. It calls wxWakeUpIdle() from a timer, then handles EVT_IDLE to poll the process for more input. I've adapted this also to send the process any fresh data at the same time, and I don't see why other functions couldn't be called too.

Regards,

David
Peer Sommerlund
Knows some wx things
Knows some wx things
Posts: 43
Joined: Tue Jun 13, 2006 7:21 am
Location: Denmark
Contact:

Post by Peer Sommerlund »

Hi David

I have not run the samples, so this is purely from reading the source. Originally I also thought that wxProcess::IsInputAvailable() would solve my problem, but unfortunately it does not.


To determine if data is ready, the sample function MyPipedProcess::HasInput() calls wxProcess::IsInputAvailable() which calls wxInputStream::CanRead()

From wxWidgets 2.6.3 source

Code: Select all

bool wxInputStream::CanRead() const
{
    // we don't know if there is anything to read or not and by default we
    // prefer to be optimistic and try to read data unless we know for sure
    // there is no more of it
    return m_lasterror != wxSTREAM_EOF;
}
Thus this is true if the child process has not yet closed, but unfortunetely also true if no data has been sent. This will block the thread that called IsInputAvailable, and make it impossible to Delete that thread until data has arrived from the child process.

If the behaviour is not as I have described it, please correct me. There are many corners in wxWidgets and it is difficult to know them all.

PS. I would have pressed "assist" but seem to have hit "solved" instead. Oh well.
Peer Sommerlund
Knows some wx things
Knows some wx things
Posts: 43
Joined: Tue Jun 13, 2006 7:21 am
Location: Denmark
Contact:

Post by Peer Sommerlund »

My explanation may not be too clear, so here are the relevant code snippets from exec sample and wxMSW 2.6.3

First the idle loop in the sample

Code: Select all

// input polling
void MyFrame::OnIdle(wxIdleEvent& event)
{
    size_t count = m_running.GetCount();
    for ( size_t n = 0; n < count; n++ )
    {
        if ( m_running[n]->HasInput() )
        {
            event.RequestMore();
        }
    }
}
... this checks to see if the process has input ...

Code: Select all

bool MyPipedProcess::HasInput()
{
    bool hasInput = false;

    if ( IsInputAvailable() )
    {
        wxTextInputStream tis(*GetInputStream());

        // this assumes that the output is always line buffered
        wxString msg;
        msg << m_cmd << _T(" (stdout): ") << tis.ReadLine();

        m_parent->GetLogListBox()->Append(msg);

        hasInput = true;
    }

    // peso : deleted similar code for error stream

    return hasInput;
}
... which checks the wxProcess ...

Code: Select all

bool wxProcess::IsInputAvailable() const
{
    return m_inputStream && m_inputStream->CanRead();
}
... which checks the wxInputStream ...

Code: Select all

bool wxInputStream::CanRead() const
{
    // we don't know if there is anything to read or not and by default we
    // prefer to be optimistic and try to read data unless we know for sure
    // there is no more of it
    return m_lasterror != wxSTREAM_EOF;
}
... so the result is true and MyPipedProcess::HasInput() progress to ReadLine(), with no input available. This causes the thread to stop, waiting for input.
DavidHart
Site Admin
Site Admin
Posts: 4252
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Post by DavidHart »

Must you use a thread at all? I have no problem with the following code, used in a (poor man's) terminal emulator. Note the first comment, though:

Code: Select all

bool MyPipedProcess::HasInput()
{
char c;

bool hasInput = FALSE;
		// The original used wxTextInputStream to read a line at a time.  Fine, except when there was no \n, whereupon the thing would hang
while ( IsInputAvailable() )															// If there's std input
	{	wxString line;
		do
			{	c = GetInputStream()->GetC();									// Get a char from the input
				if ( GetInputStream()->Eof() ) break;							// Check we've not just overrun
				
				line += c; 							 										// Add it to line
				if ( c==wxT('\n') ) break;												// If \n, break to print the line
			}
		  while ( IsInputAvailable() );												// Unless \n, loop to get another char

		text->AddInput( line );														// Either there's a full line in 'line', or we've run out of input. Either way, print it
 		matches = true;

		hasInput = TRUE;
	}

while ( IsErrorAvailable() )	// Ditto with std error
...
	{
I didn't know there might be no input, so I didn't check for an empty string. Easy enough to add though.
PS. I would have pressed "assist" but seem to have hit "solved" instead.
That's OK ;)
Peer Sommerlund
Knows some wx things
Knows some wx things
Posts: 43
Joined: Tue Jun 13, 2006 7:21 am
Location: Denmark
Contact:

Post by Peer Sommerlund »

I just compiled the exec sample, and given a client application that simply stops sending on stdout, it blocks the GUI. This is the behaviour I was seeking to avoid. Using a thread, prevents the GUI from locking up, but trades it for a shut-down problem.

Try with a client similar to the code below.

Code: Select all

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char** argv) {
  int i;
  for (i=0; i<10; i++) {
    printf("ping %d\n",i);
    long sec;
    sec = 400000000L; /* number of loops that correspond to 1 second */
    long d;
    /* wait a few seconds */
    for (d=0; d<10*sec; d++) { i = i + d - d; }
  }
  return 0;
}
DavidHart
Site Admin
Site Admin
Posts: 4252
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Post by DavidHart »

Hmm. I just ran your test program through the exec sample, and it didn't block the gui (that was running it async, of course; and I had to add a fflush(stdout); after the printf to make the input visible during running, rather that all at once at the end). :?

More importantly, I ran the test from my terminal emulator i.e. a 'real-life' situation, and everything behaved itself: the pings appeared one by one as expected, and the rest of my app stayed active and usable.

This is in wxGTK-2.6.3. Perhaps there's a platform difference?
Peer Sommerlund
Knows some wx things
Knows some wx things
Posts: 43
Joined: Tue Jun 13, 2006 7:21 am
Location: Denmark
Contact:

Post by Peer Sommerlund »

:oops: My mistake. I ran the exec sample on another computer using mingw and wxMSW-2.6.2 and the GUI did not lock up. I must have selected syncronous or something.

BTW, wxInputStream::CanRead() is virtual, and wxExecute creates a wxPipedInputStream, which uses the non-blocking PeekNamedPipe. My mistake again.

:arrow: To sum up: It works if you poll CanRead(), instead of block on GetC().

I can use this as a workaround, but I would still like to know if I can get a signal or something when input is ready. If I can avoid it I would rather not spend cpu on polling.
Post Reply