tags:

views:

94

answers:

3

So I have a program which creates a child process and executes a command (for example, ls). The parent will then use pipes to send to and get from the child. This works fine when I'm inputting commands myself from the command line.

However, when the input comes from a file, it seems like the child doesn't have enough time to run and I get NULL when reading from the pipe - even though there will be information coming from it.

Short of using sleep(), is there a better way to make sure the child has run before trying to read from it?

Thanks a lot!

+1  A: 

The question is how much control do you have on the child? If you are writing the child yourself then you can get the child to set a flag in shared memory that it has started (or touch a file or whatever tickles your fancy) and then only once you know the child is online run it.

If you don't have control over the child, there is still away to implement the above solution by wrapping the child with your own script which first launches the child then sets the shared memory, This could be in the form of a shell script as follows

#!/bin/sh

$CHILD
my_app_that_set_the_flag

Finally another alternative is to continue waiting aslong as you get NULL from the pipe, obviously this would lead to an infinite loop though if you can not garuntee that you will always get something on the pipe

hhafez
+1  A: 

Your parent thread needs to wait until the child is ready before it tries to read from the pipe. There are a couple of ways to do this.

First, you can use a condition variable. Declare a variable that both threads can access and set it to 0. Your child thread will set it to 1 when is is ready for the parent to read. The parent will wait until the variable changes to 1 (using something like while(!condvar) sleep(1);), then it will read from the pipe and reset the variable to 0 so that the child knows that the parent is finished.

Another option is to use a form of inter-process communication, such as signals. Similar to the condition variable method, the child thread will perform its work and then send a signal to the parent thread when it is done. The parent thread will wait until it receives the signal before it reads from the pipe, and then it can send a signal back to the child indicating that it is done.

Edit: Since you're spawning the child process with fork instead of with threads, you can't use a condition variable here (parent and child will have separate copies of it).

Instead, you can use the signal() and kill() functions to send signals between the processes. Before forking, use getpid to store a copy of the parent's pid (for the child process). Also store the return value of fork, since it will contain the child's pid.

To send a signal to the other process, use something like:

kill(parent_pid, SIGUSR1);

The receiving process needs to set up a signal handler. For example:

int signal_received = 0;
void signal_handler(int signal_num) {
    if (signal_num == SIGUSR1)
        signal_received = 1;
}

signal(SIGUSR1, signal_handler);

The function signal_handler now will be automatically called whenever the process receives signal number SIGUSR1. Your thread would wait in a loop, watching for this variable to change using something like:

while (1) { // Signal processing loop
    // Wait here for a signal to come in
    while (!signal_received) { sleep(1); }

    // Wake up and do something
    read_from_pipe();
    ...
    signal_received = 0;
}
bta
You can simply use `select()` to wait until the pipe is readable.
caf
@caf: True, but that only tells you that there is something there to read. Reading from the pipe as soon as `select()` tells you that you can doesn't mean that all of your data is there. Many times, it's easier to wait until all of it is ready to be read than it is to read it bit by bit as it comes in and then try to piece it back together (especially if the data itself doesn't tell you where the end is).
bta
@bta this seems like the easiest solution, so i tried it - but couldn't get it to work. the variable was declared before I did the fork and changed in the child, but the parent doesn't seem to see the change at all. Could it have something to do with the fact that I'm using exec in the child?
Gary
When using `fork` the child process will have its own, independent version of the variable, so condition variables can't be used here. When I wrote my answer I was assuming you were using threads instead of `fork`. You will probably be better off sending signals between the processes (I'll edit my answer and give more details).
bta
+2  A: 

If your haven't set your pipe file descriptor as non-blocking, then there should be no problem. Any read on the pipe will block until the child produces output; if you need to be responsive to multiple file descriptors (for example, standard input from the user and the pipe from the child process), use select() or poll().

I assume it is fgets() that is returning NULL. That indicates either end-of-file (meaning that the child has closed its end of the pipe), or an error. You can check which of these is true using feof() or ferror(). Use perror() in the error case to see what the error actually is.

caf
Should have mentioned that the file descriptor is non-blocking. I had done that because the read from fgets() was blocking when there was no new data, which caused the program to wait forever.
Gary
@Gary: In that case, add the file descriptor to the set of file descriptors you're monitoring with `select()` - eg if you want to wait for input from either the user or from the pipe, use `select()` to monitor stdin and the pipe.
caf
@caf in this case, I'm only wanting input from one file descriptor and not stdin, so I don't think I'd need select? Basically I just want to give enough time for the child to run its command and, if it's still NULL, handle that as an error.
Gary
@Gary: Then `select()` (or `poll()`) is still what you want - put the pipe into the `readfds` set, and use the `timeout` parameter. If `select()` returns due to the timeout, then handle that as appropriate.
caf
select worked great, thanks!
Gary