Skip to content

Conversation

@mtmk
Copy link
Member

@mtmk mtmk commented Dec 15, 2025

Add subject/replyTo/queueGroup validation to prevent protocol-breaking input from reaching the wire.

Validation rules:

  • Subjects: no whitespace
  • Queue groups: no whitespace

Opt-In:

Validation is turned off by default to not break any existing installations. You can turn it on:

var opts = NatsOpts.Default with { SkipSubjectValidation = false };

Exception Handling:

Validation exception is thrown at the point of the calls on the same execution thread. This will give the application immediate feedback and stack trace where the invalid string is used.

Performance Impact Summary:

Runtime Subject Length Validation Overhead
.NET 8.0 Short (11 chars) ~0-2% (within noise)
.NET 8.0 Long (95 chars) ~0% (within noise)
.NET Framework 4.8.1 Short (11 chars) ~5-8%
.NET Framework 4.8.1 Long (95 chars) ~0%*

(*) Long subject results on .NET Framework show measurement variability due to GC timing differences between benchmark runs.

Subject validation adds negligible overhead on .NET 8.0, the primary target runtime. The validation uses SearchValues<char> with SIMD on .NET 8+ and an optimized manual loop for short subjects (<16 chars).

The validation is allocation free on all frameworks.

See comments below for benchmark run results.

@mtmk mtmk requested review from Copilot and scottf December 15, 2025 07:49
@mtmk mtmk marked this pull request as ready for review December 15, 2025 07:49

This comment was marked as outdated.

This comment was marked as outdated.

…zation and remove dot-based validation tests

This comment was marked as outdated.

…bject validation details in comments and documentation

This comment was marked as outdated.

Expanded tests to include separate validations for short (<16 chars) and long (≥16 chars) subject paths, with specific checks for whitespace handling.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@scottf scottf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

mtmk added 3 commits December 16, 2025 12:38
…n `SubjectValidator`, remove inline checks, and streamline public APIs. Update tests for comprehensive validation coverage.
…s. Split `ProtocolWriterTests` into focused test classes and improve test coverage for whitespace handling and edge cases.
Ensure subject and queue group validation occurs before async operations in `SubscribeAsync`, `SubscribeCoreAsync`, and `RequestManyAsync`. Update tests to verify synchronous exceptions and add comprehensive validation coverage.
@mtmk mtmk requested a review from Copilot December 16, 2025 15:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

mtmk added 2 commits December 16, 2025 15:31
…nc` implementations. Update tests to improve clarity and replace unused variable names. Resolve BenchmarkDotNet dependency conflict for `net481`.
@mtmk
Copy link
Member Author

mtmk commented Dec 17, 2025

Method Job Runtime Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
PublishAsync_Short_NoValidation .NET 8.0 .NET 8.0 39.26 ms 0.672 ms 0.561 ms 1.00 0.02 - - 1201 B 1.00
PublishAsync_Short_WithValidation .NET 8.0 .NET 8.0 41.64 ms 0.372 ms 0.330 ms 1.06 0.02 - - 651 B 0.54
PublishAsync_Long_NoValidation .NET 8.0 .NET 8.0 107.29 ms 2.095 ms 2.494 ms 2.73 0.07 - - 41946 B 34.93
PublishAsync_Long_WithValidation .NET 8.0 .NET 8.0 107.70 ms 1.865 ms 1.653 ms 2.74 0.06 - - 42078 B 35.04
PublishAsync_Short_NoValidation .NET Framework 4.8.1 .NET Framework 4.8.1 95.82 ms 0.816 ms 0.681 ms 1.00 0.01 3833.3333 - 24166869 B 1.00
PublishAsync_Short_WithValidation .NET Framework 4.8.1 .NET Framework 4.8.1 100.25 ms 1.319 ms 1.234 ms 1.05 0.01 3833.3333 - 24149559 B 1.00
PublishAsync_Long_NoValidation .NET Framework 4.8.1 .NET Framework 4.8.1 196.68 ms 3.893 ms 5.584 ms 2.05 0.06 6333.3333 333.3333 41165797 B 1.70
PublishAsync_Long_WithValidation .NET Framework 4.8.1 .NET Framework 4.8.1 164.03 ms 2.563 ms 2.140 ms 1.71 0.02 6333.3333 - 40496824 B 1.68

@mtmk
Copy link
Member Author

mtmk commented Dec 17, 2025

Method Job Runtime Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
PublishAsync_Short_NoValidation .NET 8.0 .NET 8.0 40.14 ms 0.644 ms 0.602 ms 1.00 0.02 - - 578 B 1.00
PublishAsync_Short_WithValidation .NET 8.0 .NET 8.0 40.64 ms 0.399 ms 0.353 ms 1.01 0.02 - - 518 B 0.90
PublishAsync_Long_NoValidation .NET 8.0 .NET 8.0 108.20 ms 2.101 ms 2.657 ms 2.70 0.08 - - 42570 B 73.65
PublishAsync_Long_WithValidation .NET 8.0 .NET 8.0 107.18 ms 1.498 ms 1.401 ms 2.67 0.05 - - 41890 B 72.47
PublishAsync_Short_NoValidation .NET Framework 4.8.1 .NET Framework 4.8.1 97.02 ms 1.924 ms 2.290 ms 1.00 0.03 3833.3333 - 24198328 B 1.00
PublishAsync_Short_WithValidation .NET Framework 4.8.1 .NET Framework 4.8.1 100.86 ms 1.671 ms 1.563 ms 1.04 0.03 3800.0000 - 24104245 B 1.00
PublishAsync_Long_NoValidation .NET Framework 4.8.1 .NET Framework 4.8.1 196.11 ms 3.240 ms 5.759 ms 2.02 0.08 6333.3333 333.3333 41086603 B 1.70
PublishAsync_Long_WithValidation .NET Framework 4.8.1 .NET Framework 4.8.1 170.56 ms 2.965 ms 2.476 ms 1.76 0.05 6250.0000 - 40741804 B 1.68

@mtmk
Copy link
Member Author

mtmk commented Dec 17, 2025

Method Job Runtime Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
PublishAsync_Short_NoValidation .NET 8.0 .NET 8.0 39.98 ms 0.625 ms 0.554 ms 1.00 0.02 - - 759 B 1.00
PublishAsync_Short_WithValidation .NET 8.0 .NET 8.0 39.44 ms 0.163 ms 0.136 ms 0.99 0.01 - - 799 B 1.05
PublishAsync_Long_NoValidation .NET 8.0 .NET 8.0 109.14 ms 2.181 ms 3.057 ms 2.73 0.08 - - 42506 B 56.00
PublishAsync_Long_WithValidation .NET 8.0 .NET 8.0 107.80 ms 2.112 ms 2.671 ms 2.70 0.07 - - 41710 B 54.95
PublishAsync_Short_NoValidation .NET Framework 4.8.1 .NET Framework 4.8.1 95.08 ms 0.995 ms 0.882 ms 1.00 0.01 3833.3333 - 24177856 B 1.00
PublishAsync_Short_WithValidation .NET Framework 4.8.1 .NET Framework 4.8.1 102.77 ms 2.001 ms 2.141 ms 1.08 0.02 3800.0000 - 24186640 B 1.00
PublishAsync_Long_NoValidation .NET Framework 4.8.1 .NET Framework 4.8.1 197.44 ms 3.840 ms 4.994 ms 2.08 0.05 6333.3333 333.3333 41064699 B 1.70
PublishAsync_Long_WithValidation .NET Framework 4.8.1 .NET Framework 4.8.1 165.25 ms 2.162 ms 1.805 ms 1.74 0.02 6333.3333 - 40726128 B 1.68

@mtmk
Copy link
Member Author

mtmk commented Dec 17, 2025

> dotnet run -c Release --framework net8.0 -- --filter "*SubjectValidation*"
BenchmarkDotNet v0.14.0, Windows 11 (10.0.26200.7462)
Intel Xeon w5-2445, 1 CPU, 20 logical and 10 physical cores
.NET SDK 10.0.101
  [Host]               : .NET 8.0.22 (8.0.2225.52707), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET 8.0             : .NET 8.0.22 (8.0.2225.52707), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  .NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9221.0), X64 RyuJIT VectorSize=256


| Method                            | Job                  | Runtime              | Mean      | Error    | StdDev   | Ratio | RatioSD | Gen0      | Gen1     | Allocated  | Alloc Ratio |
|---------------------------------- |--------------------- |--------------------- |----------:|---------:|---------:|------:|--------:|----------:|---------:|-----------:|------------:|
| PublishAsync_Short_NoValidation   | .NET 8.0             | .NET 8.0             |  39.91 ms | 0.447 ms | 0.419 ms |  1.00 |    0.01 |         - |        - |     1193 B |        1.00 |
| PublishAsync_Short_WithValidation | .NET 8.0             | .NET 8.0             |  40.85 ms | 0.668 ms | 0.624 ms |  1.02 |    0.02 |         - |        - |      589 B |        0.49 |
| PublishAsync_Long_NoValidation    | .NET 8.0             | .NET 8.0             | 108.09 ms | 2.159 ms | 3.666 ms |  2.71 |    0.09 |         - |        - |    42354 B |       35.50 |
| PublishAsync_Long_WithValidation  | .NET 8.0             | .NET 8.0             | 107.14 ms | 2.116 ms | 1.767 ms |  2.68 |    0.05 |         - |        - |    41704 B |       34.96 |
|                                   |                      |                      |           |          |          |       |         |           |          |            |             |
| PublishAsync_Short_NoValidation   | .NET Framework 4.8.1 | .NET Framework 4.8.1 |  95.64 ms | 1.366 ms | 1.211 ms |  1.00 |    0.02 | 3833.3333 |        - | 24202443 B |        1.00 |
| PublishAsync_Short_WithValidation | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 100.71 ms | 1.054 ms | 0.934 ms |  1.05 |    0.02 | 3800.0000 |        - | 24116710 B |        1.00 |
| PublishAsync_Long_NoValidation    | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 192.69 ms | 3.771 ms | 4.904 ms |  2.02 |    0.06 | 6333.3333 | 333.3333 | 41152133 B |        1.70 |
| PublishAsync_Long_WithValidation  | .NET Framework 4.8.1 | .NET Framework 4.8.1 | 167.52 ms | 3.294 ms | 2.920 ms |  1.75 |    0.04 | 6250.0000 |        - | 40743854 B |        1.68 |

// * Hints *
Outliers
  SubjectValidationBench.PublishAsync_Long_NoValidation: .NET 8.0                -> 1 outlier  was  removed (119.30 ms)
  SubjectValidationBench.PublishAsync_Long_WithValidation: .NET 8.0              -> 2 outliers were removed (114.87 ms, 117.35 ms)
  SubjectValidationBench.PublishAsync_Short_NoValidation: .NET Framework 4.8.1   -> 1 outlier  was  removed (100.50 ms)
  SubjectValidationBench.PublishAsync_Short_WithValidation: .NET Framework 4.8.1 -> 1 outlier  was  removed (105.72 ms)
  SubjectValidationBench.PublishAsync_Long_NoValidation: .NET Framework 4.8.1    -> 1 outlier  was  removed (211.72 ms)
  SubjectValidationBench.PublishAsync_Long_WithValidation: .NET Framework 4.8.1  -> 2 outliers were removed (183.32 ms, 186.54 ms)

// * Legends *
  Mean        : Arithmetic mean of all measurements
  Error       : Half of 99.9% confidence interval
  StdDev      : Standard deviation of all measurements
  Ratio       : Mean of the ratio distribution ([Current]/[Baseline])
  RatioSD     : Standard deviation of the ratio distribution ([Current]/[Baseline])
  Gen0        : GC Generation 0 collects per 1000 operations
  Gen1        : GC Generation 1 collects per 1000 operations
  Allocated   : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
  Alloc Ratio : Allocated memory ratio distribution ([Current]/[Baseline])
  1 ms        : 1 Millisecond (0.001 sec)

// * Diagnostic Output - MemoryDiagnoser *


// ***** BenchmarkRunner: End *****
Run time: 00:02:26 (146.95 sec), executed benchmarks: 8

Global total time: 00:02:49 (169.5 sec), executed benchmarks: 8

@mtmk mtmk merged commit f739e63 into main Dec 17, 2025
21 checks passed
@mtmk mtmk deleted the subject-validation branch December 17, 2025 10:10
mtmk added a commit that referenced this pull request Dec 17, 2025
* Fix service hangs when stopping (#1016)
* Add Timeout to Object Get Disposal (#1014)
* Add validation for subjects (#1017)
@mtmk mtmk mentioned this pull request Dec 17, 2025
mtmk added a commit that referenced this pull request Dec 17, 2025
* Fix service hangs when stopping (#1016)
* Add Timeout to Object Get Disposal (#1014)
* Add validation for subjects (#1017)
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.

3 participants