From 289476d90a085b216cfa91da63c0ef6b64a1fae3 Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 20 Oct 2026 09:36:40 +0800 Subject: [PATCH 1/3] fix(app): retain resources when shutdown draining fails --- docs/guides/applications.md | 6 ++- docs/guides/applications_CN.md | 6 ++- src/app/main.cpp | 75 ++++++++++++++++++++++----- tests/app/business_runtime.cmake | 70 +++++++++++++++++++++---- tests/app/business_runtime_plugin.cpp | 74 ++++++++++++++++++++++++-- tests/core/README.md | 5 +- tests/core/README_CN.md | 4 +- 7 files changed, 204 insertions(+), 36 deletions(-) diff --git a/docs/guides/applications.md b/docs/guides/applications.md index 3d6ee2908..98b63c1b5 100644 --- a/docs/guides/applications.md +++ b/docs/guides/applications.md @@ -79,9 +79,11 @@ For Sample1, look for a log entry containing `sample1_module` and `PostInit`. Th On macOS/Linux, Ctrl+C or `SIGTERM` requests shutdown through the application's main loop. On Windows, enter `exit` in the application console. The launcher first stops managed top-level task admission and continues module updates plus the shared owner-thread poll until accepted tasks finish. Their services and bridges remain available so those tasks can complete later awaits. -After task completion and the active callback stack returns, the manager synchronously invokes shutdown hooks and unloading. When all hooks succeed, Sample1 logs `PreShut` and then `Shut`, and the launcher exits. A hook may block; the handler-count budget is not a shutdown-duration bound. +After task completion and the active callback stack returns, the manager waits for every module to report `ready_to_shut()`, then synchronously invokes shutdown hooks and unloading. A module must use its owner-frame readiness check to stop/drain native work and confirm native thread exit before allowing unload. When all hooks succeed, Sample1 logs `PreShut` and then `Shut`, and the launcher exits. A hook may block; the handler-count budget is not a shutdown-duration bound. -If a hook returns `false` or throws, the launcher reports its phase, module and available exception details once, keeps the process/resources alive, and does not continue unloading. There is no implicit timeout or cancellation fallback. This managed-task drain does not prove that arbitrary callbacks, native pool workers or background cleanup have finished; see [module task integration](tasks.md#shared-business-executor-for-modules). Sample1's logs alone are not a readiness or shutdown guarantee for every plugin combination. +If a shutdown hook returns `false` or throws, the launcher reports its phase, module and available exception details once, keeps the process/resources alive, and does not continue unloading. `plugin_manager::update()` returning `false` or throwing during drain also locks the launcher into retention; owner updates continue where possible, but later completion cannot resume unloading. + +`--shutdown_timeout_ms=N` configures a non-negative int64 wait in milliseconds; the default `0` waits indefinitely. A positive limit uses a monotonic clock and reports elapsed time, pending managed-task count and the waiting phase once. On expiry the process and resources stay alive, owner updates continue, and unloading remains disabled even if work completes later. This is a cooperative owner-loop wait limit: it cannot interrupt a blocking user `update()`, readiness check, shutdown hook or unload call. It is not a hard process-stop deadline, cancellation policy or successful shutdown. This managed-task drain does not prove that arbitrary callbacks, native pool workers or background cleanup have finished; see [module task integration](tasks.md#shared-business-executor-for-modules). Sample1's logs alone are not a readiness or shutdown guarantee for every plugin combination.