Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wait for signal, then continue execution

I am trying to make a program that suspends its execution until a signal arrives. Then, after the signal arrives I just want my code to continue its execution from where it was. I don't want it to execute a function handler or whatsoever. Is there a simple way of doing this? I have been struggling for a week or so, reading here and there, and didn't manage to get a fully operative code.

In particular, I want the main program to create a thread that waits for some particular event to happen (e.g., a user has input some data to stdin). Meanwhile, the main program is doing something but at some point it suspends its execution until it receives a signal.

The signal may come from the thread because it has detected the event or it may be due to a timeout because I don't want it to wait for ever.

I have made some code but it does not work as expected...

/*
 * This code SHOULD start a thread that gets messages from stdin.
 *  If the message is a "quit", the thread exits. Otherwise it raises
 *  a signal that should be caught by the main program.
 *  The main program simply waits for the message unless a timer of
 *  5.5 seconds expires before receiving the signal from the thread.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/time.h>
#include <signal.h>

#define BSIZE 100   /* Buffer size */

sigset_t mask;              
pthread_t tid;
//struct itimerval timervalue;

int milisec = 5500; /* Timeout: 5,5 seconds */
int end = 0;

char buffer[BSIZE];


//Function prototypes
void init(void);
void * thread_job(void *);
void message_rcvd(void);
void wait_for_message_or_timeout(void);
int start_timer(struct itimerval, int);
int stop_timer(struct itimerval);
void on_signal(int);


// MAIN: Wait for message or timeout
int main(int argc, char ** argv) {

    init();

    while(!end){
        wait_for_message_or_timeout();
        if(!end)
            printf("Message received [%s]\n", buffer);
    }

    return 0;
}


// INIT: Initializes the signals that the program will wait for
//       and creates a thread that will eventually generate a signal
void init()
{

    /* Init the signals I want to wait for with sigwait() */
    sigemptyset(&mask);         
    sigaddset(&mask, SIGUSR1);  
    sigaddset(&mask, SIGALRM);
    sigprocmask(SIG_BLOCK, &mask, NULL);

    //signal(SIGUSR1, SIG_IGN);
    signal(SIGUSR1, on_signal);

    /* Create the thread and put it to work */
    pthread_t tid;
    pthread_create(&tid, NULL, thread_job, NULL);

}

void on_signal(int signum){
    printf("on_signal\n");
}

// THREAD CODE -------------
// THREAD JOB: When the user inputs a message, it passes the message
//              to the main thread by invoking message_rcvd()
void * thread_job(){

    int end = 0;

    while(!end){
        printf("Input message:");
        if (fgets(buffer, BSIZE, stdin) != NULL)
            message_rcvd();
    }
}

// MESSAGE RECEIVED: If message is not equal to "quit" raise a signal
void message_rcvd(){

    if(strcmp(buffer, "quit") == 0){
        exit(0);
    }else{
        printf("Going to raise SIGUSR1...");
        if(raise(SIGUSR1) == 0)
            printf("raised!\n");
    }

}


// WAIT: Should wait for signal SIGUSR1 for some time
void wait_for_message_or_timeout(){

    int sigid;  
    struct itimerval t;

    /* Set a timer to prevent waiting for ever*/
    printf("Setting timer...\n");
    start_timer(t, milisec);

    /* Put the process to wait until signal arrives */
    sigwait(&mask, &sigid);

    switch(sigid){
        case SIGUSR1:
                printf("Received SIGUSR1: Message avaible!\n");
                break;
        case SIGALRM:
                printf("Received SIGALRM: Timeout\n");
                end = 1;
                break;
        default:
                printf("Unknown signal received\n");
                break;
    }

    printf("Stopping timer...\n");
    /* Stop timer */
    stop_timer(t);
}

// START TIMER: I don't want the timer to cause the execution
//              of a handler function 
int start_timer(struct itimerval timervalue, int msec)
//int start_timer(int msec)
{

  timervalue.it_interval.tv_sec = msec / 1000;
  timervalue.it_interval.tv_usec = (msec % 1000) * 1000;
  timervalue.it_value.tv_sec = msec / 1000;
  timervalue.it_value.tv_usec = (msec % 1000) * 1000;

  if(setitimer(ITIMER_REAL, &timervalue, NULL))
  {
    printf("\nsetitimer() error\n");
    return(-1);
  }
  return(0);
}

// STOP TIMER: 
int stop_timer(struct itimerval timervalue)
//int stop_timer()
{
  timervalue.it_interval.tv_sec = 0;
  timervalue.it_interval.tv_usec = 0;
  timervalue.it_value.tv_sec = 0;
  timervalue.it_value.tv_usec = 0;

  if(setitimer(ITIMER_REAL, &timervalue, NULL))
  {
    printf("\nsetitimer() error\n");
    return(-1);
  }
  return(0);

}

Here is a typical execution of this code.

./signaltest 
Setting timer...
Input message:hello
Going to raise SIGUSR1...raised!
Input message:friend
Going to raise SIGUSR1...raised!
Input message:Received SIGALRM: Timeout
Stopping timer...

As you can see, the signal SIGUSR1 is being raised and sigwait is being unblocked. However, the code seems does not continue after the signal has been raised. (Note that I don't need a signal handler but I just added for the debugging purposes. I have blocked its execution with sigprocmask)

Why is SIGUSR1 unblocking sigwait but the execution does not continue from there? Is there a way to make it continue after unblocking? This seems to work for SIGALRM but why not for SIGUSR1?

As I said, I have been looking at tons of stackoverflow questions, online howto's, tried with different system calls (e.g., pause, sigsuspend), ... but couldn't find a way to solve this :-(

If you are wondering why I am not doing this code much simpler by not using a thread is because this is not actually the code I am implementing but just a simpler example to make my question more clear. I am actually trying to implement a network protocol API, similar to the sockets API for my own protocol.

Thanks in advance

like image 548
rutex Avatar asked Dec 05 '17 16:12

rutex


1 Answers

The SIGUSR1 signal isn't going where you think it is.

In a multithreaded program, the raise function sends a signal to the current thread, which is the thread_job thread in this case. So the main thread never sees the signal.

You need to save off thread ID of the main thread, then use pthread_kill to send a signal to that thread.

Add a new global:

pthread_t main_tid;

Then populate it in your init function before starting the new thread:

void init()
{
    main_tid = pthread_self();
    ...

Then in message_rcvd, use pthread_kill:

    if(pthread_kill(main_tid, SIGUSR1) == 0)
        printf("raised!\n");

Also, remove the definition of end in thread_job, and remove the definition of tid in init. These definitions mask the global variables of the same name.

Sample output:

Setting timer...
Input message:hello
Going to raise SIGUSR1...raised!
Input message:Received SIGUSR1: Message avaible!
Stopping timer...
Message received [hello
]
Setting timer...
test
Going to raise SIGUSR1...raised!
Input message:Received SIGUSR1: Message avaible!
Stopping timer...
Message received [test
]
Setting timer...
Received SIGALRM: Timeout
Stopping timer...
like image 143
dbush Avatar answered Oct 07 '22 20:10

dbush