IPC Identifiers in Client-Server Apps

 

IPC Identifiers in Client-Server Apps
Chapter 45, Section 45.4 | Server Restart, Stale Clients & Cleanup
Server
Creates IPC objects
Client
Accesses objects
EIDRM
Stale ID error
Key Terms
Client-Server IPC Server Restart IPC_CREAT | IPC_EXCL EEXIST EIDRM Stale identifier connectionless IPC_RMID

How IPC Works in Client-Server Design

In a typical client-server application using System V IPC:

  • The server creates the IPC objects (message queue, semaphore, or shared memory) using a get call with IPC_CREAT.
  • The client opens the same objects using a get call with the same key but without IPC_CREAT.

This pattern works smoothly when both server and client start up normally. But what happens when the server crashes and restarts?

The Server Restart Problem

Imagine a server has been running for a while. It has a message queue with ID 12345. Clients have been sending and receiving messages. Now the server crashes (or is deliberately restarted).

Server Restart Scenario
Server v1
Creates queue (key=K) → gets ID=12345. Clients connect and start exchanging messages.
💥 CRASH
Server dies. Queue ID=12345 still exists in kernel (kernel persistence). Clients hold stale ID.
Server v2
Restarts. Cannot blindly reuse queue ID=12345 — it contains stale messages from old clients.
Solution
Server v2 deletes old queue and creates a new one with a different ID. Old clients get EIDRM errors.
Why the server can’t just reuse the existing queue: It has no knowledge of the historical state — stale client messages may be sitting in the queue, or clients may have sent secondary requests based on earlier responses from the dead server.

The Standard Solution: Create with IPC_CREAT | IPC_EXCL, then Recover

The clean pattern for a restarting server:

  1. Try to create the IPC object with IPC_CREAT | IPC_EXCL
  2. If it fails with EEXIST → the old server left it behind
  3. Fetch the old ID with a plain get call, then delete it with IPC_RMID
  4. Loop back and try creating again

This is exactly what Listing 45-1 from TLPI demonstrates. The key insight is that after the old queue is deleted and a new one created, the new queue will have a different identifier (due to the sequence-number algorithm). This invalidates any stale IDs held by clients of the old server.

Coding Example 1 — Server: Safe IPC Object Creation with Cleanup (TLPI Listing 45-1)
/* svmsg_demo_server.c — demonstrates safe server-side IPC creation */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

/* This file must exist. Create it with: touch /tmp/sv_demo_key */
#define KEY_FILE "/tmp/sv_demo_key"

int main(int argc, char *argv[]) {
    int msqid;
    key_t key;
    const int MQ_PERMS = S_IRUSR | S_IWUSR | S_IWGRP;  /* rw--w---- */

    /* Generate a key from the agreed-upon file */
    key = ftok(KEY_FILE, 1);
    if (key == -1) {
        perror("ftok");
        exit(EXIT_FAILURE);
    }
    printf("Using key = 0x%x\n", (unsigned int)key);

    /* Try to create the queue exclusively.
       Loop handles the case where an old queue exists. */
    while ((msqid = msgget(key, IPC_CREAT | IPC_EXCL | MQ_PERMS)) == -1) {
        if (errno == EEXIST) {
            /* A queue with this key already exists (from a previous
               server run). Get its ID and remove it. */
            int old_id = msgget(key, 0);
            if (old_id == -1) {
                perror("msgget (retrieve old queue)");
                exit(EXIT_FAILURE);
            }

            if (msgctl(old_id, IPC_RMID, NULL) == -1) {
                perror("msgctl IPC_RMID (delete old queue)");
                exit(EXIT_FAILURE);
            }
            printf("Removed stale queue (old ID=%d). Retrying...\n", old_id);
            /* Loop continues → tries IPC_CREAT | IPC_EXCL again */

        } else {
            /* Some other error — give up */
            perror("msgget");
            exit(EXIT_FAILURE);
        }
    }

    /* At this point: new queue created successfully */
    printf("New message queue created. ID = %d\n", msqid);
    printf("This new ID differs from any old ID clients may have.\n");
    printf("Old clients will get EIDRM when they try to use their stale IDs.\n");

    /* ... server continues to serve clients ... */

    /* Cleanup when server exits normally */
    if (msgctl(msqid, IPC_RMID, NULL) == -1) {
        perror("msgctl IPC_RMID");
    }
    return 0;
}

Coding Example 2 — Client: Handling Stale Identifier (EIDRM)

When a server restarts and recreates the IPC object with a new ID, any client holding the old ID will receive EIDRM (“identifier removed”) when it tries to use it. A well-written client handles this gracefully:

/* client_demo.c — demonstrates handling of stale IPC ID */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define KEY_FILE "/tmp/sv_demo_key"

struct mymsg { long mtype; char mtext[256]; };

int get_server_queue_id(void) {
    key_t key = ftok(KEY_FILE, 1);
    if (key == -1) { perror("ftok"); return -1; }
    int id = msgget(key, 0);   /* open existing queue, no IPC_CREAT */
    if (id == -1) { perror("msgget"); return -1; }
    return id;
}

int main(void) {
    int msqid;
    struct mymsg msg;

    /* Initial connection to server's queue */
    msqid = get_server_queue_id();
    if (msqid == -1) exit(EXIT_FAILURE);
    printf("Client: Connected to queue ID=%d\n", msqid);

    /* Simulate client doing some work, then server restarts
       In a real scenario, msqid would now be stale */

    /* Try to send a message */
    msg.mtype = 1;
    strncpy(msg.mtext, "Request from client", sizeof(msg.mtext));

    if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
        if (errno == EIDRM) {
            /* Identifier was removed — server restarted */
            printf("Client: Queue was removed (server restarted?). Reconnecting...\n");

            /* Reconnect: fetch the new queue ID */
            msqid = get_server_queue_id();
            if (msqid == -1) {
                printf("Client: Cannot reconnect. Server may be down.\n");
                exit(EXIT_FAILURE);
            }
            printf("Client: Reconnected. New queue ID=%d\n", msqid);

            /* Retry the send */
            if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
                perror("msgsnd (retry)");
                exit(EXIT_FAILURE);
            }
        } else {
            perror("msgsnd");
            exit(EXIT_FAILURE);
        }
    }
    printf("Client: Message sent successfully.\n");
    return 0;
}
Two different errors for invalid IPC identifiers:

  • EINVAL — the corresponding entries[] slot is empty (never existed or was freed)
  • EIDRM — the slot exists but the sequence number doesn’t match (old object was deleted, slot reused)

Coding Example 3 — Shared Memory Restart Caveat

Unlike message queues and semaphores, a shared memory segment is only deleted after all processes have detached. This means the above server-restart pattern works differently for shared memory. Typically, semaphores are used alongside shared memory — and since semaphores are immediately deleted on IPC_RMID, clients will notice the restart when they try to access the deleted semaphore:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#define SHM_KEY  0x1234
#define SEM_KEY  0x5678

int main(void) {
    int shmid, semid;

    /* Server creates both shared memory AND a companion semaphore */
    shmid = shmget(SHM_KEY, 4096, IPC_CREAT | IPC_EXCL | 0600);
    semid = semget(SEM_KEY, 1,    IPC_CREAT | IPC_EXCL | 0600);

    if (shmid == -1 || semid == -1) {
        perror("shmget/semget");
        exit(EXIT_FAILURE);
    }
    printf("Created: shmid=%d, semid=%d\n", shmid, semid);

    /* On server restart:
       - semctl(semid, 0, IPC_RMID) → immediate delete → clients get EIDRM
       - shmctl(shmid, IPC_RMID, NULL) → deferred until all detach */

    /* Delete semaphore: immediate */
    if (semctl(semid, 0, IPC_RMID) == -1) {
        perror("semctl IPC_RMID");
    }
    printf("Semaphore immediately deleted. Clients will detect restart.\n");

    /* Mark shared memory for deletion (actual removal deferred) */
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl IPC_RMID");
    }
    printf("Shared memory marked for deletion (deferred until detached).\n");

    return 0;
}
Design principle: Always pair shared memory with semaphores. When a server restarts, clients detect the restart via the EIDRM on the semaphore, even before the shared memory segment is fully gone.

Connectionless Nature of System V IPC

An important property of System V IPC objects: they are connectionless. The kernel does not keep a record of which processes have an IPC object “open.”

This is fundamentally different from sockets or pipes, where the kernel knows the connected endpoints. With System V IPC:

  • There is no concept of “connected” clients
  • There is no reference count of “users” (unlike file descriptors)
  • There is no automatic cleanup when all users exit (unlike pipes)
  • The server cannot know how many clients currently have the object open
This is why kernel persistence can be a problem: Since there’s no usage count, the kernel can’t automatically delete an IPC object when no processes need it anymore. You must explicitly delete it with IPC_RMID — and you must decide when to do so, which is non-trivial in a multi-process application.

🎯 Interview Questions — Client-Server IPC

Q1. In a client-server application using System V IPC, who typically creates the IPC objects?
A: The server creates the IPC objects using a get call with IPC_CREAT. The client opens existing objects using a get call with the same key but without IPC_CREAT.
Q2. What problem arises when a server using System V IPC crashes and restarts?
A: Because of kernel persistence, the old IPC object still exists. The restarted server cannot blindly reuse it because it may contain stale state (old messages, old semaphore values). Also, if the new object got the same identifier as the old one, clients would have no way to know a restart happened.
Q3. What is the standard recovery pattern for a restarting server?
A: Try to create the IPC object with IPC_CREAT | IPC_EXCL. If EEXIST is returned, fetch the old object’s ID, delete it with IPC_RMID, then retry creation. The newly created object will have a different identifier due to the sequence-number algorithm.
Q4. What error does a client receive when it tries to use an IPC identifier that has been deleted and whose slot has been reused?
A: EIDRM (“identifier removed”). This occurs when the entries[] slot has been reused by a new object with a different sequence number, so the stored sequence number doesn’t match the expected identifier. EINVAL is returned when the slot is simply empty.
Q5. Why is System V IPC described as “connectionless”?
A: Because the kernel does not track which processes have an IPC object “open.” There is no reference count of active users, no concept of “connected” processes, and no automatic deletion when the last user exits. This is different from pipes, sockets, and file descriptors which maintain connection state.
Q6. Why does the server restart pattern work differently for shared memory compared to message queues?
A: Because IPC_RMID on a shared memory segment does not immediately delete it — the deletion is deferred until all processes have detached (shmdt). For message queues and semaphores, IPC_RMID causes immediate deletion. This is why shared memory is typically used with companion semaphores for server restart detection.
Q7. How can clients detect that a server has restarted when using shared memory?
A: Since shared memory deletion is deferred, clients typically detect a restart via a companion semaphore. When the server deletes the semaphore (immediate deletion via IPC_RMID), clients attempting to access the semaphore receive EIDRM, signaling a restart.

Leave a Reply

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