2026-01-12 08:33:31 -05:00
|
|
|
#!/bin/bash
|
|
|
|
|
|
|
|
|
|
srcdir=$1
|
2026-05-12 16:44:06 -04:00
|
|
|
outdir=$2
|
Make the error pool and status registry thread safe
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>
2026-07-31 08:31:22 -04:00
|
|
|
# 1 when the library was configured with a threading backend, 0 for
|
|
|
|
|
# -DAKERR_THREADS=none. Stamped into the header so a consumer cannot disagree
|
|
|
|
|
# with the library about whether it locks and whether its per-thread state is
|
|
|
|
|
# thread local. Defaults to 1 for a hand-run of this script.
|
|
|
|
|
thread_safe=${3:-1}
|
Fix the AKERR_USE_STDLIB=OFF build (issue #12)
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>
2026-08-05 10:29:39 -04:00
|
|
|
# 1 for a normal (libc-linked) build, 0 for -DAKERR_USE_STDLIB=OFF. The
|
|
|
|
|
# freestanding build cannot shell out to `errno --list` (moreutils) or
|
|
|
|
|
# #include <errno.h>, so it skips the errno scrape entirely and stamps
|
|
|
|
|
# AKERR_LAST_ERRNO_VALUE from a fixed fallback instead. Defaults to 1 for a
|
|
|
|
|
# hand-run of this script.
|
|
|
|
|
use_stdlib=${4:-1}
|
|
|
|
|
# AKERR_LAST_ERRNO_VALUE to stamp when use_stdlib is 0. Defaults to 133
|
|
|
|
|
# (Linux's EHWPOISON) so the reserved band assertion in akerror.tmpl.h still
|
|
|
|
|
# holds.
|
|
|
|
|
last_errno_fallback=${5:-133}
|
|
|
|
|
# Bytes reserved for the fname/function fields of akerr_ErrorContext. Defaults
|
|
|
|
|
# to 4096 (PATH_MAX on Linux/glibc) so sizeof(akerr_ErrorContext) and the
|
|
|
|
|
# soname stay unchanged from before this became a build option.
|
|
|
|
|
max_error_fname_length=${6:-4096}
|
Make the error pool and status registry thread safe
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>
2026-07-31 08:31:22 -04:00
|
|
|
|
|
|
|
|
if [ "${thread_safe}" != "0" ] && [ "${thread_safe}" != "1" ]; then
|
|
|
|
|
echo "$0: thread-safe argument must be 0 or 1, got '${thread_safe}'" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
2026-05-12 16:44:06 -04:00
|
|
|
|
Fix the AKERR_USE_STDLIB=OFF build (issue #12)
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>
2026-08-05 10:29:39 -04:00
|
|
|
if [ "${use_stdlib}" != "0" ] && [ "${use_stdlib}" != "1" ]; then
|
|
|
|
|
echo "$0: use-stdlib argument must be 0 or 1, got '${use_stdlib}'" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
2026-05-12 16:44:06 -04:00
|
|
|
mkdir -p ${outdir}/src
|
|
|
|
|
mkdir -p ${outdir}/include
|
|
|
|
|
rm -f ${outdir}/src/errno.c
|
Fix the AKERR_USE_STDLIB=OFF build (issue #12)
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>
2026-08-05 10:29:39 -04:00
|
|
|
|
|
|
|
|
if [ "${use_stdlib}" = "1" ]; then
|
|
|
|
|
echo "#include <akerror.h>" >> ${outdir}/src/errno.c
|
|
|
|
|
echo "#include <errno.h>" >> ${outdir}/src/errno.c
|
|
|
|
|
cat >> ${outdir}/src/errno.c <<'EOF'
|
Raise errors from the status registry instead of returning codes
akerr_reserve_status_range() and akerr_register_status_name() returned
private int enumerations, which was the one place in the library where a
failure was not an akerr_ErrorContext *. They now return one like
everything else: NULL on success, and on refusal an error whose status is
a real code in the library's reserved band, so it can be CATCH-ed,
HANDLE-d, PASS-ed, or left to propagate into a stack trace. Both are
marked AKERR_NOIGNORE, so discarding the result warns at compile time.
AKERR_STATUS_RANGE_OK and AKERR_STATUS_NAME_OK are gone; the remaining
seven codes move into the AKERR_* offset span and get registered names.
AKERR_LAST_LIBRARY_STATUS replaces AKERR_BADEXC as the top of that span
in the reserved-band static assert and the exhaustiveness sweep.
The refusal detail that used to go straight to akerr_log_method now
travels in the error message, so a caller that handles the error decides
whether it is reported. The two-argument akerr_name_for_status() set path
is the exception: it returns a name and cannot raise, so it logs and
releases. akerr_init() likewise has no caller to raise into, so failing
to reserve its own band or name its own codes is logged and fatal --
that can only happen on a misconfigured build, and continuing would
degrade every later stack trace to "Unknown Error".
Move the 1.0.0 upgrade notice out of README.md into UPGRADING.md and
rewrite its return-code tables in terms of the statuses now raised.
Tests: ctest 29/29, coverage 97.5% line / 64.5% branch, mutation 77.5%
(was 77.6%; the new survivors are the fatal init path, which needs a
library built with an undersized name table -- TODO item 7).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-07-30 20:58:47 -04:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* These names belong to the library's own reserved band, and this runs from
|
|
|
|
|
* akerr_init(), which has no caller to raise into -- so it goes through
|
|
|
|
|
* __akerr_name_library_status(), which reports a refusal and terminates rather
|
|
|
|
|
* than leaving every later stack trace to print "Unknown Error" for an errno.
|
|
|
|
|
* Keeping the branch in src/error.c also keeps this generated file free of
|
|
|
|
|
* control flow no test can reach.
|
|
|
|
|
*/
|
|
|
|
|
EOF
|
Fix the AKERR_USE_STDLIB=OFF build (issue #12)
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>
2026-08-05 10:29:39 -04:00
|
|
|
echo "void akerr_init_errno(void) {" >> ${outdir}/src/errno.c
|
|
|
|
|
maxval=$(errno --list | cut -d ' ' -f 2 | sort -g | tail -n 1)
|
|
|
|
|
errno --list | while read LINE; do
|
|
|
|
|
define=$(echo "$LINE" | cut -d ' ' -f 1);
|
|
|
|
|
value=$(echo "$LINE" | cut -d ' ' -f 2);
|
|
|
|
|
desc=$(echo "$LINE" | cut -d ' ' -f 3-);
|
|
|
|
|
echo " __akerr_name_library_status(${define}, \"${desc}\");" >> ${outdir}/src/errno.c ;
|
|
|
|
|
done;
|
|
|
|
|
echo "}" >> ${outdir}/src/errno.c
|
|
|
|
|
else
|
|
|
|
|
# Freestanding: no `errno --list` shellout (requires moreutils and
|
|
|
|
|
# <errno.h>, neither freestanding-safe), and no errno.c at all -- CMake
|
|
|
|
|
# does not compile it into the library under AKERR_USE_STDLIB=OFF. Still
|
|
|
|
|
# write a stub so the OUTPUT this rule promises always exists.
|
|
|
|
|
echo "/* AKERR_USE_STDLIB=OFF: no errno table generated. */" >> ${outdir}/src/errno.c
|
|
|
|
|
maxval=${last_errno_fallback}
|
|
|
|
|
fi
|
|
|
|
|
|
Make the error pool and status registry thread safe
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>
2026-07-31 08:31:22 -04:00
|
|
|
sed -e "s/#define AKERR_LAST_ERRNO_VALUE .*/#define AKERR_LAST_ERRNO_VALUE ${maxval}/" \
|
|
|
|
|
-e "s/#define AKERR_THREAD_SAFE .*/#define AKERR_THREAD_SAFE ${thread_safe}/" \
|
Fix the AKERR_USE_STDLIB=OFF build (issue #12)
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>
2026-08-05 10:29:39 -04:00
|
|
|
-e "s/#define AKERR_MAX_ERROR_FNAME_LENGTH .*/#define AKERR_MAX_ERROR_FNAME_LENGTH ${max_error_fname_length}/" \
|
Make the error pool and status registry thread safe
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>
2026-07-31 08:31:22 -04:00
|
|
|
${srcdir}/include/akerror.tmpl.h > ${outdir}/include/akerror.h
|