Part 4 – Queue Limits & /proc Controls

 

⚙️ Part 4 – Queue Limits & /proc Controls
How the kernel limits inotify usage and how to tune those limits

Why Does the Kernel Impose Limits?

Every inotify event stored in the kernel queue consumes kernel memory — a finite resource shared among all processes. Without limits, a misbehaving program could create thousands of watch items or let events pile up in the queue, eventually exhausting kernel memory and crashing the system.

Linux exposes three tunable limits via files under /proc/sys/fs/inotify/. These can be read and written by the superuser.

The Three /proc Limit Files
/proc/sys/fs/inotify/ Default Value What It Controls
max_queued_events 16,384 Maximum number of events that can be queued in a single inotify instance before overflow occurs.
max_user_instances 128 Maximum number of inotify instances (inotify_init() calls) that one user (real UID) can create.
max_user_watches 8,192 Maximum number of watch items that one user can have across all inotify instances combined.

Check the current values on your system:

cat /proc/sys/fs/inotify/max_queued_events
cat /proc/sys/fs/inotify/max_user_instances
cat /proc/sys/fs/inotify/max_user_watches

What Happens When the Queue Overflows?

If events arrive faster than your program reads them and the queue reaches max_queued_events, the kernel:

  1. Drops the new event (it is permanently lost)
  2. Generates one special IN_Q_OVERFLOW event

The IN_Q_OVERFLOW event has wd = -1 (no specific watch descriptor). Your program must handle this and rescan the directory tree to find out what it missed.

inotify Event Queue (max=16384)
Event 1: IN_CREATE foo.txt
Event 2: IN_MODIFY foo.txt
Event 3: IN_CREATE bar.txt
… (16383 events) …
⚠ IN_Q_OVERFLOW (wd=-1) — events lost!
Your program must:
  1. Detect IN_Q_OVERFLOW
  2. Stop trusting the event stream as complete
  3. Rescan affected directories
  4. Rebuild your view of the file tree
Critical for production code: Always check for IN_Q_OVERFLOW in your event loop. Missing it leads to your program having a stale/wrong view of the file system.

Reading and Changing Limits

Read current limits (shell):

cat /proc/sys/fs/inotify/max_queued_events   # e.g. 16384
cat /proc/sys/fs/inotify/max_user_instances  # e.g. 128
cat /proc/sys/fs/inotify/max_user_watches    # e.g. 8192

Increase limits temporarily (lost after reboot):

# Requires root (or CAP_SYS_ADMIN)
echo 65536 > /proc/sys/fs/inotify/max_queued_events
echo 524288 > /proc/sys/fs/inotify/max_user_watches

Make limits permanent (survives reboot):

# Add to /etc/sysctl.conf
fs.inotify.max_queued_events = 65536
fs.inotify.max_user_instances = 512
fs.inotify.max_user_watches = 524288

# Apply immediately without reboot:
sudo sysctl -p
Real-world note: Development tools like VS Code, JetBrains IDEs, and webpack’s watch mode consume many inotify watches. On a developer machine watching large projects, you may need to increase max_user_watches to 524288 or more. The IDE usually tells you when it hits the limit.

💻 Code Example 1 – Read /proc Limits from a C Program
#include <stdio.h>
#include <stdlib.h>

void read_inotify_limit(const char *file)
{
    FILE *fp = fopen(file, "r");
    if (!fp) { perror(file); return; }

    int value;
    if (fscanf(fp, "%d", &value) == 1)
        printf("%-45s = %d\n", file, value);

    fclose(fp);
}

int main(void)
{
    printf("=== inotify Kernel Limits ===\n");
    read_inotify_limit("/proc/sys/fs/inotify/max_queued_events");
    read_inotify_limit("/proc/sys/fs/inotify/max_user_instances");
    read_inotify_limit("/proc/sys/fs/inotify/max_user_watches");
    return 0;
}
gcc -o read_limits read_limits.c
./read_limits
# Output:
# /proc/sys/fs/inotify/max_queued_events  = 16384
# /proc/sys/fs/inotify/max_user_instances = 128
# /proc/sys/fs/inotify/max_user_watches   = 8192

💻 Code Example 2 – Handling IN_Q_OVERFLOW
#include <stdio.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <unistd.h>

#define BUF_SIZE 4096

void rescan_directory(const char *path)
{
    /*
     * In a real program, re-read directory contents here
     * to rebuild your state, since events may have been lost.
     */
    printf("[RESCAN] Rebuilding view of: %s\n", path);
}

int main(void)
{
    int fd, wd;
    char buf[BUF_SIZE];
    ssize_t numRead;
    struct inotify_event *event;
    char *p;
    const char *watched_path = "/tmp/testdir";

    fd = inotify_init1(IN_CLOEXEC);
    if (fd == -1) { perror("inotify_init1"); exit(EXIT_FAILURE); }

    wd = inotify_add_watch(fd, watched_path, IN_ALL_EVENTS);
    if (wd == -1) { perror("inotify_add_watch"); close(fd); exit(EXIT_FAILURE); }

    printf("Watching %s\n", watched_path);

    for (;;) {
        numRead = read(fd, buf, BUF_SIZE);
        if (numRead <= 0) break;

        for (p = buf; p < buf + numRead; ) {
            event = (struct inotify_event *) p;

            /* Check for queue overflow FIRST */
            if (event->mask & IN_Q_OVERFLOW) {
                fprintf(stderr, "WARNING: inotify queue overflowed! "
                                "Events were lost. wd=-1 for this event.\n");
                /* Rescan to recover accurate state */
                rescan_directory(watched_path);
                /* Continue processing remaining events in buffer */
                p += sizeof(struct inotify_event) + event->len;
                continue;
            }

            /* Normal event processing */
            if (event->mask & IN_CREATE && event->len > 0)
                printf("Created: %s\n", event->name);

            if (event->mask & IN_DELETE && event->len > 0)
                printf("Deleted: %s\n", event->name);

            p += sizeof(struct inotify_event) + event->len;
        }
    }

    close(fd);
    return 0;
}

💻 Code Example 3 – Check How Many Watches a Process Uses

You can inspect inotify fd usage for your own process via /proc:

#include <stdio.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>

/*
 * Count how many entries are in /proc/self/fdinfo/
 * that are inotify file descriptors.
 * Each line in /proc/self/fdinfo/N that contains "inotify" is one instance.
 */
int count_inotify_fds(void)
{
    DIR *dir;
    struct dirent *ent;
    char path[256];
    char line[128];
    FILE *fp;
    int count = 0;

    dir = opendir("/proc/self/fdinfo");
    if (!dir) return -1;

    while ((ent = readdir(dir)) != NULL) {
        if (ent->d_name[0] == '.') continue;

        snprintf(path, sizeof(path), "/proc/self/fdinfo/%s", ent->d_name);
        fp = fopen(path, "r");
        if (!fp) continue;

        while (fgets(line, sizeof(line), fp)) {
            if (strstr(line, "inotify")) { count++; break; }
        }
        fclose(fp);
    }
    closedir(dir);
    return count;
}

int main(void)
{
    /* Create a few inotify instances */
    int fd1 = inotify_init1(IN_CLOEXEC);
    int fd2 = inotify_init1(IN_CLOEXEC);
    inotify_add_watch(fd1, "/tmp", IN_CREATE);
    inotify_add_watch(fd2, "/var/log", IN_MODIFY);

    printf("inotify instances open: %d\n", count_inotify_fds());

    close(fd1);
    close(fd2);
    return 0;
}

Up Next: dnotify vs inotify

Learn why the old dnotify mechanism was replaced and what makes inotify superior.

Part 5: dnotify vs inotify → ← Part 3

Leave a Reply

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