*** /dev/null Sat Nov 23 01:16:19 2024
--- - Sat Nov 23 01:16:24 2024
***************
*** 0 ****
--- 1,861 ----
+ ##
+ ## OSSP ex - Exception Handling
+ ## Copyright (c) 2002-2005 Ralf S. Engelschall <rse@engelschall.com>
+ ## Copyright (c) 2002-2005 The OSSP Project <http://www.ossp.org/>
+ ## Copyright (c) 2002-2005 Cable & Wireless <http://www.cw.com/>
+ ##
+ ## 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)
+ {
+ 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 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
+
|