4

My program needs to perform read-only access to the contents of a vector<string> in a signal handler for SIGINT. (The alternative is to use a fixed-size array of fixed-length C strings.) The program is designed to run in a POSIX environment.

Are vector::operator[] and vector::size() asynchronous-safe (or signal-safe)?

bwDraco
  • 2,514
  • 2
  • 33
  • 38

3 Answers3

5

No, it's not safe. C++11 1.9/6:

When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects which are neither

  • of type volatile std::sig_atomic_t nor
  • lock-free atomic objects (29.4)

are unspecified during the execution of the signal handler, and the value of any object not in either of these two categories that is modified by the handler becomes undefined.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Oh, well. Seems *any* access to a vector is not signal-safe. As much as I hate using a fixed-size array, I guess I'll have to use one... – bwDraco Oct 07 '14 at 18:17
  • 2
    @DragonLord Not sure about the conclusions - the standard reading implies much more: unless your array stores objects of `volatile std::sig_atomic_t`, you can't use it anyway. – milleniumbug Oct 07 '14 at 18:40
  • @milleniumbug are null-terminated C strings okay? This code is designed to run in a POSIX environment. – bwDraco Oct 07 '14 at 18:41
  • @DragonLord: Are your C strings of type `volatile std::sig_atomic_t` or lock free atomic objects? Nope. :) – Billy ONeal Oct 07 '14 at 18:43
  • 1
    @DragonLord "This code is designed to run in a POSIX environment." In that case you can assume what POSIX allows you to. I'll try to add my answer about POSIX – milleniumbug Oct 07 '14 at 18:45
  • @DragonLord: A constant pointer to a character string in read-only memory is a lock free atomic object and you can use it in a write() function for example. Do *not* use C functions like printf in a signal handler. – Zan Lynx Oct 07 '14 at 22:10
  • @ZanLynx 29.4 does not list a "constant pointer to a character string in read-only memory" as a lock-free atomic object. Can you provide a standard quote that makes it one? – Angew is no longer proud of SO Oct 08 '14 at 06:28
  • @ZanLynx 29.4 taxatively lists lock-free atomic objects, and string literals (or their equivalent) are not among them. The standard does not explicitly say that "a string literal is not a lock-free atomic object," but neither does it explicitly say that "`std::vector` is not a lock-free atomic object." They get the same treatment regarding being a lock-free atomic object. – Angew is no longer proud of SO Oct 10 '14 at 16:35
  • @Angew: The char* itself is an atomic object. Each byte of the pointed-to character string is also atomic. The lock-free property comes because both are read-only. I can't dig into the standard right at the moment but I am certain read-only data is always lock free. – Zan Lynx Oct 10 '14 at 16:37
  • @ZanLynx I am pretty certain the standard does not require `char*` to be atomic. It certainly doesn't have atomic semantics in the sense of thread-synchronisation "handshake." – Angew is no longer proud of SO Oct 10 '14 at 16:53
  • @Angew: The standard DOES provide methods to discover if a pointer is atomic. And as you'll find if you check it, on most architectures where lock-free programming is even *possible* a pointer is atomic – Zan Lynx Oct 10 '14 at 17:14
  • @ZanLynx What methods are those? I see ways to determine whether `atomic` is lock-free for a given `T` (e.g. `atomic_is_lock_free`), but not to determine if a given type `T` is atomic. – Angew is no longer proud of SO Oct 10 '14 at 18:53
3

Angew's answer is correct considering C++. Now that the question mentions POSIX environment, which could provide stronger guarantees, this needs another answer, which is:

If the process is multi-threaded, or if the process is single-threaded and a signal handler is executed other than as the result of:

  • The process calling abort(), raise(), kill(), pthread_kill(), or sigqueue() to generate a signal that is not blocked

  • A pending signal being unblocked and being delivered before the call that unblocked it returns

the behavior is undefined if the signal handler refers to any object other than errno with static storage duration other than by assigning a value to an object declared as volatile sig_atomic_t, or if the signal handler calls any function defined in this standard other than one of the functions listed in the following table.

Source: The Open Group Base Specifications Issue 7 IEEE Std 1003.1, 2013 Edition, 2.4.3

This is... still a very weak guarantee. As far as I can understand this:

vector::operator[] is not safe. Fixed arrays are not safe. Access to fixed arrays is safe if the array is non-static.

Why? vector::operator[] doesn't specify exactly how it should be implemented, only the preconditions and postconditions. The access to elements of an array is possible (if the array is non-static), this implies that the access to vector elements is also safe if you create a pointer (with vec.data() or &vec[0]) before signalling, and then accessing the elements through the pointer.

EDIT: Originally I missed that because I wasn't aware of the sigaction function - with signal you could only access your local arrays in the signal handler, but with sigaction you can provide pointers to automatic and dynamically arrays. The advice with doing as little as possible in signal handlers still applies here though.

Bottom line: You're doing too much in your signal handlers. Try doing as little as possible. One approach is to assign to a flag (of type volatile sig_atomic_t), and return. The code can later check if the flag was triggered (e.g. in an event loop)

Community
  • 1
  • 1
milleniumbug
  • 15,379
  • 3
  • 47
  • 71
  • Fixed arrays not being safe would be rather odd, considering the list of system calls that are explicitly allowed. – Zan Lynx Oct 10 '14 at 16:39
1

I believe that if you know the reason that access to a vector is not safe then you can work around it. Note that access still isn't guaranteed safe. But it will work on anything that isn't a Death Station 9000.

A signal handler interrupts the execution of the program much like a interrupt handler would if you were programming directly to the hardware. The operating system simply stops executing your program, wherever it happens to be. This might be in the middle of anything. For example, if your vector has elements being added to it and it is updating its size value or it is copying the contents to a new, longer vector, that might be interrupted by the signal. And then your signal handler would try to read from the vector resulting in disaster.

You can access the vector from the signal handler as long as it is effectively constant. If you set up the whole thing at program start and then never write to it again, it is safe to use. Note, not safe to use according to the standards documents, but effectively safe.

This is a lot like multi-threading on a single-core CPU.

Now, if you do need to update the vector while the program is running you need to "lock" the signal handler away from it by masking the signal or disabling the handler before updating the vector, to ensure that the handler won't run while the vector is in an inconsistent state.

Zan Lynx
  • 53,022
  • 10
  • 79
  • 131
  • Well, I understand that the underlying cause is a race condition where the signal handler could end up reading the vector while in an inconsistent state. I was thinking about how I could make the updates atomic so that this wouldn't happen... – bwDraco Oct 08 '14 at 00:02