[Unix, OpenVMS, popen, pipe, fflush] Pipes need to be flushed to avoid potential hang
PRODUCT: gcc V4.4.3 20100127 (Red Hat 4.4.3-4) IBM's XL C level 9.0.0.0 HP C V7.3-018
OP/SYS: IBM AIX Version 5.3 OpenVMS IA64 V8.3 Linux Fedora 12
COMPONENT: C calls popen, pipe, vfork, exec
SOURCE: Philippe Vouters Fontainebleau/France
HIGH QUALITY MOBILES+TABLETS: http://android-land.fr
SYMPTOM(S) or PROBLEM(S): When writing to a pipe either created with pipe or popen, a pipe writer may become hung if the pipe is not regularly flushed and the pipe becomes full.
SOLUTION or RESPONSE: When fork'ing + exec'ing + communicating between parent and child processes using pipes or functionally equivalent using popen, a writer must fflush its pipe end to enable a reader to get data from the pipe and thus empty it. Failure to fflush'ing may lead a pipe writer side hang when the pipe internal structure becomes full.
*** CAUTION *** These sample reproducers have been tested using gcc V4.4.3 20100127 (Red Hat 4.4.3-4) on Fedora 12, IBM's XL C compiler level 9.0.0.0 on AIX Version 5.3 and HP C V7.3-018 on OpenVMS IA64 V8.3. However, we cannot guarantee their effectiveness because of the possibility of error in transmitting or implementing them. It is meant to be used as a template for writing your own programs, and may require modification for use on your system.
WORKAROUND or ALTERNATIVE: Use fflush and empty the pipe on the reader side using fgets if text, fread or read for binary data.
REPRODUCERS NOTES: The application is composed of a test_main.c and a fork'ed + exec'ed son.c, one among the two possibilities is that son.c is fork'ed + exec'ed implicitly using popen. In both cases the parent (test_main.c) process gets the son.c child process output using an explicit or an implicit via the popen pipe. You may play using different -D combinations On most Unix including Linux, the commands are: $ cc [-DFORK] -o test_main test_main.c $ cc -o son son.c $ ./test_main On AIX with the IBM XL C compiler, replace cc by /usr/vac/bin/xlc. Without -DFORK a popen is used. With -DFORK a fork+exec is used. With or without -DFORK an explicit or implicit (popen) pipe is used. Unix information: Under both AIX and Linux Fedora 12, one noticeable effect with the code provided is that, whichever the way you compile test_main (using fork + exec + pipe or popen), the output produced by son.c is immediately output on the screen when flushed and delayed when not flushed. A Linux strace taken on both fork+exec or popen highlights in both cases a call to clone() (which is the Linux internal way to fork) followed by an exec. When fork+exec, the run time exec's "./son" whereas with popen, it exec's "sh -c './son'". On Linux Fedora 12 and AIX Version 5.3, if you replace the usual Unix approach of fork by vfork, recompiling test_main.c the following way: $ cc -Dfork=vfork -DFORK -o test_main test_main.c the run-time behavior of test_main strictly remains unchanged. So in this programming context, fork+exec appears as functionally equivalent to vfork+exec. OpenVMS information: On OpenVMS IA64 V8.3-1H1, the vfork solution only works if immediately succeeded with an exec call. Unlike the Unix world, it does look like when vfork returns 0, denoting the child process, any statement between vfork and exec is executed from within the parent process. This is suggested by the following two runs: $ define DECC$UNIX_LEVEL 30 $ cc/define=(FORK) test_main $ link test_main $ run test_main %C-F-SIGPIPE, broken pipe %TRACE-F-TRACEBACK, symbolic stack dump follows image module routine line rel PC abs PC DECC$SHR C$SIGNAL gsignal 27963 0000000000001180 FFFFFFFF84AB28B0 DECC$SHR C$SIGNAL raise 28020 0000000000001280 FFFFFFFF84AB29B0 DECC$SHR C$RECORDIO decc$$pipe_put 38006 000000000000D350 FFFFFFFF845EC220 DECC$SHR C$CLOSE decc$$close 37721 0000000000002F30 FFFFFFFF846A7920 DECC$SHR C$CLOSE close 37779 00000000000040B0 FFFFFFFF846A8AA0 DECC$SHR C$EXHANDLER decc$$exhandler 36093 00000000000000D0 FFFFFFFF84F149C0 0 FFFFFFFF801BDA20 FFFFFFFF801BDA20 0 FFFFFFFF80B42B70 FFFFFFFF80B42B70 0 FFFFFFFF80B3FEE0 FFFFFFFF80B3FEE0 DECC$SHR C$EXIT exit_common 14361 0000000000000230 FFFFFFFF849416A0 DECC$SHR C$EXIT decc$exit 14381 0000000000000280 FFFFFFFF849416F0 test_main TEST_MAIN __main 6092 00000000000000D0 00000000000200D0 0 FFFFFFFF80B43320 FFFFFFFF80B43320 DCL 0 000000000006BA90 000000007AE23A90 %TRACE-I-END, end of TRACE stack dump $ cc/define=(FORK,VMS_VFORK_WAY) test_main $ link test_main $ run test_main result=Son started. This message is flushed. Next message is not result=Message not flushed. It appears at son's exit Waiting for child termination waitpid resumed Son exited with status 0 The popen call has been tested and looks portable both at compile and at run times with one difference with Unix systems. Unlike Unix systems, the OpenVMS popen immediately flushes any printf terminated by \n. As well, the DCL command "run son" for test_main.c when popen is used ought to denote an underneath lib$spawn based popen implementation. Although affected by run-time incompatibility on immadiately outputing unflushed messages from the child process, the choice of a lib$spawn implementation under OpenVMS matches the Linux "sh -c" way for implementing popen. On the other hand and for a vfork+exec solution, if you carefully read : $ help crtl execl arguments you will notice that the first argument is an image to be executed. Hence the exec family only looking after executable images (.EXE). As a side note if you are running under GNV bash and intend to use the popen solution, do not forget to build the son executable the following way: bash$ cc -o son.EXE son.c On OpenVMS, you have four DECC$ logicals which may influence the behavior using pipes and popen: 1/ DECC$PIPE_BUFFER_SIZE 2/ DECC$PIPE_BUFFER_QUOTA 3/ DECC$POPEN_NO_CRLF_REC_ATTR 4/ DECC$STREAM_PIPE Defining DECC$POPEN_NO_CRLF_REC_ATTR to 1 causes the VMS parent program to hang, likely on the fgets.
ALTERNATIVE TO fflush C STATEMENT: Instead of calling fflush each time you output a text like in the son.c code below, you may start your equivalent of the son.c code with: setvbuf(stdout, NULL, _IONBF, 0); The above C statement sets stdout file buffer size to zero causing no buffering of stdout.
REPRODUCERS: *********** * test_main.c *********** /* * Copyright (C) 2010 by Philippe.Vouters@laposte.net * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>;. */ #include <stdlib.h> #include <time.h> #include <errno.h> #include <unistd.h> #include <stdio.h> #include <sys/wait.h> #include <sys/types.h> #include <signal.h> #include <string.h> #if defined (__VMS) #define fork vfork #endif int main(){ #if defined (__VMS) && !defined(FORK) const char *FileUtility = "run son"; #else const char *FileUtility = "./son"; #endif #ifdef FORK int pipes[2]; #else FILE *pipes; #endif pid_t file_pid; int filestatus; static char result[200]=""; #ifdef FORK if (pipe(pipes) == -1){ perror("pipe"); exit(-1); } #if defined (VMS_VFORK_WAY) close(0); close(1); dup2(pipes[0],0); dup2(pipes[1],1); #endif file_pid = fork(); switch (file_pid){ case -1: perror("fork"); exit(EXIT_FAILURE); case 0: /* child */ /* * On OpenVMS, any statement between vfork and exec is executed * by the vfork'ing process. When OpenVMS will implement * a true Unix fork, code like this will then be possible. */ #if !defined (VMS_VFORK_WAY) close(0); dup2(pipes[1],1); close(pipes[1]); #endif if (execl(FileUtility,FileUtility,(char *)0) == -1){ perror("execl error"); exit(EXIT_FAILURE); } break; default: /* parent */ #if defined (VMS_VFORK_WAY) close(0); close(1); stdin = fopen("sys$input:","r"); stdout = fopen("sys$output:","w"); #endif close(pipes[1]); memset(result,0,sizeof(result)); while (read(pipes[0],result,sizeof(result)) > 0 ){ printf("result=%s",result); memset(result,0,sizeof(result)); } printf ("Waiting for child termination\n"); waitpid(file_pid,&filestatus,0); printf ("waitpid resumed\n"); if WIFEXITED(filestatus) printf("Son exited with status %1d\n",filestatus); if (close(pipes[0]) < 0) exit(EXIT_FAILURE); break; } #else /* #ifdef FORK */ pipes=popen(FileUtility,"r"); while( fgets(result, sizeof(result), pipes) != NULL ){ printf("result=%s",result); } waitpid(-1,&filestatus,0); printf ("waitpid resumed\n"); if WIFEXITED(filestatus) printf("Son exited with status %1d\n",filestatus); #endif return EXIT_SUCCESS; } *********** * son.c *********** #include <stdio.h> #include <unistd.h> #include <stdlib.h> void main(){ printf ("Son started. This message is flushed. Next message is not\n"); fflush(stdout); printf("Message not flushed. It appears at son's exit\n"); sleep(10); exit(0); return; }
REFERENCE(S): refer to pipe or popen man