NAME
ggRegisterCleanup, ggUnregisterCleanup, ggCleanupForceExit - Cleanup
callback facilities
SYNOPSIS
#include <ggi/gg.h>
typedef void (ggcleanup_func)(void *);
int ggRegisterCleanup(ggcleanup_func *func, void *arg);
int ggUnregisterCleanup(ggcleanup_func *func, void *arg);
void ggCleanupForceExit(void);
DESCRIPTION
ggRegisterCleanup registers a callback function ("handler") to be
executed when any abnormal or unexpected program termination is
imminent. The function will be called with its argument.
ggUnregisterCleanup cancels a callback installed with
ggRegisterCleanup. If more than one exactly identical callbacks have
been installed, the most recently installed one is canceled.
ggCleanupForceExit may only be called from within a LibGG cleanup
handler. Once ggCleanupForceExit is called, _exit(2) will be
explicitly called after all registered cleanup callbacks have completed
by ggExit(3), assuming there is no error that prevents them from
completing. It is not possible to cancel such a request once it has
been made.
Cleanup functions are executed in LIFO order. They are guaranteed to
only be executed once during program termination or during ggExit(3).
These functions are for emergency use only, and should not be used as a
substitute to proper memory deallocation routines. They should only be
used to restore system state that would otherwise be left corrupted
after an abnormal program termination, for example, a video-card timing
mode or tty mode. When normal termination occurs, ggUnregisterCleanup
should be called to systematically remove the emergency callbacks
before ggExit(3) or exit(3) are called.
Callback functions registered with ggRegisterCleanup should not
themselves invoke (or invoke any subroutines that may in turn invoke)
any of the LibGG locking functions ggLockCreate(3), ggLockDestroy(3),
ggLock(3), ggUnlock(3), or ggTryLock(3). However, since callbacks are
only invoked during emergencies, they should be ignoring locks in
general. If a callback function may be used by anything other than
LibGG, it must also be reentrant. Callback functions can come at any
time, so write them with this in mind -- make them minimal and tolerant
of concurrent access to global read/write data and avoid accessing such
data in the first place if it is not absolutely necessary.
The callback functions may be called by a normal call to ggExit(3). As
such, it is considered best practice to use ggUnregisterCleanup to
remove cleanups when gracefully deinitializing LibGG or a library that
uses LibGG, before ggExit(3) is called.
ggRegisterCleanup and ggUnregisterCleanup are threadsafe, however, they
are not safe to call from a thread that may be canceled during their
execution, and they are not guaranteed to be safe to call from LibGG
tasks or any other special context such as a signal handler or a
asyncronous procedure call. Above all they should not be called from
inside a LibGG cleanup handler.
RETURN VALUE
ggRegisterCleanup returns GGI_OK on success, or one of these error
codes:
· GGI_EUNKNOWN;
· GGI_ENOMEM;
· GGI_EBUSY.
ggUnregisterCleanup returns GGI_OK on success, or one of these error
codes:
· GGI_EBUSY;
· GGI_ENOTALLOC;
· GGI_ENOMEM.
INTERACTION WITH UNIX SIGNALS
On UNIX systems the LibGG cleanup facilities install signal handlers as
per signal(3) or sigaction(2). It is advisible to use LibGG cleanup
handlers instead of UNIX signals for the purpose of catching fatal
signals because they are implemented portably, however, this is not
always an option when mixing LibGG with other libraries. When LibGG
must be used with other code that also installs signal handlers,
consult the following section.
LibGG installs signal handlers for those signals which are normally
fatal to the program. The exact set of functions that is caught
depends on the software platform. LibGG installs signal handlers when
the first LibGG cleanup handler is installed. These may in fact be
installed in ggInit(3) as LibGG may use cleanups internally. The only
way to be sure that the LibGG signal handlers are installed is to
install a cleanup after ggInit(3).
By setting any signal handler to SIG_IGN before calling ggInit(3), the
application can force LibGG to ignore the signal, so that cleanups are
not run when that particular signal is received. LibGG will also
overload any application signal handlers for fatal signals that are
present when it installs a signal handler. Overloaded signal handlers
will be run before cleanups are run when the signal occurs. The
overloaded signal handler is not guaranteed to be called exactly as it
would be by the main application, for example, support for the long
form of the signal handler prototype available through sigaction(2) on
some systems is not yet implemented, though it may be in the future.
If the system uses the tradition (broken) UNIX signal behavior where
signal handlers are set to SIG_DFL to ’block’ additional occurances,
this may result in rare instances where signal handlers are reentered.
Thus the signal handler for a given signal may call the overloaded
cleanup multiple times before cleanup functions are called. This will
also apply to signal handlers that manipulate the signal mask on more
advanced sigaction(2) based systems.
Signals that arrive after that signal which triggers the cleanup
callbacks may have their handlers run before or during the execution of
the cleanups.
After ggInit(3) and ggRegisterCleanup are called, individual signals
may be overridden by application-specific signal handlers or set to
SIG_IGN or SIG_DFL. This will prevent LibGG cleanups from being run
when the signal occurs. Within limits, it is also acceptable to
overload the LibGG signal handler. However it is not acceptable to
call the LibGG signal handler with signum equal to any signal on which
LibGG did not initially install the handler function, and
deinitializing LibGG while overloading its signal handler may cause
undefined bahavior.
SHORT TUTORIAL ON WRITING DECENT SIGNAL HANDLERS
LibGG attempts to be tolerant of badly written signal handlers, but
consistant, correct behavior can only be guaranteed if signal handlers
are written within the following guidelines. First, a signal handler
must be written using sigaction if sigaction is available, or the
results may not be perfect. If sigaction is not available, the proper
code for a simple, overloadable signal handler is such:
lasthandler = signal(signum, SIG_DFL));
/* do stuff */
if (lasthandler == SIG_DFL) signal(signum, current_handler);
else signal(signum, lasthandler);
This code looks circumlocuitous but each part is important for
maintaining overloadability in a portable fashion. The signal handler
should not reinstall itself unless it detects original UNIX signals are
in effect by detecting the automatically installed SIG_DFL, or else it
might get called directly, skipping the parent signal handler.
Installing SIG_DFL temporarily should be harmless to BSD style signals
because the OS is required to block stacked signals through some other
mechanism until the signal handler returns.
In order to overload a signal handler, again still dealing with the
situation where sigaction is not available, the above code can be
modified as such:
lasthandler = signal(signum, SIG_DFL);
/* do stuff */
if (had_oldhandler) {
signal(signum, lasthandler);
oldhandler(signum);
}
if (lasthandler == SIG_DFL) signal(signum, current_handler);
else signal(signum, lasthandler);
This is not perfect because it may allow lasthandler to be reentered
when used on a system with the original UNIX behavior, in the short
period between when lasthandler is reinstalled and the oldhandler
installs SIG_DFL. However, if the handlers are all reentrant this
should work fine. In the BSD behavior, this again is harmless because
other OS mechanisms prevent reentry.
Systems without sigaction are pretty cretinous and rarer these days,
however. When sigaction(2) is available we can assume that signal
handlers do not need to reinstall themselves as per the original UNIX
SIG_DFL behavior. As such no special consideration is needed to write
a proper overloading/overloadable handler, however, in order to assure
that cleanup functions are only run once even in multithreaded,
multiprocessor environments, LibGG may need to temporarily overload a
signal handler which has overloaded LibGG’s signal handler with a dummy
pass-through handler, and as of this writing LibGG’s behavior when the
signal mask is altered is not yet specified and should be considered
undefined.
Measures are taken within LibGG to limit the impact of interaction with
badly written signal handlers that reinstall their own handler when it
is not needed or desired, however it is recommended that libraries that
use such handlers be updated to use better code when compiled on more
modern systems.
One last note on stacking signal handlers: When writing for an
environment where different libraries may overload signals, all
libraries must prevent loops from forming. It is not sufficient that
they simply check that they never overload their own signal handler,
because another library may have overloaded it already, and thus you
may have handler A calling handler B calling handler A which then calls
handler B again. Libraries must keep track of whether their signal
handlers are installed or not through other means.
FALLBACK MODE
LibGG expects some sort of signal-like system to be present in the
environment, otherwise there is no way to implement the behavior
described above. When LibGG is compiled on a system that has no such
support, a fallback mode is invoked where cleanup handlers are
registered with the atexit(3) facility, or anything it may have that is
like atexit(3). It may not be possible to unregister cleanups
supported in such a way, and they will always run at normal program
exit, even after LibGG is exited. There is no way for them to run
during abnormal termination.