Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c / interrupted system call / fork vs. thread

I discovered an issue with thread implementation, that is strange to me. Maybe some of you can explain it to me, would be great.

I am working on something like a proxy, a program (running on different machines) that receives packets over eth0 and sends it through ath0 (wireless) to another machine which is doing the exactly same thing. Actually I am not at all sure what is causing my problem, that's because I am new to everything, linux and c programming.

I start two threads,

  • one is listening (socket) on eth0 for incoming packets and sends it out through ath0 (also socket)
  • and the other thread is listening on ath0 and sends through eth0.

If I use threads, I get an error like that:

sh-2.05b# ./socketex 
Failed to send network header packet.
: Interrupted system call

If I use fork(), the program works as expected. Can someone explain that behaviour to me?

Just to show the sender implementation here comes its code snippet:

while(keep_going) {
    memset(&buffer[0], '\0', sizeof(buffer));

    recvlen = recvfrom(sockfd_in, buffer, BUFLEN, 0, (struct sockaddr *) &incoming, &ilen);
    if(recvlen < 0) {
        perror("something went wrong / incoming\n");
        exit(-1);
    }

    strcpy(msg, buffer);
    buflen = strlen(msg);

    sentlen = ath_sendto(sfd, &btpinfo, &addrnwh, &nwh,  buflen, msg, &selpv2, &depv);

    if(sentlen == E_ERR) {
        perror("Failed to send network header packet.\n");
        exit(-1);
    }
}

UPDATE: my main file, starting either threads or processes (fork)

int main(void) {

port_config pConfig;

memset(&pConfig, 0, sizeof(pConfig));
pConfig.inPort = 2002;
pConfig.outPort = 2003;

pid_t retval = fork();

if(retval == 0) {
    // child process
    pc2wsuThread((void *) &pConfig);
} else if (retval < 0) {
    perror("fork not successful\n");
} else {
    // parent process
    wsu2pcThread((void *) &pConfig);
}

/*
wint8 rc1, rc2 = 0;

pthread_t pc2wsu;
pthread_t wsu2pc;

rc1 = pthread_create(&pc2wsu, NULL, pc2wsuThread, (void *) &pConfig);
rc2 = pthread_create(&wsu2pc, NULL, wsu2pcThread, (void *) &pConfig);

if(rc1) {
    printf("error: pthread_create() is %d\n", rc1);
    return(-1);
}

if(rc2) {
    printf("error: pthread_create() is %d\n", rc2);
    return(-1);
}

pthread_join(pc2wsu, NULL);
pthread_join(wsu2pc, NULL);
*/
return 0;
}

Does it help?

update 05/30/2011

-sh-2.05b# ./wsuproxy 192.168.1.100
mgmtsrvc
mgmtsrvc
Failed to send network header packet.
: Interrupted system call
13.254158,75.165482,DATAAAAAAmgmtsrvc
mgmtsrvc
mgmtsrvc

Still get the interrupted system call, as you can see above. I blocked all signals as followed:

sigset_t signal_mask;
sigfillset(&signal_mask);
sigprocmask(SIG_BLOCK, &signal_mask, NULL);

The two threads are working on the same interfaces, but on different ports. The problem seems to appear still in the same place (please find it in the first code snippet). I can't go further and have not enough knowledge of how to solve that problem. Maybe some of you can help me here again.

Thanks in advance.

like image 381
nyyrikki Avatar asked Jan 19 '23 17:01

nyyrikki


2 Answers

EINTR does not itself indicate an error. It means that your process received a signal while it was in the sendto syscall, and that syscall hadn't sent any data yet (that's important).

You could retry the send in this case, but a good thing would be to figure out what signal caused the interruption. If this is reproducible, try using strace.

If you're the one sending the signal, well, you know what to do :-)

Note that on linux, you can receive EINTR on sendto (and some other functions) even if you haven't installed a handler yourself. This can happen if:

  • the process is stopped (via SIGSTOP for example) and restarted (with SIGCONT)
  • you have set a send timeout on the socket (via SO_SNDTIMEO)

    See the signal(7) man page (at the very bottom) for more details.

    So if you're "suspending" your service (or something else is), that EINTR is expected and you should restart the call.

  • like image 86
    Mat Avatar answered Jan 23 '23 13:01

    Mat


    Keep in mind if you are using threads with signals that a given signal, when delivered to the process, could be delivered to any thread whose signal mask is not blocking the signal. That means if you have blocked incoming signals in one thread, and not in another, the non-blocking thread will receive the signal, and if there is no signal handler setup for the signal, you will end-up with the default behavior of that signal for the entire process (i.e., all the threads, both signal-blocking threads and non-signal-blocking threads). For instance, if the default behavior of a signal was to terminate a process, one thread catching that signal and executing it's default behavior will terminate the entire process, for all the threads, even though some threads may have been masking the signal. Also if you have two threads that are not blocking a signal, it is not deterministic which thread will handle the signal. Therefore it's typically the case that mixing signals and threads is not a good idea, but there are exceptions to the rule.

    One thing you can try, is since the signal mask for a spawned thread is inherited from the generating thread, is to create a daemon thread for handling signals, where at the start of your program, you block all incoming signals (or at least all non-important signals), and then spawn your threads. Now those spawned threads will ignore any incoming signals in the parent-thread's blocked signal mask. If you need to handle some specific signals, you can still make those signals part of the blocked signal mask for the main process, and then spawn your threads. But when you're spawning the threads, leave one thread (could even be the main process thread after it's spawned all the worker threads) as a "daemon" thread waiting for those specific incoming (and now blocked) signals using sigwait(). That thread will then dispatch whatever functions are necessary when a given signal is received by the process. This will avoid signals from interrupting system calls in your other worker-threads, yet still allow you to handle signals.

    The reason your forked version may not be having issues is because if a signal arrives at one parent process, it is not propagated to any child processes. So I would try, if you can, to see what signal it is that is terminating your system call, and in your threaded version, block that signal, and if you need to handle it, create a daemon-thread that will handle that signal's arrival, with the rest of the threads blocking that signal.

    Finally, if you don't have access to any external libraries or debuggers, etc. to see what signals are arriving, you can setup a simple procedure for seeing what signals might be arriving. You can try this code:

    #include <signal.h>
    #include <stdio.h>
    
    int main()
    {
        //block all incoming signals
        sigset_t signal_mask;
        sigfillset(&signal_mask);
        sigprocmask(SIG_BLOCK, &signal_mask, NULL);
    
        //... spawn your threads here ...
    
        //... now wait for signals to arrive and see what comes in ...
    
        int arrived_signal;
    
        while(1) //you can change this condition to whatever to exit the loop
        {
            sigwait(&signal_mask, &arrived_signal);
    
            switch(arrived_signal)
            {
                 case SIGABRT: fprintf(stderr, "SIGABRT signal arrived\n"); break;
                 case SIGALRM: fprintf(stderr, "SIGALRM signal arrived\n"); break;
    
                 //continue for the rest of the signals defined in signal.h ...
    
                 default: fprintf(stderr, "Unrecognized signal arrived\n");
            }
        }
    
        //clean-up your threads and anything else needing clean-up
    
        return 0;
    }
    
    like image 36
    Jason Avatar answered Jan 23 '23 13:01

    Jason