feat(stk.h, module, stk): improve dependency failure logging

- Add STK_MOD_DEP_LOG_BUFFER (2048) to stk.h for the dep failure message
buffer size.

- Add stk_log_dependency_failures(index, action) to module.c. Walks all
deps for the given module, skips satisfied ones, and builds a single
log line listing every unmet dep with its reason: "not found" or
"requires <constraint>, have <version>". The action parameter
("Deferring" / "Unloading") lets call sites produce contextually
appropriate messages:

  - Deferring 'test_mod_dep': unmet deps: test_mod (not found)
  - Unloading 'test_mod_dep': unmet deps: test_mod (not found), renderer (requires ^2.0.0, have 1.3.0)

- Replace the silent defer at init and the vague "unmet dependencies"
cascade log in stk_poll() with calls to stk_log_dependency_failures().
This commit is contained in:
2026-03-07 11:34:23 +01:00
parent 42a96f2bc0
commit 4250f91969
3 changed files with 166 additions and 60 deletions
+1
View File
@@ -7,6 +7,7 @@
/* Buffers */
#define STK_LOG_PREFIX_BUFFER 64
#define STK_MOD_DEP_OPERATOR_BUFFER 3
#define STK_MOD_DEP_LOG_BUFFER 2048
#define STK_MOD_DESC_BUFFER 256
#define STK_MOD_DIR_BUFFER 256
#define STK_MOD_ID_BUFFER 64
+117
View File
@@ -206,8 +206,18 @@ unsigned char stk_validate_dependencies(size_t count)
return result;
}
/*
* has_dep(i, j, ctx): return 1 if node i depends on node j, 0 otherwise.
* Used by stk_kahn_sort to abstract over loaded modules vs incoming batch.
*/
typedef int (*stk_dep_query_fn)(size_t i, size_t j, void *ctx);
/*
* Core Kahn's topological sort. Produces dependencies-first ordering.
* on_cycle is called for each node not reachable (i.e. in a cycle).
* Returns STK_MOD_INIT_SUCCESS or STK_MOD_DEP_CIRCULAR_ERROR /
* STK_MOD_REALLOC_FAILURE.
*/
static unsigned char stk_kahn_sort(size_t count, size_t *order,
stk_dep_query_fn has_dep, void *ctx,
void (*on_cycle)(size_t i))
@@ -472,6 +482,87 @@ unsigned char stk_validate_dependencies_single(size_t index)
return STK_MOD_INIT_SUCCESS;
}
void stk_log_dependency_failures(size_t index, const char *action)
{
char buf[STK_MOD_DEP_LOG_BUFFER];
size_t d, pos, len;
int found;
int first = 1;
if (stk_modules[index].dep_count == 0)
return;
pos = 0;
for (d = 0; d < stk_modules[index].dep_count; d++) {
found = is_mod_loaded(stk_modules[index].deps[d].id);
if (found >= 0 &&
(!stk_modules[index].deps[d].version[0] ||
stk_validate_constraint(stk_modules[index].deps[d].version,
stk_modules[found].version)))
continue;
if (!first && pos < sizeof(buf) - 2) {
buf[pos++] = ',';
buf[pos++] = ' ';
}
first = 0;
if (found < 0) {
len = strlen(stk_modules[index].deps[d].id);
if (pos + len + 12 < sizeof(buf)) {
memcpy(buf + pos, stk_modules[index].deps[d].id,
len);
pos += len;
memcpy(buf + pos, " (not found)", 12);
pos += 12;
}
} else {
len = strlen(stk_modules[index].deps[d].id);
if (pos + len < sizeof(buf)) {
memcpy(buf + pos, stk_modules[index].deps[d].id,
len);
pos += len;
}
if (pos < sizeof(buf) - 1) {
buf[pos++] = ' ';
}
buf[pos++] = '(';
len = strlen("requires ");
if (pos + len < sizeof(buf)) {
memcpy(buf + pos, "requires ", len);
pos += len;
}
len = strlen(stk_modules[index].deps[d].version);
if (pos + len < sizeof(buf)) {
memcpy(buf + pos,
stk_modules[index].deps[d].version, len);
pos += len;
}
len = strlen(", have ");
if (pos + len < sizeof(buf)) {
memcpy(buf + pos, ", have ", len);
pos += len;
}
len = strlen(stk_modules[found].version);
if (pos + len < sizeof(buf)) {
memcpy(buf + pos, stk_modules[found].version,
len);
pos += len;
}
if (pos < sizeof(buf) - 1)
buf[pos++] = ')';
}
}
if (first)
return;
buf[pos] = '\0';
stk_log(STK_LOG_WARN, "%s '%s': unmet deps: %s", action,
stk_modules[index].id, buf);
}
unsigned char stk_module_load(const char *path, int index)
{
unsigned char result;
@@ -631,6 +722,11 @@ static int stk_batch_has_dep(size_t i, size_t j, void *ctx)
return result;
}
/*
* Sort file_indices[] (length n) so that within the batch, modules whose ids
* appear as dependencies of others come first. Uses already-copied tmp files
* to read dep symbols. Falls back to original order if sort fails.
*/
void stk_sort_load_order(int *file_indices, size_t n,
char (*file_names)[STK_PATH_MAX], const char *tmp_dir)
{
@@ -665,6 +761,12 @@ cleanup:
free(result);
}
/*
* Expand indices[0..]*in_count to include all loaded modules that transitively
* depend on any module already in the set. Writes the expanded set back into
* indices[] and updates *out_count. indices[] must have capacity module_count.
* Returns 1 if any new dependents were added, 0 otherwise.
*/
void stk_collect_dependents(size_t *indices, size_t *count)
{
size_t i, d;
@@ -673,6 +775,7 @@ void stk_collect_dependents(size_t *indices, size_t *count)
do {
changed = 0;
for (i = 0; i < module_count; i++) {
/* skip if already in the set */
in_set = 0;
{
size_t k;
@@ -686,6 +789,7 @@ void stk_collect_dependents(size_t *indices, size_t *count)
if (in_set)
continue;
/* check if any of its deps are in the set */
for (d = 0; d < stk_modules[i].dep_count; d++) {
int dep_idx =
is_mod_loaded(stk_modules[i].deps[d].id);
@@ -708,6 +812,11 @@ void stk_collect_dependents(size_t *indices, size_t *count)
} while (changed);
}
/*
* Sort indices[] (length n) so that dependents come before their dependencies.
* This is the reverse of topological order. indices[] is sorted in-place.
* Falls back to reverse-index order if topo sort fails or alloc fails.
*/
void stk_sort_unload_order(size_t *indices, size_t n)
{
size_t *topo = NULL;
@@ -727,6 +836,10 @@ void stk_sort_unload_order(size_t *indices, size_t n)
if (stk_topo_sort(module_count, topo) != STK_MOD_INIT_SUCCESS)
goto fallback;
/*
* topo[] is dependencies-first. Walk it in reverse to get
* dependents-first, picking only indices that are in our set.
*/
k = 0;
for (i = module_count; i > 0; --i) {
size_t mod = topo[i - 1];
@@ -753,6 +866,7 @@ void stk_sort_unload_order(size_t *indices, size_t n)
fallback:
free(topo);
free(result);
/* reverse index order: higher indices (dependents) first */
for (i = 0; i < n / 2; i++) {
size_t tmp = indices[i];
indices[i] = indices[n - 1 - i];
@@ -777,6 +891,7 @@ void stk_module_unload_all(void)
stk_module_unload(order[i]);
free(order);
} else {
/* fallback: reverse index order */
for (i = module_count; i > 0; --i)
stk_module_unload(i - 1);
}
@@ -828,6 +943,7 @@ void stk_pending_add_batch(const char (*paths)[STK_PATH_MAX_OS], size_t count)
if (!paths || count == 0)
return;
/* First pass: overwrite existing entries and count truly new ones */
new_count = 0;
for (i = 0; i < count; i++) {
int found = 0;
@@ -863,6 +979,7 @@ void stk_pending_add_batch(const char (*paths)[STK_PATH_MAX_OS], size_t count)
free(stk_pending);
stk_pending = new_pending;
/* Second pass: append only the truly new ones */
for (i = 0; i < count; i++) {
int found = 0;
extract_module_id(paths[i], incoming_id);
+18 -30
View File
@@ -48,6 +48,7 @@ size_t stk_module_count(void);
unsigned char stk_module_preload(const char *path, int index);
unsigned char stk_module_activate(size_t index);
unsigned char stk_validate_dependencies_single(size_t index);
void stk_log_dependency_failures(size_t index, const char *action);
void stk_module_discard(size_t index);
unsigned char stk_module_load(const char *path, int index);
unsigned char stk_module_load_init(const char *path, int index);
@@ -130,9 +131,12 @@ static void stk_log_modules(void)
unsigned char stk_init(void)
{
char (*files)[STK_PATH_MAX] = NULL;
size_t file_count, i, successful_loads = 0;
char (*test_scan)[STK_PATH_MAX];
size_t file_count, i, j, write, successful_loads = 0;
size_t index, test_count;
char full_path[STK_PATH_MAX_OS];
char tmp_path[STK_PATH_MAX_OS];
char mod_tmp_path[STK_PATH_MAX_OS];
int load_result;
unsigned char dep_result;
size_t *order = NULL;
@@ -140,9 +144,6 @@ unsigned char stk_init(void)
platform_mkdir(stk_mod_dir);
build_path(stk_tmp_dir, sizeof(stk_tmp_dir), stk_mod_dir, stk_tmp_name);
if (platform_mkdir(stk_tmp_dir) != STK_PLATFORM_OPERATION_SUCCESS) {
char (*test_scan)[STK_PATH_MAX];
size_t test_count;
test_scan =
platform_directory_init_scan(stk_tmp_dir, &test_count);
if (test_scan)
@@ -197,42 +198,32 @@ unsigned char stk_init(void)
if (module_count == 0)
goto scanned;
{
size_t j, write;
char mod_tmp_path[STK_PATH_MAX_OS];
order = malloc(module_count * sizeof(size_t));
if (order) {
dep_result = stk_topo_sort(module_count, order);
if (dep_result != STK_MOD_INIT_SUCCESS)
stk_log(STK_LOG_ERROR,
"Dependency sort failed: %s",
stk_log(STK_LOG_ERROR, "Dependency sort failed: %s",
stk_error_string(dep_result));
} else {
order = malloc(module_count * sizeof(size_t));
if (order)
for (j = 0; j < module_count; j++)
order[j] = j;
}
for (j = 0; j < module_count; j++) {
size_t idx = order ? order[j] : j;
dep_result = stk_validate_dependencies_single(idx);
index = order ? order[j] : j;
dep_result = stk_validate_dependencies_single(index);
if (dep_result != STK_MOD_INIT_SUCCESS) {
stk_log_dependency_failures(index, "Deferring");
build_path(mod_tmp_path, sizeof(mod_tmp_path),
stk_tmp_dir, stk_modules[idx].id);
stk_tmp_dir, stk_modules[index].id);
strncat(mod_tmp_path, STK_MODULE_EXT,
sizeof(mod_tmp_path) -
strlen(mod_tmp_path) - 1);
sizeof(mod_tmp_path) - strlen(mod_tmp_path) -
1);
stk_pending_add(mod_tmp_path);
stk_module_discard(idx);
stk_module_discard(index);
continue;
}
if (stk_module_activate(idx) != STK_MOD_INIT_SUCCESS) {
stk_log(STK_LOG_ERROR,
"Failed to init module %s",
stk_modules[idx].id);
stk_module_discard(idx);
if (stk_module_activate(index) != STK_MOD_INIT_SUCCESS) {
stk_log(STK_LOG_ERROR, "Failed to init module %s",
stk_modules[index].id);
stk_module_discard(index);
}
}
@@ -250,7 +241,6 @@ unsigned char stk_init(void)
}
}
module_count = write;
}
scanned:
watch_handle = platform_directory_watch_start(stk_mod_dir);
@@ -581,9 +571,7 @@ validate_deps:
for (j = 0; j < cascade_count; j++) {
index = cascade_indices[j];
stk_log(STK_LOG_WARN,
"Unloading '%s': unmet dependencies",
stk_modules[index].id);
stk_log_dependency_failures(index, "Unloading");
build_path(cascade_tmp_path, sizeof(cascade_tmp_path),
stk_tmp_dir, stk_modules[index].id);
strncat(cascade_tmp_path, STK_MODULE_EXT,