From ce89aaf96d07ddc09e2acbd31198b9e06890c2ee Mon Sep 17 00:00:00 2001 From: Nanaloveyuki Date: Sun, 14 Jun 2026 02:19:40 +0800 Subject: [PATCH] :white_check_mark: cover library async failure flow --- docs/api/library-async-logger-run.md | 1 + docs/api/library-async-logger-shutdown.md | 1 + .../library-async-logger-to-async-logger.md | 1 + src-async/BitLoggerAsync_test.mbt | 44 +++++++++++++++++++ 4 files changed, 47 insertions(+) diff --git a/docs/api/library-async-logger-run.md b/docs/api/library-async-logger-run.md index 74eadc7..90a9016 100644 --- a/docs/api/library-async-logger-run.md +++ b/docs/api/library-async-logger-run.md @@ -38,6 +38,7 @@ Detailed rules explaining key parameters and behaviors - Failure and lifecycle state are still tracked by the wrapped async logger. - The narrower library facade does not hide the need to explicitly activate queue draining. - If the worker fails, the wrapped async logger records that through its failure-state helpers, which still require `to_async_logger()` for inspection from library-facing code. +- Unwrapping after a delegated `run()` exposes the same failure and backlog state that accumulated behind the facade; it does not create a second runtime view. ### How to Use diff --git a/docs/api/library-async-logger-shutdown.md b/docs/api/library-async-logger-shutdown.md index 275ebd5..7a14466 100644 --- a/docs/api/library-async-logger-shutdown.md +++ b/docs/api/library-async-logger-shutdown.md @@ -44,6 +44,7 @@ Detailed rules explaining key parameters and behaviors - In runtimes where shutdown waits for workers, the method then waits until the worker is no longer running before returning. - After a worker-failure short-circuit, native-worker backends can still convert remaining backlog into dropped records, while compatibility backends skip that extra forced-clear step. - The narrower library facade does not change any of these runtime-dependent shutdown rules; it only keeps the broader inspection helpers out of the direct public surface. +- Inspecting the logger later through `to_async_logger()` reveals the same delegated shutdown result rather than a rebuilt or translated lifecycle snapshot. ### How to Use diff --git a/docs/api/library-async-logger-to-async-logger.md b/docs/api/library-async-logger-to-async-logger.md index 522c17b..729c18f 100644 --- a/docs/api/library-async-logger-to-async-logger.md +++ b/docs/api/library-async-logger-to-async-logger.md @@ -37,6 +37,7 @@ Detailed rules explaining key parameters and behaviors - Queue state, sink wiring, target, min level, flush policy, and current failure/lifecycle state remain the same. - Use this when code needs wider async logger APIs outside the library facade. - This is the step required for helpers such as `pending_count()`, `dropped_count()`, `state()`, `wait_idle()`, `has_failed()`, `last_error()`, or broader composition methods that are intentionally hidden by `LibraryAsyncLogger[S]`. +- It is also the step that exposes the real post-run or post-shutdown state after facade-level lifecycle calls, because those calls delegated to this same wrapped logger all along. ### How to Use diff --git a/src-async/BitLoggerAsync_test.mbt b/src-async/BitLoggerAsync_test.mbt index c5b51b7..a78d04c 100644 --- a/src-async/BitLoggerAsync_test.mbt +++ b/src-async/BitLoggerAsync_test.mbt @@ -476,6 +476,50 @@ async test "library async logger keeps a smaller async facade" { inspect(written_field_counts.val[0], content="2") } +async test "library async shutdown preserves wrapped failure cleanup semantics" { + let writes : Ref[Int] = Ref(0) + let logger = LibraryAsyncLogger::new( + @bitlogger.callback_sink(fn(_) { + writes.val += 1 + }), + config=AsyncLoggerConfig::new( + max_pending=4, + overflow=AsyncOverflowPolicy::Blocking, + flush=AsyncFlushPolicy::Batch, + ), + min_level=@bitlogger.Level::Info, + target="async.lib.failure", + flush=fn(_) -> Int raise { + raise TestFlushError("library facade flush exploded") + }, + ) + let full = logger.to_async_logger() + + @async.with_task_group(group => { + group.spawn_bg(allow_failure=true, () => logger.run()) + logger.info("one") + logger.info("two") + full.wait_idle() + inspect(full.has_failed(), content="true") + inspect(full.pending_count(), content="1") + logger.shutdown() + }) + + inspect(full.is_closed(), content="true") + inspect(full.has_failed(), content="true") + inspect(full.last_error().contains("TestFlushError"), content="true") + inspect(full.is_running(), content="false") + inspect(writes.val, content="1") + inspect( + full.pending_count(), + content=if async_runtime_supports_background_worker() { "0" } else { "1" }, + ) + inspect( + full.dropped_count(), + content=if async_runtime_supports_background_worker() { "1" } else { "0" }, + ) +} + async test "library async logger can be built from config" { let logger = parse_and_build_library_async_logger( "{\"logger\":{\"min_level\":\"warn\",\"target\":\"async.lib.config\",\"sink\":{\"kind\":\"console\"}},\"async_config\":{\"max_pending\":2,\"overflow\":\"DropNewest\",\"max_batch\":1,\"linger_ms\":0,\"flush\":\"Never\"}}",