Enhance logging and task management; enable trace facility, add scheduler state logging, and improve shutdown handling

This commit is contained in:
Lukas Höppner
2026-08-12 14:12:43 +02:00
parent b623e5cf0a
commit b04ba14891
11 changed files with 187 additions and 119 deletions
+1 -1
View File
@@ -12,7 +12,7 @@
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 ) #define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 64 * 1024 ) ) #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 64 * 1024 ) )
#define configMAX_TASK_NAME_LEN ( 16 ) #define configMAX_TASK_NAME_LEN ( 16 )
#define configUSE_TRACE_FACILITY 0 #define configUSE_TRACE_FACILITY 1
#define configUSE_16_BIT_TICKS 0 #define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1 #define configIDLE_SHOULD_YIELD 1
+2
View File
@@ -7,4 +7,6 @@ void nova64_log(const char *format, ...);
const nova64_io_interface_t *nova64_get_io_interface(void); const nova64_io_interface_t *nova64_get_io_interface(void);
void nova64_log_scheduler_state(void);
#endif // NOVA64_INTERNAL_H #endif // NOVA64_INTERNAL_H
+3
View File
@@ -32,4 +32,7 @@ bool wasm_runner_start_program(
void wasm_runner_terminate(void); void wasm_runner_terminate(void);
uint32_t wasm_runner_get_active_slots
(void);
#endif // WASM_RUNNER_H #endif // WASM_RUNNER_H
+2 -4
View File
@@ -17,16 +17,14 @@ static void native_yield(wasm_exec_env_t exec_env, uint32_t timeout_ms) {
static void native_log(wasm_exec_env_t exec_env, const char *message) { static void native_log(wasm_exec_env_t exec_env, const char *message) {
(void)exec_env; (void)exec_env;
nova64_log("[WASM LOG] ");
const char *text = message ? message : ""; const char *text = message ? message : "";
size_t len = strlen(text); size_t len = strlen(text);
bool has_newline = len > 0 && text[len - 1] == '\n'; bool has_newline = len > 0 && text[len - 1] == '\n';
if (has_newline) { if (has_newline) {
nova64_log("%s", text); nova64_log("[WASM LOG] %s", text);
} else { } else {
nova64_log("%s\n", text); nova64_log("[WASM LOG] %s\n", text);
} }
} }
+113 -99
View File
@@ -6,6 +6,7 @@
#include "wasm_runner.h" #include "wasm_runner.h"
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#if defined(ESP_PLATFORM) #if defined(ESP_PLATFORM)
#include "esp_pm.h" #include "esp_pm.h"
@@ -19,19 +20,23 @@
#include <time.h> #include <time.h>
#endif #endif
static bool is_initialized = false; #if defined(_WIN32)
#include <windows.h>
#define nova64_host_sleep_ms(ms) Sleep(ms)
#elif defined(__unix__) || defined(__APPLE__)
#include <unistd.h>
#define nova64_host_sleep_ms(ms) usleep((ms) * 1000)
#else
#define nova64_host_sleep_ms(ms) ((void)0)
#endif
volatile bool is_initialized = false;
volatile bool shutdown_requested = false;
static nova64_io_interface_t g_io_interface_storage; static nova64_io_interface_t g_io_interface_storage;
static const nova64_io_interface_t *g_io_interface = NULL; static const nova64_io_interface_t *g_io_interface = NULL;
static QueueHandle_t event_queue = NULL; static QueueHandle_t event_queue = NULL;
static TaskHandle_t boot_task_handle = NULL;
static TaskHandle_t init_task_handle = NULL;
static TaskHandle_t heartbeat_task_handle = NULL; static TaskHandle_t heartbeat_task_handle = NULL;
#if defined(NOVA64_SHUTDOWN_AVAILABLE)
static TaskHandle_t shutdown_task_handle = NULL;
volatile bool shutdown_requested = false;
#endif
static const char *const kInternalInitPath = "SYS:/init.wasm"; static const char *const kInternalInitPath = "SYS:/init.wasm";
#if defined(_WIN32) #if defined(_WIN32)
#include <windows.h> #include <windows.h>
@@ -92,16 +97,16 @@ static bool initialize_wasm_runtime(void) {
return true; return true;
} }
static void start_init_from_internal_storage(void *params) { static void nova64_init_task(void *params) {
(void)params; (void)params;
wasm_runner_start_program(kInternalInitPath, NULL); wasm_runner_start_program(kInternalInitPath, NULL);
} }
static bool start_init_task(void) { static bool start_init_task(void) {
BaseType_t result = BaseType_t result =
xTaskCreate(start_init_from_internal_storage, "Nova64Init", xTaskCreate(nova64_init_task, "Nova64Init", 4096 / sizeof(StackType_t),
4096 / sizeof(StackType_t), NULL, tskIDLE_PRIORITY + 1, NULL, tskIDLE_PRIORITY + 1,
&init_task_handle); /*&init_task_handle*/ NULL);
if (result != pdPASS) { if (result != pdPASS) {
nova64_log("Failed to create init task.\n"); nova64_log("Failed to create init task.\n");
@@ -112,98 +117,31 @@ static bool start_init_task(void) {
return true; return true;
} }
#if defined(NOVA64_SHUTDOWN_AVAILABLE) void nova64_heartbeat_task(void *pvParameters) {
static void nova64_shutdown_task(void *params) {
(void)params;
nova64_log("Shutdown task started.\n");
shutdown_requested = false;
for (;;) {
ulTaskNotifyTake(pdTRUE,
pdMS_TO_TICKS(50)); // Wait for notification or timeout
if (shutdown_requested) {
break;
}
}
nova64_log("Shutdown requested; terminating WASM runtime.\n");
wasm_runner_terminate();
if (heartbeat_task_handle != NULL) {
vTaskDelete(heartbeat_task_handle);
heartbeat_task_handle = NULL;
}
vTaskDelay(pdMS_TO_TICKS(100)); // Allow time for tasks to clean up
shutdown_task_handle = NULL;
#if defined(_WIN32)
ExitThread(0);
#elif defined(__unix__) || defined(__APPLE__)
pthread_exit(NULL);
#else
vTaskDelete(NULL);
#endif
}
static bool start_shutdown_task(void) {
BaseType_t result = xTaskCreate(nova64_shutdown_task, "Nova64Shutdown",
2048 / sizeof(StackType_t), NULL,
tskIDLE_PRIORITY + 2, &shutdown_task_handle);
if (result != pdPASS) {
nova64_log("Failed to create shutdown task.\n");
return false;
}
nova64_log("Shutdown task created successfully.\n");
return true;
}
#endif
void heartbeat_task(void *pvParameters) {
TickType_t xLastWakeTime = xTaskGetTickCount(); TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = pdMS_TO_TICKS(1000); const TickType_t xFrequency = pdMS_TO_TICKS(1000);
uint32_t seconds = 0; uint32_t seconds = 0;
for (;;) { while (!shutdown_requested) {
vTaskDelayUntil(&xLastWakeTime, xFrequency); // vTaskDelayUntil(&xLastWakeTime, xFrequency);
ulTaskNotifyTake(pdTRUE, xFrequency);
seconds++; seconds++;
nova64_log("[Heartbeat] System running for %lu s | FreeRTOS Tick: %lu\n", nova64_log("[Heartbeat] System running for %lu s | FreeRTOS Tick: %lu\n",
(unsigned long)seconds, (unsigned long)xTaskGetTickCount()); (unsigned long)seconds, (unsigned long)xTaskGetTickCount());
} }
heartbeat_task_handle = NULL;
vTaskDelete(NULL); // Delete this task when shutdown is requested
} }
void start_heartbeat_task(void) { void start_heartbeat_task(void) {
xTaskCreate(heartbeat_task, "heartbeat", 2048, NULL, 1, xTaskCreate(nova64_heartbeat_task, "heartbeat", 2048, NULL, 1,
&heartbeat_task_handle); &heartbeat_task_handle);
} }
static void nova64_boot_task(void *params) {
(void)params;
nova64_log("Boot task started.\n");
if (!initialize_wasm_runtime()) {
nova64_log("WASM runtime initialization failed.\n");
vTaskDelete(NULL);
return;
}
if (!start_init_task()) {
nova64_log("Init startup failed.\n");
vTaskDelete(NULL);
return;
}
start_heartbeat_task();
nova64_log("Boot process completed.\n");
vTaskDelete(NULL);
}
const nova64_io_interface_t *nova64_get_io_interface(void) { const nova64_io_interface_t *nova64_get_io_interface(void) {
return g_io_interface; return g_io_interface;
} }
@@ -224,20 +162,17 @@ NOVA64_API bool nova64_init(const nova64_io_interface_t *io_interface) {
return false; return false;
} }
#if defined(NOVA64_SHUTDOWN_AVAILABLE) nova64_log("Initializing...\n");
if (!start_shutdown_task()) { if (!initialize_wasm_runtime()) {
nova64_log("WASM runtime initialization failed.\n");
return false; return false;
} }
#endif
nova64_log("Starting boot process...\n"); if (!start_init_task()) {
BaseType_t created = nova64_log("Init startup failed.\n");
xTaskCreate(nova64_boot_task, "Nova64Boot", 4096 / sizeof(StackType_t),
NULL, tskIDLE_PRIORITY + 2, &boot_task_handle);
if (created != pdPASS) {
nova64_log("Failed to create boot task.\n");
return false; return false;
} }
start_heartbeat_task();
is_initialized = true; is_initialized = true;
return true; return true;
@@ -267,15 +202,30 @@ NOVA64_API bool nova64_shutdown(void) {
nova64_log("Scheduler shutdown requested from host.\n"); nova64_log("Scheduler shutdown requested from host.\n");
shutdown_requested = true; shutdown_requested = true;
if (shutdown_task_handle != NULL) {
xTaskNotifyGive(shutdown_task_handle); if (heartbeat_task_handle) {
return true; // vTaskDelete(heartbeat_task_handle);
// heartbeat_task_handle = NULL;
xTaskNotifyGive(heartbeat_task_handle);
} }
wasm_runner_terminate();
nova64_host_sleep_ms(100); // Allow time for tasks to clean up
nova64_log_scheduler_state();
is_initialized = false;
return true; return true;
} }
#endif #endif
NOVA64_API bool nova64_is_finalized(void) {
bool heartbeat_finished = (heartbeat_task_handle == NULL);
bool wasm_finished = (wasm_runner_get_active_slots() == 0);
return heartbeat_finished && wasm_finished;
}
NOVA64_API uint32_t nova64_get_version(void) { NOVA64_API uint32_t nova64_get_version(void) {
return 0x010000; // v1.0.0 return 0x010000; // v1.0.0
} }
@@ -312,3 +262,67 @@ void vApplicationIdleHook(void) {
#endif #endif
} }
void nova64_log_scheduler_state() {
/* Log currently active tasks for debugging shutdown issues. If FreeRTOS was
* built with trace facility, enumerate tasks and print their names and
* states. Otherwise, at least log the current total task count and known
* handles. */
#if configUSE_TRACE_FACILITY == 1
{
UBaseType_t task_count = uxTaskGetNumberOfTasks();
TaskStatus_t *tasks =
(TaskStatus_t *)pvPortMalloc(task_count * sizeof(TaskStatus_t));
if (tasks) {
uint32_t total_run_time = 0;
UBaseType_t fetched =
uxTaskGetSystemState(tasks, task_count, &total_run_time);
nova64_log("Active tasks: %u (fetched %u)\n", (unsigned)task_count,
(unsigned)fetched);
for (UBaseType_t i = 0; i < fetched; i++) {
const char *state_str = "UNKNOWN";
switch (tasks[i].eCurrentState) {
case eRunning:
state_str = "Running";
break;
case eReady:
state_str = "Ready";
break;
case eBlocked:
state_str = "Blocked";
break;
case eSuspended:
state_str = "Suspended";
break;
case eDeleted:
state_str = "Deleted";
break;
default:
state_str = "Unknown";
break;
}
nova64_log(" - %s (handle=%p) state=%s priority=%u\n",
tasks[i].pcTaskName, (void *)tasks[i].xHandle, state_str,
(unsigned)tasks[i].uxBasePriority);
}
vPortFree(tasks);
} else {
nova64_log("Failed to allocate task list buffer.\n");
}
}
#else
{
UBaseType_t task_count = uxTaskGetNumberOfTasks();
nova64_log("Task count: %u\n", (unsigned)task_count);
if (boot_task_handle)
nova64_log("Boot task: %s\n", pcTaskGetName(boot_task_handle));
// if (init_task_handle)
// nova64_log("Init task: %s\n", pcTaskGetName(init_task_handle));
if (heartbeat_task_handle)
nova64_log("Heartbeat task: %s\n", pcTaskGetName(heartbeat_task_handle));
if (shutdown_task_handle)
nova64_log("Shutdown task: %s\n", pcTaskGetName(shutdown_task_handle));
}
#endif
}
+49 -5
View File
@@ -14,7 +14,7 @@ typedef enum { SLOT_FREE = 0, SLOT_RUNNING, SLOT_STOPPING } slot_state_t;
typedef struct { typedef struct {
int id; int id;
slot_state_t state; slot_state_t state;
// TaskHandle_t freertos_handle; TaskHandle_t freertos_handle;
wasm_module_inst_t module_inst; wasm_module_inst_t module_inst;
wasm_exec_env_t exec_env; wasm_exec_env_t exec_env;
} wasm_task_slot_t; } wasm_task_slot_t;
@@ -29,7 +29,7 @@ bool wasm_runner_initialize(void) {
for (int i = 0; i < MAX_WASM_TASKS; i++) { for (int i = 0; i < MAX_WASM_TASKS; i++) {
g_wasm_slots[i].id = i; g_wasm_slots[i].id = i;
g_wasm_slots[i].state = SLOT_FREE; g_wasm_slots[i].state = SLOT_FREE;
// g_wasm_slots[i].freertos_handle = NULL; g_wasm_slots[i].freertos_handle = NULL;
g_wasm_slots[i].module_inst = NULL; g_wasm_slots[i].module_inst = NULL;
} }
@@ -45,6 +45,16 @@ bool wasm_runner_destroy(void) {
return true; return true;
} }
uint32_t wasm_runner_get_active_slots() {
uint32_t result = 0;
for (int i = 0; i < MAX_WASM_TASKS; i++) {
if (g_wasm_slots[i].state != SLOT_FREE) {
result++;
}
}
return result;
}
bool wasm_runner_start_program( bool wasm_runner_start_program(
const char *module_path, const char *module_path,
const wasm_runner_framebuffer_config_t *framebuffer_config) { const wasm_runner_framebuffer_config_t *framebuffer_config) {
@@ -111,6 +121,10 @@ bool wasm_runner_start_program(
slot->state = SLOT_RUNNING; slot->state = SLOT_RUNNING;
/* Record the FreeRTOS task handle for this slot so the task can be
* referenced or force-deleted later if necessary. */
slot->freertos_handle = xTaskGetCurrentTaskHandle();
if (wasm_runtime_call_wasm(slot->exec_env, start_func, 0, NULL)) { if (wasm_runtime_call_wasm(slot->exec_env, start_func, 0, NULL)) {
nova64_log("WASM module executed successfully.\n"); nova64_log("WASM module executed successfully.\n");
} else { } else {
@@ -129,6 +143,10 @@ bool wasm_runner_start_program(
slot->module_inst = NULL; slot->module_inst = NULL;
slot->exec_env = NULL; slot->exec_env = NULL;
slot->state = SLOT_FREE; slot->state = SLOT_FREE;
nova64_log("WASM module execution completed and resources cleaned up.\n");
nova64_log("WASM task slot %d is now free. Deleting task %p...\n", slot->id,
(void *)xTaskGetCurrentTaskHandle());
slot->freertos_handle = NULL;
vTaskDelete(NULL); vTaskDelete(NULL);
return true; return true;
@@ -138,12 +156,17 @@ void wasm_runner_terminate(void) {
nova64_log("Stopping all WASM tasks...\n"); nova64_log("Stopping all WASM tasks...\n");
for (int i = 0; i < MAX_WASM_TASKS; i++) { for (int i = 0; i < MAX_WASM_TASKS; i++) {
if (g_wasm_slots[i].state == SLOT_RUNNING && g_wasm_slots[i].module_inst) { if (g_wasm_slots[i].state == SLOT_RUNNING) {
g_wasm_slots[i].state = SLOT_STOPPING; g_wasm_slots[i].state = SLOT_STOPPING;
if (g_wasm_slots[i].module_inst) {
wasm_runtime_terminate(g_wasm_slots[i].module_inst); wasm_runtime_terminate(g_wasm_slots[i].module_inst);
} }
} }
}
/* Wait a short grace period for tasks to exit on their own. */
const TickType_t grace = pdMS_TO_TICKS(2000);
TickType_t start = xTaskGetTickCount();
bool any_running = true; bool any_running = true;
while (any_running) { while (any_running) {
any_running = false; any_running = false;
@@ -153,9 +176,30 @@ void wasm_runner_terminate(void) {
break; break;
} }
} }
if (any_running) {
vTaskDelay(pdMS_TO_TICKS(10)); if (!any_running) {
break;
} }
if ((xTaskGetTickCount() - start) > grace) {
/* Force-delete any remaining FreeRTOS tasks */
for (int i = 0; i < MAX_WASM_TASKS; i++) {
if (g_wasm_slots[i].state != SLOT_FREE &&
g_wasm_slots[i].freertos_handle != NULL) {
nova64_log("Force-deleting WASM task %d\n", i);
vTaskDelete(g_wasm_slots[i].freertos_handle);
g_wasm_slots[i].freertos_handle = NULL;
}
/* Mark slot free even if we couldn't fully clean up module state to
* avoid blocking shutdown; it's better to free the native thread. */
g_wasm_slots[i].module_inst = NULL;
g_wasm_slots[i].exec_env = NULL;
g_wasm_slots[i].state = SLOT_FREE;
}
break;
}
vTaskDelay(pdMS_TO_TICKS(10));
} }
nova64_log("All WASM tasks successfully stopped.\n"); nova64_log("All WASM tasks successfully stopped.\n");
+3
View File
@@ -42,6 +42,9 @@ internal static class Nova64Core
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl)] [DllImport(LibName, CallingConvention = CallingConvention.Cdecl)]
public static extern bool nova64_shutdown(); public static extern bool nova64_shutdown();
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl)]
public static extern bool nova64_is_finalized();
[DllImport(LibName, CallingConvention = CallingConvention.Cdecl)] [DllImport(LibName, CallingConvention = CallingConvention.Cdecl)]
public static extern uint nova64_get_version(); public static extern uint nova64_get_version();
} }
+6 -2
View File
@@ -145,10 +145,14 @@ internal static class Program
Console.WriteLine("[Sim] Requesting core shutdown..."); Console.WriteLine("[Sim] Requesting core shutdown...");
if (Nova64Core.nova64_shutdown()) if (Nova64Core.nova64_shutdown())
{ {
if (!_coreThread.Join(TimeSpan.FromSeconds(5))) int timeoutMs = 1000;
while (!Nova64Core.nova64_is_finalized() && timeoutMs > 0)
{ {
Console.WriteLine("[Sim] Core thread did not exit in time."); Thread.Sleep(10);
timeoutMs -= 10;
} }
Console.WriteLine($"[Sim] Core shutdown complete after {1000 - timeoutMs}ms.");
_coreThread = null;
} }
else else
{ {
+2 -1
View File
@@ -4,7 +4,8 @@ pub fn main() void {
_ = add(1, 2); _ = add(1, 2);
while (true) { while (true) {
nova64.log("Hello from Zig!"); nova64.log("Hello from Zig!");
// nova64.yield(1000); // nova64.yield();
// nova64.sleep(1000);
} }
} }
+5 -1
View File
@@ -5,6 +5,10 @@ pub fn log(message: []const u8) void {
nova64_log(message.ptr); nova64_log(message.ptr);
} }
pub fn yield(timeout_ms: u32) void { pub fn sleep(timeout_ms: u32) void {
nova64_yield(timeout_ms); nova64_yield(timeout_ms);
} }
pub fn yield() void {
nova64_yield(0);
}
-5
View File
@@ -1,5 +0,0 @@
extern "nova64" fn log(message: [*]const u8) void;
pub fn main() void {
log(&"Hello from Zig!"[0]);
}