Document and test handing an error context between threads
The thread-safety section filed two different things under "does not cover, and cannot": sharing a context between threads, and passing one to another thread. Only the first is unsupported. Transfer already works by construction -- the reference count is the only field the library reads across an ownership boundary, and it is only ever touched under the pool lock, so akerr_release_error() does not care which thread checked the slot out. The pool is process-global, not thread-local, so a context outlives the thread that raised it. Calling that unsupported told readers the worker/collector shape was off the table, which either cost them the pattern or cost them the stack trace when they rolled their own struct instead. Split the bullet: transfer joins the covered list and gets its own section with the rule, the worked pattern, and the four receiving-side hazards (PREPARE_ERROR cannot adopt, CATCH assigns over the pointer, FINISH in a void helper still parses its return, and an unhandled error now terminates from the collector's thread). Sharing keeps the "cannot" bullet, narrowed to what it actually is. err_threads_handoff.c proves it: the existing thread tests all keep every context on the thread that raised it, so the transfer path was exercised nowhere. Seven producers hand errors to one collector through a bounded mutex/condvar queue -- the mutex is the thing under test, since it is what publishes the unlocked content writes -- and the collector asserts the context is still a live slot at refcount 1, that message and trace arrive whole and in each producer's order, that the slot was never recycled in flight, and that a thread which never called akerr_next_error() can release it. A second phase reads a context whose raising thread has already exited. Also document why copying a context by assignment is silently wrong: stacktracebufptr is self-referential, so the copy's cursor points into the source's buffer and the first append corrupts a slot the copier no longer owns. TODO.md records the akerr_copy_error() shape that would fix it and the trigger for building it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -159,6 +159,11 @@ typedef struct
|
||||
typedef void (*akerr_ErrorUnhandledErrorHandler)(akerr_ErrorContext *errctx);
|
||||
typedef void (*akerr_ErrorLogFunction)(const char *f, ...);
|
||||
|
||||
/*
|
||||
* The pool. Process-global, not thread-local: a context outlives the thread that
|
||||
* raised it, which is what lets one be handed to another thread and released
|
||||
* there.
|
||||
*/
|
||||
extern akerr_ErrorContext AKERR_ARRAY_ERROR[AKERR_MAX_ARRAY_ERROR];
|
||||
/*
|
||||
* Set these before starting threads. They are read on every error and written
|
||||
@@ -174,6 +179,17 @@ extern akerr_ErrorLogFunction akerr_log_method;
|
||||
*/
|
||||
extern AKERR_THREAD_LOCAL akerr_ErrorContext *__akerr_last_ignored;
|
||||
|
||||
/*
|
||||
* Drop one reference, returning NULL once the last one is gone so the caller can
|
||||
* null its own pointer.
|
||||
*
|
||||
* This need not be the thread that checked the context out. The reference count
|
||||
* is the only field the library reads across threads, and it is only ever
|
||||
* touched under the pool lock, so a context handed to another thread is released
|
||||
* there. Exactly once, though: releasing a stale pointer takes the
|
||||
* refcount-zero branch a second time and wipes a slot that by then holds
|
||||
* somebody else's live error.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akerr_release_error(akerr_ErrorContext *ptr);
|
||||
/*
|
||||
* Check a context out of the pool. The returned context already carries one
|
||||
|
||||
Reference in New Issue
Block a user