How to Skip a Sleep in C

Published 28 Dec 2020 on Andrea Feletto

Introduction

cstatus is a daemon written in c99 which calls a function every ten seconds in order to keep the statusbar up-to-date. The implementation is pretty simple: just an infinite loop with the function call followed by sleep.

#include <unistd.h>

while (1) {
	refresh();
	sleep(10);
}

The implementation becomes less obvious (at least for me) when you want to implement a mechanism to somehow force the refresh to happen without waiting for the sleep routine to finish.

The Wrong Way

One way to do this would be to listen for a UNIX signal with the refresh function as a callback. The <signal.h> header provides the sigaction function which expects a signal number and a struct containing various options. The last parameter is useless in this case so it can be set to null.

int sigaction(int sig, const struct sigaction *act, struct sigaction *old);

I decided to listen for USR1. There is no need to know the number associated with it since the <signal.h> header also provides macros with the correct value.

The callback must be provided through the struct, taking care to initialize the flags to zero.

#include <signal.h>

struct sigaction sa;
sa.sa_handler = signalhandler;
sa.sa_flags = 0;
sigaction(SIGUSR1, &sa, NULL);

The implementation of the signalhandler function is straight forward, since I am listening for just one signal. The assertion is there just in case. Keep in mind that refresh is the same function that is called every 10 second by the main thread.

#include <assert.h>

void signalhandler(int n) {
	assert(n == SIGUSR1);
	refresh();
}

This methods has two problems:

  1. refresh is not async-signal-safe
  2. sleep returns when a signal is captured and handled

The refresh function contains numerous calls to IO functions from <stdio.h>, <X11/Xlib.h> and <alsa/asoundlib.h>. Since those methods are not atomic with respect to signals, if the latter are caught during the execution of the former, the program has undefined behavior.

Also, the fact that sleep returns when a signal is handled means that refresh gets called twice.

The Right Way

Problem n°2 is actually the solution. Since sleep returns, there is no need to call refresh from the handler. With a little change to signalhandler the problem is solved:

void signalhandler(int n) {
	assert(n == SIGUSR1);
}

Putting it all together:

#include <assert.h>
#include <signal.h>
#include <unistd.h>

void refresh(void) {
	/* ... */
}

void signalhandler(int n) {
	assert(n == SIGUSR1);
}

int main() {
	struct sigaction sa;

	sa.sa_handler = signalhandler;
	sa.sa_flags = 0;
	sigaction(SIGUSR1, &sa, NULL);

	while (1) {
		refresh();
		sleep(10);
	}
}