Release the error context when a path resolves against its root
akgl_path_relative took its ENOENT branch by returning from inside the HANDLE block, which skips the RELEASE_ERROR that FINISH ends with. The handled context was never given back, so one entry of AKERR_ARRAY_ERROR was lost per call and the 129th call hit "Unable to pull an error context from the array!" and exited the process. That branch is not an edge case: every asset path inside a tilemap is resolved relative to the map's own directory, so it is taken several times per map load. A game that loaded fifty levels died in the loader. It was found by a benchmark that loaded the fixture map in a loop, which is the first thing in this tree to call it more than a hundred times. The fallback now runs after FINISH, so the context is released on the way past. tests/util.c resolves AKERR_MAX_ARRAY_ERROR * 2 paths through the branch and asserts the pool is where it started -- against the old code that test does not fail, it terminates the suite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
70
tests/util.c
70
tests/util.c
@@ -1,8 +1,30 @@
|
||||
#include <SDL3/SDL.h>
|
||||
#include <akerror.h>
|
||||
#include <akgl/error.h>
|
||||
#include <akgl/heap.h>
|
||||
#include <akgl/staticstring.h>
|
||||
#include <akgl/util.h>
|
||||
|
||||
/**
|
||||
* @brief How many entries of AKERR_ARRAY_ERROR are currently held by somebody.
|
||||
*
|
||||
* The error contexts are a fixed pool exactly like the object pools: a context
|
||||
* is in use while its reference count is non-zero, and a function that finishes
|
||||
* without releasing one has leaked a slot out of AKERR_MAX_ARRAY_ERROR.
|
||||
*/
|
||||
static int live_error_contexts(void)
|
||||
{
|
||||
int live = 0;
|
||||
int i = 0;
|
||||
|
||||
for ( i = 0; i < AKERR_MAX_ARRAY_ERROR; i++ ) {
|
||||
if ( AKERR_ARRAY_ERROR[i].refcount != 0 ) {
|
||||
live += 1;
|
||||
}
|
||||
}
|
||||
return live;
|
||||
}
|
||||
|
||||
akerr_ErrorContext *test_akgl_rectangle_points_nullpointers(void)
|
||||
{
|
||||
RectanglePoints points;
|
||||
@@ -306,6 +328,53 @@ akerr_ErrorContext *test_akgl_collide_rectangles_logic(void)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Resolving a path through the root fallback must give its context back.
|
||||
*
|
||||
* akgl_path_relative tries the working directory first and falls back to
|
||||
* resolving against @p root when that reports ENOENT. That fallback used to be
|
||||
* taken by returning from inside the HANDLE block, which skips the
|
||||
* RELEASE_ERROR that FINISH ends with -- so every call down that branch leaked
|
||||
* one entry of AKERR_ARRAY_ERROR, and the 129th call aborted the whole process
|
||||
* with "Unable to pull an error context from the array!". A single map load
|
||||
* resolves several paths this way.
|
||||
*
|
||||
* The loop runs well past AKERR_MAX_ARRAY_ERROR on purpose: at the old
|
||||
* behaviour this test does not fail, it terminates the suite.
|
||||
*/
|
||||
akerr_ErrorContext *test_akgl_path_relative_releases_contexts(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akgl_String *dst = NULL;
|
||||
int before = 0;
|
||||
int after = 0;
|
||||
int i = 0;
|
||||
|
||||
PASS(errctx, akgl_heap_init());
|
||||
PASS(errctx, akgl_heap_next_string(&dst));
|
||||
before = live_error_contexts();
|
||||
|
||||
for ( i = 0; i < (AKERR_MAX_ARRAY_ERROR * 2); i++ ) {
|
||||
PASS(errctx, akgl_path_relative("assets", "testcharacter.json", dst));
|
||||
}
|
||||
after = live_error_contexts();
|
||||
|
||||
ATTEMPT {
|
||||
if ( after != before ) {
|
||||
FAIL_BREAK(
|
||||
errctx,
|
||||
AKGL_ERR_BEHAVIOR,
|
||||
"akgl_path_relative leaked %d error context(s) over %d root-fallback resolutions",
|
||||
(after - before),
|
||||
(AKERR_MAX_ARRAY_ERROR * 2));
|
||||
}
|
||||
} CLEANUP {
|
||||
IGNORE(akgl_heap_release_string(dst));
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
@@ -316,6 +385,7 @@ int main(void)
|
||||
CATCH(errctx, test_akgl_collide_point_rectangle_nullpointers());
|
||||
CATCH(errctx, test_akgl_collide_rectangles_nullpointers());
|
||||
CATCH(errctx, test_akgl_collide_rectangles_logic());
|
||||
CATCH(errctx, test_akgl_path_relative_releases_contexts());
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH_NORETURN(errctx);
|
||||
|
||||
Reference in New Issue
Block a user