pahpaul
pahpaul

Reputation: 25

Handling 'intterupted system call' error when using timer

I'm writing an application that uses timer to do some data acquisition and processing at a fix sample rate (200Hz). The application acts like a server and run in background. It should be controllable from other processes or other machines from UDP.

To do so, I use the timer_create() API to generate SIGUSR1 periodically and call an handler that do the acquisition and the processing.

The code to configure the timer is as follow (minus error check for clarity):

sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGUSR1, &sa, NULL);
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGUSR1;
sev.sigev_value.sival_ptr = &timerid;
timer_create(CLOCK_REALTIME, &sev, &timerid);
timer_settime(...)

The code above is called when a 'start' command is received from UDP. To check for command I have an infinite loop in my main program that call recvfrom() syscall.

The problem is, when a 'start' command is received, and then, the timer is properly started and running (using the code above), I get an 'interrupted system calls' error (EINTR) due the SIGUSR1 signal sent by the timer interrupting the recvfrom() call. If I check for this particular error code and ignore it, I finally get a 'connection refused' error when calling recvfrom().

So here my questions:

  1. How to solve this 'interrupted system calls' error as it seems to ignore it and re-do the recvfrom() doesn't work?
  2. Why do I get the 'connection refused' error after about twenty tries?
  3. I have the feeling that using SIGEV_THREAD could be a solution, as I understand it, create a new thread (like phread_create) without generate a signal. Am I right?
  4. Is the signal number important here? Is there any plus to use real time signal?
  5. Is there any other way to do what I intent to do: having a background loop checking for command from UDP and real-time periodic task?

And here the bonus question:

Solution: As suggest in an answer and in the comments, using SA_RESTART seems to fix the main issue.

Solution 2: Using SIGEV_THREAD over SIGEV_SIGNAL works too. I've read somewhere that using SIGEV_THREAD could require more ressources than SIGEV_SIGNAL. However I have not seen significant difference regarding the timing of the task.

Upvotes: 1

Views: 3760

Answers (4)

slowjelj
slowjelj

Reputation: 702

Question 5: Your use of a message and real-time periodic thread is perfectly fine. However, I would suggest you avoid using timers altogether, precisely because they use signals. I've run into this problem myself and eventually replaced the timer with a simple clock_nanosleep() that uses TIMER_ABSTIME with time updated to maintain the desired rate (i.e. add the period to the absolute time). The result was simpler code, no more problems with signals, and a more accurate timer than the signal-based timer. BTW, you should measure your timer's period in the handler to make sure it is accurate enough. My experience with timers was 8 years ago, so the problem with accuracy might be fixed. However, the other problems with signals are inherent to signals themselves and thus can't be "solved" -- only worked around.

Also, I see no problem with doing data acquisition from the handler, it should certainly reduce latency in retrieving the data.

Upvotes: 0

caf
caf

Reputation: 239041

Restarting the interrupted system call is the correct response to EINTR. You "Connection Refused" problem is an unrelated error - on a UDP socket, it indicates that a previous packet sent on that socket was rejected by the destination (notified through an ICMP message).

Upvotes: 0

user1277476
user1277476

Reputation: 2909

Timers tend to be implemented using SIGALARM.

Signal receipt, including SIGALARM, tends to cause long running system calls to return early with EINTR in errno.

SA_RESTART is one way around this, so system calls interrupted by receipt of a signal, will be automatically restarted. Another is to check for EINTR from your system calls' errno's and restart them when you receive EINTR.

With read() and write() of course, you can't just restart, you need to pick up where you left off. That's why these return the length of data transmitted.

Upvotes: 1

Giel
Giel

Reputation: 2875

Given that you're using Linux, I would opt for using timerfd_create instead.

That way you can just select(2), poll(2) or epoll(7) instead and handle timer events without the difficulty of signal handlers in your main loop.

As for EINTR (Interrupted System Call), those are properly handled by just restarting the specific system call that got interrupted.

Upvotes: 1

Related Questions