πŸ” Timer Overruns & Thread Notification

πŸ” Timer Overruns & Thread Notification
Chapter 23 | Part 8 of 9 β€” Overrun Counting & SIGEV_THREAD
⚑ Overrun
timer_getoverrun()
🧡 Thread
SIGEV_THREAD
πŸ”” Signal
si_overrun

What Is a Timer Overrun?

A timer overrun happens when a periodic timer expires multiple times before its notification signal is caught or the notification thread is invoked.

Example: your timer fires every 100ms. If your process is preempted for 350ms, the timer has expired 3 times but you only receive 1 signal. The overrun count is 2 (the extra 2 expirations).

πŸ“Š Timer Overrun β€” Visual Example
Timeline (timer interval = 100ms):
Expiry 1
T=100ms
β†’ signal sent, pending
Expiry 2
T=200ms
β†’ queued (overrun)
Expiry 3
T=300ms
β†’ queued (overrun)
Handler runs
T=350ms
β†’ Signal delivered ONCE. timer_getoverrun() returns 2. Total expirations = 1 + 2 = 3.

πŸ“‹ Overrun API
#define _POSIX_C_SOURCE 199309
#include <time.h>

/* Returns the overrun count for the specified timer.
   Async-signal-safe β€” safe to call from signal handler.
   Returns: count (0 if no overruns), or -1 on error.

   Overrun count = extra expirations since last signal.
   If timer expired 5 times but 1 signal was delivered,
   overrun count = 4. */
int timer_getoverrun(timer_t timerid);

/* Linux extension: si_overrun field in siginfo_t
   Available in signal handler when SA_SIGINFO is set:
   int overrun = si->si_overrun;
   (saves a syscall, but non-portable) */

🧡 SIGEV_THREAD β€” Thread Function Notification

Instead of delivering a signal, the kernel invokes a user function in a new thread on each timer expiration. This avoids signal-safety restrictions.

Advantages

  • No async-signal restrictions
  • Can call any function (printf, malloc…)
  • Thread gets sigval data
Caveats

  • Thread creation overhead per expiry
  • Need mutex to protect shared state
  • Requires linking with -lpthread
/* Setting up SIGEV_THREAD notification */
struct sigevent sev;

sev.sigev_notify           = SIGEV_THREAD;
sev.sigev_notify_function  = my_thread_func;   /* Called on expiry */
sev.sigev_notify_attributes = NULL;            /* Or pthread_attr_t* */
sev.sigev_value.sival_ptr  = &timerid;         /* Passed to function */

/* Thread function signature: */
void my_thread_func(union sigval sv) {
    timer_t *tid = sv.sival_ptr;
    printf("Timer %ld expired, overrun=%d\n",
           (long)*tid, timer_getoverrun(*tid));
}

πŸ’» Example 1: Detecting Timer Overruns via Signal
#define _POSIX_C_SOURCE 199309
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>

static timer_t timerid;
static int total_expirations = 0;

void timer_handler(int sig, siginfo_t *si, void *uc) {
    (void)sig; (void)uc;
    int overrun = timer_getoverrun(timerid);
    /* Also available as si->si_overrun (Linux extension) */
    total_expirations += 1 + overrun;  /* This delivery + overruns */
    printf("Signal received: +1 delivery, +%d overruns (total=%d)\n",
           overrun, total_expirations);
}

int main(void) {
    struct sigaction sa;
    struct sigevent  sev;
    struct itimerspec ts;

    sa.sa_flags     = SA_SIGINFO;
    sa.sa_sigaction = timer_handler;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGRTMIN, &sa, NULL);

    sev.sigev_notify          = SIGEV_SIGNAL;
    sev.sigev_signo           = SIGRTMIN;
    sev.sigev_value.sival_ptr = &timerid;
    timer_create(CLOCK_REALTIME, &sev, &timerid);

    /* 200ms interval timer */
    ts.it_value.tv_sec    = 0;
    ts.it_value.tv_nsec   = 200000000L;
    ts.it_interval.tv_sec  = 0;
    ts.it_interval.tv_nsec = 200000000L;
    timer_settime(timerid, 0, &ts, NULL);

    printf("Timer running at 200ms. Sleeping 1s to accumulate overruns...\n");

    /* Block signals for 1 second to force overruns */
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGRTMIN);
    sigprocmask(SIG_BLOCK, &mask, NULL);

    sleep(1);   /* Timer fires ~5 times, all queued */

    /* Unblock β€” handler called once, overrun=4 */
    sigprocmask(SIG_UNBLOCK, &mask, NULL);

    pause();   /* Wait for the one deferred signal */

    timer_delete(timerid);
    printf("Total expirations accounted for: %d\n", total_expirations);
    return 0;
}

/* Expected output:
   Timer running at 200ms. Sleeping 1s to accumulate overruns...
   Signal received: +1 delivery, +4 overruns (total=5)
   Total expirations accounted for: 5
*/

πŸ’» Example 2: Timer with SIGEV_THREAD Notification
#define _POSIX_C_SOURCE 199309
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>

static timer_t timerid;
static pthread_mutex_t mtx  = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  cond = PTHREAD_COND_INITIALIZER;
static int total = 0;

/* This function runs in a NEW THREAD on each expiry */
void thread_notify(union sigval sv) {
    int overrun = timer_getoverrun(*(timer_t *)sv.sival_ptr);

    pthread_mutex_lock(&mtx);
    total += 1 + overrun;
    printf("[Thread] Timer expired! overrun=%d, total=%d\n",
           overrun, total);
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mtx);
}

int main(void) {
    struct sigevent  sev;
    struct itimerspec ts;

    /* SIGEV_THREAD setup */
    sev.sigev_notify            = SIGEV_THREAD;
    sev.sigev_notify_function   = thread_notify;
    sev.sigev_notify_attributes = NULL;     /* Use default thread attrs */
    sev.sigev_value.sival_ptr   = &timerid;

    if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1) {
        perror("timer_create"); return 1;
    }

    /* Arm: 500ms first, 500ms interval */
    ts.it_value.tv_sec    = 0;
    ts.it_value.tv_nsec   = 500000000L;
    ts.it_interval.tv_sec  = 0;
    ts.it_interval.tv_nsec = 500000000L;
    timer_settime(timerid, 0, &ts, NULL);

    /* Main thread waits for 5 expirations */
    pthread_mutex_lock(&mtx);
    while (total < 5) {
        pthread_cond_wait(&cond, &mtx);
        printf("[Main] total so far: %d\n", total);
    }
    pthread_mutex_unlock(&mtx);

    timer_delete(timerid);
    printf("Done. Total expirations: %d\n", total);
    return 0;
}

/* Compile: gcc -o sigev_thread sigev_thread.c -lrt -lpthread
   No signal handling needed β€” thread function is called directly! */

πŸ’» Example 3: Using si_overrun (Linux Extension)

Access overrun count directly from siginfo_t without a syscall.

#define _GNU_SOURCE
#define _POSIX_C_SOURCE 199309
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>

static timer_t g_tid;
static int done = 0;

void handler(int sig, siginfo_t *si, void *uc) {
    (void)sig; (void)uc;

    /* Portable way: timer_getoverrun() */
    int overrun_portable = timer_getoverrun(g_tid);

    /* Linux-specific: si_overrun directly in siginfo_t
       (saves the syscall overhead) */
    int overrun_linux = si->si_overrun;

    printf("si_code=%d (SI_TIMER=%d), si_overrun=%d, "
           "timer_getoverrun()=%d\n",
           si->si_code, SI_TIMER,
           overrun_linux, overrun_portable);

    /* si_value holds what we set in sev.sigev_value */
    printf("si_value.sival_ptr = %p (timer addr = %p)\n",
           si->si_value.sival_ptr, (void *)&g_tid);

    done = 1;
}

int main(void) {
    struct sigaction sa;
    struct sigevent  sev;
    struct itimerspec ts;

    sa.sa_flags     = SA_SIGINFO;
    sa.sa_sigaction = handler;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGRTMIN, &sa, NULL);

    sev.sigev_notify          = SIGEV_SIGNAL;
    sev.sigev_signo           = SIGRTMIN;
    sev.sigev_value.sival_ptr = &g_tid;

    timer_create(CLOCK_REALTIME, &sev, &g_tid);

    /* One-shot 500ms timer */
    ts.it_value.tv_sec    = 0;
    ts.it_value.tv_nsec   = 500000000L;
    ts.it_interval.tv_sec  = 0;
    ts.it_interval.tv_nsec = 0;
    timer_settime(g_tid, 0, &ts, NULL);

    while (!done) pause();
    timer_delete(g_tid);
    return 0;
}
/* Output:
   si_code=SI_TIMER (==SI_TIMER), si_overrun=0, timer_getoverrun()=0
   si_value.sival_ptr = 0x... (timer addr = 0x...)
*/

πŸŽ“ Interview Questions
Q1. What is a timer overrun?

Extra timer expirations that occurred between the time the notification signal was generated and when it was handled. If timer expires 5 times but 1 signal is delivered, overrun count = 4.

Q2. Why can’t you use a realtime signal queue to avoid overruns?

There are system limits on queued realtime signals. The kernel guarantees at most one instance per timer, using the overrun count instead of queuing multiple copies.

Q3. What is the difference between SIGEV_SIGNAL and SIGEV_THREAD?

SIGEV_SIGNAL delivers a signal to the process (restricted async-signal context). SIGEV_THREAD invokes a function in a new thread (full C runtime available β€” can call printf, malloc, etc.).

Q4. How do you account for all expirations in a signal handler?

total += 1 + timer_getoverrun(timerid) β€” count 1 for the delivery itself plus the overrun count for missed ones.

Q5. What extra library must be linked for SIGEV_THREAD?

Both -lrt (POSIX timers) and -lpthread (POSIX threads) are required.

Next: timerfd β€” Timers via File Descriptors β†’

Part 9: timerfd API ← Part 7: POSIX Timers

Leave a Reply

Your email address will not be published. Required fields are marked *