diff --git a/docs/api/async-logger-has-failed.md b/docs/api/async-logger-has-failed.md index 9a04f7d..a827acd 100644 --- a/docs/api/async-logger-has-failed.md +++ b/docs/api/async-logger-has-failed.md @@ -36,7 +36,7 @@ Detailed rules explaining key parameters and behaviors - `run()` clears previous failure state at startup. - `run()` also clears the stored `last_error()` string at startup before drain work begins. - If the worker loop raises an error, the logger records that failure and exposes it through this flag. -- Once set by a failed run, the flag stays `true` until a later `run()` start resets it. +- Once set by a failed run, the flag stays `true` until a later `run()` invocation actually starts and resets it. - This helper is intentionally compact and should usually be paired with `last_error()` for details. - Failure state is about runtime drain execution, not whether records were dropped due to overflow policy. @@ -73,7 +73,7 @@ e.g.: - If `has_failed()` is `true`, callers should inspect `last_error()` or `state()` for more context. -- `close()` or `shutdown()` do not clear this flag by themselves; only a later `run()` start resets it. +- `close()` or `shutdown()` do not clear this flag by themselves; only a later `run()` that has already started resets it. ### Notes diff --git a/docs/api/async-logger-last-error.md b/docs/api/async-logger-last-error.md index a0fc26f..0f54585 100644 --- a/docs/api/async-logger-last-error.md +++ b/docs/api/async-logger-last-error.md @@ -36,7 +36,7 @@ Detailed rules explaining key parameters and behaviors - `run()` resets the stored error string when the worker starts. - If the worker loop fails, the error text is captured from the raised exception. - An empty string normally means no failure has been recorded. -- Once a failure string is recorded, it stays in place until a later `run()` start clears it. +- Once a failure string is recorded, it stays in place until a later `run()` invocation actually starts and clears it. - This helper reports worker execution errors, not ordinary overflow or backpressure conditions. - A new successful `run()` attempt clears any previously stored error text before drain work begins again. @@ -73,7 +73,7 @@ e.g.: - If callers need broader context than just the error text, they should use `state()`. -- `close()` or `shutdown()` do not clear a previously recorded error string by themselves. +- `close()` or `shutdown()` do not clear a previously recorded error string by themselves; the reset happens when a later `run()` has already started. ### Notes diff --git a/docs/api/async-logger-run.md b/docs/api/async-logger-run.md index 3f8cc89..f30b3c5 100644 --- a/docs/api/async-logger-run.md +++ b/docs/api/async-logger-run.md @@ -34,7 +34,7 @@ pub async fn[S : @bitlogger.Sink] AsyncLogger::run(self : AsyncLogger[S]) -> Uni Detailed rules explaining key parameters and behaviors - `run()` sets `is_running` to `true` before worker execution begins. -- Every invocation clears previous failure state first by setting `has_failed=false` and `last_error()` to an empty string. +- Every invocation clears previous failure state first by setting `has_failed=false` and `last_error()` to an empty string once that `run()` call has actually started executing. - The method then keeps draining records until `queue.get()` stops with `AsyncLoggerClosed` or a worker error escapes. - On a normal queue-close exit, `run()` clears `is_running` and returns normally. - On failure, the logger records `has_failed=true`, stores the error text in `last_error`, clears `is_running`, and then raises the error back out of `run()`. diff --git a/src-async/BitLoggerAsync_test.mbt b/src-async/BitLoggerAsync_test.mbt index 3eefa52..e9fee5c 100644 --- a/src-async/BitLoggerAsync_test.mbt +++ b/src-async/BitLoggerAsync_test.mbt @@ -355,6 +355,57 @@ async test "async logger records worker failures and wait_idle stops early" { inspect(logger.pending_count(), content="0") } +async test "later started run resets async failure state before draining remaining backlog" { + let writes : Ref[Int] = Ref(0) + let flushes : Ref[Int] = Ref(0) + let logger = async_logger( + @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.retry", + flush=fn(_) -> Int raise { + flushes.val += 1 + if flushes.val == 1 { + raise TestFlushError("flush exploded once") + } + 1 + }, + ) + + @async.with_task_group(group => { + group.spawn_bg(allow_failure=true, () => logger.run()) + logger.info("one") + logger.info("two") + logger.wait_idle() + inspect(logger.has_failed(), content="true") + inspect(logger.last_error().contains("TestFlushError"), content="true") + inspect(logger.is_running(), content="false") + inspect(logger.pending_count(), content="1") + + group.spawn_bg(() => logger.run()) + while !logger.is_running() { + @async.pause() + } + inspect(logger.has_failed(), content="false") + inspect(logger.last_error(), content="") + logger.shutdown() + }) + + inspect(writes.val, content="2") + inspect(flushes.val, content="2") + inspect(logger.has_failed(), content="false") + inspect(logger.last_error(), content="") + inspect(logger.is_running(), content="false") + inspect(logger.is_closed(), content="true") + inspect(logger.pending_count(), content="0") +} + async test "library async logger keeps a smaller async facade" { let written_targets : Ref[Array[String]] = Ref([]) let written_messages : Ref[Array[String]] = Ref([])