c++centosinfinite-looppopenpclose

Way to force file descriptor to close so that pclose() will not block?


I am creating a pipe using popen() and the process is invoking a third party tool which in some rare cases I need to terminate.

::popen(thirdPartyCommand.c_str(), "w");

If I just throw an exception and unwind the stack, my unwind attempts to call pclose() on the third party process whose results I no longer need. However, pclose() never returns as it blocks with the following stack trace on Centos 4:

#0  0xffffe410 in __kernel_vsyscall ()
#1  0x00807dc3 in __waitpid_nocancel () from /lib/libc.so.6
#2  0x007d0abe in _IO_proc_close@@GLIBC_2.1 () from /lib/libc.so.6
#3  0x007daf38 in _IO_new_file_close_it () from /lib/libc.so.6
#4  0x007cec6e in fclose@@GLIBC_2.1 () from /lib/libc.so.6
#5  0x007d6cfd in pclose@@GLIBC_2.1 () from /lib/libc.so.6

Is there any way to force the call to pclose() to be successful before calling it so I can programmatically avoid this situation of my process getting hung up waiting for pclose() to succeed when it never will because I've stopped supplying input to the popen()ed process and wish to throw away its work?

Should I write an end of file somehow to the popen()ed file descriptor before trying to close it?

Note that the third party software is forking itself. At the point where pclose() has hung, there are four processes, one of which is defunct:

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
abc       6870  0.0  0.0   8696   972 ?        S    04:39   0:00 sh -c /usr/local/bin/third_party /home/arg1 /home/arg2 2>&1
abc       6871  0.0  0.0  10172  4296 ?        S    04:39   0:00 /usr/local/bin/third_party /home/arg1 /home/arg2
abc       6874 99.8  0.0  10180  1604 ?        R    04:39 141:44 /usr/local/bin/third_party /home/arg1 /home/arg2
abc       6875  0.0  0.0      0     0 ?        Z    04:39   0:00 [third_party] <defunct>

Solution

  • I see two solutions here:

    I strongly advise the neat way here, or you could just ask whomever responsible to fix that infinite loop in your third party tool haha.

    Good luck!

    EDIT:

    For you first question: I don't know. Doing some researches on how to find processes by name using sysctl() shoud tell you what you need to know, I myself have never pushed it this far.

    For your second and third question: popen() is basically a wrapper to fork() + pipe() + dup2() + execl().

    fork() duplicates the process, execl() replaces the duplicated process' image with a new one, pipe() handles inter process communication and dup2() is used to redirect the output... And then pclose() will wait() for the duplicated process to die, which is why we're here.

    If you want to know more, you should check this answer where I've recently explained how to perform a simple fork with standard IPC. In this case, it's just a bit more complicated as you have to use dup2() to redirect the standard output to your pipe.

    You should also take a look at popen()/pclose() source codes, as they are of course open source.

    Finally, here's a brief example, I cannot make it clearer than that:

    int    pipefd[2];
    
    pipe(pipefd); 
    if (fork() == 0) // I'm the child
    {
        close(pipefd[0]);    // I'm not going to read from this pipe
        dup2(pipefd[1], 1);  // redirect standard output to the pipe
        close(pipefd[1]);    // it has been duplicated, close it as we don't need it anymore
        execve()/execl()/execsomething()... // execute the program you want
    }
    else // I'm the parent
    {
        close(pipefd[1]);  // I'm not going to write to this pipe
        while (read(pipefd[0], &buf, 1) > 0) // read while EOF
            write(1, &buf, 1);
        close(pipefd[1]);  // cleaning
    }
    

    And as always, remember to read the man pages and to check all your return values.

    Again, good luck!