Define explicit ownership for custom appenders and formatters

Add SharedLogState for synchronized value data and factories for
independently owned appender state and captured formatter closures.
Reject unsafe configuration state, update the test collector to value
snapshots, and document the breaking APIs in version 3.0.0.

Expand ORC ownership and sanitizer coverage and reject stale test
executables.
This commit is contained in:
jdb committed 2026-09-28 14:28:24 -05:00
1 parent 851caef706
commit b2d0e9dade
11 files changed
+808 -110

No files matched your search

+50 -12
View File
@@ -9,12 +9,15 @@ Run from the repository root:
| --- | --- |
| `nimble test` | All unit, regression, and ThreadSanitizer tests under ORC with threads enabled |
| `nimble testUnit` | Existing embedded unit tests |
| `nimble testRegression` | Handle lifetime, configuration freshness, refresh failure recovery, appender-state cleanup, and writer shutdown |
| `nimble testThreadSafety` | Separate ThreadSanitizer processes for service ownership, last-owner teardown, reconfiguration, custom state, and formatter closures, plus an independent-service control |
| `nimble testRegression` | Custom-state/formatter ownership, handle lifetime, configuration freshness, refresh failure recovery, appender-state cleanup, and writer shutdown |
| `nimble testThreadSafety` | Separate ThreadSanitizer processes for service ownership, last-owner teardown, reconfiguration, shared state, factory state, formatter closures, collector snapshots, timestamps, and the Nim allocator, plus an independent-service control |
`nimble test` attempts every suite even if an earlier suite fails to compile or
run, summarizes the failing suites, and exits unsuccessfully if any suite fails.
The subset commands use the same build and execution procedures as the full run.
Old executables are removed before compilation, and a new executable must exist
before a suite runs. This prevents stale binaries from hiding compilation errors
when a nested Nimble command fails to propagate its exit status.
Executables are generated in ignored `tests/bin/`. The shutdown child is built
with `-d:multithreaded`; `--threads:on` alone does not enable background writers.
@@ -39,13 +42,43 @@ Thread arguments contain borrowed pointers so no hidden owning argument keeps
the service alive. Atomic construction/destruction counters verify that every
tracked appender is released exactly once after the workers join.
`service-reconfigure` exercises concurrent updates and local cache refresh.
The other scenarios retain parent owners until all workers join. Custom-state
and closure tests only read application data; their concurrent writes are the
`shared-state-lifetime` drops the original state handle before concurrent
mutations and verifies one final destruction with all updates present.
`formatter-lifetime` drops an already-used original formatter and checks both
independent closure counters and exact cleanup on worker threads. `factory-state`
uses per-thread cyclic reference graphs, explicitly runs the ORC collector, and
checks that each worker maintains its own counter. `collector` concurrently
records messages containing JSON and exceptions and mutates returned snapshots.
These ownership scenarios use explicit timestamps where messages are needed.
The separate `timestamp` case exercises concurrent `initLogMessage()` calls. It
currently exposes a sanitizer report in libc local-time conversion reached via
Nim's `std/times.now()`; this remains a separate thread-safety issue.
`allocator` calls the standalone reproduction in `runtime_allocator.nim`, which
imports no logging code. It exercises sequence growth and clearing under a lock
alongside thread-local closure allocations. On Nim 2.2.10 it reproduces a report
in `system/alloc.nim`'s `compensateCounters` / shared-free-list path. The same
runtime report can occur in `factory-state` and other allocation-heavy cases.
The ten ownership scenarios passed debug and release diagnostic builds with
`-d:useMalloc`;
`nimble test` deliberately retains the default allocator and exposes these
reports rather than suppressing them.
The original custom-state and formatter scenarios retain parent owners until all
workers join. They only read application data; their concurrent writes are the
implicit ownership operations caused by cloning and destruction. The independent
case creates a separate service inside each worker and should pass. Custom-state
and formatter cloning is serialized with a lock, matching configuration refresh;
the copied appenders are used and destroyed outside that lock.
`ownership.nim`, imported by `regressions.nim`, checks independent snapshots,
exception recovery, factory and clone behavior, and the built-in formatter paths.
Compile-time checks reject direct and nested refs, captured formatter arguments,
and embedded synchronization primitives. The formerly unsafe custom-state and
formatter scenarios now exercise the supported shared-handle and factory APIs;
the unsafe forms are tested as compilation failures.
The in-process regression tests are deterministic. The lifetime suite checks
copying, self-assignment, replacement, moving, container copies, and exporting a
handle from a local service. Shutdown tests launch
@@ -55,12 +88,17 @@ real exit hook without sleeps to hide queued-message loss. Like the race tests,
the shutdown failure can depend on scheduling; a passing run is not proof of
thread safety or reliable draining.
This suite does not yet force an active producer/channel-close race, separately
exercise the shared `testutil.LoggedMessages` graph, or exhaust ORC cycle-collector
interleavings. It provides targeted regressions for the reproduced ownership
defects and the configuration, cleanup, and shutdown findings.
This suite does not yet force an active producer/channel-close race or exhaust
ORC cycle-collector interleavings. It provides targeted regressions for ownership,
configuration, cleanup, timestamps, allocator behavior, and shutdown.
After the shared-handle implementation, the service sanitizer scenarios, lifetime
suite, and appender-state cleanup regression should pass. The custom-state and
formatter sanitizer cases, `isEnabled` freshness, failed-refresh recovery, and
writer shutdown remain failing regressions for subsequent work.
The ownership scenarios, lifetime suite, and appender-state cleanup regression
exercise the new ownership contracts. `isEnabled` freshness, failed-refresh
recovery, local-time timestamp construction, allocator reports, and writer
shutdown remain for subsequent work.
The standalone `ownership.nim` regression suite passed AddressSanitizer and
LeakSanitizer with `--mm:orc -d:useMalloc`. An additional worker-thread build found
no invalid accesses in the shared-state, factory-state, formatter-lifetime, and collector
scenarios. LeakSanitizer reported per-thread ORC cycle-registration buffers
allocated by the Nim runtime; this is not a clean leak-check result.