No way to keep an error context and report it at the same time #11

Open
opened 2026-08-02 18:59:12 -04:00 by tachikoma · 0 comments
Collaborator

Source: TODO.md §9 (at 5695061)

A context can be handed to another thread and released there --
docs/thread-safety.md documents the pattern and tests/err_threads_handoff.c
proves it -- but it is a move. A thread that wants to both keep its error and
report it upward has to read the fields out into its own record, and it loses
the stack trace doing so
, because stacktracebuf is the one thing that cannot
be usefully summarized.

Copying the struct is not a workaround, and this is the part worth knowing
before anybody tries:

  • stacktracebufptr is self-referential, so akerr_ErrorContext c = *src; leaves
    the copy's cursor pointing into the source's buffer -- the copy logs correctly
    and then corrupts a slot it does not own
    the first time anything appends to it.
  • arrayid is restored after the wipe in akerr_release_error()
    (src/error.c:395-398), so a copied id makes the destination impersonate the
    source's slot for the life of the process.

If this is ever worth an API, the shape is:

akerr_ErrorContext AKERR_NOIGNORE *akerr_copy_error(akerr_ErrorContext *source,
						    akerr_ErrorContext *destination);

taking the destination as a parameter rather than allocating it. An allocating
copy could fail on pool exhaustion, and reporting that failure needs a pool slot,
so it would have to abort -- adding a third exit() site to a library that
deliberately has two. Caller-allocates puts the pool pressure where it can be
managed.

The copy must repair four fields: arrayid (the destination's own), refcount
(set to 1, never inherited), handled (false -- a copy is a fresh obligation, or
FINISH_NORETURN on the receiving side drops it silently), and stacktracebufptr
(re-anchored to the destination's buffer at the same offset, so a later append
continues the trace instead of overwriting it).

Not worth building yet: no consumer needs it. The trigger is a consumer that
needs a worker's stack trace, not just its status and message, at the join point
-- libakstdlib's planned pthread_* wrappers are the likely first.

Files: include/akerror.tmpl.h, src/error.c:395-398


Filed by Tachikoma (Claude Code, Opus 5, 1M context)

**Source:** TODO.md §9 (at 5695061) A context can be handed to another thread and released there -- `docs/thread-safety.md` documents the pattern and `tests/err_threads_handoff.c` proves it -- **but it is a *move*.** A thread that wants to both keep its error and report it upward has to read the fields out into its own record, **and it loses the stack trace doing so**, because `stacktracebuf` is the one thing that cannot be usefully summarized. **Copying the struct is not a workaround**, and this is the part worth knowing before anybody tries: - `stacktracebufptr` is self-referential, so `akerr_ErrorContext c = *src;` leaves the copy's cursor pointing into the source's buffer -- **the copy logs correctly and then corrupts a slot it does not own** the first time anything appends to it. - `arrayid` is restored after the wipe in `akerr_release_error()` (`src/error.c:395-398`), so **a copied id makes the destination impersonate the source's slot for the life of the process.** If this is ever worth an API, the shape is: ```c akerr_ErrorContext AKERR_NOIGNORE *akerr_copy_error(akerr_ErrorContext *source, akerr_ErrorContext *destination); ``` **taking the destination as a parameter rather than allocating it.** An allocating copy could fail on pool exhaustion, and reporting *that* failure needs a pool slot, so it would have to abort -- adding a third `exit()` site to a library that deliberately has two. Caller-allocates puts the pool pressure where it can be managed. The copy must repair four fields: `arrayid` (the destination's own), `refcount` (set to 1, never inherited), `handled` (false -- a copy is a fresh obligation, or `FINISH_NORETURN` on the receiving side drops it silently), and `stacktracebufptr` (re-anchored to the destination's buffer **at the same offset**, so a later append continues the trace instead of overwriting it). **Not worth building yet: no consumer needs it.** The trigger is a consumer that needs a worker's stack *trace*, not just its status and message, at the join point -- `libakstdlib`'s planned `pthread_*` wrappers are the likely first. **Files:** `include/akerror.tmpl.h`, `src/error.c:395-398` --- Filed by Tachikoma (Claude Code, Opus 5, 1M context)
tachikoma added this to the 2.1.0 milestone 2026-08-02 18:59:12 -04:00
tachikoma added the api-gapdesign-decisionblast-radius:lowstatus::grooming labels 2026-08-02 18:59:12 -04:00
Sign in to join this conversation.