Release ignored error contexts #21
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
# 1.0.0 replaced the consumer-sized __AKERR_ERROR_NAMES array with private
|
||||
# storage. 2.0.0 makes the library thread safe, which is a second ABI break in
|
||||
# the same places: __akerr_last_ignored became thread-local storage, and
|
||||
# the same places: akerr_last_ignored became thread-local storage, and
|
||||
# ENSURE_ERROR_READY no longer takes the pool reference that akerr_next_error()
|
||||
# now takes for it. Consumer code compiled against a 1.x header would
|
||||
# double-count every reference. Hence the major bump and the SOVERSION, so a
|
||||
|
||||
@@ -170,7 +170,7 @@ the exit code was the status truncated to a byte, and every consumer status
|
||||
starts at 256. Use `akerr_exit()` instead of `exit()` — see
|
||||
[docs/exit-status.md](docs/exit-status.md). No ABI break.
|
||||
|
||||
2.0.0 makes the library thread safe. That is an ABI break — `__akerr_last_ignored`
|
||||
2.0.0 makes the library thread safe. That is an ABI break — `akerr_last_ignored`
|
||||
became thread-local storage and the pool now takes its own reference — so
|
||||
everything built against a 1.x header must be rebuilt. 1.0.0 replaced the
|
||||
consumer-sized status-name array with a private, ownership-enforced registry.
|
||||
|
||||
@@ -52,7 +52,7 @@ accident.
|
||||
|
||||
What moved at the ABI:
|
||||
|
||||
* `__akerr_last_ignored` is thread-local storage. An ignored error is a fact
|
||||
* `akerr_last_ignored` is thread-local storage. An ignored error is a fact
|
||||
about the thread that ignored it, and one shared slot had two threads
|
||||
overwriting each other's. The `IGNORE` macro expands at *your* call site, so
|
||||
your objects reference the symbol under whichever storage model your header
|
||||
|
||||
@@ -15,7 +15,7 @@ What that covers:
|
||||
cannot both win — exactly one gets `NULL` and the other gets
|
||||
`AKERR_STATUS_RANGE_OVERLAP` naming the winner.
|
||||
* **Per-thread state.** `IGNORE` copies the swallowed context into its
|
||||
thread-local `__akerr_last_ignored` snapshot before releasing the pool slot.
|
||||
thread-local `akerr_last_ignored` snapshot before releasing the pool slot.
|
||||
The snapshot remains valid until that thread ignores another error, so a
|
||||
later pool checkout cannot overwrite it. The snapshot and the last-ditch
|
||||
context used to report `akerr_release_error(NULL)` are thread-local, so
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* scripts/generrno.sh stamps this value in at build time from the AKERR_THREADS
|
||||
* build option, the same way it stamps AKERR_LAST_ERRNO_VALUE. It is generated
|
||||
* rather than defined by the consumer on purpose: whether the library
|
||||
* serializes its global state and whether __akerr_last_ignored is a
|
||||
* serializes its global state and whether akerr_last_ignored is a
|
||||
* thread-local are the same decision, and a consumer that disagreed with the
|
||||
* library about it would link against a differently shaped symbol.
|
||||
*
|
||||
@@ -173,12 +173,12 @@ extern akerr_ErrorContext AKERR_ARRAY_ERROR[AKERR_MAX_ARRAY_ERROR];
|
||||
extern akerr_ErrorUnhandledErrorHandler akerr_handler_unhandled_error;
|
||||
extern akerr_ErrorLogFunction akerr_log_method;
|
||||
/*
|
||||
* IGNORE()'s per-thread snapshot. IGNORE() copies the swallowed error here
|
||||
* IGNORE()'s public per-thread snapshot. IGNORE() copies the swallowed error here
|
||||
* before releasing its pool context, so this remains a useful debugging aid
|
||||
* after the pool slot is reused. The snapshot is read-only and is replaced by
|
||||
* the next ignored error. Thread local only when AKERR_THREAD_SAFE is 1.
|
||||
*/
|
||||
static AKERR_THREAD_LOCAL akerr_ErrorContext __akerr_last_ignored;
|
||||
static AKERR_THREAD_LOCAL akerr_ErrorContext akerr_last_ignored;
|
||||
|
||||
/*
|
||||
* Drop one reference, returning NULL once the last one is gone so the caller can
|
||||
@@ -438,12 +438,12 @@ akerr_ErrorContext AKERR_NOIGNORE *__akerr_copy_string(char *destination, int ca
|
||||
do { \
|
||||
akerr_ErrorContext *__akerr_ignored = __stmt; \
|
||||
if ( __akerr_ignored != NULL ) { \
|
||||
memcpy(&__akerr_last_ignored, __akerr_ignored, \
|
||||
sizeof(__akerr_last_ignored)); \
|
||||
__akerr_last_ignored.stacktracebufptr = \
|
||||
(char *)&__akerr_last_ignored.stacktracebuf; \
|
||||
memcpy(&akerr_last_ignored, __akerr_ignored, \
|
||||
|
logikoma marked this conversation as resolved
Outdated
|
||||
sizeof(akerr_last_ignored)); \
|
||||
akerr_last_ignored.stacktracebufptr = \
|
||||
(char *)&akerr_last_ignored.stacktracebuf; \
|
||||
akerr_ErrorContext *__akerr_ignored_snapshot = \
|
||||
&__akerr_last_ignored; \
|
||||
&akerr_last_ignored; \
|
||||
LOG_ERROR_WITH_MESSAGE(__akerr_ignored_snapshot, \
|
||||
"** IGNORED ERROR **"); \
|
||||
RELEASE_ERROR(__akerr_ignored); \
|
||||
|
||||
@@ -107,7 +107,7 @@ The remaining survivors are dominated by:
|
||||
|
||||
* **Equivalent mutants** in `akerr_init`: deleting the `memset`/`NULL` setup of
|
||||
file-scope statics (`AKERR_ARRAY_ERROR`, `__akerr_last_ditch`,
|
||||
`__akerr_last_ignored`) changes nothing, because C already zero-initializes
|
||||
`akerr_last_ignored`) changes nothing, because C already zero-initializes
|
||||
objects with static storage duration. `int oldid = 0;` → `1` is likewise
|
||||
dead: it is overwritten before use, and so is clearing `akerr_initializing`
|
||||
at the end of initialization — nothing reads that flag once the once-routine
|
||||
|
||||
@@ -24,8 +24,8 @@ int main(void)
|
||||
* iteration. */
|
||||
for ( int i = 0; i < AKERR_MAX_ARRAY_ERROR + 1; i++ ) {
|
||||
IGNORE(boom());
|
||||
AKERR_CHECK(__akerr_last_ignored.status == AKERR_VALUE);
|
||||
AKERR_CHECK(strcmp(__akerr_last_ignored.message,
|
||||
AKERR_CHECK(akerr_last_ignored.status == AKERR_VALUE);
|
||||
AKERR_CHECK(strcmp(akerr_last_ignored.message,
|
||||
"this error is ignored on purpose") == 0);
|
||||
AKERR_CHECK(akerr_slots_in_use() == 0);
|
||||
}
|
||||
|
||||
@@ -103,8 +103,8 @@ static void *pool_body(void *raw)
|
||||
/* IGNORE's snapshot is thread-local while logging and remains valid after
|
||||
* release. Concurrent ignored errors must all return their pool slots. */
|
||||
IGNORE(ignorable(arg));
|
||||
AKERR_TCHECK(arg, __akerr_last_ignored.status == AKERR_IO);
|
||||
|
andrew marked this conversation as resolved
Outdated
andrew
commented
IGNORE() retained a reference to the last ignored error on purpose, so that subsequent errors that may be related could reference it. Instead of completely abandoning it (and the pattern that allows it), why not turn __akerr_last_ignored into a static variable, IGNORE() does a IGNORE() retained a reference to the last ignored error on purpose, so that subsequent errors that may be related could reference it. Instead of completely abandoning it (and the pattern that allows it), why not turn __akerr_last_ignored into a static variable, IGNORE() does a `memcpy()` into it from the exception being ignored, then releases the exception? That seems like it would give us the best of both worlds.
andrew
commented
@logikoma ^ implement this > IGNORE() retained a reference to the last ignored error on purpose, so that subsequent errors that may be related could reference it. Instead of completely abandoning it (and the pattern that allows it), why not turn __akerr_last_ignored into a static variable, IGNORE() does a `memcpy()` into it from the exception being ignored, then releases the exception? That seems like it would give us the best of both worlds.
@logikoma ^ implement this
|
||||
AKERR_TCHECK(arg, strcmp(__akerr_last_ignored.message, expected) == 0);
|
||||
AKERR_TCHECK(arg, akerr_last_ignored.status == AKERR_IO);
|
||||
AKERR_TCHECK(arg, strcmp(akerr_last_ignored.message, expected) == 0);
|
||||
|
||||
/* Reuse a slot after IGNORE and prove that the copied snapshot did not
|
||||
* become an alias for the newly acquired context. */
|
||||
@@ -113,8 +113,8 @@ static void *pool_body(void *raw)
|
||||
if ( reused != NULL ) {
|
||||
RELEASE_ERROR(reused);
|
||||
}
|
||||
AKERR_TCHECK(arg, __akerr_last_ignored.status == AKERR_IO);
|
||||
AKERR_TCHECK(arg, strcmp(__akerr_last_ignored.message, expected) == 0);
|
||||
AKERR_TCHECK(arg, akerr_last_ignored.status == AKERR_IO);
|
||||
AKERR_TCHECK(arg, strcmp(akerr_last_ignored.message, expected) == 0);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@logikoma the
__akerr_last_ignoredvariable is only really useful to users of the public facing API, so I'm not sure it makes sense to prefix it with__as part of the private API. Let's remove the__prefix on this variable and make it public.