Michael Parker
Michael Parker

Reputation: 12966

C - Gracefully interrupting msgrcv system call

I'm working on a program that is supposed to act like a server and continuously read from a message queue and process the received messages.

The main loop looks something like this:

while (1) {
    /* Receive message */
    if (msgrcv(msqid, &msg, sizeof(struct msgbuffer) - sizeof(long), 0, 0) == -1) {
        perror("msgrcv");
        exit(1);
    }
    //more code here
}

The problem I'm having is that I cannot figure out a way to gracefully exit this loop without relying on a client to send a message to the server indicating that it should stop. I do a lot of resource clean-up after the loop, and my code can never get to that point because the loop will not end.

One thing I tried to do was listen for a SIGINT to end the loop....

volatile sig_atomic_t stop;

void end(int signum) {
    stop = 1;
}

int main(int argc, char* argv[]) {
    signal(SIGINT, end);
    //some code
    while (!stop) {
        /* Receive message */
        if (msgrcv(msqid, &msg, sizeof(struct msgbuffer) - sizeof(long), 0, 0) == -1) {
            perror("msgrcv");
            exit(1);
        }
        //more code here
    }
    //cleanup
}

...but since the loop is hanging on the system call itself, this doesn't work, and just results in perror printing out msgrcv: Interrupted system call, instead of terminating the loop and cleaning up my resources.

Is there a way I can terminate a system call and gracefully exit my loop?

SOLUTION:

Thanks to rivimey, I was able to solve my problem. Here is what I did to make it work:

volatile sig_atomic_t stop;

void end(int signum) {
    stop = 1;
}

int main(int argc, char* argv[]) {
    signal(SIGINT, end);
    //some code
    while (!stop) {
        /* Receive message */
        if (msgrcv(msqid, &msg, sizeof(struct msgbuffer) - sizeof(long), 0, 0) == -1) {
            if (errno == EINTR) break;
            else {
                perror("msgrcv");
                exit(1);
            }
        }
        //more code here
    }
    //I can now reach this code segment
}

Upvotes: 1

Views: 2568

Answers (2)

user3629249
user3629249

Reputation: 16540

the code could have the following implemented:

have the msgflg parameter contain 'IPC_NOWAIT'
then, the next line in the code should check 'errno'
for the value 'EAGIN'  
when errno is EAGIN,  either loop to recall msgrcv() or exit
    the loop due to some other criteria.
    optionally the code could nanosleep() for a while
    before jumping back to the top of the loop

extracted from the man page for msgrcv()

"EAGAIN No message was available in the queue
        and IPC_NOWAIT was  specified in msgflg."

Upvotes: 0

rivimey
rivimey

Reputation: 931

You'd do well to go look at existing software that does this; it's a very common pattern and not as simple to get right as you'd hope. However the basics are:

  • The signal handler - it won't help (as you've found)
  • check errno return from msgrcv() == EINTR; if so you have seen an interrupt.
  • be aware that if msgrcv has received some but not all of the message at the time of interrupt, you will have to clean up both your own state and the sender.
  • Finally, you may find that sometimes the signal handler is called - if the interrupt happens when the user code is running rather than a system call. And also that msgrcv is not the only system call... I did say it was complicated.

For a non-trivial program you'd be better using th 'poison' method of killing the loop. Send yourself a message using msgsend that says kill me. That way, you get predictable results.

HTH, Ruth

Upvotes: 1

Related Questions