cover library async failure flow

This commit is contained in:
Nanaloveyuki
2026-06-14 02:19:40 +08:00
parent c53aa38b89
commit ce89aaf96d
4 changed files with 47 additions and 0 deletions
+1
View File
@@ -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
@@ -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
@@ -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
+44
View File
@@ -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\"}}",