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.