@heinrich5991: Depending on the program, there may be a signal-safe way to perform fflush()
. And the answer to your last question (in your comment to Brett Hale's answer) should be yes, provided that things are done carefully. However, it can get complicated.
To illustrate, let's start with a simple version of the cat
program:
int main(void)
{
int c;
while (1) {
c = getchar();
if (c < 0 || c > UCHAR_MAX) /* Includes EOF */
break;
putchar(c);
}
fflush(stdout);
return 0;
}
The program gracefully terminates when getchar()
returns EOF
.
Signals may need to be used when this program is getting input from a stream which may never send an EOF
(e.g. a terminal in raw mode). For such cases we can try to implement graceful termination via
a signal which it catches, say SIGUSR1
:
void sig_handler(int signo)
{
if (signo == SIGUSR1)
_exit(0);
}
int main(void)
{
/* Code for installing SIGUSR1 signal handler here */
/* Remaining code here as before */
}
This handler is safe, since _exit()
is async signal safe. The problem is that a SIGUSR1
signal will terminate the program without flushing the output buffers, which can cause loss of output. We may try to fix that by forcing flushing, as in:
void sig_handler(int signo)
{
if (signo == SIGUSR1) {
fflush(stdout);
_exit(0);
}
}
But then it is no longer signal-safe, since fflush()
isn't. The putchar()
library function periodically flushes stdout
. The signal may arrive in the middle of a call to putchar()
, which may be in the middle of flushing stdout
. Our handler then does another call of fflush(stdout)
, while the previous flushing of stdout
(from main
's putchar
) hasn't finished yet. Depending on the standard library implementation, this can cause the stdout
buffer overwriting parts of itself (mangling output), or the remaining part of the stdout
buffer getting duplicated.
One possible way out is to make the handler just set a global flag variable, and then let the main program handle the flushing:
volatile int gotsignal = 0;
void sig_handler(int signo)
{
if (signo == SIGUSR1)
gotsignal = 1;
}
int main()
{
int c;
/* Code for installing signal handler here */
while (1) {
c = getchar();
if (c < 0 || c > UCHAR_MAX)
break;
putchar(c);
if (gotsig == 1)
break;
}
fflush(stdout);
return 0;
}
This becomes signal-safe again, but introduces a deadlock. Suppose that the program has already received all input that it's ever going to get, and we send a SIGUSR1
signal to gracefully terminate it. The signal arrives when the process is blocked on a read call via getchar()
. The signal will be handled by the handler, gotsig
will get set to 1, but control will go back to the blocked getchar()
call, and so it will block for ever, never terminating (gracefully). We have a deadlock.
A solution:
The same GNU C Libarary documentation that you refer to (subsection "Signal Handling and Nonreentrant Functions") says:
If you call a function in the handler, make sure it is reentrant with respect to signals, or else make sure that the signal cannot interrupt a call to a related function.
We can get some idea from the "or else" part of the above sentence, about interrupting call to a "related function". Which functions in our main code are related to fflush(stdout)
? Our main code calls only two functions (standard library), getchar()
and putchar()
. It is reasonable to assume that getchar() only touches stdin
, and so will not call fflush(stodut)
or any subordinate functions. So the "related" function here is putchar()
, since as mentioned before, it periodically triggers fflush(stdout)
, or related stuff.
Following the GNU C Libarary recommendation, we can make the handler do this:
If the handler is invoked while inside a putchar()
call in main()
, it will not fflush(stdout)
; just return and let main()
do the flush
Otherwise, it should be safe to fflush(stdout)
from the handler
This solves all the problems above (flushing / unsafe / deadlock).
To implement this, we use another global flag, unsafe_to_flush
.
The final code below should be signal-safe and without deadlocks, but is more complex than the earlier attempts above.
volatile int unsafe_to_flush = 0;
volatile int gotsig = 0;
void sig_handler(int signo)
{
if (signo == SIGUSR1) {
gotsig = 1; /* Caller can flush using this flag */
if (unsafe_to_flush) {
/* We got called from putchar()! */
return;
}
else {
/* Safe to call fflush(stdout) */
fflush(stdout);
_exit(0);
}
}
}
int main(void)
{
int c;
/* Code for installing signal handler here */
while (1) {
c = getchar();
if (c < 0 || c > UCHAR_MAX)
break;
/* Critical region */
unsafe_to_flush = 1;
putchar(c); /* non-reentrant fflush related */
unsafe_to_flush = 0;
if (gotsig == 1)
break;
}
fflush(stdout);
return 0;
}