SimGrid: Exception support

SimGrid 3.10 Versatile Simulation of Distributed Systems

- Home page
- Online documentation
- Dev's Corner

All Data Structures Functions Variables Typedefs Enumerations Enumerator Groups Pages
Data Structures | Macros | Enumerations | Functions
Exception support
Grounding features

A set of macros providing exception a la C++ in ANSI C (grounding feature) More...

## Data Structures
struct | xbt_ex_t
| Structure describing an exception. More...

## Macros
#define | TRY
| Introduce a block where exception may be dealed with.
#define | TRY_CLEANUP
| optional(!) block for cleanup
#define | CATCH(e)
| the block for catching (ie, deal with) an exception
#define | CATCH_ANONYMOUS
| like CATCH(e) but without argument
#define | THROW_PREPARE(_throw_ctx, c, v, m)
| Helper macro for THROW and THROWF.
#define | THROW(c, v)
| Builds and throws an exception.
#define | THROWF(c, v,...)
| Builds and throws an exception with a printf-like formatted message.
#define | RETHROW
| re-throwing of an already caught exception (ie, pass it to the upper catch block)
#define | RETHROWF(...)
| like THROWF, but adding some details to the message of an existing exception

## Enumerations
enum | xbt_errcat_t { unknown_error = 0, arg_error, bound_error, mismatch_error, not_found_error, system_error, network_error, timeout_error, cancel_error, thread_error, host_error, tracing_error, io_error }
| different kind of errors More...

## Functions
const char * | xbt_ex_catname (xbt_errcat_t cat)
| returns a short name for the given exception category
void | xbt_ex_free (xbt_ex_t e)
| Exception destructor.
void | xbt_backtrace_display_current (void)
| Shows a backtrace of the current location.
int | xbt_backtrace_no_malloc (void **bt, int size)
| reimplementation of glibc backtrace based directly on gcc library, without implicit malloc
void | xbt_backtrace_current (xbt_ex_t *e)
| Captures a backtrace for further use.
void | xbt_backtrace_display (xbt_ex_t *e)
| Display a previously captured backtrace.
int | xbt_libunwind_backtrace (void *bt[XBT_BACKTRACE_SIZE], int size)
| Get current backtrace with libunwind.

## Detailed Description

A set of macros providing exception a la C++ in ANSI C (grounding feature)

This module is a small ISO-C++ style exception handling library for use in the ISO-C language. It allows you to use the paradigm of throwing and catching exceptions in order to reduce the amount of error handling code without hindering program robustness.

This is achieved by directly transferring exceptional return codes (and the program control flow) from the location where the exception is raised (throw point) to the location where it is handled (catch point) – usually from a deeply nested sub-routine to a parent routine. All intermediate routines no longer have to make sure that the exceptional return codes from sub-routines are correctly passed back to the parent.

These features are brought to you by a modified version of the libex library, one of the numerous masterpiece of Ralf S. Engelschall.

# DESCRIPTION

In SimGrid, an exception is a triple <msg , category , value> where msg is a human-readable text describing the exceptional condition, code an integer describing what went wrong and value providing a sort of sub-category. (this is different in the original libex).

# BASIC USAGE

TRY TRIED_BLOCK [TRY_CLEANUP CLEANUP_BLOCK] CATCH (variable) CATCH_BLOCK

This is the primary syntactical construct provided. It is modeled after the ISO-C++ try-catch clause and should sound familiar to most of you.

Any exception thrown directly from the TRIED_BLOCK block or from called subroutines is caught. Cleanups which must be done after this block (whenever an exception arose or not) should be placed into the optionnal CLEANUP_BLOCK. The code dealing with the exceptions when they arise should be placed into the (mandatory) CATCH_BLOCK.

In absence of exception, the control flow goes into the blocks TRIED_BLOCK and CLEANUP_BLOCK (if present); The CATCH_BLOCK block is then ignored.

When an exception is thrown, the control flow goes through the following blocks: TRIED_BLOCK (up to the statement throwing the exception), CLEANUP_BLOCK (if any) and CATCH_BLOCK. The exception is stored in a variable for inspection inside the CATCH_BLOCK. This variable must be declared in the outter scope, but its value is only valid within the CATCH_BLOCK block.

Some notes:

- TRY, CLEANUP and CATCH cannot be used separately, they work only in combination and form a language clause as a whole.
- In contrast to the syntax of other languages (such as C++ or Jave) there is only one CATCH block and not multiple ones (all exceptions are of the same xbt_ex_t C type).
- the variable of CATCH can naturally be reused in subsequent CATCH clauses.
- it is possible to nest TRY clauses.

The TRY block is a regular ISO-C language statement block, but

it is not allowed to jump into it via "goto" or longjmp(3) or out of it via "break", "return", "goto" or longjmp(3).

This is because there is some hidden setup and cleanup that needs to be done regardless of whether an exception is caught. Bypassing these steps will break the exception handling facility. The symptom are likely to be a segfault at the next exception raising point, ie far away from the point where you did the mistake. If you suspect that kind of error in your code, have a look at the little script `tools/xbt_exception_checker` in the CVS. It extracts all the TRY blocks from a set of C files you give it and display them (and only them) on the standard output. You can then grep for the forbidden keywords on that output.

The CLEANUP and CATCH blocks are regular ISO-C language statement blocks without any restrictions. You are even allowed to throw (and, in the CATCH block, to re-throw) exceptions.

There is one subtle detail you should remember about TRY blocks: Variables used in the CLEANUP or CATCH clauses must be declared with the storage class "volatile", otherwise they might contain outdated information if an exception is thrown.

This is because you usually do not know which commands in the TRY were already successful before the exception was thrown (logically speaking) and because the underlying ISO-C setjmp(3) facility applies those restrictions (technically speaking). As a matter of fact, value changes between the TRY and the THROW may be discarded if you forget the "volatile" keyword.

# PROGRAMMING PITFALLS

Exception handling is a very elegant and efficient way of dealing with exceptional situation. Nevertheless it requires additional discipline in programming and there are a few pitfalls one must be aware of. Look the following code which shows some pitfalls and contains many errors (assuming a mallocex() function which throws an exception if malloc(3) fails):

/* BAD_EXAMPLE */
TRY {
char *cp1, *cp2, *cp3;
cp1 = mallocex(SMALLAMOUNT);
globalcontext->first = cp1;
cp2 = mallocex(TOOBIG);
cp3 = mallocex(SMALLAMOUNT);
strcpy(cp1, "foo");
strcpy(cp2, "bar");
}
TRY_CLEANUP {
free(cp3);
free(cp2);
free(cp1);
}
CATCH_ANONYMOUS {
printf("cp3=%s", cp3);
RETHROW;
}
/* end_of_bad_example */

This example raises a few issues:

1. variable scope
   Variables which are used in the CLEANUP or CATCH clauses must be declared before the TRY clause, otherwise they only exist inside the TRY block. In the example above, cp1, cp2 and cp3 only exist in the TRY block and are invisible from the CLEANUP and CATCH blocks.
2. variable initialization
   Variables which are used in the CLEANUP or CATCH clauses must be initialized before the point of the first possible THROW is reached. In the example above, CLEANUP would have trouble using cp3 if mallocex() throws a exception when allocating a TOOBIG buffer.
3. volatile variable
   Variables which are used in the CLEANUP or CATCH clauses MUST BE DECLARED AS "volatile", otherwise they might contain outdated information when an exception is thrown.
4. clean before catch
   The CLEANUP clause is not only place before the CATCH clause in the source code, it also occures before in the control flow. So, resources being cleaned up cannot be used in the CATCH block. In the example, c3 gets freed before the printf placed in CATCH.
5. variable uninitialization
   If resources are passed out of the scope of the TRY/CLEANUP/CATCH construct, they naturally shouldn't get cleaned up. The example above does free(3) cp1 in CLEANUP although its value was affected to globalcontext->first, invalidating this pointer.

The following is fixed version of the code (annotated with the pitfall items for reference):

/* GOOD_EXAMPLE */
{ /*01 */
char *volatile /*03 */ cp1 = NULL /*02 */ ;
char *volatile /*03 */ cp2 = NULL /*02 */ ;
char *volatile /*03 */ cp3 = NULL /*02 */ ;
TRY {
cp1 = mallocex(SMALLAMOUNT);
global_context->first = cp1;
cp1 = NULL /*05 give away */ ;
cp2 = mallocex(TOOBIG);
cp3 = mallocex(SMALLAMOUNT);
strcpy(cp1, "foo");
strcpy(cp2, "bar");
}
TRY_CLEANUP { /*04 */
printf("cp3=%s", cp3 == NULL /*02 */ ? "" : cp3);
free(cp3);
free(cp2);
/*05 cp1 was given away */
}
CATCH_ANONYMOUS {
/*05 global context untouched */
RETHROW;
}
}
/* end_of_good_example */

## Macro Definition Documentation

#define CATCH_ANONYMOUS

like CATCH(e) but without argument

Useful if you only want to rethrow the exception caught, and do not want to bother with an unused variable.

#define THROW_PREPARE | ( |  | _throw_ctx,
|  |  | c,
|  |  | v,
|  |  | m
| ) |  |

Helper macro for THROW and THROWF.

Parameters

  _throw_ctx,: | the throwing context in which we should construct the exception
  c,: | category code (integer)
  v,: | value (integer)
  m,: | message text

If called from within a TRY/CATCH construct, this exception is copied into the CATCH relevant variable program control flow is derouted to the CATCH (after the optional sg_cleanup).

If no TRY/CATCH construct embeds this call, the program calls abort(3).

The THROW can be performed everywhere, including inside TRY, CLEANUP and CATCH blocks.

## Enumeration Type Documentation

enum xbt_errcat_t

different kind of errors

Enumerator:

  unknown_error | unknown error
  arg_error | Invalid argument
  bound_error | Out of bounds argument
  mismatch_error | The provided ID does not match
  not_found_error | The searched element was not found
  system_error | a syscall did fail
  network_error | error while sending/receiving data
  timeout_error | not quick enough, dude
  cancel_error | an action was canceled
  thread_error | error while [un]locking
  host_error | host failed
  tracing_error | error during the simulation tracing
  io_error | disk or file error

## Function Documentation

int xbt_backtrace_no_malloc | ( | void ** | array,
|  | int | size
| ) |  |

reimplementation of glibc backtrace based directly on gcc library, without implicit malloc

See http://webloria.loria.fr/~quinson/blog/2012/0208/system_programming_fun_in_SimGrid/ for the motivation behind this function