OSSP CVS Repository

ossp - ossp-pkg/ex/ex.pod 1.13
Not logged in
[Honeypot]  [Browse]  [Directory]  [Home]  [Login
[Reports]  [Search]  [Ticket]  [Timeline
  [Raw

ossp-pkg/ex/ex.pod 1.13
##
##  Copyright (c) 2001-2002 The OSSP Project <http://www.ossp.org/>
##  Copyright (c) 2001-2002 Cable & Wireless Deutschland <http://www.cw.com/de/>
##
##  This file is part of OSSP ex, an exception library
##  which can be found at http://www.ossp.org/pkg/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 Library

=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_shield> I<BLOCK>

if (B<ex_catching>) ...

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 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 send
to the catcher. All three parts are of type "C<void *>" internally,
but every value can be used which can be (automatically) casted to the
B<ISO-C> type "C<void *>". 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> block
and just used read-only inside the 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 globally and uniquely identifies the class of
I<ex_value>. Usually this is a pointer to a static object (variable,
structure or function) which identifies the thrower.

=item C<void *>I<ex_object>

This is the I<object> argument of the B<ex_throw> call which created
the exception. This globally and uniquely identifies the class instance
of I<ex_value> (in case multiple instances exists). Usually this a
pointer to a dynamic object (structure) which identifiers the particular
instance of the thrower.

=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 which uniquely identifies
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 source where the B<ex_throw> call
was performed. It is 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 source file name where the
B<ex_throw> call was performed. It is 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 source file name where the B<ex_throw> call was performed. It is
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 main 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 of the exception handling (i.e.
exceptions directly thrown there or thrown from its sub-routines are
catched), 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), optionally followed by I<BLOCK2>, followed by
I<BLOCK3>.
    
The B<ex_try>, B<ex_cleanup> and B<ex_catch> cannot be used seperately,
they work only in combination. And 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 (or re-throwing)
inside the B<ex_catch> block. Although declared outside, the I<variable>
is only valid within the B<ex_catch> block. But it can be re-used in
following B<ex_try>/B<ex_catch> constructs, of course.

The B<ex_try> block is a regular B<ISO-C> language block of
statement(s), but it is not allowed to jump into it via C<goto> or
C<longjmp>(3) or out of it via 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 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
block of statement(s) without any restrictions. You are even allowed to
throw (and in the B<ex_catch> block to re-throw) an exception.

=item B<ex_throw>(I<class>, I<object>, I<value>);

This is second main construct. It builds an exception from the supplied
arguments and throws it. If an B<ex_try>/B<ex_catch> clause exists in
the dynamic scope of the B<ex_throw> call, this exception is copied into
the I<variable> of B<ex_catch> and the program control flow is continued
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 C<abort>(3)s. The
B<ex_throw> can be performed everywhere, including inside B<ex_try> 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) but with the difference that the C<ex_file>,
C<ex_line> and C<ex_func> elements of the thrown exception are kept.

=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 ignored.

=item B<ex_catching>

This is a boolean flag which can be tested inside a block to test
whether the current scope is exception catching (by B<ex_catch>
somewhere in the dynamic scope) or not.

=item B<ex_shielding>

This is a boolean flag which can be tested inside a block to test
whether the current scope is exception shielding (by B<ex_shield>
somewhere in the dynamic scope) or not.

=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 is 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>(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>).

=item B<__ex_mctx_restored>(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>(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_USE_SJLJ__> or as long
C<__EX_MCTX_USE_CUSTOM__> is not defined) uses B<ISO-C> jmp_buf(3):

 #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 one can define C<__EX_MCTX_USE_SSJLJ__> to use B<POSIX.1>
sigjmp_buf(3) and C<__EX_MCTX_USE_MCSC__> to use B<POSIX.1> ucontext(3).
For using a custom implementation define C<__EX_MCTX_USE_CUSTOM__> and
provide own macro definitions for the four B<__ex_mctx_xxxx>.

=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 "ex_ctx_t
*(*B<__ex_ctx>)(void)".

The default B<__ex_ctx> (B<__ex_ctx_default> as provided by F<libex>)
returns a pointer to a static B<ex_ctx_t> context. For use in
multi-threading environments, this should be overwritten with a
per-thread context structure.

To initialize an exception context structure there are two macros
defined: B<EX_CTX_INITIALIZER> for static initialization and
B<EX_CTX_INITIALIZE>(B<ex_ctx_t *>I<ctx>) 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 "void
(*B<__ex_terminate>)(C<ex_t *>)". It receives a pointer to the exception
object which should have been thrown.

The default B<__ex_terminate> (B<__ex_terminate_default> as provided by
F<libex>) this prints a message of the form "C<**EX: UNCAUGHT EXCEPTION:
class=0xXXXXXXXX object=0xXXXXXXXX value=0xXXXXXXX [file:123:func]>" 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.

=head2 Namespace Mapping

B<OSSP ex> implementation consistently uses the B<ex_>, B<__ex_>
and B<__EX_> prefixes for namespace protection. But at least the
B<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 a unpleasant
optical appearance. Especially because B<OSSP rc> is modeled after the
exception facility in B<ISO-C++> where there is no such prefix on the
directives.

For this B<OSSP ex> optionally provides the ability to provide
additional namespace mappings for those API macros. By default (define
C<__EX_NS_USE_CXX__> or as long as C<__EX_NS_USE_CUSTOM__> and
C<__cplusplus> is not defined) you can additional C++ style macros
named B<catch>, B<cleanup>, B<throw>, B<rethrow> and B<shield>. As an
alternative you can define C<__EX_NS_USE_UCCXX__> to get the same but
with an (more namespace safe) upper case first letter.

=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 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. Here are two examples:

=head2 GNU pth

Using B<OSSP ex> with B<GNU pth> is straight-forward, because B<GNU pth>
already has support for B<OSSP ex> built-in. All which is needed is that
B<GNU pth> is configured with the Autoconf option C<--with-ex>. Then
each thread has its own B<OSSP ex> exception context.

=head2 POSIX pthreads

Using B<OSSP ex> inside a standard B<POSIX pthreads> environment is
also straight-forward, although it requires additional 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 4

=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 *thread, const pthread_attr_t *attr, void *(*entry)(void *), void *arg);

#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)
{
    return (ex_ctx_t *)pthread_getspecific(pthread_ex_ctx_key);
}

/* callback: termination */
static void pthread_ex_terminate(ex_t *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 full 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 4

=item F<foo.h>

 ...
 foo_rc_t foo_ex(foo_t *foo, int use);
 ...

=item F<foo.c>

 #include "foo.h"

 #ifdef WITH_EX
 #include "ex.h"
 char foo_ex_class = "foo"; /* class identifier */
 int  foo_ex_use   = 0;     /* class disable flag */
 #endif
 
 struct foo_st {
 #ifdef WITH_EX
     int ex_use; /* object disable flag */
 #endif
     ...
 }

 #ifdef WITH_EX
 #define FOO_ERR(ctx,err) \
     ((   ex_shielding \
       || !foo_ex_use \
       || ((ctx) != NULL && !(ctx)->ex_use)) 
      ? FOO_ERR_##err \
      : ex_throw(&foo_ex_class, ctx, FOO_ERR_##err))
 #else
 #define FOO_ERR(ctx,err) \
      (FOO_ERR_##err)
 #endif

 foo_rc_t foo_create(foo_t **foo)
 {
     if ((*foo = (foo_t)malloc(sizeof(foo))) == NULL)
         return FOO_ERR(NULL, SYS);
     (*foo)->ex_use = 0;
     (*foo)->... = ...
     return FOO_OK;
 }

 foo_rc_t foo_ex(foo_t *foo, int use)
 {
     if (foo == NULL)
         return FOO_ERR(foo, ARG);
#ifndef WITH_EX
     return FOO_ERR_IMP;
#else
     foo->ex_use = use;
     return FOO_OK;
#endif
 }

 foo_rc_t foo_perform(foo_t *foo)
 {
     if (foo == NULL)
         return FOO_ERR(foo, ARG);
     if (...)
         return FOO_ERR(foo, XXX);
     return FOO_OK;
 }

 foo_rc_t foo_destroy(foo_t *foo)
 {
     if (foo == NULL)
         return FOO_ERR(foo, 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 build optional exception handling
support into it, although it is still disabled and the library behaviour
is the same. But if you now enable execptions via C<foo_ex(foo, 1);>
the library throws exceptions where C<ex_value> is the C<foo_rc_t>
instead of returning this value. Notice that in our example we allow the
enabling/disabling also on a per object basis, i.e., some B<foo> objects
could have exception handling enabled and others not.

=head1 SEE ALSO

C++ try/catch/throw language directives;
setjmp(3), longjmp(3), sigsetjmp(3), siglongjmp(3).

=head1 HISTORY

B<OSSP ex> was invented in January 2002 by Ralf S. Engelschall
for use inside the 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 implementation is
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>.

=head1 AUTHORS

 Ralf S. Engelschall
 rse@engelschall.com
 www.engelschall.com

=cut


CVSTrac 2.0.1