OpenMP and exceptions

We continue developing our static analyzer VivaMP and now we would like to speak about diagnosing errors relating to using C++ exceptions in parallel regions. By a parallel region we understand a program fragment which is divided into two threads executed parallel. Parallel executed threads are formed by such OpenMP directives as for and sections.

You can use exceptions inside parallel regions. But they mustn't leave these parallel regions. Exceptions should be caught and processed inside a parallel region by using try/catch. If the exception leaves the parallel region it will cause fail and most surely program crash. Let's consider an example of incorrect code:

#pragma omp parallel for num_threads(4)
for(int i = 0; i < 4; i++)
{
//...
throw 1;
}


This code is incorrect as exceptions will leave the parallel region. To avoid this you should use other mechanisms for transferring information about error occurring. For example, the code may be rewritten as follows:

size_t errCount = 0;
#pragma omp parallel for num_threads(4) reduction(+: errCount)
for(int i = 0; i < 4; i++)
{
try {
//...
throw 1;
}
catch (...)
{
++errCount;
}
}
if (errCount != 0)
throw 1;


It looks a bit complicated but if you need to use exceptions inside parallel regions, there is no other way and you'll have to create a mechanism like this. But certainly, it's better to try to avoid exceptions.

There is some difficulty in that an exception may call not only your code in which you'll write throw. Exceptions can be generated in functions you use or memory allocating operators as well. Let's consider the following example which at first sight looks safe:

#pragma omp parallel for num_threads(4)
for(int i = 0; i < 4; i++)
{
float *ptr = new float[10000];
delete [] ptr;
}


This code can work safely for years and it can cause a program crash if at some moment 'new' operator cannot allocate the needed memory size. According to C++ standard new operator throws std::bad_alloc exception if it cannot allocate the needed memory size. This approach allows us not to check if the necessary memory size has been allocated as we do when using malloc function, and begin to work with it immediately. If memory is not allocated the program will proccess this situation in the necessary place. In case of a parallel region we need some additional work to correctly process the error of memory allocation inside the region itself. This is the corrected example:

#pragma omp parallel for num_threads(4) reduction(+: errCount)
for(int i = 0; i < 4; i++)
{
try {
float *ptr = new float[10000];
delete [] ptr;
}
catch (std::bad_alloc &)
{
//process error
}
}


I think you've already guessed that using functions inside parallel sections is also an unsafe and thankless task. Either you are sure that the functions don't generate exceptions or wrap them in try/catch. An unpleasant thing here is that if by the moment of writing parallel code the functions used in it haven't generated an exception, this can change later and you should be very careful.

Summarizing the information, we can say that exceptions are a thing which you should always keep in mind when developing a program using OpenMP. To simplify programmers' life we added three new diagnostic messages into VivaMP which will help you detect errors relating to using exceptions:

V1301. The 'throw' keyword cannot be used outside of a try..catch block in a parallel section

V1302. The 'new' operator cannot be used outside of a try..catch block in a parallel section.

V1303. The FOO function which throws an exception cannot be
used in a parallel section outside of a try..catch block.

V1301 diagnostic message indicates the error in the first example while V1302 diagnoses errors of calling 'new' operator outside the exception handler. V1303 is much more complicated. Now VivaMP will warn only about call of functions explicitly marked as throwing exceptions, i.e.:

void MyThrowFoo() throw(...) { }



Functions not marked with can also throw exceptions but they are not considered unsafe. This step is made deliberately to reduce the number of unnecessary warning messages. Otherwise any function call inside the parallel section not screened with try/catch will be unsafe. Although it is really so, it seems that there is little help from such a large number of diagnostic messages. But perhaps VivaMP analyzer will behave in this way in future in "pedantic mode". And in that case functions marked explicitly as not throwing exceptions will be considered safe:

void MyNotThrowFoo() throw() { }



The last thing I would like to say about exceptions in parallel regions is that you should be careful when using barriers. Let's consider the following example:

#pragma omp parallel num_threads (4)
{
try {
if (omp_get_thread_num () == 0) {
throw CException();
}
#pragma omp barrier
}
catch(CException &) {
}
}


When an exception is generated one of the threads will "skip" barrier directive and after that a hang will occur. The other threads will eternally wait for the thread in which the exception occurred. Perhaps, in the next version of the analyzer search of the corresponding errors will be added, but at present there are some difficulties of technical character relating to this.

Additional resources:



For more complete information about compiler optimizations, see our Optimization Notice.
Tags: