The freestanding build (-DAKERR_USE_STDLIB=OFF) did not compile at all:
bool, PATH_MAX and NULL were used unconditionally in the public header
but only included under the stdlib branch, and the CMake option was
pasted straight into a preprocessor definition, so a non-numeric cache
spelling (-DAKERR_USE_STDLIB=ON) silently evaluated to 0.
- Normalize AKERR_USE_STDLIB to a plain 1/0 in CMake before stamping it,
and use a consistent '#if AKERR_USE_STDLIB' everywhere it is tested.
- Include <stdbool.h>/<stddef.h> unconditionally (freestanding-safe);
keep <stdlib.h>/<string.h>/<stdio.h> behind AKERR_USE_STDLIB; move
<limits.h> out of the public header into src/error.c, its only user.
- Define the freestanding runtime contract: AKERR_RUNTIME_HEADER must
name a header providing exit, memset, snprintf, strcmp, strlen and
strncpy when AKERR_USE_STDLIB is OFF, or the header #errors naming
them. Add cmake/akerr_default_runtime.h, a libc-backed convenience
default so this repo's own OFF build and tests work out of the box.
- Route ENSURE_ERROR_READY's pool-exhaustion path through akerr_exit()
instead of a direct exit(1). Deliberate behavior change: that exit
code moves from 1 to AKERR_EXIT_STATUS_UNREPRESENTABLE (125), the same
sentinel every other unrepresentable status already uses. Documented
in docs/building.md and UPGRADING.md. Not an ABI break.
- Retire PATH_MAX: AKERR_MAX_ERROR_FNAME_LENGTH is now stamped by
scripts/generrno.sh from a new AKERR_MAX_ERROR_FNAME_LENGTH cache
variable, defaulting to 4096 (PATH_MAX on Linux/glibc) so
sizeof(akerr_ErrorContext) and the soname are unchanged. Rewrite the
now-stale PATH_MAX justification in src/lock.h's feature-test-macro
comment.
- Fail the configure with a FATAL_ERROR, not a warning, when
AKERR_USE_STDLIB=OFF and AKERR_THREADS would resolve to pthread,
naming -DAKERR_THREADS=none as the fix.
- Keep the generated errno table (errno.c) out of the OFF build; skip
the 'errno --list' shellout in scripts/generrno.sh under OFF and
stamp AKERR_LAST_ERRNO_VALUE from a new fallback cache variable
(default 133, Linux's EHWPOISON) instead.
- Update docs/building.md: drop the known-defect paragraph, fix
sprintf -> snprintf, add size_t, drop PATH_MAX, and document the new
options and the exit-code change.
- Guard tests/err_errno.c's registered-name assertion behind
AKERR_USE_STDLIB: akerr_init_errno() is not called when it is OFF.
- Add a CI job that configures/builds/tests AKERR_USE_STDLIB=OFF with
AKERR_THREADS=none, plus a compile-only -nostdinc -ffreestanding
check of tests/freestanding_fixture.c against the generated header.
Bump the project version to 2.0.2 (no ABI break: soname and struct
layout are unchanged).
Verified locally: OFF+none configures and builds clean with all tests
passing; ON and the plain default build both build and pass their full
test suites (37/37); OFF with the default/auto thread backend fails
configure with a message naming -DAKERR_THREADS=none; sizeof(akerr_ErrorContext)
is unchanged (37296 bytes, fname/function still 4096 each) versus the
pre-change tree.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every entry point may now be called from any thread. akerr_init() runs
exactly once however many threads race into it, the pool hands each slot
to exactly one thread, and reservations, registrations and lookups are
serialized against each other.
One recursive lock covers both tables (src/lock.h, private). Recursive
because raising an error re-enters the library -- FAIL needs a pool slot
and a status name -- and single because two locks would mean an ordering
to get wrong. Registry bodies that use the early-returning FAIL_*_RETURN
macros are split into *_locked functions behind wrappers that take and
release the lock on one path; consumer callbacks are never called under
it.
This is an ABI break, hence 2.0.0 and SOVERSION 2:
- akerr_next_error() now returns a context that already holds its
reference. Finding a free slot and claiming it has to be one operation,
or two threads scanning at once are handed the same slot.
ENSURE_ERROR_READY no longer increments.
- __akerr_last_ignored is thread-local, as is the last-ditch context used
to report akerr_release_error(NULL).
The threading backend is chosen at configure time by AKERR_THREADS
(auto, pthread, none). auto fails the configure when it cannot find
POSIX threads rather than quietly building a library that reports itself
thread safe and is not. generrno.sh stamps the decision into the
generated header as AKERR_THREAD_SAFE, so a consumer cannot disagree
with the library about it.
Tests: err_threads_init, err_threads_pool and err_threads_registry
assert exclusive slot ownership, exactly one winner for a contested
range, and every registered name readable back under contention.
AKERR_SANITIZE builds the library and the tests with any sanitizer;
scripts/thread_test.sh runs the suite under ThreadSanitizer and CI runs
it. Removing the pool lock makes both the sanitizer and the plain
assertions fail, so the tests are not vacuous.
Documented in README.md and UPGRADING.md, including what this does not
cover: renaming a status while another thread looks it up, and which of
two simultaneous unhandled errors sets the exit status.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>