Skip to content

HostLoom 0.5.0

Choose a tag to compare

@kidoz kidoz released this 05 Sep 14:47
· 148 commits to a0c94ddea26287d6d5bab1de80dba011139b443e since this release

The fifth release of HostLoom. One thread runs through it: composition. HostLoom.Composition turns registration into an explicit plan — an immutable, inspectable description of what would be registered, with provenance for every entry and for every candidate a rule deliberately rejected. A plan is probed before a provider exists, validated in full before the service collection is mutated, and applied once with explicit cardinality and collision strategies; a failed validation leaves the collection untouched. There is no runtime assembly scanning, so the whole path is Native AOT friendly. The package bundles an incremental source generator and a declaration-use analyzer as compiler-only assets: rules are declared in a partial class the generator turns into a CompositionPlan factory, supporting explicit type sets, assignability and attribute filters honoring AttributeUsage.Inherited, namespace guards, groups, self/matched/all-interface projections and self aliases, lifetimes, cardinality, implementation counts, registration strategies, and positional open-generic pairs. Eight diagnostics, HLM0009 through HLM0016, reject unsupported declaration syntax, unbounded or inaccessible selection, missing or repeated policies, incompatible service projection, in-plan conflicts, unsatisfied counts, unsupported generic shapes, and singleton capture proven through known plan constructor paths.

The evidence ships with it. HostLoom.Composition.Testing provides container-free registration multiset, sequence, matched-type, service-policy and provenance assertions with no test-framework dependency. Seven reviewed source snapshots, incremental-invalidation regression tests, and a Native AOT sample that executes both handwritten and generated plans cover the generator; a packed-consumer verifier builds isolated application-only and helper-only consumers from the real NuGet artifacts, checks their dependency graphs and negative diagnostic builds, and runs in the release workflow before publication. A measurement harness separates plan creation, application, probing and optional ledger phases, compares against handwritten registration and a pinned Scrutor, measures 46, 160 and 1,000-candidate incremental compiler cost against paired clean builds, and publishes reference-machine distributions with numeric budgets that a deterministic gate enforces.

Upgrading changes one backend contract. IDistributedCacheStore.SetIfAbsentAsync takes the tag-index keys SetAsync already took, so a custom store implementation gains the parameter and a caller that passed a CancellationToken positionally names it instead; applications using the packaged stores are unaffected. LockOptions.MaxWait also becomes the hard bound it documented, which can end acquisition slightly before the bound rather than making one last attempt exactly on it. Both breaks are stated under Changed below.

Two packages are published for the first time: HostLoom.Composition and HostLoom.Composition.Testing. The generator and analyzer travel inside HostLoom.Composition under analyzers/dotnet/cs with build-transitive props, so an application adds one package reference and gains no runtime dependency beyond Microsoft.Extensions.DependencyInjection.Abstractions. The @hostloom/websocket-client fixes listed under Fixed are not part of this NuGet set; that package carries its own version and ships when a websocket-client-vX.Y.Z tag is published.

Added

  • Composition performance harness with separate creation/application/probe/ledger phases, handwritten and pinned Scrutor comparisons, 46/160/1,000-candidate incremental measurements and paired clean consumer builds. Published reference-machine distributions and explicit timing/allocation budgets include cold startup overhead; deterministic gate tests reject regressions and invalid evidence.
  • Complete composition reference, migration and explanation guides, plus incremental regression tests for inherited attribute/interface, accessibility and rule changes with output restoration.
  • HostLoom.Composition.Testing: container-free registration multiset/sequence, matched-type, service policy and provenance assertions, with explicit semantic identities for opaque activations.
  • Bundled composition generator/analyzer and build-transitive provenance props in the application NuGet. An isolated packed-consumer verifier checks dependency boundaries, diagnostics, helper consumption and optional native execution; the release workflow verifies the packed artifacts.
  • Application-owned composition ledger example aggregating enumerable implementations without false conflicts and retaining skip/replacement/rejection details.
  • Composition rule semantics: inherited attribute filters, namespace guards, all-interface and self alias projections, implementation counts, explicit strategies and positional open-generic pairs. Diagnostics HLM0014–HLM0016 cover counts, generic shape and proven singleton capture. Probes retain selectors, rejected candidates and known alias targets. The native sample executes generated aliases, open generics and synchronous/asynchronous disposal.
  • Initial composition source generator and declaration DSL: explicit type sets, compile-time class discovery with inherited generic matching, groups, service projection, explicit lifetimes and cardinality, diagnostics HLM0009–HLM0013, and reviewable source snapshots. The AOT sample now resolves generated services.
  • HostLoom.Composition runtime foundation: immutable explicit DI plans, rejection provenance, passive application reports, explicit cardinality and registration strategies, and validation before collection mutation. An executable Native AOT sample exercises explicit plans, scoped aliases and known closed open-generic resolution.

Changed

  • Breaking. IDistributedCacheStore.SetIfAbsentAsync takes the tag-index keys SetAsync already took, so an entry written with Tags is reachable by RemoveByTagAsync in the distributed tier as well as the in-process one. A custom store implementation gains the parameter; a caller that passed a CancellationToken positionally names it instead. LocalCacheStore.SetIfAbsent gains the same tags parameter before its size one.
  • LockOptions.MaxWait is the hard bound it documented. Provider calls run under it and are cancelled at it, so a backend that never answers can no longer stretch the wait; no attempt starts on or after the bound, which means acquisition can now end slightly before it rather than making one last attempt exactly on it. TimeSpan.Zero still makes exactly one attempt, bounded only by the caller's token.
  • The distributed tag index is documented as monotonic: it gains members and loses them only when the whole index is removed, so an entry rewritten under different tags stays in its earlier indexes and RemoveByTagAsync may evict more than currently carries the tag. Over-eviction costs a refill; reading an entry's tags before every write would cost a round trip on the hot path.

Fixed

  • The TypeScript WebSocket client now enforces maximumMessageSize against encoded UTF-8 before a frame reaches the socket and reports a typed error without consuming request capacity. Acknowledgements made during reconnect are safe no-ops because old-session sequences cannot be applied to a replacement session; other invalid subscription states use a typed error. The client also sends one unsubscribe for an unowned subscribed or event stream while preserving subscriptions deliberately created through its low-level frame API.
  • Calling the TypeScript client's connect() during a caller-requested close now queues one shared reconnect until the close event completes teardown instead of rejecting with a transient still-closing error. Protocol-failure closes remain terminal.
  • A malformed or poisoned distributed-cache payload can no longer force a large allocation. The uncompressed length a compressed payload declares is believed only up to Caching:MaxPayloadBytes; beyond it the entry is a miss logged as corrupt. The same bound now applies to the serialized body when writing, not only to the encoded payload, so an entry this cache wrote always reads back.
  • A lock lease is measured from the request rather than from the answer. A provider starts the lease when it accepts the call, so LeaseEnd, the local expiry timer, and ExtendAsync all count the round trip as spent lease, and IsHeld can no longer stay true after the backend's key expired and another instance took it.
  • A full invalidation queue is reported. BoundedChannelFullMode.DropWrite drops the message and still reports the write as successful, so the warning that watched the return value never fired; drops now count on hostloom.cache.invalidations with direction dropped and log once per Caching:Diagnostics:DegradedLogInterval.
  • A get-or-create factory that outlives the stampede lease no longer releases it. The release is an unconditional delete, so releasing an expired lease removed whichever instance held it next and let further factories run concurrently.
  • The IDistributedCache adapter counts store failures on hostloom.cache.errors and logs one warning per key per Caching:Diagnostics:DegradedLogInterval, instead of one unthrottled warning per failed operation during an outage.