Skip to content

[RELEASE] 3.0#1070

Merged
mtmk merged 58 commits into
mainfrom
release/3.0
Jul 10, 2026
Merged

[RELEASE] 3.0#1070
mtmk merged 58 commits into
mainfrom
release/3.0

Conversation

@mtmk

@mtmk mtmk commented Feb 18, 2026

Copy link
Copy Markdown
Member

NATS .NET v3.0. Adds .NET 10 and drops .NET 6 (target frameworks: netstandard2.0, netstandard2.1, net8.0, net10.0).

Changes on this branch (preview release bumps omitted):

Remaining:

  • Update Platform Compatibility docs

mtmk and others added 2 commits February 18, 2026 11:19
- Initial release with .NET 10 target
- Also dropped .NET 6 target
@mtmk mtmk added this to the Release 3.0 milestone Feb 19, 2026
@mtmk mtmk mentioned this pull request Feb 19, 2026
4 tasks
@mtmk mtmk self-assigned this Feb 24, 2026
@mtmk mtmk changed the title Release 3.0 Line [RELEASE[ 3.0 Feb 25, 2026
@mtmk mtmk changed the title [RELEASE[ 3.0 [RELEASE] 3.0 Feb 25, 2026
mtmk added 2 commits February 27, 2026 08:56
* Clean up NET6 and optimize NETSTANDARD

- Remove .NET 5/6/7 from `#if` directives and build
- Delete commented-out file
- Optimize NETSTANDARD encoding polyfills used in hot paths
  to eliminate intermediate array allocations
- Fix `PeriodicTimer` CancellationTokenRegistration leak
- Add encoding polyfill benchmark

* Fix `PeriodicTimer` cancellation to prevent leak

- Ensure static lambda used to avoid allocation.
Switch project dependency from `NATS.Net` to `NATS.Client.Simplified` for better modularity. Add `NATS.Extensions.Microsoft.DependencyInjection` to `NATS.Net` dependencies. Update documentation to include comparisons between `NATS.Extensions.Microsoft.DependencyInjection` and `NATS.Client.Hosting`, clarifying usage scenarios. Add new DI guide to advanced docs.
@github-actions

github-actions Bot commented Feb 27, 2026

Copy link
Copy Markdown

mtmk and others added 19 commits March 4, 2026 12:37
# Conflicts:
#	src/NATS.Client.Core/NATS.Client.Core.csproj
# Conflicts:
#	version.txt
# Conflicts:
#	src/NATS.Client.Core/NATS.Client.Core.csproj
#	src/NATS.Client.Core/NKeyPair.cs
#	version.txt
* Add support for optional NATS headers in serialization/deserialization

* Make `Serialize` method header-aware and add unit tests for header serialization logic

* Add recursion guard to prevent infinite loops in default interface methods for `Serialize`/`Deserialize`, update tests and examples accordingly

* Fix `Content-Type` header deserialization by updating to use `StringValues`

* Fix NuidTests flap

* Improve NuidTests stability

* Remove old default methods

* Remove `IDictionary` implementation from `NatsHeaders`

* Add INatsSerializeWithHeaders

* serializer: remove unnecessary obsolete warning pragmas

The CS0618 suppressions were leftover from the DIM approach
where interface members were marked obsolete. No longer needed
with the separate interface design.

* tests: add serialization extension and ABI checks

Add unit tests for NatsSerializationExtensions covering both
the header-aware path and the fallback path for serialize and
deserialize. Make the ABI check program assert correctness
instead of just printing, including the new WithHeaders
interfaces.

* serializer: replace WithHeaders with WithContext interfaces

Replace INatsSerializeWithHeaders<T>/INatsDeserializeWithHeaders<T>
with INatsSerializeWithContext<T>/INatsDeserializeWithContext<T>,
passing a NatsMsgContext struct that carries Subject, ReplyTo, and
Headers.

This gives serializers access to the full message envelope, enabling
subject-based dispatch and reply-to inspection without requiring
further interface changes in the future.

Also fixes the null-headers asymmetry in the extension methods:
both Serialize and Deserialize now always dispatch to the context
overload when the interface is implemented, regardless of whether
Headers is null.

* Revert "Improve NuidTests stability"

This reverts commit 42b8cbd.

* Revert "Fix NuidTests flap"

This reverts commit dcc3588.

* docs: fix stale DIM references and update serialization docs

Remove incorrect DIM claims from serialization docs, rewrite the
headers section to describe the WithContext interface design. Clean
up NativeAot example that had leftover INatsHeaders parameter
overloads from the old DIM approach. Restore dropped <returns>
doc comment on INatsDeserialize<T>.Deserialize.

* headers: drop read-only state

The read-only flag was set on NatsHeaders before passing to publish to
catch accidental mutation after publish. It was a weak guarantee since
headers are copied to a byte buffer synchronously inside CommandWriter,
so post-publish mutation cannot affect wire data anyway.

Removing it lets serializers implementing INatsSerializeWithContext<T>
mutate headers (e.g. set Content-Type) during serialization, which was
otherwise blocked by ThrowIfReadOnly. Telemetry's SetOverrideReadOnly
escape hatch is no longer needed and uses the normal indexer instead.

* tests: add net10.0 TFM and improve ABI checks

Add net10.0 target to CoreUnit, Core, and Core2 test projects.
Add CheckAbiOld control project that runs against the old NuGet
version to prove type forwarding works by contrasting assembly
versions. Centralize the old NuGet version in Directory.Build.props
and make the run script assert loaded assembly versions.

* tests: fix NU1510 package pruning errors on net10.0

Condition System.Net.Http and System.Text.RegularExpressions
package references to net481 only, where they are actually needed.
On net10.0 these are built-in and NuGet's package pruning treats
the unnecessary references as errors.

* serializers: address context review feedback

- NatsMsgContext: require Subject via constructor; drop init setters so
  netstandard shim for IsExternalInit is no longer needed.
- Add INatsSerializerWithContext<T> as a combined interface mirroring
  INatsSerializer<T>, simplifying class declarations for context-aware
  serializer+deserializer pairs.
- Built-in NatsUtf8PrimitivesSerializer, NatsRawSerializer and
  NatsJsonContextSerializer now implement INatsSerializerWithContext<T>
  and propagate the context through _next when delegating. Without this,
  chaining a built-in with a context-aware leaf silently dropped context
  at the leaf.
- Document NatsHeaders thread-safety: not safe to share across concurrent
  publishes, since readonly-locking was removed.
- Document that header-mutating serializers must get non-null headers
  from the caller; the library does not allocate on their behalf.
- Add chain-propagation tests for serialize and deserialize paths.

* sln: remove x64/x86 platform noise
* core: use System.Threading.Lock on NET9_0_OR_GREATER

* core: fix reentrant lock in PublishToClientHandlersAsync

* bench: add lock and publish runtime comparison benchmarks
mtmk and others added 28 commits June 4, 2026 08:19
* Bound reconnect-loss test by reconnect count

The loss <= 1% threshold tracks reconnects/sent, so under CI CPU
contention the forced-disconnect cadence balloons (seen 7% loss, 40+
reconnects) and the absolute percentage trips. Bound loss by reconnect
count instead: each disconnect can drop at most the in-flight msg(s).
The no-duplicate check (received <= sent) is unchanged.

* Skip first ping in slow-consumer consume RTT max

The warm-up ping added earlier did not fully fix the flap: the first
measured ping still legitimately catches the tail of the 100-message
publish burst and spikes near the 1000ms threshold. The sibling fetch
test already skips i==0 from the max for this reason; apply the same
guard to the consume test. All 20 pings must still succeed.

* Widen fetch expiry in ordered-consumer timeout test

The test swallows idle heartbeats and expects exactly one heartbeat
timeout notification, which fires at 2x IdleHeartbeat (2s). With expiry
at 5s the server-side request timeout (408) raced the client heartbeat
timer: under CI thread-pool contention the Timer callback slips past 5s,
the fetch ends on the 408 with no notification, and the count is 0.
Widen expiry to 20s so the heartbeat timeout reliably wins; happy-path
runtime is unchanged since the timer still fires at ~2s.

* Throttle publisher in reconnect-loss test

The reconnect-count bound still tripped (lost 71 vs 2x of 29 reconnects):
publishes coalesce into the unflushed write buffer, so a single forced
disconnect drops the whole batch and loss scales with publish rate under
CI contention. Throttle the send loop so in-flight stays ~1; each
disconnect then drops at most that one message and the bound holds with
margin (observed 0 loss locally). The no-duplicate check is unchanged.
* core: always validate subjects

Always run ValidateSubject / ValidateReplyTo / ValidateQueueGroup on the
publish, subscribe, and request paths instead of gating them behind
NatsOpts.SkipSubjectValidation.

Subject validation rejects empty and whitespace-bearing subjects, which
is what blocks CRLF protocol injection. Making that an opt-out was the
wrong default to offer: the cost is negligible next to the network round
trip, and a caller who controls all subjects gains nothing measurable by
disabling it.

SkipSubjectValidation is kept but marked obsolete and ignored, so
existing code keeps compiling (with a warning) rather than breaking. It
will be removed in a future release.

* core: deprecate SkipSubjectValidation

Mark NatsOpts.SkipSubjectValidation obsolete rather than removing it.
The option still works: setting it true bypasses subject, reply-to, and
queue-group validation on the publish, subscribe, and request paths.

Disabling validation is discouraged. The likely failure is mundane, not
exotic: a subject that merely contains a space or tab is split into
tokens on the wire. "PUB foo bar 5" is read by the server as subject
"foo" with reply-to "bar", and "SUB foo bar 1" as subject "foo" joined
to queue group "bar", so the call is silently misrouted with no error.
Validation also blocks CRLF protocol injection. Either way the saving is
negligible next to the network round trip, so the obsolete warning tells
callers to leave validation on and signals the option may be removed in a
future release.

Internal call sites and the tests/benchmark that exercise the option
suppress CS0618 locally so the warnings-as-errors build stays clean.
* core: default request-reply to Direct mode

Flip the NatsOpts.RequestReplyMode default from SharedInbox to Direct.
Direct skips per-request subscription setup and channel-based delivery,
correlating replies through the connection's inbox subscription instead.

Direct mode previously ignored ThrowIfNoResponders and always returned the
503 sentinel as a message. Make it honor the flag so RequestAsync behaves
the same in both modes. The JetStream publish and API paths inspect the
sentinel themselves to retry, so they now opt out with
ThrowIfNoResponders=false, matching the shared-inbox path.

Tests that assert exact wire frames, subscription sids, or active
subscription counts are pinned to SharedInbox: Direct opens an inbox
subscription at connect, which shifts those values.

* core: preserve no-responders behavior for explicit Direct

Honoring ThrowIfNoResponders in Direct mode (previous commit) changed
no-responders from "return a message" to "throw" for callers that had
intentionally selected Direct. Record whether Direct was set explicitly
(vs inherited as the new default) and default such requests to not throw,
restoring their pre-3.x behavior. Default and SharedInbox connections keep
throwing.

The flag is captured in the RequestReplyMode init accessor; the field
initializer bypasses it, so an unset mode is treated as the throwing
default. RequestManyAsync always uses the shared inbox path and is scoped
out of the opt-out. A per-call NatsSubOpts.ThrowIfNoResponders still wins.

* test: assert default request-reply mode is Direct

* test: disable test collection parallelization

MaxParallelThreads was 1.0x (one thread per core), so collections ran
concurrently. Suites that assert on process-global state, notably the
OpenTelemetry tests whose ActivityListener and MeterListener observe
every NATS.Net activity and measurement in the process, then see another
collection's activities and counters and fail with false positives.
Defaulting request-reply to Direct made this worse by routing JetStream
publish through the instrumented RequestAsync path.

Run collections serially across all test projects.
NatsClient and the DI builder forced SubPendingChannelFullMode to Wait,
while NatsConnection defaulted to DropNewest, so overflow behavior
differed by entry point. Wait applies backpressure to the socket read
loop and risks the server disconnecting the client as a slow consumer.

Make all entry points inherit the NatsOpts default DropNewest and raise
SubPendingChannelCapacity from 1024 to 16384 so overflow stays rare.
Drop the now-redundant pending parameter from the NatsClient(NatsOpts)
constructor.

Breaking for NatsClient and DI users under sustained overload: messages
are dropped (surfaced via MessageDropped) instead of blocking.
* core: add explicit subscription drain API

Expose DrainAsync on INatsSub<T> so callers can stop receiving new
messages and finish reading already-buffered messages while keeping the
connection usable, instead of only getting drain behaviour on the
dispose path behind DrainSubscriptionsOnDispose.

The drain mechanics (UNSUB, PING/PONG fence, complete channel) move into
a shared core. The dispose-path entry point is renamed to
DrainOnDisposeAsync and keeps the opt-in flag gate and reader wait.

* core: clarify drain cancellation and DrainPingTimeout docs

* jetstream: add opt-in consume drain on cancel

Cancelling ConsumeAsync was an abrupt stop that abandoned messages already buffered in the consumer, so handlers could not finish post-yield work like acking on a still-open connection. Add NatsJSConsumeOpts.DrainOnCancel: when set, cancellation drains the consumer (stop pulling, fence in-flight with PING/PONG, complete the channel) and keeps the connection usable. Default false preserves the previous behavior.

* test: tidy consume drain tests

Drop the unused ITestOutputHelper field and tighten the no-drain assertion: cancellation fires after the first message, so assert a prompt stop rather than just fewer than the total.

* test: run xunit tests single-threaded

Set MaxParallelThreads to 1 (was 1.0x, i.e. one thread per core). Integration tests share a server per collection and several historic flaps came from collections racing each other under parallelism; serialising removes that axis at the cost of wall-clock time.

* test: fix Reconnect_counter racing the metric increment

The reconnects counter is incremented just after the ConnectionOpened event is queued, so the test, which waited on the event and read the metric once, could observe zero. Poll for the measurement after the reconnect, and make MeterTracker hand out locked snapshots since callbacks fire on background threads. Reproduced in isolation (failed 6/6, now passes).

* core: fix subscription drain hang and message loss

DrainCoreAsync could skip TryComplete() when the UNSUB or PING threw an
unexpected exception (e.g. ObjectDisposedException if the connection is
disposed mid-drain), leaving the message channel forever incomplete; a
drain-on-cancel consumer reading with CancellationToken.None then hangs.
Wrap the unsubscribe and ping in one try, treat the dispose race as
best-effort, and complete the channel and clear routing in a finally so
a reader is never left waiting. Disarm the lifecycle timers before
taking the unsubscribe gate so a timeout callback is less likely to win
the gate and complete the channel without the drain fence.

For JetStream consume, the idle-heartbeat timer could re-arm during a
drain and complete the user channel ahead of the PING/PONG fence,
dropping in-flight messages that drain-on-cancel is meant to deliver.
Track a draining flag so the heartbeat callback, its re-arm, and
CompleteStop defer to the drain.

* jetstream: drain consumer delivery via overridable hook

The base drain only fenced the UNSUB with a PING/PONG; stopping a
subclass delivery engine (the JetStream pull loop and idle-heartbeat
timer) was done ad hoc in NatsJSConsume's DrainAsync override, so only
the explicit cancel path stopped it. The dispose-drain path left the
heartbeat running, where it could complete the user channel ahead of
the fence and drop in-flight messages.

Add a StopDelivery hook on NatsSubBase, called at the start of the
shared drain core so both the explicit and dispose-drain paths stop the
engine before the fence. NatsJSConsume overrides the hook instead of the
whole DrainAsync.

* test: cover drain in-flight message fence

The existing drain tests buffer every message before asserting (publish
then PING, or a count that fits a single pull), so they pass even if the
UNSUB+PING fence is removed. Add a test that publishes without a
pre-drain PING, so the tail is still in flight when the UNSUB is sent,
and asserts all of it is delivered.

* jetstream: scope DrainOnCancel docs to the pull consumer

* core: clarify DrainAsync fence timeout docs

* jetstream: remove redundant drain-start in consume loop

The consume loop started the drain in two places: a top-of-loop check
for an already-cancelled token and the WaitToReadAsync cancellation
catch. WaitToReadAsync throws OperationCanceledException when the token
is already cancelled (even with buffered messages), so the catch already
covers the cancel-before-read case identically. Drop the top-of-loop
block and let the catch be the single place that reacts to cancellation.

* core: complete drain channel even if teardown throws

The hang fix put TryComplete() in a finally, but the later StopDelivery
hook and the lifecycle-timer disarm ran before the try, so if any of
them threw (StopDelivery is virtual and calls Timer.Change, which can
throw on a timer disposed by a racing DisposeAsync) the finally was
never reached and a drain-on-cancel reader blocked on the channel could
hang again. Take the unsubscribe gate first, then run the teardown
(decrement, StopDelivery, timer disarms, UNSUB, PING) inside the try so
TryComplete() always runs.

The timers are now disarmed after the gate rather than before it; the
gate was already the real protection (once it is taken a later timer
callback bails on the unsubscribe gate), so the prior before-gate disarm
only narrowed a microscopic window and is not worth skipping the
completion guard for.

* test: assert drain message count before order
* test: retry-gate positive-path test connections

Many integration tests construct a NatsConnection and exercise it without
first gating on a successful connect. The default ConnectTimeout is 2s and
covers the wait for the server INFO frame; when CI runs multiple TFMs of a
project in parallel, that 2s wait loses under load and the connect throws a
TimeoutException before the test's own logic runs. The failure then points
at an unrelated test rather than the connect, and the assertions never ran.

Gate positive-path connections with the existing ConnectRetryAsync helper
(retries ConnectAsync for up to 60s), and convert plain ConnectAsync setup
gates to it. Negative-path tests (expected connect failures/timeouts,
reconnect/cancel/TLS/auth behaviour) are left as-is.

* test: fix request-reply and slow-consumer flakes

Two timing flakes distinct from the connect-timeout class:

Request_reply_many_* sent the request before the responder's SUB was
registered server-side, so under load the request could race ahead and
get zero replies. Fence the responder with a PING/PONG round-trip before
requesting, matching the existing pattern in this file.

SlowConsumerDetected_fires_again_after_recovery waited for an absolute
slowConsumerCount >= 2, but episode 1 alone can reach 2, so the episode-2
wait could pass without a new event and the >-than assertion then failed.
Wait for a count strictly greater than the episode-1 snapshot instead.
Add an Abstractions-only test project (references just NATS.Client.Abstractions,
no Core) covering the issue #851 use case: a third party such as a CloudEvents
adapter can deconstruct a received message and build an outgoing one using only
the envelope (NatsMsgContext / INatsHeaders) and the serializer interfaces. Wire
it into the Linux and Windows CI runs.
net6.0 was already removed from the target frameworks (netstandard2.0,
netstandard2.1, net8.0, net10.0). Update the platform-compatibility page and
fix csproj/script comments that still described netstandard dependency groups
as net6.0.
consumed.messages and received.bytes count only messages delivered to the
application, per the OTel messaging convention. NATS status/control frames
(no-responder 503s, JetStream heartbeats, flow-control, protocol notifications)
are consumed internally and never reach the application, but the base receive
path counted them. Skip them via the inline status code on the version line,
which covers every subscription type through one chokepoint.
* jetstream: record operation.duration for ack

Wire the SendAckAsync path so every Ack* variant records the
messaging.client.operation.duration histogram with messaging.operation=ack,
including error.type on failure, matching the publish/request/subscribe paths.

* core: add dropped-messages counter

Emit nats.client.messages.dropped on the OnMessageDropped path under the
NATS.Net meter, reusing the receive tag set so it correlates with
consumed.messages. Pending channel depth stays on the MessageDropped event
rather than becoming an unbounded tag value.

* core: collapse inbox subjects in trace tags

Inbox subjects (_INBOX.<nuid>) are unique per request, so emitting them on
indexed activity tags (destination.name, destination_publish.name,
nats.message.subject, reply_to) pushes unbounded cardinality into tracing
backends. Collapse them to "inbox", matching SpanDestinationName; the raw
subject remains reachable through the Enrich callback.

* telemetry: document inbox-prefix and ack-op choices [no ci]
Move INatsSocketConnection and INatsTlsUpgradeableSocketConnection to
NATS.Client.Abstractions so custom transport implementers can depend on the
contract without referencing Core, same rationale as the serialization
interfaces already there. Both types are BCL-only.

Keep the NATS.Client.Core namespace on the moved types and add
TypeForwardedTo in Core to preserve source and binary compatibility. The
obsolete ISocketConnection and INatsSocketConnectionFactory stay in Core; the
factory takes NatsOpts so it cannot move without dragging Core options along.

Add Microsoft.Bcl.AsyncInterfaces to the Abstractions netstandard2.0 group for
IAsyncDisposable and ValueTask, which are not in that target's BCL.
* otel: add OpenTelemetry package

* otel: add options configure overload

Mirrors AddRabbitMQInstrumentation(configure): exposes a discoverable
entry point to set Filter and Enrich instead of reaching into the Core
static. Writes the process-wide NatsInstrumentationOptions.Default, so
configuration is global, not per provider.

* otel: add subject-pattern trace filter helper

FilterSubjects compiles NATS subject patterns (include/exclude, with * and >
wildcards) into a NatsInstrumentationOptions.Filter predicate, combined with any
existing filter. Document it alongside the operation.duration histogram bucket
view, which stays out of the package to avoid pulling in the OpenTelemetry SDK
dependency.
* Reduce allocation while calculating span name

Signed-off-by: Zhen Wu <colprog@gmail.com>

* Add span destination name formatter

Signed-off-by: Zhen Wu <colprog@gmail.com>

---------

Signed-off-by: Zhen Wu <colprog@gmail.com>
Use SearchValues for header key validation on .NET 8+ while preserving fallback behavior for older targets.

Add microbenchmarks for the new SearchValues variants and a byte-boundary test for header key validation.
* objectstore: simplify base64url encoder

Replace the hand-rolled bit-twiddling encode loop with the in-box
Base64.EncodeToUtf8 (vectorized, available on every target framework via
System.Memory) plus an in-place '+'/'/' -> '-'/'_' swap. The '=' padding
is kept so output stays byte-for-byte compatible with the padded
base64.URLEncoding other clients use for digests and metadata subject
names. On read, compare the digest as spans instead of building a
"SHA-256=" interpolated string per object.

Co-authored-by: to11mtm <12536917+to11mtm@users.noreply.github.com>

* Update src/NATS.Client.ObjectStore/Internal/Encoder.cs

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* objectstore: encode base64url straight to chars

The encoder built the string by encoding to UTF8 bytes and then
transcoding with Encoding.ASCII.GetString. Encode straight to a char
span instead and build the string from it, dropping the transcode:
net9+ uses Base64Url (url-safe in one pass), net8 and netstandard2.1
use Convert.TryToBase64Chars, and netstandard2.0 keeps the byte path
since it has no span-based encode-to-chars. The per-framework branches
are flattened into a single #if chain.

* objectstore: cover base64url encoder on all frameworks

Move the encoder test into a lean project that references only the
ObjectStore assembly so it can multi-target down to net6.0; the
integration test project cannot, as some of its tests do not build on
net6.0. Each test TFM pins a distinct compiled branch via asset
selection: net6.0 -> netstandard2.1, net8.0 -> net8.0, net10.0 ->
net9+, net481 -> netstandard2.0 (Windows only).

Add a BenchmarkDotNet project under sandbox that compares the same
branches side by side with per-runtime jobs.

* ci: run encoder tests and target net10 sdk

Run the new encoder tests on Linux (net6/8/10) and Windows (also
net481). Install the net10 SDK explicitly instead of relying on the
runner image happening to ship it, and drop the unused net9 SDK as no
project targets net9.

* objectstore: stackalloc table encoder below net9

net9+ keeps Base64Url. Below net9 there is no url-safe base64 encoder,
so use the lookup table; its 63rd/64th entries are '-'/'_', so it maps
to the url-safe alphabet directly and needs no post-swap. Encode into a
stack or pooled buffer instead of a fresh char[], so only the result
string is allocated: half the allocations of the original encoder with
no measurable CPU change, and net9+ is faster on top of that.

* bench: compare base64url encoder on net8 and net10

Below net9 every build shares one code path (the lookup table), so net8
represents it and net10 covers Base64Url; drop the net6/net481 jobs.
Keep a table baseline next to the current encoder for comparison.

* bench: rename benchmark project to MicroBench2

Make it a general lean BenchmarkDotNet host (only the project under test
plus BenchmarkDotNet, no heavier third-party deps like MicroBenchmark)
so future minimal-dependency micro-benchmarks can be added alongside the
base64url encoder one.

* objectstore: use unchecked pointer loop in table encoder

* ci: install net6/net8/net10 sdks for apicompat

The apicompat job builds the whole solution, which now includes the
encoder test project targeting net6.0. Building net6.0 needs the net6.0
apphost pack, absent when only the 8.x SDK is installed. Match the test
workflows and install 6.x/8.x/10.x.

* bench: add net481 to base64url encoder benchmark

* objectstore: test Sha256 digest base64url encoding

---------

Co-authored-by: to11mtm <12536917+to11mtm@users.noreply.github.com>
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
The Default instance is configured at startup but its callbacks are read on
the publish/subscribe path from other threads. Back the properties with
volatile fields so a configuration change made after traffic has started is
observed by those readers instead of relying on incidental barriers.
…1206)

* otel: match subject filters without per-message allocation

FilterSubjects split the subject into a string[] on every traced operation.
Walk the subject token by token with spans instead; the patterns are still
tokenized once at configuration time. Adds coverage for empty-token boundaries.

* otel: clarify invalid-subject matcher comments and test names
The non-NatsConnection receive path allocated a fixed 10-slot tag array but
only filled the reply-to slot when a reply-to was present, leaving a default
{null, null} entry that was handed to StartActivity. Size the array to the
tags actually set, matching the send path.
The two DI entry points are easy to confuse. Document AddNatsClient,
which had no XML doc, and rewrite the AddNats summary to state that it
registers no INatsClient and no JSON serializer; each package now points
at the other for the case it does not cover. Also document the public
NatsBuilder surface.

Drop the keyed-services usage example from the platform compatibility
page since it duplicated the dependency injection page; keep the
per-target-framework signature difference and link out for usage.
@mtmk
mtmk merged commit c6b2e01 into main Jul 10, 2026
31 checks passed
@mtmk
mtmk temporarily deployed to github-pages July 10, 2026 07:55 — with GitHub Actions Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants