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 */ /* Buffers */
#define STK_LOG_PREFIX_BUFFER 64 #define STK_LOG_PREFIX_BUFFER 64
#define STK_MOD_DEP_OPERATOR_BUFFER 3 #define STK_MOD_DEP_OPERATOR_BUFFER 3
#define STK_MOD_DEP_LOG_BUFFER 2048
#define STK_MOD_DESC_BUFFER 256 #define STK_MOD_DESC_BUFFER 256
#define STK_MOD_DIR_BUFFER 256 #define STK_MOD_DIR_BUFFER 256
#define STK_MOD_ID_BUFFER 64 #define STK_MOD_ID_BUFFER 64
+117
View File
@@ -206,8 +206,18 @@ unsigned char stk_validate_dependencies(size_t count)
return result; 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); 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, static unsigned char stk_kahn_sort(size_t count, size_t *order,
stk_dep_query_fn has_dep, void *ctx, stk_dep_query_fn has_dep, void *ctx,
void (*on_cycle)(size_t i)) void (*on_cycle)(size_t i))
@@ -472,6 +482,87 @@ unsigned char stk_validate_dependencies_single(size_t index)
return STK_MOD_INIT_SUCCESS; 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 stk_module_load(const char *path, int index)
{ {
unsigned char result; unsigned char result;
@@ -631,6 +722,11 @@ static int stk_batch_has_dep(size_t i, size_t j, void *ctx)
return result; 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, void stk_sort_load_order(int *file_indices, size_t n,
char (*file_names)[STK_PATH_MAX], const char *tmp_dir) char (*file_names)[STK_PATH_MAX], const char *tmp_dir)
{ {
@@ -665,6 +761,12 @@ cleanup:
free(result); 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) void stk_collect_dependents(size_t *indices, size_t *count)
{ {
size_t i, d; size_t i, d;
@@ -673,6 +775,7 @@ void stk_collect_dependents(size_t *indices, size_t *count)
do { do {
changed = 0; changed = 0;
for (i = 0; i < module_count; i++) { for (i = 0; i < module_count; i++) {
/* skip if already in the set */
in_set = 0; in_set = 0;
{ {
size_t k; size_t k;
@@ -686,6 +789,7 @@ void stk_collect_dependents(size_t *indices, size_t *count)
if (in_set) if (in_set)
continue; continue;
/* check if any of its deps are in the set */
for (d = 0; d < stk_modules[i].dep_count; d++) { for (d = 0; d < stk_modules[i].dep_count; d++) {
int dep_idx = int dep_idx =
is_mod_loaded(stk_modules[i].deps[d].id); 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); } 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) void stk_sort_unload_order(size_t *indices, size_t n)
{ {
size_t *topo = NULL; 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) if (stk_topo_sort(module_count, topo) != STK_MOD_INIT_SUCCESS)
goto fallback; goto fallback;
/*
* topo[] is dependencies-first. Walk it in reverse to get
* dependents-first, picking only indices that are in our set.
*/
k = 0; k = 0;
for (i = module_count; i > 0; --i) { for (i = module_count; i > 0; --i) {
size_t mod = topo[i - 1]; size_t mod = topo[i - 1];
@@ -753,6 +866,7 @@ void stk_sort_unload_order(size_t *indices, size_t n)
fallback: fallback:
free(topo); free(topo);
free(result); free(result);
/* reverse index order: higher indices (dependents) first */
for (i = 0; i < n / 2; i++) { for (i = 0; i < n / 2; i++) {
size_t tmp = indices[i]; size_t tmp = indices[i];
indices[i] = indices[n - 1 - i]; indices[i] = indices[n - 1 - i];
@@ -777,6 +891,7 @@ void stk_module_unload_all(void)
stk_module_unload(order[i]); stk_module_unload(order[i]);
free(order); free(order);
} else { } else {
/* fallback: reverse index order */
for (i = module_count; i > 0; --i) for (i = module_count; i > 0; --i)
stk_module_unload(i - 1); 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) if (!paths || count == 0)
return; return;
/* First pass: overwrite existing entries and count truly new ones */
new_count = 0; new_count = 0;
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
int found = 0; 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); free(stk_pending);
stk_pending = new_pending; stk_pending = new_pending;
/* Second pass: append only the truly new ones */
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
int found = 0; int found = 0;
extract_module_id(paths[i], incoming_id); extract_module_id(paths[i], incoming_id);
+48 -60
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_preload(const char *path, int index);
unsigned char stk_module_activate(size_t index); unsigned char stk_module_activate(size_t index);
unsigned char stk_validate_dependencies_single(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); void stk_module_discard(size_t index);
unsigned char stk_module_load(const char *path, int index); unsigned char stk_module_load(const char *path, int index);
unsigned char stk_module_load_init(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) unsigned char stk_init(void)
{ {
char (*files)[STK_PATH_MAX] = NULL; 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 full_path[STK_PATH_MAX_OS];
char tmp_path[STK_PATH_MAX_OS]; char tmp_path[STK_PATH_MAX_OS];
char mod_tmp_path[STK_PATH_MAX_OS];
int load_result; int load_result;
unsigned char dep_result; unsigned char dep_result;
size_t *order = NULL; size_t *order = NULL;
@@ -140,9 +144,6 @@ unsigned char stk_init(void)
platform_mkdir(stk_mod_dir); platform_mkdir(stk_mod_dir);
build_path(stk_tmp_dir, sizeof(stk_tmp_dir), stk_mod_dir, stk_tmp_name); 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) { if (platform_mkdir(stk_tmp_dir) != STK_PLATFORM_OPERATION_SUCCESS) {
char (*test_scan)[STK_PATH_MAX];
size_t test_count;
test_scan = test_scan =
platform_directory_init_scan(stk_tmp_dir, &test_count); platform_directory_init_scan(stk_tmp_dir, &test_count);
if (test_scan) if (test_scan)
@@ -197,61 +198,50 @@ unsigned char stk_init(void)
if (module_count == 0) if (module_count == 0)
goto scanned; goto scanned;
{ order = malloc(module_count * sizeof(size_t));
size_t j, write; if (order) {
char mod_tmp_path[STK_PATH_MAX_OS]; dep_result = stk_topo_sort(module_count, order);
if (dep_result != STK_MOD_INIT_SUCCESS)
order = malloc(module_count * sizeof(size_t)); stk_log(STK_LOG_ERROR, "Dependency sort failed: %s",
if (order) { stk_error_string(dep_result));
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_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);
if (dep_result != STK_MOD_INIT_SUCCESS) {
build_path(mod_tmp_path, sizeof(mod_tmp_path),
stk_tmp_dir, stk_modules[idx].id);
strncat(mod_tmp_path, STK_MODULE_EXT,
sizeof(mod_tmp_path) -
strlen(mod_tmp_path) - 1);
stk_pending_add(mod_tmp_path);
stk_module_discard(idx);
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 (order) {
free(order);
order = NULL;
}
write = 0;
for (j = 0; j < module_count; j++) {
if (stk_modules[j].handle != NULL) {
if (write != j)
stk_modules[write] = stk_modules[j];
write++;
}
}
module_count = write;
} }
for (j = 0; j < module_count; j++) {
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[index].id);
strncat(mod_tmp_path, STK_MODULE_EXT,
sizeof(mod_tmp_path) - strlen(mod_tmp_path) -
1);
stk_pending_add(mod_tmp_path);
stk_module_discard(index);
continue;
}
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);
}
}
if (order) {
free(order);
order = NULL;
}
write = 0;
for (j = 0; j < module_count; j++) {
if (stk_modules[j].handle != NULL) {
if (write != j)
stk_modules[write] = stk_modules[j];
write++;
}
}
module_count = write;
scanned: scanned:
watch_handle = platform_directory_watch_start(stk_mod_dir); watch_handle = platform_directory_watch_start(stk_mod_dir);
if (!watch_handle) { if (!watch_handle) {
@@ -581,9 +571,7 @@ validate_deps:
for (j = 0; j < cascade_count; j++) { for (j = 0; j < cascade_count; j++) {
index = cascade_indices[j]; index = cascade_indices[j];
stk_log(STK_LOG_WARN, stk_log_dependency_failures(index, "Unloading");
"Unloading '%s': unmet dependencies",
stk_modules[index].id);
build_path(cascade_tmp_path, sizeof(cascade_tmp_path), build_path(cascade_tmp_path, sizeof(cascade_tmp_path),
stk_tmp_dir, stk_modules[index].id); stk_tmp_dir, stk_modules[index].id);
strncat(cascade_tmp_path, STK_MODULE_EXT, strncat(cascade_tmp_path, STK_MODULE_EXT,