Skip to content

HostLoom 0.14.0

Choose a tag to compare

@kidoz kidoz released this 04 Oct 10:29

The fourteenth release of HostLoom. Two threads run through it, plus a new sample: what HostLoom.Logging captures and protects, and six lifecycle defects outside logging that could stop work silently.

The first thread is logging fidelity. A dictionary key written through a destructured hole was rendered by calling ToString() on the key, so a key of a type whose members are masked or excluded appeared in full while the same type used as a value was protected; complex keys now go through the same masking plan as values and use their protected JSON rendering as key text, subject to the destructuring caps and cycle detection, while scalar keys keep their invariant text. A member protected by more than one rule — a Mask<T> rule and a masking attribute, or a native and a legacy attribute — kept only one of them, and which one survived depended on registration order; the rules now combine, each end reveals the smallest count any rule allows, every rule's short-value guard remains effective, different replacement texts resolve to the ordinal minimum, and exclusion still wins. Int128, UInt128, Half, nint/nuint, and BigInteger are written as JSON numbers on every capture path, like the other numeric scalars; in a {@...} hole one used to become {} or an object of its flag properties, and as a dictionary key it lost its digit spelling. A quoted CLEF @r rendering escapes the reverse solidus and control characters as well as the quotation mark, so a rendering is a string literal a JSON reader can hand back. A character-counted cap (MaxStringLength and the exception cap) no longer splits a surrogate pair at its cut: an emoji straddling the boundary loses its lead unit instead of producing half a pair. A template larger than MaxMessageLength no longer captures or retains formatted-token renderings that CLEF cannot emit after the template is discarded. And a finite logging enqueue, flush, shutdown, or process-exit flush timeout of about 24.8 days or longer no longer becomes an infinite wait; long waits use finite intervals and recalculate their remaining budget, while a null EnqueueTimeout still waits without limit.

The second thread is lifecycle defects that stopped work without a word. HostLoom.Redis invoked cache-invalidation handlers inside the channel gate, so a handler that subscribed, detached its own subscription, or disposed the channel deadlocked the non-reentrant gate on the channel's single reader thread and silently stopped all invalidation for the process; handlers now run outside the gate, as the Valkey channel already did. Redis lock acquire and extend truncated a positive sub-millisecond lease to PEXPIRE 0, deleting the lock while reporting success; leases now round up to whole milliseconds, as the Valkey adapter already did. LeaderElector.ResignAsync threw ObjectDisposedException from the term's disposed cancellation source when leadership ended concurrently with the call; it now completes gracefully. A schedule's timeout timer that fired after its run scope had ended threw ObjectDisposedException on a timer thread, which terminated the process; the late timer is now ignored. An outbox PollInterval beyond the Task.Delay ceiling of about 49.7 days passed validation, and the first poll wait then faulted the relay loop, leaving messages pending until a restart; the interval is clamped to the ceiling. TieredCache subscribed to its invalidation channel after starting the invalidation loop, so a Subscribe that threw left the constructor to propagate while the loop task parked forever, rooting the whole cache graph for the process lifetime; it now subscribes before the loop starts.

The repository also gains examples/HostLoom.Examples.MappingAot, a self-checking Native AOT sample that publishes the mapping packages natively and verifies closed-map injection, the scoped IMapper dispatcher, MapMany and MapOrNull, and an update map in the produced binary.

Upgrading from 0.13.0 changes behaviour an application can observe; each change is stated under Fixed. MessagePack moves from 3.1.9 to 3.1.10 for its upstream security fix against catastrophic regular-expression backtracking.

No package is published for the first time; the set is the same as 0.13.0. @hostloom/websocket-client is not part of this NuGet set; that package carries its own version and ships when a websocket-client-vX.Y.Z tag is published, and it has no changes since its 0.3.0 release.

Added

  • examples/HostLoom.Examples.MappingAot, a self-checking Native AOT sample that publishes the mapping packages natively and verifies closed-map injection, the scoped IMapper dispatcher, MapMany and MapOrNull, and an update map in the produced binary.

Changed

  • Update MessagePack from 3.1.9 to 3.1.10, including its upstream security fix for catastrophic regular-expression backtracking.

Fixed

  • HostLoom.Logging protects complex dictionary keys in {@...} holes through the same masking plan as values, instead of calling ToString() and exposing excluded or masked members. These keys now use their protected JSON representation as key text, subject to destructuring caps and cycle detection; scalar keys keep their invariant text.
  • Applicable Mask<T> rules and native or legacy masking attributes combine independently of registration order. Each end reveals the smallest allowed count, every rule's short-value guard remains effective, and different replacement texts resolve to the ordinal minimum. Exclusion always wins, including when a mask is registered later for the same type and member.
  • Templates larger than MaxMessageLength no longer capture or retain formatted-token renderings that CLEF cannot emit after the template is discarded. Safe message rendering and captured fields remain available within their configured caps.
  • Finite logging enqueue, flush, shutdown, and process-exit flush timeouts of about 24.8 days or longer no longer become infinite waits. Long waits use finite intervals and recalculate their remaining budget; a null EnqueueTimeout still waits without limit.
  • HostLoom.Logging writes Int128, UInt128, Half, nint/nuint, and BigInteger as JSON numbers on every capture path, like the other numeric scalars. A {@...} hole of one used to become {} or an object of its flag properties, and a dictionary key of one lost its digit spelling.
  • A quoted CLEF @r rendering escapes the reverse solidus and control characters, not only the quotation mark, so a rendering is a string literal a JSON reader can hand back.
  • Character-counted logging caps (MaxStringLength and the exception cap) no longer split a surrogate pair at the cut: an emoji straddling the boundary loses its lead unit instead of producing half a pair.
  • HostLoom.Redis invokes invalidation handlers outside the channel gate, as the Valkey channel already did. A handler that subscribed, detached its own subscription, or disposed the channel deadlocked the non-reentrant gate on the channel's single reader thread and silently stopped all invalidation for the process.
  • Redis lock acquire and extend round a lease up to whole milliseconds. A positive sub-millisecond lease used to truncate to PEXPIRE 0, deleting the lock while reporting success; the Valkey adapter already rounded up.
  • LeaderElector.ResignAsync completes gracefully when leadership ends concurrently with the call, instead of throwing ObjectDisposedException from the term's disposed cancellation source.
  • A schedule's timeout timer that fires after its run scope has ended no longer throws ObjectDisposedException on a timer thread, which terminated the process.
  • An outbox PollInterval beyond the Task.Delay ceiling of about 49.7 days is clamped to it. Validation accepted the value, and the first poll wait then faulted the relay loop, leaving messages pending until a restart.
  • TieredCache subscribes to its invalidation channel before starting the invalidation loop. A Subscribe that threw left the constructor to propagate while the loop task parked forever, rooting the whole cache graph for the process lifetime.