Question

Well, title says most of it.

Say my application is logging to stdout/file. However, when terminated it is not always completely flushed. One solution would be to flush after each logging action, however that slowed the program down unacceptably.

So, the question is, is fflush(file) safe to be called from a signal handler, maybe even fflush(NULL)?

If not, please give an idea why. Are there other solutions solutions to this problem? Is it maybe safe if I know that I'm not in a file handling routine?

Was it helpful?

Solution

You can only use async-safe functions from within a signal handler, and that does not include the stdio library. The POSIX standard(s) define a set of functions that are async-safe. You haven't specified the platform, and I don't know of a general solution.

If you store the file descriptor backing the FILE - using fileno() - on a POSIX / BSD system, you may be able to salvage something using async-safe functions: write(), lseek() (flush), fsync(), etc. This of course, won't help if stdio is using its own buffers.

(some Cert-C guidelines)

OTHER TIPS

@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;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top