msgrcv - SA_RESTART flag doesn't work

2019-07-07 22:41发布

问题:

There is something wrong with my code that uses IPC queue to communicate between threads. I need to handle SIGINT safely - let program finish all active threads when SIGINT appeared before shutting down. Though, I have serious problem of finding solution because even with sigaction for SIGINT with flag SA_RESTART the msgrcv function is getting EINTR error.

My question is - is there any way to avoid EINTR with msgrcv function other than specifying the error condition in some "if" such as:

if (msgrcv<0){
  if (errno == EINTR){
    errno = 0;
  }
}

Here is really simplified version of my program to demonstate problem :

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <sys/msg.h>

#define IPC_KEY 11000
#define BUF_SIZE 128

//structure of IPC message
typedef struct msgbuf{
  long mtype;
  char mtext[BUF_SIZE];
} message_buf;


void handle_sigint(int sig){
  signal(SIGINT,SIG_IGN);
  /*
    some operation to handle sigint,
    here it's really simple setting 
    SIGINT to be ignored
  */ 

}

int main(){

  long ipc_id;
  message_buf msg;
  struct sigaction setup_action;
  sigset_t block_mask;

  //setup sigaction for SIGINT
  sigemptyset(&block_mask);
  sigaddset(&block_mask,SIGINT);
  setup_action.sa_handler = handle_sigint;
  setup_action.sa_mask = block_mask;
  setup_action.sa_flags = SA_RESTART;
  if (sigaction(SIGINT, &setup_action, 0) < 0){
    perror("sigaction");
    exit(1);
  }

  //creating the ipc queue
  if ((ipc_id = msgget(IPC_KEY, IPC_CREAT | IPC_EXCL | 0666))<0){
    perror("error in msgget");
    exit(1);
  }

  for(;;){
    if (msgrcv(ipc_id,&msg,BUF_SIZE,1,0)<0){
      perror("error in msgrcv");
      exit(1);
    }
    printf("received message : %s\n",msg.mtext);
  }

}

And here is simple program to clean up the IPC queue :

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <sys/msg.h>

#define IPC_KEY 11000


int main(){

  long ipc_id;

  if ((ipc_id = msgget(IPC_KEY, IPC_CREAT | 0666 )) < 0) {
    perror("error in msgget");
    exit(1);
  }

  if (msgctl(ipc_id, IPC_RMID, 0) != 0){
    perror("error in msgctl");
    exit(1);        
  }


  return 0;

}

Thanks in advance for help! I really hope I didn't make duplicate question but I tried to look for a while for a solution and unfortunately didn't find any other than explicitly catching EINTR errno with if function.

回答1:

From the (Linux) manual:

The following interfaces are never restarted after being interrupted by a signal handler, regardless of the use of SA_RESTART; they always fail with the error EINTR when interrupted by a signal handler:

....

  • System V IPC interfaces: msgrcv(2), msgsnd(2), semop(2), and semtimedop(2).

The way SA_RESTART is handled is a bit implementation defined. You didn't tag with a specific Unix flavor but I assume your Unix simply doesn't obey SA_RESTART for your specific system call.



回答2:

@cnicutar beat me to that by 10 seconds (so +1), but I'd add that all you need to do is to wrap the call to msgrcv in a do/while loop, e.g.

int res;
do
{
  res = msgrcv(your parameters here);
} while ((res < 0 ) && (errno == EINTR));

You can of course define a tiny function to do that for you if you use msgrcv a lot.



标签: c signals ipc