2

I am using boost::process::child to spawn new process. Start time of process which I am start isn't instant, so I have to wait some time until full initialization of it.

auto is_ptr = std::make_shared<bp::ipstream>();
auto child_pr = std::make_shared<bp::child>(executable, args, bp::std_out > *is_ptr);
m_childs[port] = {child_pr, is_ptr};

std::string line;
while (child_pr->running() && std::getline(*is_ptr, line)) {
    std::cerr <<"SI: \t" << line << std::endl;
    if( 0 == line.compare(0, string_to_find.size(), string_to_find)){
        break;
    }
}
...

After this cycle I don't need to have ipstream anymore. Is any way to detach it from the child process?

Dcow
  • 1,413
  • 1
  • 19
  • 42
  • Just let `is_ptr` go out of scope (which will end its life and release your handle to the shared pointer). – Some programmer dude Oct 28 '19 at 10:37
  • @Someprogrammerdude looks like destruction of this object also ends child process – Dcow Oct 28 '19 at 11:16
  • Are you sure the child process is no longer writing to that output when `is_ptr` is destroyed? – Ton van den Heuvel Oct 28 '19 at 19:56
  • @TonvandenHeuvel It is writing. I don't need to capture that output anymore. That is the problem. – Dcow Oct 29 '19 at 09:36
  • I am also ok with different method to signal of completed initialization, but it don't come to my mind. Any suggestions? – Dcow Oct 29 '19 at 09:39
  • 1
    @Dcow, that explains the behavior you see then. In case `is_ptr` is destroyed, it will close the pipe/stream in the parent process, while it is still open in the child process. As soon as the child process then writes to that output, it will receive `SIGPIPE` and terminate, at least on Linux. I am not sure what happens on Windows. – Ton van den Heuvel Oct 29 '19 at 09:40
  • @TonvandenHeuvel So, when I start process like in topic, it is boost::process pass descriptor of `is_ptr` stream to process like it is stdout. When it is destroyed, descriptor invalidates. So I have to set signal handler to `SIGPIPE` and reopen stdout. Right? – Dcow Oct 29 '19 at 10:10
  • @Dcow, sorry I missed your comment. Probably the easiest thing to do in the child process is to simply ignore `SIGPIPE`: `signal(SIGPIPE, SIG_IGN);`. – Ton van den Heuvel Nov 05 '19 at 21:02
  • @TonvandenHeuvel may I ask to create an answer to this question? Btw, what would happen if just ignore that signal? – Dcow Nov 07 '19 at 10:26

1 Answers1

1

Since you asked to provide answer, I'll put some additional information here, although I am not sure it will completely answer your question.

Assuming the target platform is Linux, once ipstream is destroyed in the parent process, it effectively means that the file descriptor for the associated pipe between the parent and child process is closed in the parent process. Once the child process writes to the pipe after the parent process closed its read end of the pipe, SIGPIPE is generated for the child process, which will cause it to terminate in case no extra measures are taken.

To prevent this, one option is to ignore SIGPIPE in the child. This will now cause errors in the child process when writing to that pipe. It depends on the implementation of the child process what cause that will have. A solution in your case could be to ignore SIGPIPE, and take measures in the child process once it can no longer successfully write data, to prevent a lot of wasted CPU cycles.

To experiment with this on a lower level, you can use the following program. It will fork a child process that will keep on writing to some output as long as that succeeds. The parent process will close the corresponding pipe as soon as it has read some data from it.

The behavior of the program differs depending on how SIGPIPE is handled in the child process. In case it is ignored, the write() in the child process will fail, and the child process will exit with a non-zero exit code. In case the SIGPIPE is not ignored, the child process is terminated by the operating system. The parent process will tell you what happened in the child process.

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char** argv)
{
    int pipe_fds[2];
    if (pipe(pipe_fds) < 0) {
        perror("pipe");
        exit(1);
    }

    pid_t pid;
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }

    if (pid == 0)
    {
        close(pipe_fds[0]); /* close read-end in the child */

        /* Uncomment the following line, and the child will terminate as soon
           as the parent closes the read end of the pipe...This is here merely
           for illustrative purposes, production code should use either
           sigaction() or pthreads related signal functionality in case of a
           multi-threaded program. */

        /* signal(SIGPIPE, SIG_IGN); */

        /* Child process, start writing to the write-end of the pipe. */
        const char message[] = "Hello world!\n";
        while (write(pipe_fds[1], message, strlen(message)) >= 0);

        exit(1);
    }

    close(pipe_fds[1]);
    char buf[256];
    ssize_t count;
    while ((count = read(pipe_fds[0], buf, sizeof(buf) - 1)) == 0);
    if (count < 0) {
        perror("read");
        exit(1);
    }

    buf[count] = '\0';
    printf("%s", buf);

    /* Close read-end in the parent, this will trigger SIGPIPE in the child
       once the child writes to the pipe. */
    close(pipe_fds[0]);

    int stat;
    if (waitpid(pid, &stat, 0) < 0) {
        perror("waitpid");
        exit(1);
    }

    if (WIFSIGNALED(stat) && WTERMSIG(stat) == SIGPIPE) {
        printf("\nChild terminated by SIGPIPE\n");
    }
    if (WIFEXITED(stat)) {
        printf("\nChild exited with exit code %d\n", WEXITSTATUS(stat));
    }

    exit(0);
}
Ton van den Heuvel
  • 10,157
  • 6
  • 43
  • 82