Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Signalling all threads in a process

Without keeping a list of current threads, I'm trying to see that a realtime signal gets delivered to all threads in my process. My idea is to go about it like this:

  • Initially the signal handler is installed and the signal is unblocked in all threads.
  • When one thread wants to send the 'broadcast' signal, it acquires a mutex and sets a global flag that the broadcast is taking place.
  • The sender blocks the signal (using pthread_sigmask) for itself, and enters a loop repeatedly calling raise(sig) until sigpending indicates that the signal is pending (there were no threads remaining with the signal blocked).
  • As threads receive the signal, they act on it but wait in the signal handler for the broadcast flag to be cleared, so that the signal will remain masked.
  • The sender finishes the loop by unblocking the signal (in order to get its own delivery).
  • When the sender handles its own signal, it clears the global flag so that all the other threads can continue with their business.

The problem I'm running into is that pthread_sigmask is not being respected. Everything works right if I run the test program under strace (presumably due to different scheduling timing), but as soon as I run it alone, the sender receives its own signal (despite having blocked it..?) and none of the other threads ever get scheduled.

Any ideas what might be wrong? I've tried using sigqueue instead of raise, probing the signal mask, adding sleep all over the place to make sure the threads are patiently waiting for their signals, etc. and now I'm at a loss.

Edit: Thanks to psmears' answer, I think I understand the problem. Here's a potential solution. Feedback would be great:

  • At any given time, I can know the number of threads running, and I can prevent all thread creation and exiting during the broadcast signal if I need to.
  • The thread that wants to do the broadcast signal acquires a lock (so no other thread can do it at the same time), then blocks the signal for itself, and sends num_threads signals to the process, then unblocks the signal for itself.
  • The signal handler atomically increments a counter, and each instance of the signal handler waits until that counter is equal to num_threads to return.
  • The thread that did the broadcast also waits for the counter to reach num_threads, then it releases the lock.

One possible concern is that the signals will not get queued if the kernel is out of memory (Linux seems to have that issue). Do you know if sigqueue reliably informs the caller when it's unable to queue the signal (in which case I would loop until it succeeds), or could signals possibly be silently lost?

Edit 2: It seems to be working now. According to the documentation for sigqueue, it returns EAGAIN if it fails to queue the signal. But for robustness, I decided to just keep calling sigqueue until num_threads-1 signal handlers are running, interleaving calls to sched_yield after I've sent num_threads-1 signals.

There was a race condition at thread creation time, counting new threads, but I solved it with a strange (ab)use of read-write locks. Thread creation is "reading" and the broadcast signal is "writing", so unless there's a thread trying to broadcast, it doesn't create any contention at thread-creation.

like image 416
R.. GitHub STOP HELPING ICE Avatar asked Nov 17 '10 14:11

R.. GitHub STOP HELPING ICE


People also ask

How do you signal between threads?

The common ways of signalling are through WaitHandle and Monitor , that can coordinate between threads by notifying (signal) them when to go ahead and when to halt. All the EventWaitHandle mentioned below are basically WaitHandle objects that can signal and wait for signals.

Where should a signal be delivered for multi threaded processes?

A process-directed signal may be delivered to any one of the threads that does not currently have the signal blocked. If more than one of the threads has the signal unblocked, then the kernel chooses an arbitrary thread to which to deliver the signal.

Are signals per thread or per process?

signal is a per process call, not a per thread one, if you call it it sets the handler for all threads in the process. Signals and threads is a complex topic. You might be better off asking about what you really what to do. Using signal in a multithreaded program is essentially undefined behaviour.

Are signals shared between threads?

Signals sent to any thread are considered as signal sent to the main process. Thus, if a thread gets a signal, it is quite possible that an other thread will execute the handler.


1 Answers

raise() sends the signal to the current thread (only), so other threads won't receive it. I suspect that the fact that strace makes things work is a bug in strace (due to the way it works it ends up intercepting all signals sent to the process and re-raising them, so it may be re-raising them in the wrong way...).

You can probably get round that using kill(getpid(), <signal>) to send the signal to the current process as a whole.

However, another potential issue you might see is that sigpending() can indicate that the signal is pending on the process before all threads have received it - all that means is that there is at least one such signal pending for the process, and no CPU has yet become available to run a thread to deliver it...

Can you describe more details of what you're aiming to achieve? And how portable you want it to be? There's almost certainly a better way of doing it (signals are almost always a major headache, especially when mixed with threads...)

like image 119
psmears Avatar answered Oct 20 '22 16:10

psmears