6

I'm currently writing a small shell in C++.

Jobs and the PIDs associated with them are stored within a queue of job pointers (job *). When a new job is run, information about it is added to the queue. Since multiple jobs can be handled simultaneously and new jobs can be entered at the shell's console at any time, I have a signal handler to wait on jobs which are terminated.

When a job is terminated, I need to remove it's information from the active job queue and move it to my deque of terminated jobs. However, it is possible that a user's new job is being added to the queue when another job stops.

In such a case, their insert queue operation would be suspended and my signal handler would be called, which would perform it's pop operation.

I'm trying to understand how I can resolve this potential race condition, as I imagine corruption can occur during this process. I cannot use a mutex, as a deadlock would occur if the interrupted parent process is using the queue at the time.

I see some information about C++11 being capable of atomic operations as declared by the user, along with information regarding tasklets. I'm not sure if these are relevant to my question though.

Interestingly enough, an example shell (MSH - http://code.google.com/p/mini-shell-msh/) which I am using as a reference does not appear to do any handling of such conditions. The signal handler immediately modifies the job list, along with the main console. Perhaps there is something I am overlooking here?

As always, all feedback is appriciated.

BSchlinker
  • 3,401
  • 11
  • 51
  • 82
  • Could you disable interrupts when you make the modification? Make inserting atomic? – chustar Nov 16 '11 at 03:00
  • a) What on earth is `STL::Queue`? b) Be very careful what you do in signal handlers. I think if you modify anything but `sigatomic_t` variables, you have undefined behaviour. – Kerrek SB Nov 16 '11 at 03:01
  • @chustar, Most systems which are multiprocessor will not allow disabling interrupts. Since I don't know the target environment, I can't do this. I imagine there must be a better way. – BSchlinker Nov 16 '11 at 03:03
  • @KerrekSB, I'm referring to the queue library provided within the "STL" library, which I guess is actually the std library. http://www.cplusplus.com/reference/stl/queue/ Refers to it as an STL container.. – BSchlinker Nov 16 '11 at 03:04
  • @BSchlinker: And [what did we learn about cplusplus](http://programmers.stackexchange.com/questions/88241/whats-wrong-with-cplusplus-com)? :-) – Kerrek SB Nov 16 '11 at 03:05
  • @KerrekSB. How would you recommend handling this condition then? How can I wait for these processes and update their representative data structures? – BSchlinker Nov 16 '11 at 03:06
  • @BSchlinker: Typically a signal handler only sets a flag (of the aforementioned type), and your main program loop would check for the flag. (I don't actually know if concurrent updates of a naked `sigatomic_t` are allowed. Are you multithreaded?) – Kerrek SB Nov 16 '11 at 03:10
  • @KerrekSB, I only have one main thread handling execution. Looks like I should go multithreaded, have the signal handler set a flag, and then process the update from the secondary thread. – BSchlinker Nov 16 '11 at 03:12
  • Nah, you can be totally fine if you're single-threaded. The thing is that I don't even know if any of what I said is true in a multi-threaded setting. But in a single-threaded one, you definitely want short signal handlers and flag checking in the main loop. – Kerrek SB Nov 16 '11 at 03:15
  • @KerrekSB -- That's the issue here, the main loop is the console waiting for additional input. – BSchlinker Nov 16 '11 at 03:16
  • You could switch to a `select`-based main loop... or use something like `libevent2`... or some polling-based library whose name escapes me. Not everything needs to me multi-threaded. Concurrent programming is *very* hard. – Kerrek SB Nov 16 '11 at 03:18

2 Answers2

4

You have several ways to avoid race condition.

  • Use a wait free (atomic) queue for job pointers;
  • use any other kind of queue, but protect it with sigprocmask (in the non-handler code) and with proper sa_mask value in the sigaction call;
  • don't use signal handler at all, use some non-portable system call which allows working with signals in synchronous way: in Linux it is possible with signalfd, not sure about other platforms.
Evgeny Kluev
  • 24,287
  • 7
  • 55
  • 98
1

You need to disable signals with sigprocmask() around your critical sections in the non-handler code. This is analogous to device drivers in the kernel disabling interrupts in the user half of the driver while updating structures shared with an interrupt handler.

Ben Jackson
  • 90,079
  • 9
  • 98
  • 150