A well-designed and uniform error handling scheme is extremely important for every kind of software, and it is even more important when developing code which is meant to be (re)used in other applications.
The NASPRO core library provides an easy-to-use yet well-behaving, consistent and easily portable infrastructure for error handling; this, combined with its message reporting facility, consistutes an unique and natural way to take care of race conditions and possibly keep the end user informed as much as possible on what's happening.
NASPRO error codes are defined as 31 bit-long constants (declared as #define directives), where:
Some host id values are already reserved; in particular:
Furthermore the values 0 and 255 for the local error code are not allowed.
All of this might seem unnecessary complicated at first glance, but in practice such scheme has a lot of positive side-effects:
float, double - no chance to get NaNs and INFs). The NASPRO core library will provide C and Autoconf macros to properly set up error codes;-ERRCODE-like return values means that bitwise-operators can be used directly on error codes (again, the NASPRO core library API will help);errno.h and NASPRO coreThe C89 standard defines only three error codes as macros (EDOM, EILSEQ and ERANGE), while the current POSIX.1 standard (IEEE Std 1003.1, 2004 Edition) defines circa 80 error codes (including the three C89 ones). Both do not specify their exact representation, but POSIX states that these should be ints and that their number can grow.
Due to the representation of error codes in NASPRO core, and to the (theoretical?) possibility of having strange values in some implementation, these error codes should never be returned by a NASPRO core-based library function but, instead, such functions should use the corresponding values defined by the NASPRO core API.
errno examinationOn some systems the errno lvalue set by many functions in the standard C library is thread-specific, while on others it is not. In the latter case it is not safe to read its content to determine what kind of error might have happened when working with multiple threads.
While, at first glance, this might seem a simple issue to solve, it is absolutely not the case, since this involves the whole application + libraries used (+ modules) environment.
To clarify things somewhat, let's make an example: one may think to use a mutex and write two functions, one locking the mutex and the other reading errno and unlocking it. Of course, these functions should be used whenever calling a function which can modify, directly or indirectly, the errno value (unless the called function itself uses the same mutex - i.e. a function from our same library or, hopefully, from a derived piece of code). It's clear that external (and unrelated) libraries the application might use know nothing about our errno lock, so, when dealing with concurrent code, they are very likely to "blindly" overwrite its value (and this is extremely probable too, since the C library itself does that).
So, one of the two: either limit ourselves only to OSs with thread-specific errno lvalue, or invent some synchronization mechanism to be used by the host developer willing to use NASPRO core together with some other library (can't force every library developer on the planet to change its code to be NASPRO compatible).
I chose the second option, and this is the proposed solution (going to be implemented in the first development release): offer a function to be called before library initialization (nacore_err_set_errno_lock()) which sets which function should be used by the other two functions (nacore_err_lock_errno() and nacore_err_unlock_errno()).
In terms of code, for an application it could work like this:
nacore_sync_mutex_t errno_lock;
long
lock_errno()
{
return nacore_sync_mutex_lock(errno_lock);
}
long
unlock_errno()
{
return nacore_sync_mutex_unlock(errno_lock);
}
int
main(int argc, char argv[])
{
...
nacore_sync_mutex_new(&errno_lock, NULL);
nacore_err_set_errno_lock(lock_errno, unlock_errno);
...
nacore_init();
...
}
int
some_multithread_function()
{
...
nacore_err_lock_errno(); /* or lock_errno(); */
mylib_some_function();
printf("errno code: %d\n", errno);
nacore_err_unlock_errno(); /* or unlock_errno(); */
...
}
Notice that the lock is generic (here we used a NASPRO core mutex, but it can be every locking mechanism).
The NASPRO core library (and hopefully host libraries too) will use these functions exactly where needed (much more fine grained critical sections) to gain higher performance.
On OSs with thread-specific errno, these three functions do nothing since they are not needed at all.
strerror, perror, runtime error registration et similiaThe NASPRO core library does not provide similar functions because of its message reporting facility.
The standard C library offers the well known assert() macro to make sanity checks against some condition at runtime. What is wrong about it, in some contexts, is that if that condition is not verified it hopefully gives a notice to the user and then brings the program to an abnormal termination.
While this behaviour may, or may not, be acceptable in a certain application, it is way less likely to be good for a library for a good number of reasons. Also consider that such macro, on its own, is actually nothing more than a easily replaceable "primitive" on which real debugging code has to be built.
So, this library will offer a more consistent and integrated API for handling assertioning and debugging in general, made of several macro/function couples. There will be:
assert() (nacore_assert() and NACORE_ASSERT()), not causing abnormal termination, but trying to inform the end user on what happened someway (via the application or by itself);nacore_assert_params() and NACORE_ASSERT_PARAMS_BEGIN(), NACORE_ASSERT_PARAM() and NACORE_ASSERT_PARAMS_END); hosts should treat this issue separately from the generic assertion;NACORE_DEBUG_CODE()); this is very useful to specify that a piece of code has to be included only in the debugging version and/or to make more complex checks.Of course, as it goes for the old assert(), checks in hosts will be actually compiled only if a certain macro (NACORE_DEBUG_ENABLE) is defined when including the NASPRO core header files (that means when compiling in "debugging mode"), otherwise they will just disappear (meaning there is absolutely no slowdown nor increase in binary code size).