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>
This commit is contained in:
@@ -11,7 +11,11 @@
|
||||
* Covers: arbitrary int status values, name truncation, range reservation
|
||||
* semantics (overlap, idempotency, endpoints, validation, overflow), and both
|
||||
* capacity limits -- the range table and the name table -- each of which must
|
||||
* report the failure rather than dropping the registration quietly.
|
||||
* raise rather than dropping the registration quietly.
|
||||
*
|
||||
* Every refusal is an error context the caller owns, so each check below also
|
||||
* asserts the context returns to the pool; a leak here would exhaust the
|
||||
* 128-slot pool long before these loops finish.
|
||||
*/
|
||||
|
||||
int main(void)
|
||||
@@ -20,120 +24,119 @@ int main(void)
|
||||
akerr_init();
|
||||
|
||||
/* Any int is a legal status, at either extreme of the range. */
|
||||
AKERR_CHECK(akerr_reserve_status_range(INT_MIN, 1, "min-owner") ==
|
||||
AKERR_STATUS_RANGE_OK);
|
||||
AKERR_CHECK(akerr_reserve_status_range(INT_MAX, 1, "max-owner") ==
|
||||
AKERR_STATUS_RANGE_OK);
|
||||
AKERR_CHECK(akerr_register_status_name("max-owner", INT_MAX, "Maximum Status") ==
|
||||
AKERR_STATUS_NAME_OK);
|
||||
AKERR_CHECK(akerr_register_status_name("min-owner", INT_MIN, "Minimum Status") ==
|
||||
AKERR_STATUS_NAME_OK);
|
||||
AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(INT_MIN, 1, "min-owner"));
|
||||
AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(INT_MAX, 1, "max-owner"));
|
||||
AKERR_CHECK_SUCCEEDS(akerr_register_status_name("max-owner", INT_MAX, "Maximum Status"));
|
||||
AKERR_CHECK_SUCCEEDS(akerr_register_status_name("min-owner", INT_MIN, "Minimum Status"));
|
||||
AKERR_CHECK(strcmp(akerr_name_for_status(INT_MAX, NULL), "Maximum Status") == 0);
|
||||
AKERR_CHECK(strcmp(akerr_name_for_status(INT_MIN, NULL), "Minimum Status") == 0);
|
||||
|
||||
/* A name longer than the buffer is truncated and always terminated. */
|
||||
AKERR_CHECK(akerr_reserve_status_range(1000000, 1, "trunc") ==
|
||||
AKERR_STATUS_RANGE_OK);
|
||||
AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(1000000, 1, "trunc"));
|
||||
const char *long_name =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-extra";
|
||||
AKERR_CHECK(akerr_register_status_name("trunc", 1000000, long_name) ==
|
||||
AKERR_STATUS_NAME_OK);
|
||||
AKERR_CHECK_SUCCEEDS(akerr_register_status_name("trunc", 1000000, long_name));
|
||||
char *stored = akerr_name_for_status(1000000, NULL);
|
||||
AKERR_CHECK(strlen(stored) == AKERR_MAX_ERROR_NAME_LENGTH - 1);
|
||||
AKERR_CHECK(stored[AKERR_MAX_ERROR_NAME_LENGTH - 1] == '\0');
|
||||
|
||||
/* Reservation: overlap detection, and idempotency for an exact repeat. */
|
||||
AKERR_CHECK(akerr_reserve_status_range(256, 16, "component-a") ==
|
||||
AKERR_STATUS_RANGE_OK);
|
||||
AKERR_CHECK(akerr_reserve_status_range(256, 16, "component-a") ==
|
||||
AKERR_STATUS_RANGE_OK);
|
||||
AKERR_CHECK(akerr_reserve_status_range(260, 2, "component-b") ==
|
||||
AKERR_STATUS_RANGE_OVERLAP);
|
||||
AKERR_CHECK_CONTAINS("component-a");
|
||||
AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(256, 16, "component-a"));
|
||||
AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(256, 16, "component-a"));
|
||||
AKERR_CHECK_RAISES(akerr_reserve_status_range(260, 2, "component-b"),
|
||||
AKERR_STATUS_RANGE_OVERLAP);
|
||||
AKERR_CHECK_MESSAGE_CONTAINS("component-a");
|
||||
|
||||
/* The library's own 0..255 band is reserved and cannot be encroached on. */
|
||||
AKERR_CHECK(akerr_reserve_status_range(255, 1, "component-b") ==
|
||||
AKERR_STATUS_RANGE_OVERLAP);
|
||||
AKERR_CHECK(akerr_reserve_status_range(0, AKERR_RESERVED_STATUS_COUNT,
|
||||
AKERR_LIBRARY_OWNER) ==
|
||||
AKERR_STATUS_RANGE_OK); /* exact repeat by the owner */
|
||||
AKERR_CHECK_RAISES(akerr_reserve_status_range(255, 1, "component-b"),
|
||||
AKERR_STATUS_RANGE_OVERLAP);
|
||||
AKERR_CHECK_MESSAGE_CONTAINS(AKERR_LIBRARY_OWNER);
|
||||
/* An exact repeat by the owner is still a no-op. */
|
||||
AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(0, AKERR_RESERVED_STATUS_COUNT,
|
||||
AKERR_LIBRARY_OWNER));
|
||||
|
||||
/* Argument validation. */
|
||||
AKERR_CHECK(akerr_reserve_status_range(INT_MAX, 2, "overflow") ==
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
AKERR_CHECK(akerr_reserve_status_range(300, 0, "empty") ==
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
AKERR_CHECK(akerr_reserve_status_range(300, -1, "negative") ==
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
AKERR_CHECK(akerr_reserve_status_range(300, 1, NULL) ==
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
AKERR_CHECK(akerr_reserve_status_range(300, 1, "") ==
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
AKERR_CHECK_RAISES(akerr_reserve_status_range(INT_MAX, 2, "overflow"),
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
AKERR_CHECK_RAISES(akerr_reserve_status_range(300, 0, "empty"),
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
AKERR_CHECK_MESSAGE_CONTAINS("empty"); /* the message names the caller */
|
||||
AKERR_CHECK_RAISES(akerr_reserve_status_range(300, -1, "negative"),
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
AKERR_CHECK_RAISES(akerr_reserve_status_range(300, 1, NULL),
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
AKERR_CHECK_RAISES(akerr_reserve_status_range(300, 1, ""),
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
|
||||
/* Owner strings: 63 chars fit, 64 do not. */
|
||||
/* Owner strings: 63 chars fit, 64 do not, and neither does anything past. */
|
||||
char owner63[AKERR_MAX_ERROR_NAME_LENGTH];
|
||||
char owner64[AKERR_MAX_ERROR_NAME_LENGTH + 1];
|
||||
char owner70[AKERR_MAX_ERROR_NAME_LENGTH + 7];
|
||||
memset(owner63, 'a', sizeof(owner63) - 1);
|
||||
owner63[sizeof(owner63) - 1] = '\0';
|
||||
memset(owner64, 'b', sizeof(owner64) - 1);
|
||||
owner64[sizeof(owner64) - 1] = '\0';
|
||||
AKERR_CHECK(akerr_reserve_status_range(400, 1, owner63) ==
|
||||
AKERR_STATUS_RANGE_OK);
|
||||
AKERR_CHECK(akerr_reserve_status_range(401, 1, owner64) ==
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
memset(owner70, 'c', sizeof(owner70) - 1);
|
||||
owner70[sizeof(owner70) - 1] = '\0';
|
||||
AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(400, 1, owner63));
|
||||
AKERR_CHECK_RAISES(akerr_reserve_status_range(401, 1, owner64),
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
AKERR_CHECK_RAISES(akerr_reserve_status_range(402, 1, owner70),
|
||||
AKERR_STATUS_RANGE_INVALID);
|
||||
|
||||
/* Partial overlaps at either endpoint, and a same-range different owner. */
|
||||
AKERR_CHECK(akerr_reserve_status_range(500, 2, "endpoint") ==
|
||||
AKERR_STATUS_RANGE_OK);
|
||||
AKERR_CHECK(akerr_reserve_status_range(499, 2, "left") ==
|
||||
AKERR_STATUS_RANGE_OVERLAP);
|
||||
AKERR_CHECK(akerr_reserve_status_range(500, 1, "endpoint") ==
|
||||
AKERR_STATUS_RANGE_OVERLAP); /* subset, not an exact repeat */
|
||||
AKERR_CHECK(akerr_reserve_status_range(501, 1, "endpoint") ==
|
||||
AKERR_STATUS_RANGE_OVERLAP);
|
||||
AKERR_CHECK(akerr_reserve_status_range(500, 2, "other") ==
|
||||
AKERR_STATUS_RANGE_OVERLAP); /* same range, wrong owner */
|
||||
AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(500, 2, "endpoint"));
|
||||
AKERR_CHECK_RAISES(akerr_reserve_status_range(499, 2, "left"),
|
||||
AKERR_STATUS_RANGE_OVERLAP);
|
||||
AKERR_CHECK_RAISES(akerr_reserve_status_range(500, 1, "endpoint"),
|
||||
AKERR_STATUS_RANGE_OVERLAP); /* subset, not an exact repeat */
|
||||
AKERR_CHECK_RAISES(akerr_reserve_status_range(501, 1, "endpoint"),
|
||||
AKERR_STATUS_RANGE_OVERLAP);
|
||||
AKERR_CHECK_RAISES(akerr_reserve_status_range(500, 2, "other"),
|
||||
AKERR_STATUS_RANGE_OVERLAP); /* same range, wrong owner */
|
||||
|
||||
/* Claim room for the name-exhaustion sweep before filling the range table. */
|
||||
AKERR_CHECK(akerr_reserve_status_range(2000000, 100000, "fill") ==
|
||||
AKERR_STATUS_RANGE_OK);
|
||||
AKERR_CHECK_SUCCEEDS(akerr_reserve_status_range(2000000, 100000, "fill"));
|
||||
|
||||
/*
|
||||
* Range table capacity. The limit is private to src/error.c on purpose, so
|
||||
* discover it by filling rather than by hardcoding it here.
|
||||
*/
|
||||
akerr_capture_reset();
|
||||
int ranges_added = 0;
|
||||
int range_rc = AKERR_STATUS_RANGE_OK;
|
||||
akerr_ErrorContext *range_err = NULL;
|
||||
for ( int i = 0; i < 100000; i++ ) {
|
||||
range_rc = akerr_reserve_status_range(1000 + (i * 2), 1, "pad");
|
||||
if ( range_rc != AKERR_STATUS_RANGE_OK ) {
|
||||
range_err = akerr_reserve_status_range(1000 + (i * 2), 1, "pad");
|
||||
if ( range_err != NULL ) {
|
||||
break;
|
||||
}
|
||||
ranges_added++;
|
||||
}
|
||||
AKERR_CHECK(ranges_added > 0);
|
||||
AKERR_CHECK(range_rc == AKERR_STATUS_RANGE_FULL);
|
||||
AKERR_CHECK_CONTAINS("range table is full");
|
||||
AKERR_CHECK_STATUS(range_err, AKERR_STATUS_RANGE_FULL);
|
||||
AKERR_CHECK(strstr(range_err->message, "range table is full") != NULL);
|
||||
RELEASE_ERROR(range_err);
|
||||
AKERR_CHECK(range_err == NULL);
|
||||
|
||||
/*
|
||||
* Name table capacity. Exhaustion must be reported, not silent: a dropped
|
||||
* name degrades every future stack trace for that code to "Unknown Error".
|
||||
*/
|
||||
akerr_capture_reset();
|
||||
int full_at = -1;
|
||||
for ( int i = 0; i < 100000; i++ ) {
|
||||
char name[32];
|
||||
snprintf(name, sizeof(name), "Filled %d", i);
|
||||
int rc = akerr_register_status_name("fill", 2000000 + i, name);
|
||||
if ( rc != AKERR_STATUS_NAME_OK ) {
|
||||
AKERR_CHECK(rc == AKERR_STATUS_NAME_FULL);
|
||||
akerr_ErrorContext *name_err =
|
||||
akerr_register_status_name("fill", 2000000 + i, name);
|
||||
if ( name_err != NULL ) {
|
||||
AKERR_CHECK_STATUS(name_err, AKERR_STATUS_NAME_FULL);
|
||||
AKERR_CHECK(strstr(name_err->message, "registry is full") != NULL);
|
||||
AKERR_CHECK(strstr(name_err->message, "AKERR_STATUS_NAME_SLOTS") != NULL);
|
||||
RELEASE_ERROR(name_err);
|
||||
AKERR_CHECK(name_err == NULL);
|
||||
full_at = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
AKERR_CHECK_CONTAINS("registry is full");
|
||||
AKERR_CHECK_CONTAINS("AKERR_STATUS_NAME_SLOTS");
|
||||
|
||||
/*
|
||||
* The table must actually hold everything it accepted. A probe sequence
|
||||
@@ -154,6 +157,9 @@ int main(void)
|
||||
"Unknown Error") == 0);
|
||||
AKERR_CHECK(strcmp(akerr_name_for_status(INT_MIN, NULL), "Minimum Status") == 0);
|
||||
|
||||
/* Thousands of refusals later, every context went back to the pool. */
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
|
||||
fprintf(stderr, "err_maxval ok (%d consumer names before full)\n", full_at);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user