ossp-pkg/ex/ex.pod
##
## OSSP ex - Exception Handling
## Copyright (c) 2002-2007 Ralf S. Engelschall <rse@engelschall.com>
## Copyright (c) 2002-2007 The OSSP Project <http://www.ossp.org/>
##
## This file is part of OSSP ex, an exception library
## which can be found at http://www.ossp.org/pkg/lib/ex/.
##
## Permission to use, copy, modify, and distribute this software for
## any purpose with or without fee is hereby granted, provided that
## the above copyright notice and this permission notice appear in all
## copies.
##
## THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
## WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
## IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
## USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
## OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
## OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
## SUCH DAMAGE.
##
## ex.pod: exception library manual page
##
=pod
=head1 NAME
B<OSSP ex> - Exception Handling
=head1 VERSION
B<OSSP ex EX_VERSION_STR>
=head1 SYNOPSIS
B<ex_t> I<variable>;
B<ex_try> I<BLOCK1> [B<ex_cleanup> I<BLOCK2>] B<ex_catch> (I<variable>) I<BLOCK3>
B<ex_throw>(I<class>, I<object>, I<value>);
B<ex_rethrow>;
B<ex_defer> I<BLOCK>
B<ex_shield> I<BLOCK>
if (B<ex_catching>) ...
if (B<ex_deferred>) ...
if (B<ex_shielding>) ...
=head1 DESCRIPTION
B<OSSP ex> is a small B<ISO-C++> style exception handling library for
use in the B<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 making your program less robust.
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.
=head2 EXCEPTIONS
An B<OSSP ex> exception is a triple
E<lt>I<class>,I<object>,I<value>E<gt> where I<class> identifies the
class of the exception thrower, I<object> identifies the particular
class instance of the exception thrower, and I<value> is the exceptional
return code value the thrower wants to communicate. All three parts are
of type "C<void *>" internally, but every value which can be lossless
"casted" to this type is usable. Exceptions are created on-the-fly by
the B<ex_throw> command.
=head2 APPLICATION PROGRAMMER INTERFACE (API)
The B<OSSP ex> API consists of the following elements:
=over 4
=item B<ex_t> I<variable>;
This is the declaration of an exception variable. It is usually never
initialized manually. Instead it is initialized by an B<ex_catch> clause
and just used read-only inside its block. Such a variable of type
B<ex_t> consists of six attributes:
=over 2
=item C<void *>I<ex_class>
This is the I<class> argument of the B<ex_throw> call which created
the exception. This can globally and uniquely identify the class to
which I<ex_value> belongs to. Usually this is a pointer to a static
object (variable, structure or function) which identifies the class of
the thrower and allows the catcher to correctly handle I<ex_value>. It
is usually just an additional (optional) information to I<ex_value>.
=item C<void *>I<ex_object>
This is the I<object> argument of the B<ex_throw> call which created the
exception. This can globally and uniquely identify the class instance
I<ex_value> belongs to (in case multiple instances exists at all).
Usually this a pointer to a dynamic object (structure) which identifiers
the particular instance of the thrower. It is usually just an additional
(optional) information to I<ex_value>.
=item C<void *>I<ex_value>
This is the I<value> argument of the B<ex_throw> call which created
the exception. This is the exceptional return code value which has to
uniquely identify the type of exception. Usually this is the value which
is C<return>ed if no exceptions would be thrown. In the simple case
this is just a numerical return code. In the complex case this can be a
pointer to an arbitrary complex data structure describing the exception.
=item C<char *>I<ex_file>
This is the file name of the B<ISO-C> source where the B<ex_throw> call
was performed. It is automatically provided as an additional information
about the throw point and is intended mainly for tracing and debugging
purposes.
=item C<int> I<ex_line>
This is the line number inside the B<ISO-C> source file name where the
B<ex_throw> call was performed. It is automatically provided as an
additional information about the throw point and is intended mainly for
tracing and debugging purposes.
=item C<char *>I<ex_func>
This is the function name (if determinable, else "C<#NA#>") inside the
B<ISO-C> source file name where the B<ex_throw> call was performed. It
is automatically provided as an additional information about the throw
point and is intended mainly for tracing and debugging purposes.
=back
=item B<ex_try> I<BLOCK1> [B<ex_cleanup> I<BLOCK2>] B<ex_catch> (I<variable>) I<BLOCK3>
This is the primary syntactical construct provided by B<OSSP ex>. It
is modeled after the B<ISO-C++> C<try>-C<catch> clause which in turn
is very similar to an B<ISO-C> C<if>-C<else> clause. It consists of an
B<ex_try> block I<BLOCK1> which forms the dynamic scope for exception
handling (i.e. exceptions directly thrown there or thrown from its
sub-routines are caught), an optional B<ex_cleanup> block I<BLOCK2> for
performing cleanup operations and an B<ex_catch> block I<BLOCK3> where
the caught exceptions are handled.
The control flow in case no exception is thrown is simply I<BLOCK1>,
optionally followed by I<BLOCK2>; I<BLOCK3> is skipped. The control
flow in case an exception is thrown is: I<BLOCK1> (up to the statement
where the exception is thrown only), optionally followed by I<BLOCK2>,
followed by I<BLOCK3>.
The B<ex_try>, B<ex_cleanup> and B<ex_catch> cannot be used separately,
they work only in combination because they form a language clause
as a whole. In contrast to B<ISO-C++> there is only one B<ex_catch>
block and not multiple ones (all B<OSSP ex> exceptions are of the same
B<ISO-C> type B<ex_t>). If an exception is caught, it is stored in
I<variable> for inspection inside the B<ex_catch> block. Although having
to be declared outside, the I<variable> value is only valid within
the B<ex_catch> block. But the variable can be re-used in subsequent
B<ex_catch> clauses, of course.
The B<ex_try> block is a regular B<ISO-C> language statement block, but
it is not allowed to jump into it via "C<goto>" or C<longjmp>(3) or out
of it via "C<break>", "C<return>", "C<goto>" or C<longjmp>(3) because there
is some hidden setup and cleanup that needs to be done by B<OSSP ex>
regardless of whether an exception is caught. Jumping into an B<ex_try>
clause would avoid doing the setup, and jumping out of it would avoid
doing the cleanup. In both cases the result is a broken exception
handling facility. Nevertheless you are allowed to nest B<ex_try>
clauses.
The B<ex_cleanup> and B<ex_catch> blocks are regular B<ISO-C> language
statement blocks without any restrictions. You are even allowed to throw
(and in the B<ex_catch> block to re-throw) an exception.
There is just one subtle portability detail you have to remember about
B<ex_try> blocks: all accessible B<ISO-C> objects have the (expected)
values as of the time B<ex_throw> was called, except that the values
of objects of automatic storage invocation duration that do not have
the "C<volatile>" storage class I<and> have been changed between the
B<ex_try> invocation and B<ex_throw> are indeterminate. This is because
both you usually do not know which commands in the B<ex_try> were
already successful before the exception was thrown (logically speaking)
and because the underlying B<ISO-C> setjmp(3) facility applies those
restrictions (technically speaking).
=item B<ex_throw>(I<class>, I<object>, I<value>);
This builds an exception from the supplied arguments and throws it.
If an B<ex_try>/B<ex_catch> clause formed the dynamic scope of the
B<ex_throw> call, this exception is copied into the I<variable> of
its B<ex_catch> clause and the program control flow is continued in
the (optional B<ex_cleanup> and then in the) B<ex_catch> block. If
no B<ex_try>/B<ex_catch> clause exists in the dynamic scope of the
B<ex_throw> call, the program calls C<abort>(3). The B<ex_throw> can
be performed everywhere, including inside B<ex_try>, B<ex_cleanup> and
B<ex_catch> blocks.
=item B<ex_rethrow>;
This is only valid within an B<ex_catch> block and re-throws
the current exception (in I<variable>). It is similar to the
call B<ex_throw>(I<variable>.ex_class, I<variable>.ex_object,
I<variable>.ex_value) except for the difference that the C<ex_file>,
C<ex_line> and C<ex_func> elements of the caught exception are passed
through as it would have been never caught.
=item B<ex_defer> I<BLOCK>
This directive executes I<BLOCK> while deferring the throwing of
exceptions, i.e., inside the dynamic scope of B<ex_defer> all
B<ex_throw> operations are remembered but deferred and on leaving the
I<BLOCK> the I<first> occurred exception is thrown. The second and
subsequent exceptions are ignored.
The B<ex_defer> block I<BLOCK> is a regular B<ISO-C> language statement
block, but it is not allowed to jump into it via "C<goto>" or
C<longjmp>(3) or out of it via "C<break>", "C<return>", "C<goto>" or
C<longjmp>(3) because this would cause the deferral level to become out
of sync. Jumping into an B<ex_defer> clause would avoid increasing the
exception deferral level, and jumping out of it would avoid decreasing
it. In both cases the result is an incorrect exception deferral level.
Nevertheless you are allowed to nest B<ex_defer> clauses.
=item B<ex_shield> I<BLOCK>
This directive executes I<BLOCK> while shielding it against the throwing
of exceptions, i.e., inside the dynamic scope of B<ex_shield> all
B<ex_throw> operations are just silently ignored.
The B<ex_shield> block is a regular B<ISO-C> language statement block,
but it is not allowed to jump into it via "C<goto>" or C<longjmp>(3)
or out of it via "C<break>", "C<return>", "C<goto>" or C<longjmp>(3)
because this would cause the shielding level to become out of sync.
Jumping into an B<ex_shield> clause would avoid increasing the exception
shielding level, and jumping out of it would avoid decreasing it.
In both cases the result is an incorrect exception shielding level.
Nevertheless you are allowed to nest B<ex_shield> clauses.
=item B<ex_catching>
This is a boolean flag which can be checked inside the dynamic scope
of an B<ex_try> clause to test whether the current scope is exception
catching (see B<ex_try>/B<ex_catch> clause).
=item B<ex_deferred>
This is a boolean flag which can be checked inside the dynamic scope of
an B<ex_defer> clause to test whether the current scope is exception
deferring (see B<ex_defer> clause).
=item B<ex_shielding>
This is a boolean flag which can be checked inside the dynamic scope of
an B<ex_shield> clause to test whether the current scope is exception
shielding (see B<ex_shield> clause).
=back
=head1 IMPLEMENTATION CONTROL
B<OSSP ex> uses a very light-weight but still flexible exception
facility implementation. The following adjustments can be made before
including the F<ex.h> header:
=head2 Machine Context
In order to move the program control flow from the exception throw point
(B<ex_throw>) to the catch point (B<ex_catch>), B<OSSP ex> uses four
macros:
=over 4
=item B<__ex_mctx_struct>
This holds the contents of the machine context structure. A pointer to
such a machine context is passed to the following macros as I<mctx>.
=item B<__ex_mctx_save>(__ex_mctx_struct *I<mctx>)
This is called by the prolog of B<ex_try> to save the current machine
context in I<mctx>. This function has to return true (not C<0>) after
saving. If the machine context is restored (by B<__ex_mctx_restore>) it
has to return false (C<0>). In other words, this function has to return
twice and indicate the particular situation with the provided return
code.
=item B<__ex_mctx_restored>(__ex_mctx_struct *I<mctx>)
This is called by the epilog of B<ex_try> to perform additional
operations at the new (restored) machine context after an exception was
caught. Usually this is a no-operation macro.
=item B<__ex_mctx_restore>(__ex_mctx_struct *I<mctx>)
This is called by B<ex_throw> at the old machine context in order to
restore the machine context of the B<ex_try>/B<ex_catch> clause which
will catch the exception.
=back
The default implementation (define C<__EX_MCTX_SJLJ__> or as long as
C<__EX_MCTX_CUSTOM__> is not defined) uses the B<ISO-C> jmp_buf(3)
facility:
#define __ex_mctx_struct jmp_buf jb;
#define __ex_mctx_save(mctx) (setjmp((mctx)->jb) == 0)
#define __ex_mctx_restored(mctx) /* noop */
#define __ex_mctx_restore(mctx) (void)longjmp((mctx)->jb, 1)
Alternatively, you can define C<__EX_MCTX_SSJLJ__> to use B<POSIX.1>
sigjmp_buf(3) or C<__EX_MCTX_MCSC__> to use B<POSIX.1> ucontext(3). For
using a custom implementation define C<__EX_MCTX_CUSTOM__> and provide
own definitions for the four B<__ex_mctx_xxxx> macros.
=head2 Exception Context
In order to maintain the exception catching stack and for passing the
exception between the throw and the catch point, B<OSSP ex> uses a
global exception context, returned on-the-fly by the callback "C<ex_ctx_t
*(*>B<__ex_ctx>C<)(void)>".
By default, B<__ex_ctx> (which is B<__ex_ctx_default> as provided by
F<libex>) returns a pointer to a static C<ex_ctx_t> context. For use in
multi-threading environments, this should be overwritten with a
callback function returning a per-thread context structure (see section
B<MULTITHREADING ENVIRONMENTS> below).
To initialize an exception context structure there are two macros
defined: "B<EX_CTX_INITIALIZER>" for static initialization and
"C<void >B<EX_CTX_INITIALIZE>C<(ex_ctx_t *)>" for dynamic
initialization.
=head2 Termination Handler
In case there is an exception thrown which is not caught by any
B<ex_try>/B<ex_catch> clauses, B<OSSP ex> calls the callback "C<void
(*>B<__ex_terminate>C<)(ex_t *)>". It receives a pointer to the
exception object which was thrown.
By default, B<__ex_terminate> (which is B<__ex_terminate_default>
as provided by F<libex>) prints a message of the form "C<**EX:
UNCAUGHT EXCEPTION: class=0xXXXXXXXX object=0xXXXXXXXX value=0xXXXXXXX
[xxxx:NNN:xxxx]>" to F<stderr> and then calls abort(3) in order to
terminate the application. For use in multi-threading environments, this
should be overwritten with a callback function which terminates only
the current thread. Even better, a real application always should have
a top-level B<ex_try>/B<ex_catch> clause in its "C<main()>" in order to
more gracefully terminate the application.
=head2 Namespace Mapping
The B<OSSP ex> implementation consistently uses the "C<ex_>", "C<__ex_>"
and "C<__EX_>" prefixes for namespace protection. But at least
the "C<ex_>" prefix for the API macros B<ex_try>, B<ex_cleanup>,
B<ex_catch>, B<ex_throw>, B<ex_rethrow> and B<ex_shield> sometimes have
an unpleasant optical appearance. Especially because B<OSSP ex> is
modeled after the exception facility of B<ISO-C++> where there is no
such prefix on the language directives, of course.
For this, B<OSSP ex> optionally provides the ability to provide
additional namespace mappings for those API elements. By default (define
C<__EX_NS_CXX__> or as long as C<__EX_NS_CUSTOM__> and C<__cplusplus>
is not defined) you can additionally use the B<ISO-C++> style names
B<catch>, B<cleanup>, B<throw>, B<rethrow> and B<shield>. As an
alternative you can define C<__EX_NS_UCCXX__> to get the same but with a
more namespace safe upper case first letter.
=head1 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 */
ex_try {
char *cp1, *cp2, cp3;
cp1 = mallocex(SMALLAMOUNT);
globalcontext->first = cp1;
cp2 = mallocex(TOOBIG);
cp3 = mallocex(SMALLAMOUNT);
strcpy(cp1, "foo");
strcpy(cp2, "bar");
}
ex_cleanup {
if (cp3 != NULL) free(cp3);
if (cp2 != NULL) free(cp2);
if (cp1 != NULL) free(cp1);
}
ex_catch(ex) {
printf("cp3=%s", cp3);
ex_rethrow;
}
This example raises a few issues:
=over 4
=item B<01: variable scope>
Variables which are used in the B<ex_cleanup> or B<ex_catch> clauses
must be declared before the B<ex_try> clause, otherwise they only
exist inside the B<ex_try> block. In the example above, C<cp1>, C<cp2>
and C<cp3> are automatic variables and only exist in the block of the
B<ex_try> clause, the code in the B<ex_cleanup> and B<ex_catch> clauses
does not know anything about them.
=item B<02: variable initialization>
Variables which are used in the B<ex_cleanup> or B<ex_catch> clauses must
be initialized before the point of the first possible B<ex_throw> is
reached. In the example above, B<ex_cleanup> would have trouble using
C<cp3> if mallocex() throws a exception when allocating a C<TOOBIG>
buffer.
=item B<03: volatile variables>
Variables which are used in the B<ex_cleanup> or B<ex_catch> clauses
must be declared with the storage class "C<volatile>", otherwise they
might contain outdated information if B<ex_throw> throws an exception.
If using a "free if unset" approach like the example does in the
B<ex_cleanup> clause, the variables must be initialized (see B<02>) I<and>
remain valid upon use.
=item B<04: clean before catch>
The B<ex_cleanup> clause is not only written down before the B<ex_catch>
clause, it is also evaluated before the B<ex_catch> clause. So,
resources being cleaned up must no longer be used in the B<ex_catch>
block. The example above would have trouble referencing the character
strings in the printf(3) statement because these have been freed
before.
=item B<05: variable uninitialization>
If resources are passed away and out of the scope of the
B<ex_try>/B<ex_cleanup>/B<ex_catch> construct and the variables were
initialized for using a "free if unset" approach then they must be
uninitialized after being passed away. The example above would free(3)
C<cp1> in the B<ex_cleanup> clause if mallocex() throws an exception if
allocating a C<TOOBIG> buffer. The C<globalcontext->first> pointer
hence becomes invalid.
=back
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);
globalcontext->first = cp1;
cp1 = NULL /*05 give away*/;
cp2 = mallocex(TOOBIG);
cp3 = mallocex(SMALLAMOUNT);
strcpy(cp1, "foo");
strcpy(cp2, "bar");
}
clean { /*04*/
printf("cp3=%s", cp3 == NULL /*02*/ ? "" : cp3);
if (cp3 != NULL)
free(cp3);
if (cp2 != NULL)
free(cp2);
/*05 cp1 was given away */
}
catch(ex) {
/*05 global context untouched */
rethrow;
}
}
Alternatively, this could also be used:
/* ALTERNATIVE 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);
globalcontext->first = cp1;
/*05 keep responsibility*/
cp2 = mallocex(TOOBIG);
cp3 = mallocex(SMALLAMOUNT);
strcpy(cp1, "foo");
strcpy(cp2, "bar");
}
clean { /*04*/
printf("cp3=%s", cp3 == NULL /*02*/ ? "" : cp3);
if (cp3 != NULL)
free(cp3);
if (cp2 != NULL)
free(cp2);
if (cp1 != NULL)
free(cp1);
}
catch(ex) {
globalcontext->first = NULL;
rethrow;
}
}
=head1 MULTITHREADING ENVIRONMENTS
B<OSSP ex> is designed to work both in single-threading and
multi-threading environments. The default is to support single-threading
only. But it is easy to configure B<OSSP ex> to work correctly in a
multi-threading environment like B<POSIX pthreads> or B<GNU pth>.
There are only two issues: which machine context to use and where to
store the exception context to make sure exception throwing happens
only within a thread and does not conflict with the regular thread
dispatching mechanism.
=head2 GNU pth
Using B<OSSP ex> together with B<GNU pth> is straight-forward, because
B<GNU pth> 2.0 (and higher) already has support for B<OSSP ex> built-in.
All which is needed is that B<GNU pth> is configured with the B<GNU
Autoconf> option C<--with-ex>. Then each B<GNU pth> user-space thread
has its own B<OSSP ex> exception context automatically. The default of
using B<ISO-C> jmp_buf(3) does not conflict with the thread dispatching
mechanisms used by B<GNU pth>.
=head2 POSIX pthreads
Using B<OSSP ex> inside an arbitrary B<POSIX pthreads> standard
compliant environment is also straight-forward, although it requires
extra coding. What you basically have to do is to make sure that the
B<__ex_ctx> becomes a per-thread context and that B<__ex_terminate>
terminates only the current thread. To get an impression, a small
utility library for this follows:
=over 2
=item F<pthread_ex.h>
#ifndef __PTHREAD_EX_H__
#define __PTHREAD_EX_H__
#include <pthread.h>
int pthread_init_ex (void);
int pthread_create_ex (pthread_t *, const pthread_attr_t *,
void *(*)(void *), void *);
#ifndef PTHREAD_EX_INTERNAL
#define pthread_init pthread_init_ex
#define pthread_create pthread_create_ex
#endif
#endif /* __PTHREAD_EX_H__ */
=item F<pthread_ex.c>
#include <stdlib.h>
#include <pthread.h>
#define PTHREAD_EX_INTERNAL
#include "pthread_ex.h"
#include "ex.h"
/* context storage key */
static pthread_key_t pthread_ex_ctx_key;
/* context destructor */
static void pthread_ex_ctx_destroy(void *data)
{
if (data != NULL)
free(data);
return;
}
/* callback: context fetching */
static ex_ctx_t *pthread_ex_ctx(void)
{
ex_ctx_t *ctx;
if ((ctx = (ex_ctx_t *)pthread_getspecific(pthread_ex_ctx_key)) == NULL)
return __ex_ctx_default();
return ctx;
}
/* callback: termination */
static void pthread_ex_terminate(ex_t *e)
{
ex_ctx_t *ctx;
if ((ctx = (ex_ctx_t *)pthread_getspecific(pthread_ex_ctx_key)) == NULL)
__ex_terminate_default(e);
pthread_exit(e->ex_value);
}
/* pthread init */
int pthread_init_ex(void)
{
int rc;
/* additionally create thread data key
and override OSSP ex callbacks */
pthread_key_create(&pthread_ex_ctx_key,
pthread_ex_ctx_destroy);
__ex_ctx = pthread_ex_ctx;
__ex_terminate = pthread_ex_terminate;
return rc;
}
/* internal thread entry wrapper information */
typedef struct {
void *(*entry)(void *);
void *arg;
} pthread_create_ex_t;
/* internal thread entry wrapper */
static void *pthread_create_wrapper(void *arg)
{
pthread_create_ex_t *wrapper;
ex_ctx_t *ex_ctx;
/* create per-thread exception context */
wrapper = (pthread_create_ex_t *)arg;
ex_ctx = (ex_ctx_t *)malloc(sizeof(ex_ctx_t));
EX_CTX_INITIALIZE(ex_ctx);
pthread_setspecific(pthread_ex_ctx_key, ex_ctx);
/* perform original operation */
return wrapper->entry(wrapper->arg);
}
/* pthread_create() wrapper */
int pthread_create_ex(pthread_t *thread,
const pthread_attr_t *attr,
void *(*entry)(void *), void *arg)
{
pthread_create_ex_t wrapper;
/* spawn thread but execute start
function through wrapper */
wrapper.entry = entry;
wrapper.arg = arg;
return pthread_create(thread, attr,
pthread_create_wrapper, &wrapper);
}
=back
Now all which is required is that you include F<pthread_ex.h> after the
standard F<pthread.h> header and to call B<pthread_init> once at startup
of your program.
=head1 EXAMPLES
As a real-life example we will look how you can add optional B<OSSP
ex> based exception handling support to a library B<foo>. The original
library looks like this:
=over 2
=item F<foo.h>
typedef enum {
FOO_OK,
FOO_ERR_ARG,
FOO_ERR_XXX,
FOO_ERR_SYS,
FOO_ERR_IMP,
...
} foo_rc_t;
struct foo_st;
typedef struct foo_st foo_t;
foo_rc_t foo_create (foo_t **foo);
foo_rc_t foo_perform (foo_t *foo);
foo_rc_t foo_destroy (foo_t *foo);
=item F<foo.c>
#include "foo.h"
struct foo_st {
...
}
foo_rc_t foo_create(foo_t **foo)
{
if ((*foo = (foo_t)malloc(sizeof(foo))) == NULL)
return FOO_ERR_SYS;
(*foo)->... = ...
return FOO_OK;
}
foo_rc_t foo_perform(foo_t *foo)
{
if (foo == NULL)
return FOO_ERR_ARG;
if (...)
return FOO_ERR_XXX;
return FOO_OK;
}
foo_rc_t foo_destroy(foo_t *foo)
{
if (foo == NULL)
return FOO_ERR_ARG;
free(foo);
return FOO_OK;
}
=back
Then the typical usage of this library is:
#include "foo.h"
...
foo_t foo;
foo_rc_t rc;
...
if ((rc = foo_create(&foo)) != FOO_OK)
die(rc);
if ((rc = foo_perform(foo)) != FOO_OK)
die(rc);
if ((rc = foo_destroy(foo)) != FOO_OK)
die(rc);
But what you really want, is to use exception handling to get rid of the
intermixed error handling code:
#include "foo.h"
#include "ex.h"
...
foo_t foo;
ex_t ex;
...
ex_try {
foo_create(&foo);
foo_perform(foo);
foo_destroy(foo);
}
ex_catch (ex) {
die((foo_rc_t)ex->ex_value);
}
You can achieve this very easily by changing the library as following:
=over 2
=item F<foo.h>
...
extern const char foo_id[];
...
=item F<foo.c>
#include "foo.h"
const char foo_id[] = "foo 1.0";
#ifdef WITH_EX
#include "ex.h"
#define FOO_RC(rv) \
( (rv) != FOO_OK && (ex_catching && !ex_shielding) \
? (ex_throw(foo_id, NULL, (rv)), (rv)) : (rv) )
#else
#define FOO_RC(rv) (rv)
#endif
struct foo_st {
...
}
foo_rc_t foo_create(foo_t **foo)
{
if ((*foo = (foo_t)malloc(sizeof(foo))) == NULL)
return FOO_RC(FOO_ERR_SYS);
(*foo)->... = ...
return FOO_OK;
}
foo_rc_t foo_perform(foo_t *foo)
{
if (foo == NULL)
return FOO_RC(FOO_ERR_ARG);
if (...)
return FOO_RC(FOO_ERR_XXX);
return FOO_OK;
}
foo_rc_t foo_destroy(foo_t *foo)
{
if (foo == NULL)
return FOO_RC(FOO_ERR_ARG);
free(foo);
return FOO_OK;
}
=back
This way the library by default is still exactly the same. If you now
compile it with C<-DWITH_EX> you activate exception handling support.
This means that all API functions throw exceptions where C<ex_value> is
the C<foo_rc_t> instead of returning this value.
=head1 SEE ALSO
B<ISO-C++> C<try>, C<catch>, C<throw>.
B<Java> C<try>, C<catch>, C<finally>, C<throw>.
B<ISO-C> jmp_buf(3), setjmp(3), longjmp(3).
B<POSIX.1> sigjmp_buf(3), sigsetjmp(3), siglongjump(3).
B<POSIX.1> ucontext(3), setcontext(3), getcontext(3).
=head1 HISTORY
B<OSSP ex> was invented in January 2002 by Ralf S. Engelschall
E<lt>rse@engelschall.comE<gt> for use inside the B<OSSP> project. Its
creation was prompted by the requirement to reduce the error handling
inside B<OSSP lmtp2nntp>.
The core B<try>/B<catch> clause was inspired by B<ISO-C++> and the
implementation was partly derived from B<cexcept> 2.0.0, a similar
library written 2000 by Adam M. Costello E<lt>amc@cs.berkeley.eduE<gt>
and Cosmin Truta E<lt>cosmin@cs.toronto.eduE<gt>.
The B<cleanup> clause was inspired by the B<Java> C<finally> clause.
The B<shield> feature was inspired by an "C<errno>" shielding facility
used in the B<GNU pth> implementation. The B<defer> feature was invented
to simplify an application's cleanup handling if multiple independent
resources are allocated and have to be freed on error.
=head1 AUTHORS
Ralf S. Engelschall
rse@engelschall.com
www.engelschall.com
=cut