Compare commits
25 Commits
01df4ccff3
...
codex/defe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b94a67be6e | ||
|
|
c0aaae9236 | ||
|
|
4e96fb2ba8 | ||
|
|
ae0a553ab8 | ||
|
|
a660e38575 | ||
|
|
8849265780 | ||
|
|
ba4f41cf71 | ||
|
|
4e61314c1c | ||
|
|
db1de2a384 | ||
|
|
7a338dd510 | ||
|
|
3297334261 | ||
|
|
4972f998b7 | ||
|
|
7518b97b79 | ||
|
|
485c7b0c2e | ||
|
|
9e2d763741 | ||
|
|
0c9eb2a06c | ||
|
|
a91cfbc7bd | ||
|
|
26d6d7fe68 | ||
|
|
0436e08fc1 | ||
|
|
2dd23211c7 | ||
|
|
c5c6fbc027 | ||
|
|
84dc9d1e1d | ||
|
|
60dce2dc9a | ||
|
|
e7f259710a | ||
|
|
810ef29dbb |
228
AGENTS.md
Normal file
228
AGENTS.md
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## Project Summary
|
||||||
|
|
||||||
|
This project ports the NATS messaging server from Go to .NET 10 C#. The Go source (~130K LOC) is the reference at `golang/nats-server/`. Porting progress is tracked in an SQLite database (`porting.db`) managed by the PortTracker CLI tool.
|
||||||
|
|
||||||
|
## Folder Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
natsnet/
|
||||||
|
├── golang/nats-server/ # Go source (read-only reference)
|
||||||
|
├── dotnet/
|
||||||
|
│ ├── src/ZB.MOM.NatsNet.Server/ # Main server library
|
||||||
|
│ ├── src/ZB.MOM.NatsNet.Server.Host/ # Host entry point
|
||||||
|
│ └── tests/
|
||||||
|
│ ├── ZB.MOM.NatsNet.Server.Tests/ # Unit tests
|
||||||
|
│ └── ZB.MOM.NatsNet.Server.IntegrationTests/ # Integration tests
|
||||||
|
├── tools/NatsNet.PortTracker/ # CLI tracking tool
|
||||||
|
├── docs/standards/dotnet-standards.md # .NET coding standards (MUST follow)
|
||||||
|
├── docs/plans/phases/ # Phase instruction guides
|
||||||
|
├── reports/current.md # Latest porting status
|
||||||
|
├── porting.db # SQLite tracking database
|
||||||
|
└── porting-schema.sql # Database schema
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build and Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build the solution
|
||||||
|
dotnet build dotnet/
|
||||||
|
|
||||||
|
# Run all unit tests
|
||||||
|
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.Tests/
|
||||||
|
|
||||||
|
# Run filtered tests (by namespace/class)
|
||||||
|
dotnet test --filter "FullyQualifiedName~ZB.MOM.NatsNet.Server.Tests.Protocol" \
|
||||||
|
dotnet/tests/ZB.MOM.NatsNet.Server.Tests/
|
||||||
|
|
||||||
|
# Run integration tests
|
||||||
|
dotnet test dotnet/tests/ZB.MOM.NatsNet.Server.IntegrationTests/
|
||||||
|
|
||||||
|
# Generate porting report
|
||||||
|
./reports/generate-report.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## .NET Coding Standards
|
||||||
|
|
||||||
|
**MUST follow all rules in `docs/standards/dotnet-standards.md`.**
|
||||||
|
|
||||||
|
Critical rules (non-negotiable):
|
||||||
|
|
||||||
|
- .NET 10, C# latest, nullable enabled
|
||||||
|
- **xUnit 3** + **Shouldly** + **NSubstitute** for testing
|
||||||
|
- **NEVER use FluentAssertions or Moq** — these are forbidden
|
||||||
|
- PascalCase for public members, `_camelCase` for private fields
|
||||||
|
- File-scoped namespaces: `ZB.MOM.NatsNet.Server.[Module]`
|
||||||
|
- Use `CancellationToken` on all async signatures
|
||||||
|
- Use `ReadOnlySpan<byte>` on hot paths
|
||||||
|
- Test naming: `[Method]_[Scenario]_[Expected]`
|
||||||
|
- Test class naming: `[ClassName]Tests`
|
||||||
|
- Structured logging with `ILogger<T>` and `LogContext.PushProperty`
|
||||||
|
|
||||||
|
## PortTracker CLI
|
||||||
|
|
||||||
|
All tracking commands use this base:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- <command> --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
### Querying
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| `report summary` | Show overall porting progress |
|
||||||
|
| `dependency ready` | List items ready to port (no unported deps) |
|
||||||
|
| `dependency blocked` | List items blocked by unported deps |
|
||||||
|
| `feature list --status <s>` | List features by status |
|
||||||
|
| `feature list --module <id>` | List features in a module |
|
||||||
|
| `feature show <id>` | Show feature details (Go source path, .NET target) |
|
||||||
|
| `test list --status <s>` | List tests by status |
|
||||||
|
| `test show <id>` | Show test details |
|
||||||
|
| `module list` | List all modules |
|
||||||
|
| `module show <id>` | Show module with its features and tests |
|
||||||
|
|
||||||
|
### Updating Status
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| `feature update <id> --status <s>` | Update one feature |
|
||||||
|
| `feature batch-update --ids "1-10" --set-status <s> --execute` | Bulk update features |
|
||||||
|
| `test update <id> --status <s>` | Update one test |
|
||||||
|
| `test batch-update --ids "1-10" --set-status <s> --execute` | Bulk update tests |
|
||||||
|
| `module update <id> --status <s>` | Update module status |
|
||||||
|
|
||||||
|
### Audit Verification
|
||||||
|
|
||||||
|
Status updates are verified against Roslyn audit results. If the audit disagrees with your requested status, add `--override "reason"` to force it.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
feature update 42 --status verified --override "manually verified logic"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Audit Commands
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| `audit --type features` | Dry-run audit of features against .NET source |
|
||||||
|
| `audit --type tests` | Dry-run audit of tests against test project |
|
||||||
|
| `audit --type features --execute` | Apply audit classifications to DB |
|
||||||
|
| `audit --type tests --execute` | Apply test audit classifications to DB |
|
||||||
|
|
||||||
|
### Valid Statuses
|
||||||
|
|
||||||
|
```
|
||||||
|
not_started → stub → complete → verified
|
||||||
|
└→ n_a (not applicable)
|
||||||
|
└→ deferred (blocked, needs server infra)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch ID Syntax
|
||||||
|
|
||||||
|
`--ids` accepts: ranges `"100-200"`, lists `"1,5,10"`, or mixed `"1-5,10,20-25"`.
|
||||||
|
|
||||||
|
All batch commands default to dry-run. Add `--execute` to apply.
|
||||||
|
|
||||||
|
## Porting Workflow
|
||||||
|
|
||||||
|
### Finding Work
|
||||||
|
|
||||||
|
1. Query for features ready to port:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- dependency ready --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Or find deferred/stub features in a specific module:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature list --module <id> --status deferred --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
3. To find tests that need implementing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- test list --status stub --db porting.db
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- test list --status deferred --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementing a Feature
|
||||||
|
|
||||||
|
1. **Claim it** — mark as stub before starting:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature update <id> --status stub --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Read the Go source** — use `feature show <id>` to get the Go file path and line numbers, then read the Go implementation.
|
||||||
|
|
||||||
|
3. **Write idiomatic C#** — translate intent, not lines:
|
||||||
|
- Use `async`/`await`, not goroutine translations
|
||||||
|
- Use `Channel<T>` for Go channels
|
||||||
|
- Use `CancellationToken` for `context.Context`
|
||||||
|
- Use `ReadOnlySpan<byte>` on hot paths
|
||||||
|
- Use `Lock` (C# 13) for `sync.Mutex`
|
||||||
|
- Use `ReaderWriterLockSlim` for `sync.RWMutex`
|
||||||
|
|
||||||
|
4. **Ensure it compiles** — run `dotnet build dotnet/`
|
||||||
|
|
||||||
|
5. **Mark complete**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature update <id> --status complete --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementing a Unit Test
|
||||||
|
|
||||||
|
1. **Read the Go test** — use `test show <id>` to get Go source location.
|
||||||
|
2. **Read the corresponding .NET feature** to understand the API surface.
|
||||||
|
3. **Write the test** in `dotnet/tests/ZB.MOM.NatsNet.Server.Tests/` using xUnit 3 + Shouldly + NSubstitute.
|
||||||
|
4. **Run it**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet test --filter "FullyQualifiedName~TestClassName" \
|
||||||
|
dotnet/tests/ZB.MOM.NatsNet.Server.Tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Mark verified** (if passing):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- test update <id> --status verified --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
### After Completing Work
|
||||||
|
|
||||||
|
1. Run affected tests to verify nothing broke.
|
||||||
|
2. Update DB status for all items you changed.
|
||||||
|
3. Check what's newly unblocked:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- dependency ready --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Generate updated report:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./reports/generate-report.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Go to .NET Translation Reference
|
||||||
|
|
||||||
|
| Go Pattern | .NET Equivalent |
|
||||||
|
|------------|-----------------|
|
||||||
|
| `goroutine` | `Task.Run` or `async`/`await` |
|
||||||
|
| `chan T` | `Channel<T>` |
|
||||||
|
| `select` | `Task.WhenAny` |
|
||||||
|
| `sync.Mutex` | `Lock` (C# 13) |
|
||||||
|
| `sync.RWMutex` | `ReaderWriterLockSlim` |
|
||||||
|
| `sync.WaitGroup` | `Task.WhenAll` or `CountdownEvent` |
|
||||||
|
| `atomic.Int64` | `Interlocked` methods on `long` field |
|
||||||
|
| `context.Context` | `CancellationToken` |
|
||||||
|
| `defer` | `try`/`finally` or `using` |
|
||||||
|
| `error` return | Exceptions or Result pattern |
|
||||||
|
| `[]byte` | `byte[]`, `ReadOnlySpan<byte>`, `ReadOnlyMemory<byte>` |
|
||||||
|
| `map[K]V` | `Dictionary<K,V>` or `ConcurrentDictionary<K,V>` |
|
||||||
|
| `interface{}` | `object` or generics |
|
||||||
|
| `time.Duration` | `TimeSpan` |
|
||||||
|
| `weak.Pointer[T]` | `WeakReference<T>` |
|
||||||
29
docs/plans/2026-02-27-agents-md-design.md
Normal file
29
docs/plans/2026-02-27-agents-md-design.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# AGENTS.md Design
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Create an `AGENTS.md` file at the project root for OpenAI Codex agents working on this codebase. The file provides project context, PortTracker CLI reference, porting workflow guidance, and pointers to .NET coding standards.
|
||||||
|
|
||||||
|
## Target
|
||||||
|
|
||||||
|
OpenAI Codex — follows Codex's AGENTS.md discovery conventions (root-level, markdown format, under 32KB).
|
||||||
|
|
||||||
|
## Structure Decision
|
||||||
|
|
||||||
|
**Flat single-file** at project root. The project information is tightly coupled — PortTracker commands are needed regardless of which directory Codex is editing. A single file keeps everything in context for every session.
|
||||||
|
|
||||||
|
## Sections
|
||||||
|
|
||||||
|
1. **Project Summary** — What the project is, where Go source and .NET code live
|
||||||
|
2. **Folder Layout** — Directory tree with annotations
|
||||||
|
3. **Build and Test** — Commands to build, run unit tests, run filtered tests, run integration tests
|
||||||
|
4. **.NET Coding Standards** — Pointer to `docs/standards/dotnet-standards.md` with critical rules inlined (forbidden packages, naming, testing framework)
|
||||||
|
5. **PortTracker CLI** — Full command reference: querying, updating, audit verification, valid statuses, batch syntax
|
||||||
|
6. **Porting Workflow** — Step-by-step: finding work, implementing features, implementing tests, post-completion checklist
|
||||||
|
7. **Go to .NET Translation Reference** — Quick-reference table for common Go-to-.NET pattern translations
|
||||||
|
|
||||||
|
## Size
|
||||||
|
|
||||||
|
~3.5KB — well within Codex's 32KB default limit.
|
||||||
|
|
||||||
|
<!-- Last verified against codebase: 2026-02-27 -->
|
||||||
85
docs/plans/2026-02-27-audit-verified-updates-design.md
Normal file
85
docs/plans/2026-02-27-audit-verified-updates-design.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# Audit-Verified Status Updates Design
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Require audit verification before applying status changes to features or unit tests. When the requested status disagrees with what the Roslyn audit determines, require an explicit override with a comment. Track all overrides in a new table for later review.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Inline audit verification: when `feature update`, `feature batch-update`, `test update`, or `test batch-update` runs, build the `SourceIndexer` on the fly, classify each item, and compare. If the requested status doesn't match the audit, block the update unless `--override "comment"` is provided.
|
||||||
|
|
||||||
|
## Override Table Schema
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE status_overrides (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
table_name TEXT NOT NULL CHECK (table_name IN ('features', 'unit_tests')),
|
||||||
|
item_id INTEGER NOT NULL,
|
||||||
|
audit_status TEXT NOT NULL,
|
||||||
|
audit_reason TEXT NOT NULL,
|
||||||
|
requested_status TEXT NOT NULL,
|
||||||
|
comment TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Each row records: which table/item, what the audit said, what the user requested, and their justification.
|
||||||
|
|
||||||
|
## CLI Interface
|
||||||
|
|
||||||
|
### Single update
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Audit agrees — applied directly
|
||||||
|
dotnet run -- feature update 123 --status verified --db porting.db
|
||||||
|
|
||||||
|
# Audit disagrees — blocked
|
||||||
|
# Error: "Audit classifies feature 123 as 'stub'. Use --override 'reason' to force."
|
||||||
|
|
||||||
|
# Override
|
||||||
|
dotnet run -- feature update 123 --status verified --override "Manual review confirms complete" --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch update
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# All items agree — applied
|
||||||
|
dotnet run -- feature batch-update --module 5 --set-status verified --execute --db porting.db
|
||||||
|
|
||||||
|
# Some items disagree — blocked
|
||||||
|
# "15 items match audit, 3 require override. Use --override 'reason' to force all."
|
||||||
|
|
||||||
|
# Override entire batch (one comment covers all mismatches)
|
||||||
|
dotnet run -- feature batch-update --module 5 --set-status verified --override "Batch approved" --execute --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Same interface for `test update` and `test batch-update`.
|
||||||
|
|
||||||
|
## Verification Flow
|
||||||
|
|
||||||
|
1. Build `SourceIndexer` for the appropriate directory (features → `dotnet/src/...`, tests → `dotnet/tests/...`).
|
||||||
|
2. For each item: query its `dotnet_class`, `dotnet_method`, `go_file`, `go_method` from DB. Run `FeatureClassifier.Classify()`.
|
||||||
|
3. Compare requested status vs audit status. Collect mismatches.
|
||||||
|
4. If mismatches and no `--override`: print details and exit with error.
|
||||||
|
5. If `--override` provided: apply all updates. Insert one `status_overrides` row per mismatched item.
|
||||||
|
6. Items that agree with audit: apply normally, no override row logged.
|
||||||
|
|
||||||
|
Items that cannot be audited (no dotnet_class/dotnet_method) are treated as mismatches requiring override.
|
||||||
|
|
||||||
|
## Override Review Command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run -- override list --db porting.db
|
||||||
|
dotnet run -- override list --type features --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Tabular output: id, table, item_id, audit_status, requested_status, comment, date.
|
||||||
|
|
||||||
|
## Changes Required
|
||||||
|
|
||||||
|
1. **porting-schema.sql**: Add `status_overrides` table.
|
||||||
|
2. **FeatureCommands.cs**: Add `--override` option to `update` and `batch-update`. Integrate audit verification before applying.
|
||||||
|
3. **TestCommands.cs**: Same changes as FeatureCommands.
|
||||||
|
4. **New `OverrideCommands.cs`**: `override list` command.
|
||||||
|
5. **Program.cs**: Wire `override` command group.
|
||||||
|
6. **Shared helper**: Extract audit verification logic (build indexer, classify, compare) into a reusable method since both feature and test commands need it.
|
||||||
154
docs/plans/2026-02-27-feature-audit-script-design.md
Normal file
154
docs/plans/2026-02-27-feature-audit-script-design.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
# Feature Audit Script Design
|
||||||
|
|
||||||
|
**Date:** 2026-02-27
|
||||||
|
**Status:** Approved
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
3394 features in module 8 (`server`) are marked `unknown`. The existing plan (`2026-02-27-feature-status-audit-plan.md`) describes a manual 68-batch process of inspecting .NET source and classifying each feature. This design automates that process.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
A new PortTracker CLI command `feature audit` that uses Roslyn syntax tree analysis to parse .NET source files, build a method index, and classify all unknown features automatically.
|
||||||
|
|
||||||
|
## Command Interface
|
||||||
|
|
||||||
|
```
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature audit \
|
||||||
|
--source dotnet/src/ZB.MOM.NatsNet.Server/ \
|
||||||
|
--output reports/audit-results.csv \
|
||||||
|
--db porting.db \
|
||||||
|
[--module 8] \
|
||||||
|
[--execute]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Flag | Default | Description |
|
||||||
|
|------|---------|-------------|
|
||||||
|
| `--source` | `dotnet/src/ZB.MOM.NatsNet.Server/` | .NET source directory to parse |
|
||||||
|
| `--output` | `reports/audit-results.csv` | CSV report output path |
|
||||||
|
| `--db` | `porting.db` | SQLite database (inherited from root) |
|
||||||
|
| `--module` | *(all)* | Restrict to a specific module ID |
|
||||||
|
| `--execute` | `false` | Apply DB updates (default: dry-run) |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Component 1: Source Indexer (`Audit/SourceIndexer.cs`)
|
||||||
|
|
||||||
|
Parses all `.cs` files under the source directory into Roslyn syntax trees and builds a lookup index.
|
||||||
|
|
||||||
|
**Process:**
|
||||||
|
1. Recursively glob `**/*.cs` (skip `obj/`, `bin/`)
|
||||||
|
2. Parse each file with `CSharpSyntaxTree.ParseText()`
|
||||||
|
3. Walk syntax trees for `ClassDeclarationSyntax` and `StructDeclarationSyntax`
|
||||||
|
4. Extract all method, property, and constructor declarations
|
||||||
|
5. Build dictionary: `Dictionary<(string className, string memberName), List<MethodInfo>>`
|
||||||
|
|
||||||
|
**`MethodInfo`:**
|
||||||
|
- `FilePath` — source file path
|
||||||
|
- `LineNumber` — starting line
|
||||||
|
- `BodyLineCount` — lines in method body (excluding braces)
|
||||||
|
- `IsStub` — body is `throw new NotImplementedException(...)` or empty
|
||||||
|
- `IsPartial` — body has some logic AND a `NotImplementedException` throw
|
||||||
|
- `StatementCount` — number of meaningful statements
|
||||||
|
|
||||||
|
**Partial class handling:** Same class name across multiple files produces multiple entries in the index. Lookup checks all of them — a feature is matched if the method exists in ANY file for that class.
|
||||||
|
|
||||||
|
**Name matching:** Case-insensitive comparison for both class and method names. Handles `dotnet_class` values that contain commas (e.g. `ClosedRingBuffer,ClosedClient`) by splitting and checking each.
|
||||||
|
|
||||||
|
### Component 2: Feature Classifier (`Audit/FeatureClassifier.cs`)
|
||||||
|
|
||||||
|
Classifies each feature using the source index. Priority order (first match wins):
|
||||||
|
|
||||||
|
**1. N/A Lookup Table**
|
||||||
|
|
||||||
|
Checked first against `(go_file, go_method)` or `dotnet_class` patterns:
|
||||||
|
|
||||||
|
| Pattern | Reason |
|
||||||
|
|---------|--------|
|
||||||
|
| Go logging functions (`Noticef`, `Debugf`, `Tracef`, `Warnf`, `Errorf`, `Fatalf`) | .NET uses Microsoft.Extensions.Logging |
|
||||||
|
| Go signal handling (`HandleSignals`, `processSignal`) | .NET uses IHostApplicationLifetime |
|
||||||
|
| Go HTTP handler setup (`Statz`, `Varz`, `Connz`, etc.) | .NET uses ASP.NET middleware |
|
||||||
|
|
||||||
|
Table is extensible — add entries as new patterns are identified.
|
||||||
|
|
||||||
|
**2. Method Not Found** -> `deferred`
|
||||||
|
- `dotnet_class` not found in source index, OR
|
||||||
|
- `dotnet_method` not found within the class
|
||||||
|
|
||||||
|
**3. Stub Detection** -> `stub`
|
||||||
|
- Body is solely `throw new NotImplementedException(...)` (expression-bodied or block)
|
||||||
|
- Body is empty (no statements)
|
||||||
|
- Body has logic but also contains `NotImplementedException` (partial implementation)
|
||||||
|
|
||||||
|
**4. Verified** -> `verified`
|
||||||
|
- Method exists with 1+ meaningful statements that are not `NotImplementedException` throws
|
||||||
|
|
||||||
|
### Component 3: Audit Command (`Commands/AuditCommand.cs`)
|
||||||
|
|
||||||
|
Orchestrates the audit:
|
||||||
|
|
||||||
|
1. Query `SELECT id, dotnet_class, dotnet_method, go_file, go_method FROM features WHERE status = 'unknown'` (optionally filtered by module)
|
||||||
|
2. Build source index via `SourceIndexer`
|
||||||
|
3. Classify each feature via `FeatureClassifier`
|
||||||
|
4. Write CSV report
|
||||||
|
5. Print console summary
|
||||||
|
6. If `--execute`: update DB in a single transaction per status group
|
||||||
|
|
||||||
|
### DB Update Strategy
|
||||||
|
|
||||||
|
- Group features by `(new_status, notes)` tuple
|
||||||
|
- One `UPDATE features SET status = @s, notes = @n WHERE id IN (...)` per group
|
||||||
|
- All groups in a single transaction
|
||||||
|
- For `n_a` features: set `notes` to the reason from the lookup table
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
### CSV Report
|
||||||
|
|
||||||
|
```csv
|
||||||
|
id,dotnet_class,dotnet_method,go_file,go_method,old_status,new_status,reason
|
||||||
|
150,ServiceRespType,String,server/accounts.go,String,unknown,verified,Method found with 3 statements
|
||||||
|
151,Account,NewAccount,server/accounts.go,NewAccount,unknown,stub,Body is throw NotImplementedException
|
||||||
|
```
|
||||||
|
|
||||||
|
### Console Summary
|
||||||
|
|
||||||
|
```
|
||||||
|
Feature Status Audit Results
|
||||||
|
=============================
|
||||||
|
Source: dotnet/src/ZB.MOM.NatsNet.Server/ (142 files, 4821 methods indexed)
|
||||||
|
Features audited: 3394
|
||||||
|
|
||||||
|
verified: NNNN
|
||||||
|
stub: NNNN
|
||||||
|
n_a: NNNN
|
||||||
|
deferred: NNNN
|
||||||
|
|
||||||
|
Dry-run mode. Add --execute to apply changes.
|
||||||
|
Report: reports/audit-results.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
New NuGet package required:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files to Create/Modify
|
||||||
|
|
||||||
|
| File | Action |
|
||||||
|
|------|--------|
|
||||||
|
| `tools/NatsNet.PortTracker/NatsNet.PortTracker.csproj` | Add Roslyn package reference |
|
||||||
|
| `tools/NatsNet.PortTracker/Audit/SourceIndexer.cs` | New — Roslyn source parsing and indexing |
|
||||||
|
| `tools/NatsNet.PortTracker/Audit/FeatureClassifier.cs` | New — classification heuristics |
|
||||||
|
| `tools/NatsNet.PortTracker/Commands/AuditCommand.cs` | New — CLI command wiring |
|
||||||
|
| `tools/NatsNet.PortTracker/Program.cs` | Add `AuditCommand.Create()` to root command |
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
- No semantic analysis (full compilation) — syntax trees are sufficient
|
||||||
|
- No Go source parsing — we only inspect .NET source
|
||||||
|
- No unit test reclassification — separate effort
|
||||||
|
- No code changes to the server project — classification only
|
||||||
813
docs/plans/2026-02-27-feature-audit-script-plan.md
Normal file
813
docs/plans/2026-02-27-feature-audit-script-plan.md
Normal file
@@ -0,0 +1,813 @@
|
|||||||
|
# Feature Audit Script Implementation Plan
|
||||||
|
|
||||||
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task.
|
||||||
|
|
||||||
|
**Goal:** Add a `feature audit` command to the PortTracker CLI that uses Roslyn syntax tree analysis to automatically classify 3394 unknown features into verified/stub/n_a/deferred.
|
||||||
|
|
||||||
|
**Architecture:** Three new files — `SourceIndexer` parses all .cs files and builds a method lookup index, `FeatureClassifier` applies classification heuristics, `AuditCommand` wires the CLI and orchestrates the audit. Direct DB updates via the existing `Database` class.
|
||||||
|
|
||||||
|
**Tech Stack:** `Microsoft.CodeAnalysis.CSharp` (Roslyn) for C# parsing, `Microsoft.Data.Sqlite` (existing), `System.CommandLine` (existing)
|
||||||
|
|
||||||
|
**Design doc:** `docs/plans/2026-02-27-feature-audit-script-design.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Important Rules (Read Before Every Task)
|
||||||
|
|
||||||
|
1. All new files go under `tools/NatsNet.PortTracker/`
|
||||||
|
2. Follow the existing code style — see `FeatureCommands.cs` and `BatchFilters.cs` for patterns
|
||||||
|
3. Use `System.CommandLine` v3 (preview) APIs — `SetAction`, `parseResult.GetValue()`, etc.
|
||||||
|
4. The `Database` class methods: `Query()`, `Execute()`, `ExecuteScalar<T>()`, `ExecuteInTransaction()`
|
||||||
|
5. Run `dotnet build --project tools/NatsNet.PortTracker` after each file creation to verify compilation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 0: Add Roslyn NuGet package
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `tools/NatsNet.PortTracker/NatsNet.PortTracker.csproj`
|
||||||
|
|
||||||
|
**Step 1: Add the package reference**
|
||||||
|
|
||||||
|
Add `Microsoft.CodeAnalysis.CSharp` to the csproj:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
|
||||||
|
```
|
||||||
|
|
||||||
|
The `<ItemGroup>` should look like:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
|
||||||
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.3" />
|
||||||
|
<PackageReference Include="System.CommandLine" Version="3.0.0-preview.1.26104.118" />
|
||||||
|
</ItemGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Restore and build**
|
||||||
|
|
||||||
|
Run: `dotnet build --project tools/NatsNet.PortTracker`
|
||||||
|
Expected: Build succeeded. 0 Error(s).
|
||||||
|
|
||||||
|
**Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add tools/NatsNet.PortTracker/NatsNet.PortTracker.csproj
|
||||||
|
git commit -m "chore: add Roslyn package to PortTracker for feature audit"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Create SourceIndexer — data model and file parsing
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `tools/NatsNet.PortTracker/Audit/SourceIndexer.cs`
|
||||||
|
|
||||||
|
**Step 1: Create the SourceIndexer with MethodInfo record and indexing logic**
|
||||||
|
|
||||||
|
Create `tools/NatsNet.PortTracker/Audit/SourceIndexer.cs`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
|
namespace NatsNet.PortTracker.Audit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses .cs files using Roslyn syntax trees and builds a lookup index
|
||||||
|
/// of (className, memberName) -> list of MethodInfo.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SourceIndexer
|
||||||
|
{
|
||||||
|
public record MethodInfo(
|
||||||
|
string FilePath,
|
||||||
|
int LineNumber,
|
||||||
|
int BodyLineCount,
|
||||||
|
bool IsStub,
|
||||||
|
bool IsPartial,
|
||||||
|
int StatementCount);
|
||||||
|
|
||||||
|
// Key: (className lowercase, memberName lowercase)
|
||||||
|
private readonly Dictionary<(string, string), List<MethodInfo>> _index = new();
|
||||||
|
|
||||||
|
public int FilesIndexed { get; private set; }
|
||||||
|
public int MethodsIndexed { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recursively parses all .cs files under <paramref name="sourceDir"/>
|
||||||
|
/// (skipping obj/ and bin/) and populates the index.
|
||||||
|
/// </summary>
|
||||||
|
public void IndexDirectory(string sourceDir)
|
||||||
|
{
|
||||||
|
var files = Directory.EnumerateFiles(sourceDir, "*.cs", SearchOption.AllDirectories)
|
||||||
|
.Where(f =>
|
||||||
|
{
|
||||||
|
var rel = Path.GetRelativePath(sourceDir, f);
|
||||||
|
return !rel.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}")
|
||||||
|
&& !rel.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}")
|
||||||
|
&& !rel.StartsWith($"obj{Path.DirectorySeparatorChar}")
|
||||||
|
&& !rel.StartsWith($"bin{Path.DirectorySeparatorChar}");
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
IndexFile(file);
|
||||||
|
FilesIndexed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up all method declarations for a given class and member name.
|
||||||
|
/// Case-insensitive. Returns empty list if not found.
|
||||||
|
/// </summary>
|
||||||
|
public List<MethodInfo> Lookup(string className, string memberName)
|
||||||
|
{
|
||||||
|
var key = (className.ToLowerInvariant(), memberName.ToLowerInvariant());
|
||||||
|
return _index.TryGetValue(key, out var list) ? list : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the class exists anywhere in the index (any member).
|
||||||
|
/// </summary>
|
||||||
|
public bool HasClass(string className)
|
||||||
|
{
|
||||||
|
var lower = className.ToLowerInvariant();
|
||||||
|
return _index.Keys.Any(k => k.Item1 == lower);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IndexFile(string filePath)
|
||||||
|
{
|
||||||
|
var source = File.ReadAllText(filePath);
|
||||||
|
var tree = CSharpSyntaxTree.ParseText(source, path: filePath);
|
||||||
|
var root = tree.GetCompilationUnitRoot();
|
||||||
|
|
||||||
|
foreach (var typeDecl in root.DescendantNodes().OfType<TypeDeclarationSyntax>())
|
||||||
|
{
|
||||||
|
var className = typeDecl.Identifier.Text.ToLowerInvariant();
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
foreach (var method in typeDecl.Members.OfType<MethodDeclarationSyntax>())
|
||||||
|
{
|
||||||
|
var info = AnalyzeMethod(filePath, method.Body, method.ExpressionBody, method.GetLocation());
|
||||||
|
AddToIndex(className, method.Identifier.Text.ToLowerInvariant(), info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties (get/set are like methods)
|
||||||
|
foreach (var prop in typeDecl.Members.OfType<PropertyDeclarationSyntax>())
|
||||||
|
{
|
||||||
|
var info = AnalyzeProperty(filePath, prop);
|
||||||
|
AddToIndex(className, prop.Identifier.Text.ToLowerInvariant(), info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructors — index as class name
|
||||||
|
foreach (var ctor in typeDecl.Members.OfType<ConstructorDeclarationSyntax>())
|
||||||
|
{
|
||||||
|
var info = AnalyzeMethod(filePath, ctor.Body, ctor.ExpressionBody, ctor.GetLocation());
|
||||||
|
AddToIndex(className, ctor.Identifier.Text.ToLowerInvariant(), info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodInfo AnalyzeMethod(string filePath, BlockSyntax? body, ArrowExpressionClauseSyntax? expressionBody, Location location)
|
||||||
|
{
|
||||||
|
var lineSpan = location.GetLineSpan();
|
||||||
|
var lineNumber = lineSpan.StartLinePosition.Line + 1;
|
||||||
|
|
||||||
|
if (expressionBody is not null)
|
||||||
|
{
|
||||||
|
// Expression-bodied: => expr;
|
||||||
|
var isStub = IsNotImplementedExpression(expressionBody.Expression);
|
||||||
|
return new MethodInfo(filePath, lineNumber, 1, IsStub: isStub, IsPartial: false, StatementCount: isStub ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body is null || body.Statements.Count == 0)
|
||||||
|
{
|
||||||
|
// No body or empty body
|
||||||
|
return new MethodInfo(filePath, lineNumber, 0, IsStub: true, IsPartial: false, StatementCount: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bodyLines = body.GetLocation().GetLineSpan();
|
||||||
|
var bodyLineCount = bodyLines.EndLinePosition.Line - bodyLines.StartLinePosition.Line - 1; // exclude braces
|
||||||
|
|
||||||
|
var statements = body.Statements;
|
||||||
|
var hasNotImplemented = statements.Any(s => IsNotImplementedStatement(s));
|
||||||
|
var meaningfulCount = statements.Count(s => !IsNotImplementedStatement(s));
|
||||||
|
|
||||||
|
// Pure stub: single throw NotImplementedException
|
||||||
|
if (statements.Count == 1 && hasNotImplemented)
|
||||||
|
return new MethodInfo(filePath, lineNumber, bodyLineCount, IsStub: true, IsPartial: false, StatementCount: 0);
|
||||||
|
|
||||||
|
// Partial: has some logic AND a NotImplementedException
|
||||||
|
if (hasNotImplemented && meaningfulCount > 0)
|
||||||
|
return new MethodInfo(filePath, lineNumber, bodyLineCount, IsStub: false, IsPartial: true, StatementCount: meaningfulCount);
|
||||||
|
|
||||||
|
// Real logic
|
||||||
|
return new MethodInfo(filePath, lineNumber, bodyLineCount, IsStub: false, IsPartial: false, StatementCount: meaningfulCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodInfo AnalyzeProperty(string filePath, PropertyDeclarationSyntax prop)
|
||||||
|
{
|
||||||
|
var lineSpan = prop.GetLocation().GetLineSpan();
|
||||||
|
var lineNumber = lineSpan.StartLinePosition.Line + 1;
|
||||||
|
|
||||||
|
// Expression-bodied property: int Foo => expr;
|
||||||
|
if (prop.ExpressionBody is not null)
|
||||||
|
{
|
||||||
|
var isStub = IsNotImplementedExpression(prop.ExpressionBody.Expression);
|
||||||
|
return new MethodInfo(filePath, lineNumber, 1, IsStub: isStub, IsPartial: false, StatementCount: isStub ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-property: int Foo { get; set; } — this is valid, not a stub
|
||||||
|
if (prop.AccessorList is not null && prop.AccessorList.Accessors.All(a => a.Body is null && a.ExpressionBody is null))
|
||||||
|
return new MethodInfo(filePath, lineNumber, 0, IsStub: false, IsPartial: false, StatementCount: 1);
|
||||||
|
|
||||||
|
// Property with accessor bodies — check if any are stubs
|
||||||
|
if (prop.AccessorList is not null)
|
||||||
|
{
|
||||||
|
var hasStub = prop.AccessorList.Accessors.Any(a =>
|
||||||
|
(a.ExpressionBody is not null && IsNotImplementedExpression(a.ExpressionBody.Expression)) ||
|
||||||
|
(a.Body is not null && a.Body.Statements.Count == 1 && IsNotImplementedStatement(a.Body.Statements[0])));
|
||||||
|
return new MethodInfo(filePath, lineNumber, 0, IsStub: hasStub, IsPartial: false, StatementCount: hasStub ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MethodInfo(filePath, lineNumber, 0, IsStub: false, IsPartial: false, StatementCount: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsNotImplementedExpression(ExpressionSyntax expr)
|
||||||
|
{
|
||||||
|
// throw new NotImplementedException(...)
|
||||||
|
if (expr is ThrowExpressionSyntax throwExpr)
|
||||||
|
return throwExpr.Expression is ObjectCreationExpressionSyntax oc
|
||||||
|
&& oc.Type.ToString().Contains("NotImplementedException");
|
||||||
|
// new NotImplementedException() — shouldn't normally be standalone but handle it
|
||||||
|
return expr is ObjectCreationExpressionSyntax oc2
|
||||||
|
&& oc2.Type.ToString().Contains("NotImplementedException");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsNotImplementedStatement(StatementSyntax stmt)
|
||||||
|
{
|
||||||
|
// throw new NotImplementedException(...);
|
||||||
|
if (stmt is ThrowStatementSyntax throwStmt && throwStmt.Expression is not null)
|
||||||
|
return throwStmt.Expression is ObjectCreationExpressionSyntax oc
|
||||||
|
&& oc.Type.ToString().Contains("NotImplementedException");
|
||||||
|
// Expression statement containing throw expression
|
||||||
|
if (stmt is ExpressionStatementSyntax exprStmt)
|
||||||
|
return IsNotImplementedExpression(exprStmt.Expression);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddToIndex(string className, string memberName, MethodInfo info)
|
||||||
|
{
|
||||||
|
var key = (className, memberName);
|
||||||
|
if (!_index.TryGetValue(key, out var list))
|
||||||
|
{
|
||||||
|
list = [];
|
||||||
|
_index[key] = list;
|
||||||
|
}
|
||||||
|
list.Add(info);
|
||||||
|
MethodsIndexed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Build to verify compilation**
|
||||||
|
|
||||||
|
Run: `dotnet build --project tools/NatsNet.PortTracker`
|
||||||
|
Expected: Build succeeded. 0 Error(s).
|
||||||
|
|
||||||
|
**Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add tools/NatsNet.PortTracker/Audit/SourceIndexer.cs
|
||||||
|
git commit -m "feat: add SourceIndexer — Roslyn-based .NET source parser for audit"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Create FeatureClassifier — classification heuristics
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `tools/NatsNet.PortTracker/Audit/FeatureClassifier.cs`
|
||||||
|
|
||||||
|
**Step 1: Create the FeatureClassifier with n_a lookup and heuristics**
|
||||||
|
|
||||||
|
Create `tools/NatsNet.PortTracker/Audit/FeatureClassifier.cs`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
namespace NatsNet.PortTracker.Audit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Classifies features by inspecting the SourceIndexer for their .NET implementation status.
|
||||||
|
/// Priority: n_a lookup → method-not-found → stub detection → verified.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class FeatureClassifier
|
||||||
|
{
|
||||||
|
public record ClassificationResult(string Status, string Reason);
|
||||||
|
|
||||||
|
public record FeatureRecord(
|
||||||
|
long Id,
|
||||||
|
string DotnetClass,
|
||||||
|
string DotnetMethod,
|
||||||
|
string GoFile,
|
||||||
|
string GoMethod);
|
||||||
|
|
||||||
|
private readonly SourceIndexer _indexer;
|
||||||
|
|
||||||
|
// N/A lookup: (goMethod pattern) -> reason
|
||||||
|
// Checked case-insensitively against go_method
|
||||||
|
private static readonly Dictionary<string, string> NaByGoMethod = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["Noticef"] = ".NET uses Microsoft.Extensions.Logging",
|
||||||
|
["Debugf"] = ".NET uses Microsoft.Extensions.Logging",
|
||||||
|
["Tracef"] = ".NET uses Microsoft.Extensions.Logging",
|
||||||
|
["Warnf"] = ".NET uses Microsoft.Extensions.Logging",
|
||||||
|
["Errorf"] = ".NET uses Microsoft.Extensions.Logging",
|
||||||
|
["Fatalf"] = ".NET uses Microsoft.Extensions.Logging",
|
||||||
|
};
|
||||||
|
|
||||||
|
// N/A lookup: go_file + go_method patterns
|
||||||
|
private static readonly List<(Func<FeatureRecord, bool> Match, string Reason)> NaPatterns =
|
||||||
|
[
|
||||||
|
// Signal handling — .NET uses IHostApplicationLifetime
|
||||||
|
(f => f.GoMethod.Equals("handleSignals", StringComparison.OrdinalIgnoreCase), ".NET uses IHostApplicationLifetime"),
|
||||||
|
(f => f.GoMethod.Equals("processSignal", StringComparison.OrdinalIgnoreCase), ".NET uses IHostApplicationLifetime"),
|
||||||
|
];
|
||||||
|
|
||||||
|
public FeatureClassifier(SourceIndexer indexer)
|
||||||
|
{
|
||||||
|
_indexer = indexer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Classify a single feature. Returns status and reason.
|
||||||
|
/// </summary>
|
||||||
|
public ClassificationResult Classify(FeatureRecord feature)
|
||||||
|
{
|
||||||
|
// 1. N/A lookup — check go_method against known patterns
|
||||||
|
if (NaByGoMethod.TryGetValue(feature.GoMethod, out var naReason))
|
||||||
|
return new ClassificationResult("n_a", naReason);
|
||||||
|
|
||||||
|
foreach (var (match, reason) in NaPatterns)
|
||||||
|
{
|
||||||
|
if (match(feature))
|
||||||
|
return new ClassificationResult("n_a", reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Handle comma-separated dotnet_class (e.g. "ClosedRingBuffer,ClosedClient")
|
||||||
|
var classNames = feature.DotnetClass.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
var methodName = feature.DotnetMethod;
|
||||||
|
|
||||||
|
// Try each class name
|
||||||
|
foreach (var className in classNames)
|
||||||
|
{
|
||||||
|
var methods = _indexer.Lookup(className, methodName);
|
||||||
|
if (methods.Count > 0)
|
||||||
|
{
|
||||||
|
// Found the method — classify based on body analysis
|
||||||
|
// Use the "best" match: prefer non-stub over stub
|
||||||
|
var best = methods.OrderByDescending(m => m.StatementCount).First();
|
||||||
|
|
||||||
|
if (best.IsStub)
|
||||||
|
return new ClassificationResult("stub", $"Body is throw NotImplementedException at {Path.GetFileName(best.FilePath)}:{best.LineNumber}");
|
||||||
|
|
||||||
|
if (best.IsPartial)
|
||||||
|
return new ClassificationResult("stub", $"Partial implementation with NotImplementedException at {Path.GetFileName(best.FilePath)}:{best.LineNumber}");
|
||||||
|
|
||||||
|
return new ClassificationResult("verified", $"Method found with {best.StatementCount} statement(s) at {Path.GetFileName(best.FilePath)}:{best.LineNumber}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Method not found — check if any class exists
|
||||||
|
var anyClassFound = classNames.Any(c => _indexer.HasClass(c));
|
||||||
|
if (anyClassFound)
|
||||||
|
return new ClassificationResult("deferred", "Class exists but method not found");
|
||||||
|
|
||||||
|
return new ClassificationResult("deferred", "Class not found in .NET source");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Build to verify compilation**
|
||||||
|
|
||||||
|
Run: `dotnet build --project tools/NatsNet.PortTracker`
|
||||||
|
Expected: Build succeeded. 0 Error(s).
|
||||||
|
|
||||||
|
**Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add tools/NatsNet.PortTracker/Audit/FeatureClassifier.cs
|
||||||
|
git commit -m "feat: add FeatureClassifier — heuristic-based feature classification"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Create AuditCommand — CLI wiring and orchestration
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `tools/NatsNet.PortTracker/Commands/AuditCommand.cs`
|
||||||
|
- Modify: `tools/NatsNet.PortTracker/Program.cs:36` — add `AuditCommand` to root command
|
||||||
|
|
||||||
|
**Step 1: Create the AuditCommand**
|
||||||
|
|
||||||
|
Create `tools/NatsNet.PortTracker/Commands/AuditCommand.cs`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using System.CommandLine;
|
||||||
|
using System.Text;
|
||||||
|
using NatsNet.PortTracker.Audit;
|
||||||
|
using NatsNet.PortTracker.Data;
|
||||||
|
|
||||||
|
namespace NatsNet.PortTracker.Commands;
|
||||||
|
|
||||||
|
public static class AuditCommand
|
||||||
|
{
|
||||||
|
public static Command Create(Option<string> dbOption)
|
||||||
|
{
|
||||||
|
var sourceOpt = new Option<string>("--source")
|
||||||
|
{
|
||||||
|
Description = "Path to the .NET source directory",
|
||||||
|
DefaultValueFactory = _ => Path.Combine(Directory.GetCurrentDirectory(), "dotnet", "src", "ZB.MOM.NatsNet.Server")
|
||||||
|
};
|
||||||
|
|
||||||
|
var outputOpt = new Option<string>("--output")
|
||||||
|
{
|
||||||
|
Description = "CSV report output path",
|
||||||
|
DefaultValueFactory = _ => Path.Combine(Directory.GetCurrentDirectory(), "reports", "audit-results.csv")
|
||||||
|
};
|
||||||
|
|
||||||
|
var moduleOpt = new Option<int?>("--module")
|
||||||
|
{
|
||||||
|
Description = "Restrict to a specific module ID"
|
||||||
|
};
|
||||||
|
|
||||||
|
var executeOpt = new Option<bool>("--execute")
|
||||||
|
{
|
||||||
|
Description = "Apply DB updates (default: dry-run preview)",
|
||||||
|
DefaultValueFactory = _ => false
|
||||||
|
};
|
||||||
|
|
||||||
|
var cmd = new Command("audit", "Classify unknown features by inspecting .NET source code");
|
||||||
|
cmd.Add(sourceOpt);
|
||||||
|
cmd.Add(outputOpt);
|
||||||
|
cmd.Add(moduleOpt);
|
||||||
|
cmd.Add(executeOpt);
|
||||||
|
|
||||||
|
cmd.SetAction(parseResult =>
|
||||||
|
{
|
||||||
|
var dbPath = parseResult.GetValue(dbOption)!;
|
||||||
|
var sourcePath = parseResult.GetValue(sourceOpt)!;
|
||||||
|
var outputPath = parseResult.GetValue(outputOpt)!;
|
||||||
|
var moduleId = parseResult.GetValue(moduleOpt);
|
||||||
|
var execute = parseResult.GetValue(executeOpt);
|
||||||
|
|
||||||
|
RunAudit(dbPath, sourcePath, outputPath, moduleId, execute);
|
||||||
|
});
|
||||||
|
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunAudit(string dbPath, string sourcePath, string outputPath, int? moduleId, bool execute)
|
||||||
|
{
|
||||||
|
// Validate source directory
|
||||||
|
if (!Directory.Exists(sourcePath))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error: source directory not found: {sourcePath}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Build source index
|
||||||
|
Console.WriteLine($"Parsing .NET source files in {sourcePath}...");
|
||||||
|
var indexer = new SourceIndexer();
|
||||||
|
indexer.IndexDirectory(sourcePath);
|
||||||
|
Console.WriteLine($"Indexed {indexer.FilesIndexed} files, {indexer.MethodsIndexed} methods/properties.");
|
||||||
|
|
||||||
|
// 2. Query unknown features
|
||||||
|
using var db = new Database(dbPath);
|
||||||
|
var sql = "SELECT id, dotnet_class, dotnet_method, go_file, go_method FROM features WHERE status = 'unknown'";
|
||||||
|
var parameters = new List<(string, object?)>();
|
||||||
|
if (moduleId is not null)
|
||||||
|
{
|
||||||
|
sql += " AND module_id = @module";
|
||||||
|
parameters.Add(("@module", moduleId));
|
||||||
|
}
|
||||||
|
sql += " ORDER BY id";
|
||||||
|
|
||||||
|
var rows = db.Query(sql, parameters.ToArray());
|
||||||
|
if (rows.Count == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine("No unknown features found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Console.WriteLine($"Found {rows.Count} unknown features to classify.\n");
|
||||||
|
|
||||||
|
// 3. Classify each feature
|
||||||
|
var classifier = new FeatureClassifier(indexer);
|
||||||
|
var results = new List<(FeatureClassifier.FeatureRecord Feature, FeatureClassifier.ClassificationResult Result)>();
|
||||||
|
|
||||||
|
foreach (var row in rows)
|
||||||
|
{
|
||||||
|
var feature = new FeatureClassifier.FeatureRecord(
|
||||||
|
Id: Convert.ToInt64(row["id"]),
|
||||||
|
DotnetClass: row["dotnet_class"]?.ToString() ?? "",
|
||||||
|
DotnetMethod: row["dotnet_method"]?.ToString() ?? "",
|
||||||
|
GoFile: row["go_file"]?.ToString() ?? "",
|
||||||
|
GoMethod: row["go_method"]?.ToString() ?? "");
|
||||||
|
|
||||||
|
var result = classifier.Classify(feature);
|
||||||
|
results.Add((feature, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Write CSV report
|
||||||
|
WriteCsvReport(outputPath, results);
|
||||||
|
|
||||||
|
// 5. Print console summary
|
||||||
|
var grouped = results.GroupBy(r => r.Result.Status)
|
||||||
|
.ToDictionary(g => g.Key, g => g.Count());
|
||||||
|
|
||||||
|
Console.WriteLine("Feature Status Audit Results");
|
||||||
|
Console.WriteLine("=============================");
|
||||||
|
Console.WriteLine($"Source: {sourcePath} ({indexer.FilesIndexed} files, {indexer.MethodsIndexed} methods indexed)");
|
||||||
|
Console.WriteLine($"Features audited: {results.Count}");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine($" verified: {grouped.GetValueOrDefault("verified", 0)}");
|
||||||
|
Console.WriteLine($" stub: {grouped.GetValueOrDefault("stub", 0)}");
|
||||||
|
Console.WriteLine($" n_a: {grouped.GetValueOrDefault("n_a", 0)}");
|
||||||
|
Console.WriteLine($" deferred: {grouped.GetValueOrDefault("deferred", 0)}");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
if (!execute)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Dry-run mode. Add --execute to apply changes.");
|
||||||
|
Console.WriteLine($"Report: {outputPath}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Apply DB updates
|
||||||
|
ApplyUpdates(db, results);
|
||||||
|
Console.WriteLine($"Report: {outputPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteCsvReport(
|
||||||
|
string outputPath,
|
||||||
|
List<(FeatureClassifier.FeatureRecord Feature, FeatureClassifier.ClassificationResult Result)> results)
|
||||||
|
{
|
||||||
|
// Ensure directory exists
|
||||||
|
var dir = Path.GetDirectoryName(outputPath);
|
||||||
|
if (!string.IsNullOrEmpty(dir))
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("id,dotnet_class,dotnet_method,go_file,go_method,old_status,new_status,reason");
|
||||||
|
foreach (var (feature, result) in results)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"{feature.Id},{CsvEscape(feature.DotnetClass)},{CsvEscape(feature.DotnetMethod)},{CsvEscape(feature.GoFile)},{CsvEscape(feature.GoMethod)},unknown,{result.Status},{CsvEscape(result.Reason)}");
|
||||||
|
}
|
||||||
|
File.WriteAllText(outputPath, sb.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyUpdates(
|
||||||
|
Database db,
|
||||||
|
List<(FeatureClassifier.FeatureRecord Feature, FeatureClassifier.ClassificationResult Result)> results)
|
||||||
|
{
|
||||||
|
// Group by (status, notes) for efficient batch updates
|
||||||
|
var groups = results
|
||||||
|
.GroupBy(r => (r.Result.Status, Notes: r.Result.Status == "n_a" ? r.Result.Reason : (string?)null))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var totalUpdated = 0;
|
||||||
|
using var transaction = db.Connection.BeginTransaction();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var group in groups)
|
||||||
|
{
|
||||||
|
var ids = group.Select(r => r.Feature.Id).ToList();
|
||||||
|
var status = group.Key.Status;
|
||||||
|
var notes = group.Key.Notes;
|
||||||
|
|
||||||
|
// Build parameterized IN clause
|
||||||
|
var placeholders = new List<string>();
|
||||||
|
using var cmd = db.CreateCommand("");
|
||||||
|
for (var i = 0; i < ids.Count; i++)
|
||||||
|
{
|
||||||
|
placeholders.Add($"@id{i}");
|
||||||
|
cmd.Parameters.AddWithValue($"@id{i}", ids[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Parameters.AddWithValue("@status", status);
|
||||||
|
|
||||||
|
if (notes is not null)
|
||||||
|
{
|
||||||
|
cmd.CommandText = $"UPDATE features SET status = @status, notes = @notes WHERE id IN ({string.Join(", ", placeholders)})";
|
||||||
|
cmd.Parameters.AddWithValue("@notes", notes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cmd.CommandText = $"UPDATE features SET status = @status WHERE id IN ({string.Join(", ", placeholders)})";
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Transaction = transaction;
|
||||||
|
var affected = cmd.ExecuteNonQuery();
|
||||||
|
totalUpdated += affected;
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
Console.WriteLine($"Updated {totalUpdated} features.");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
transaction.Rollback();
|
||||||
|
Console.WriteLine("Error: transaction rolled back.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CsvEscape(string value)
|
||||||
|
{
|
||||||
|
if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
|
||||||
|
return $"\"{value.Replace("\"", "\"\"")}\"";
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Wire the command into Program.cs**
|
||||||
|
|
||||||
|
In `tools/NatsNet.PortTracker/Program.cs`, add after the existing command registrations (after line 41, before `var parseResult`):
|
||||||
|
|
||||||
|
Find this line:
|
||||||
|
```csharp
|
||||||
|
rootCommand.Add(PhaseCommands.Create(dbOption, schemaOption));
|
||||||
|
```
|
||||||
|
|
||||||
|
Add immediately after it:
|
||||||
|
```csharp
|
||||||
|
rootCommand.Add(AuditCommand.Create(dbOption));
|
||||||
|
```
|
||||||
|
|
||||||
|
Also add the import — but since the file uses top-level statements and already imports `NatsNet.PortTracker.Commands`, no new using is needed (AuditCommand is in the same namespace).
|
||||||
|
|
||||||
|
**Step 3: Build to verify compilation**
|
||||||
|
|
||||||
|
Run: `dotnet build --project tools/NatsNet.PortTracker`
|
||||||
|
Expected: Build succeeded. 0 Error(s).
|
||||||
|
|
||||||
|
**Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add tools/NatsNet.PortTracker/Commands/AuditCommand.cs tools/NatsNet.PortTracker/Program.cs
|
||||||
|
git commit -m "feat: add audit command — orchestrates feature status classification"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Smoke test — dry-run on the real database
|
||||||
|
|
||||||
|
**Files:** None — testing only.
|
||||||
|
|
||||||
|
**Step 1: Run the audit in dry-run mode**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- audit --source dotnet/src/ZB.MOM.NatsNet.Server/ --db porting.db --output reports/audit-results.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected output similar to:
|
||||||
|
```
|
||||||
|
Parsing .NET source files in dotnet/src/ZB.MOM.NatsNet.Server/...
|
||||||
|
Indexed ~92 files, ~NNNN methods/properties.
|
||||||
|
Found 3394 unknown features to classify.
|
||||||
|
|
||||||
|
Feature Status Audit Results
|
||||||
|
=============================
|
||||||
|
Source: dotnet/src/ZB.MOM.NatsNet.Server/ (92 files, NNNN methods indexed)
|
||||||
|
Features audited: 3394
|
||||||
|
|
||||||
|
verified: NNNN
|
||||||
|
stub: NNNN
|
||||||
|
n_a: NNNN
|
||||||
|
deferred: NNNN
|
||||||
|
|
||||||
|
Dry-run mode. Add --execute to apply changes.
|
||||||
|
Report: reports/audit-results.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Inspect the CSV report**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
head -20 reports/audit-results.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
- Header row matches: `id,dotnet_class,dotnet_method,go_file,go_method,old_status,new_status,reason`
|
||||||
|
- Each row has a classification and reason
|
||||||
|
- The known n_a features (Noticef, Debugf etc.) show as `n_a`
|
||||||
|
|
||||||
|
**Step 3: Spot-check a few classifications**
|
||||||
|
|
||||||
|
Pick 3-5 features from the CSV and manually verify:
|
||||||
|
- A `verified` feature: check the .NET method has real logic
|
||||||
|
- A `stub` feature: check the .NET method is `throw new NotImplementedException`
|
||||||
|
- A `deferred` feature: check the class/method doesn't exist
|
||||||
|
- An `n_a` feature: check it's a Go logging function
|
||||||
|
|
||||||
|
If any classifications are wrong, fix the heuristics before proceeding.
|
||||||
|
|
||||||
|
**Step 4: Check the counts add up**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wc -l reports/audit-results.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: 3395 lines (3394 data rows + 1 header).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: Execute the audit and update the database
|
||||||
|
|
||||||
|
**Files:** None — execution only.
|
||||||
|
|
||||||
|
**Step 1: Back up the database**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp porting.db porting.db.pre-audit-backup
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Run with --execute**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- audit --source dotnet/src/ZB.MOM.NatsNet.Server/ --db porting.db --output reports/audit-results.csv --execute
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `Updated 3394 features.`
|
||||||
|
|
||||||
|
**Step 3: Verify zero unknown features remain**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature list --status unknown --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `Total: 0 features`
|
||||||
|
|
||||||
|
**Step 4: Verify status breakdown**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- report summary --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Review the numbers match the dry-run output.
|
||||||
|
|
||||||
|
**Step 5: Generate updated porting report**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./reports/generate-report.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 6: Commit everything**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add porting.db reports/ tools/NatsNet.PortTracker/
|
||||||
|
git commit -m "feat: run feature status audit — classify 3394 unknown features
|
||||||
|
|
||||||
|
Automated classification using Roslyn syntax tree analysis:
|
||||||
|
verified: NNNN (update with actual numbers)
|
||||||
|
stub: NNNN
|
||||||
|
n_a: NNNN
|
||||||
|
deferred: NNNN"
|
||||||
|
```
|
||||||
|
|
||||||
|
(Update the commit message with the actual numbers from the output.)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: Cleanup — remove backup
|
||||||
|
|
||||||
|
**Files:** None.
|
||||||
|
|
||||||
|
**Step 1: Verify everything is committed and the database is correct**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature list --status unknown --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: clean working tree, 0 unknown features.
|
||||||
|
|
||||||
|
**Step 2: Remove the pre-audit backup**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm porting.db.pre-audit-backup
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3: Final summary**
|
||||||
|
|
||||||
|
Print:
|
||||||
|
```
|
||||||
|
Feature Status Audit Complete
|
||||||
|
=============================
|
||||||
|
Total features audited: 3394
|
||||||
|
verified: NNNN
|
||||||
|
stub: NNNN
|
||||||
|
n_a: NNNN
|
||||||
|
deferred: NNNN
|
||||||
|
```
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"planPath": "docs/plans/2026-02-27-feature-audit-script-plan.md",
|
||||||
|
"tasks": [
|
||||||
|
{"id": 0, "subject": "Task 0: Add Roslyn NuGet package", "status": "pending"},
|
||||||
|
{"id": 1, "subject": "Task 1: Create SourceIndexer", "status": "pending", "blockedBy": [0]},
|
||||||
|
{"id": 2, "subject": "Task 2: Create FeatureClassifier", "status": "pending", "blockedBy": [1]},
|
||||||
|
{"id": 3, "subject": "Task 3: Create AuditCommand + wire CLI", "status": "pending", "blockedBy": [2]},
|
||||||
|
{"id": 4, "subject": "Task 4: Smoke test dry-run", "status": "pending", "blockedBy": [3]},
|
||||||
|
{"id": 5, "subject": "Task 5: Execute audit and update DB", "status": "pending", "blockedBy": [4]},
|
||||||
|
{"id": 6, "subject": "Task 6: Cleanup and final verification", "status": "pending", "blockedBy": [5]}
|
||||||
|
],
|
||||||
|
"lastUpdated": "2026-02-27T00:00:00Z"
|
||||||
|
}
|
||||||
106
docs/plans/2026-02-27-feature-status-audit-design.md
Normal file
106
docs/plans/2026-02-27-feature-status-audit-design.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# Feature Status Audit Design
|
||||||
|
|
||||||
|
**Date:** 2026-02-27
|
||||||
|
**Status:** Approved
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
3394 features in module 8 (`server`) are marked as `unknown` status after a bulk reclassification. Each needs to be checked against its .NET implementation to determine the correct status.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
- **Module:** 8 (server) — all 3394 unknown features
|
||||||
|
- **Go source files:** 64 distinct files
|
||||||
|
- **All features have `dotnet_class` and `dotnet_method` mappings** — no unmapped features
|
||||||
|
|
||||||
|
## Classification Criteria
|
||||||
|
|
||||||
|
| Status | Criteria | Example |
|
||||||
|
|--------|----------|---------|
|
||||||
|
| `verified` | .NET method exists with non-trivial logic matching Go behavior | `MemStore.StoreRawMsg` — full implementation |
|
||||||
|
| `stub` | .NET method exists but is `throw new NotImplementedException()`, empty, or only partially implemented | `FileStore.Compact` — no real logic |
|
||||||
|
| `n_a` | Go feature doesn't apply to .NET — .NET uses a different approach (different library, runtime feature, or platform pattern) | Go logging functions → .NET uses `Microsoft.Extensions.Logging` |
|
||||||
|
| `deferred` | .NET method doesn't exist, or classification requires running the server end-to-end | Server-integration features needing full runtime |
|
||||||
|
|
||||||
|
**Partial implementations** (method exists with some logic but missing significant functionality) are classified as `stub`.
|
||||||
|
|
||||||
|
## Batch Execution Process
|
||||||
|
|
||||||
|
Features are processed in fixed batches of 50. Each batch follows this workflow:
|
||||||
|
|
||||||
|
### Step 1: Fetch next 50 unknown features
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature list --module 8 --status unknown --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Take the first 50 IDs from the output.
|
||||||
|
|
||||||
|
### Step 2: Inspect .NET source for each feature
|
||||||
|
|
||||||
|
For each feature:
|
||||||
|
1. Read the `dotnet_class` and `dotnet_method` from the feature record
|
||||||
|
2. Find the .NET source file containing that class
|
||||||
|
3. Check the method body:
|
||||||
|
- Real logic matching Go = `verified`
|
||||||
|
- Stub / empty / partial = `stub`
|
||||||
|
- .NET alternative exists = `n_a`
|
||||||
|
- Method not found = `deferred`
|
||||||
|
|
||||||
|
### Step 3: Dry-run the batch update (MANDATORY)
|
||||||
|
|
||||||
|
Group features by their determined status and dry-run using PortTracker:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Dry-run — verify correct features affected
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature batch-update --ids 150-160 --set-status deferred --db porting.db
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature batch-update --ids 2068-2077 --set-status verified --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Review the preview output. Only proceed if the listed features match expectations.
|
||||||
|
|
||||||
|
### Step 4: Execute once dry-run verified
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature batch-update --ids 150-160 --set-status deferred --execute --db porting.db
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature batch-update --ids 2068-2077 --set-status verified --execute --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Verify remaining count
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature list --status unknown --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Confirm the count decreased by ~50.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. **ALWAYS dry-run before executing** — no exceptions
|
||||||
|
2. **NEVER use direct SQL** (`sqlite3`) — use PortTracker CLI exclusively
|
||||||
|
3. **Process exactly 50 per batch** (or fewer if fewer remain)
|
||||||
|
4. **Report classification breakdown** after each batch (e.g. "Batch 3: 12 verified, 30 stub, 3 n_a, 5 deferred")
|
||||||
|
5. **68 batches total** (3394 / 50 = ~68)
|
||||||
|
|
||||||
|
## Key .NET Source Locations
|
||||||
|
|
||||||
|
```
|
||||||
|
dotnet/src/ZB.MOM.NatsNet.Server/
|
||||||
|
Accounts/Account.cs, AccountResolver.cs, DirJwtStore.cs
|
||||||
|
Auth/AuthHandler.cs, JwtProcessor.cs
|
||||||
|
Config/ReloadOptions.cs, ServerOptionsConfiguration.cs
|
||||||
|
JetStream/MemStore.cs, FileStore.cs, JetStreamTypes.cs
|
||||||
|
JetStream/NatsStream.cs, NatsConsumer.cs, RaftTypes.cs
|
||||||
|
Protocol/ProtocolParser.cs, ProxyProtocol.cs
|
||||||
|
Routes/RouteTypes.cs, LeafNode/LeafNodeTypes.cs, Gateway/GatewayTypes.cs
|
||||||
|
Mqtt/MqttHandler.cs, WebSocket/WebSocketTypes.cs
|
||||||
|
Internal/ (various data structures)
|
||||||
|
NatsServer.cs, NatsServer.*.cs (partial class files)
|
||||||
|
ClientConnection.cs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
- No code changes — this is classification only
|
||||||
|
- No unit_tests reclassification (separate effort)
|
||||||
|
- No schema changes needed (`unknown` already added)
|
||||||
236
docs/plans/2026-02-27-feature-status-audit-plan.md
Normal file
236
docs/plans/2026-02-27-feature-status-audit-plan.md
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
# Feature Status Audit Implementation Plan
|
||||||
|
|
||||||
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task.
|
||||||
|
|
||||||
|
**Goal:** Classify 3394 features currently marked `unknown` into the correct status (`verified`, `stub`, `n_a`, or `deferred`) by inspecting .NET source code against Go feature specifications.
|
||||||
|
|
||||||
|
**Architecture:** Process features in sequential batches of 50. Each batch: fetch 50 unknown features via PortTracker CLI, inspect the corresponding .NET source files, classify each feature, dry-run the batch updates, then execute. Repeat until zero unknown features remain.
|
||||||
|
|
||||||
|
**Tech Stack:** PortTracker CLI (`dotnet run --project tools/NatsNet.PortTracker`), .NET source at `dotnet/src/ZB.MOM.NatsNet.Server/`
|
||||||
|
|
||||||
|
**Design doc:** `docs/plans/2026-02-27-feature-status-audit-design.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Important Rules (Read Before Every Task)
|
||||||
|
|
||||||
|
1. **ALWAYS dry-run before executing** — no exceptions. Every `batch-update` command must be run WITHOUT `--execute` first to preview.
|
||||||
|
2. **NEVER use direct SQL** (`sqlite3`) — use the PortTracker CLI exclusively for all database operations.
|
||||||
|
3. **Process exactly 50 per batch** (or fewer if fewer remain in the final batch).
|
||||||
|
4. **Report classification breakdown** after each batch (e.g. "Batch 3: 12 verified, 30 stub, 3 n_a, 5 deferred").
|
||||||
|
|
||||||
|
## Classification Criteria Reference
|
||||||
|
|
||||||
|
| Status | Criteria |
|
||||||
|
|--------|----------|
|
||||||
|
| `verified` | .NET method exists with non-trivial logic that matches the Go implementation's behavior |
|
||||||
|
| `stub` | .NET method exists but is `throw new NotImplementedException()`, empty, or only **partially** implemented (has structure but missing significant logic) |
|
||||||
|
| `n_a` | Go feature doesn't apply to .NET — .NET uses a different approach (e.g. Go logging → .NET uses `Microsoft.Extensions.Logging`) |
|
||||||
|
| `deferred` | .NET method doesn't exist at all, or classification requires the server running end-to-end |
|
||||||
|
|
||||||
|
## Key .NET Source Locations
|
||||||
|
|
||||||
|
When looking for a `dotnet_class`, search in these directories:
|
||||||
|
|
||||||
|
```
|
||||||
|
dotnet/src/ZB.MOM.NatsNet.Server/
|
||||||
|
Accounts/ — Account, AccountResolver, DirJwtStore, AccountTypes
|
||||||
|
Auth/ — AuthHandler, JwtProcessor, CipherSuites, AuthTypes
|
||||||
|
Config/ — ReloadOptions, ServerOptionsConfiguration, NatsJsonConverters
|
||||||
|
Events/ — EventTypes
|
||||||
|
Gateway/ — GatewayTypes
|
||||||
|
Internal/ — Subscription, WaitGroup, ClosedRingBuffer, RateCounter, DataStructures/
|
||||||
|
JetStream/ — MemStore, ConsumerMemStore, FileStore, FileStoreTypes, MessageBlock
|
||||||
|
JetStreamTypes, JetStreamApiTypes, JetStreamErrors, JetStreamVersioning
|
||||||
|
NatsStream, NatsConsumer, RaftTypes, JetStreamClusterTypes
|
||||||
|
LeafNode/ — LeafNodeTypes
|
||||||
|
MessageTrace/ — MsgTraceTypes
|
||||||
|
Monitor/ — MonitorTypes, MonitorSortOptions
|
||||||
|
Mqtt/ — MqttConstants, MqttTypes, MqttHandler
|
||||||
|
Protocol/ — ParserTypes, ProtocolParser, ProxyProtocol
|
||||||
|
Routes/ — RouteTypes
|
||||||
|
WebSocket/ — WebSocketConstants, WebSocketTypes
|
||||||
|
NatsServer.cs, NatsServer.Auth.cs, NatsServer.Signals.cs, NatsServer.Init.cs
|
||||||
|
NatsServer.Accounts.cs, NatsServer.Lifecycle.cs, NatsServer.Listeners.cs
|
||||||
|
ClientConnection.cs, ClientTypes.cs, NatsMessageHeaders.cs
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 0: Verify starting state and PortTracker commands
|
||||||
|
|
||||||
|
**Files:** None — verification only.
|
||||||
|
|
||||||
|
**Step 1: Check how many unknown features exist**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature list --module 8 --status unknown --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: Output shows ~3394 features. Note the total count at the bottom.
|
||||||
|
|
||||||
|
**Step 2: Verify batch-update dry-run works**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature batch-update --ids 150-152 --set-status verified --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: Preview output showing 3 features. Should say "Would affect 3 items:" and "Add --execute to apply these changes." Do NOT execute — this is just confirming the tool works.
|
||||||
|
|
||||||
|
**Step 3: Record the starting count**
|
||||||
|
|
||||||
|
Note the total unknown count. This is your baseline. After all batches complete, the count should be 0.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task N (repeat for N=1 through 68): Process batch of 50 unknown features
|
||||||
|
|
||||||
|
> **This task is a template.** Repeat it until zero unknown features remain. Each execution processes the next 50.
|
||||||
|
|
||||||
|
**Files:** None — classification only, no code changes.
|
||||||
|
|
||||||
|
**Step 1: Fetch the next 50 unknown features**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature list --module 8 --status unknown --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
From the output, take the **first 50 feature IDs**. Note the `dotnet_class` and `dotnet_method` columns for each.
|
||||||
|
|
||||||
|
**Step 2: For each feature, inspect the .NET implementation**
|
||||||
|
|
||||||
|
For each of the 50 features:
|
||||||
|
|
||||||
|
1. **Find the .NET source file** — use `Grep` to search for the class:
|
||||||
|
```
|
||||||
|
Grep pattern: "class {dotnet_class}" path: dotnet/src/ZB.MOM.NatsNet.Server/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Find the method** — search within that file for the method name:
|
||||||
|
```
|
||||||
|
Grep pattern: "{dotnet_method}" path: {the file found above}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Read the method body** — use `Read` to view the method implementation.
|
||||||
|
|
||||||
|
4. **Classify the feature:**
|
||||||
|
- If the method has real, non-trivial logic matching the Go behavior → `verified`
|
||||||
|
- If the method is `throw new NotImplementedException()`, empty, or only partially there → `stub`
|
||||||
|
- If the Go feature has a .NET-native replacement (e.g., Go's custom logging → `Microsoft.Extensions.Logging`, Go's `sync.Mutex` → C#'s `Lock`) → `n_a`
|
||||||
|
- If the method doesn't exist in the .NET codebase at all → `deferred`
|
||||||
|
|
||||||
|
**Efficiency tip:** Features from the same `dotnet_class` should be inspected together — read the .NET file once and classify all features from that class in the batch.
|
||||||
|
|
||||||
|
**Step 3: Group IDs by classification result**
|
||||||
|
|
||||||
|
After inspecting all 50, organize the IDs into groups:
|
||||||
|
|
||||||
|
```
|
||||||
|
verified_ids: 2068,2069,2070,2071,...
|
||||||
|
stub_ids: 2080,2081,...
|
||||||
|
n_a_ids: 2090,...
|
||||||
|
deferred_ids: 2095,2096,...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 4: Dry-run each group (MANDATORY — DO NOT SKIP)**
|
||||||
|
|
||||||
|
Run the dry-run for EACH status group. Review the output carefully.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Dry-run verified
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature batch-update --ids {verified_ids} --set-status verified --db porting.db
|
||||||
|
|
||||||
|
# Dry-run stub
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature batch-update --ids {stub_ids} --set-status stub --db porting.db
|
||||||
|
|
||||||
|
# Dry-run n_a (include reason in notes)
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature batch-update --ids {n_a_ids} --set-status n_a --set-notes "{reason}" --db porting.db
|
||||||
|
|
||||||
|
# Dry-run deferred
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature batch-update --ids {deferred_ids} --set-status deferred --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Check that:
|
||||||
|
- The feature names in the preview match what you inspected
|
||||||
|
- The count per group adds up to 50 (or the batch size)
|
||||||
|
- No unexpected features appear
|
||||||
|
|
||||||
|
**Step 5: Execute each group**
|
||||||
|
|
||||||
|
Only after verifying ALL dry-runs look correct:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Execute verified
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature batch-update --ids {verified_ids} --set-status verified --execute --db porting.db
|
||||||
|
|
||||||
|
# Execute stub
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature batch-update --ids {stub_ids} --set-status stub --execute --db porting.db
|
||||||
|
|
||||||
|
# Execute n_a (with notes)
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature batch-update --ids {n_a_ids} --set-status n_a --set-notes "{reason}" --execute --db porting.db
|
||||||
|
|
||||||
|
# Execute deferred
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature batch-update --ids {deferred_ids} --set-status deferred --execute --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 6: Verify remaining count decreased**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature list --status unknown --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Confirm the total decreased by ~50 from the previous batch.
|
||||||
|
|
||||||
|
**Step 7: Report batch summary**
|
||||||
|
|
||||||
|
Print: `Batch N: X verified, Y stub, Z n_a, W deferred (Total remaining: NNNN)`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 69: Final verification and report
|
||||||
|
|
||||||
|
**Files:** None — verification only.
|
||||||
|
|
||||||
|
**Step 1: Confirm zero unknown features remain**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature list --status unknown --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `Total: 0 features`
|
||||||
|
|
||||||
|
**Step 2: Generate the full status breakdown**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature list --module 8 --status verified --db porting.db
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature list --module 8 --status stub --db porting.db
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature list --module 8 --status n_a --db porting.db
|
||||||
|
dotnet run --project tools/NatsNet.PortTracker -- feature list --module 8 --status deferred --db porting.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the count for each status.
|
||||||
|
|
||||||
|
**Step 3: Generate updated porting report**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./reports/generate-report.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 4: Commit the updated report**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add reports/
|
||||||
|
git commit -m "chore: update porting report after feature status audit"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 5: Print final summary**
|
||||||
|
|
||||||
|
```
|
||||||
|
Feature Status Audit Complete
|
||||||
|
=============================
|
||||||
|
Total features audited: 3394
|
||||||
|
verified: NNNN
|
||||||
|
stub: NNNN
|
||||||
|
n_a: NNNN
|
||||||
|
deferred: NNNN
|
||||||
|
```
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"planPath": "docs/plans/2026-02-27-feature-status-audit-plan.md",
|
||||||
|
"tasks": [
|
||||||
|
{"id": 0, "subject": "Task 0: Verify starting state and PortTracker commands", "status": "pending"},
|
||||||
|
{"id": 1, "subject": "Task 1-68: Process batches of 50 unknown features (repeating template)", "status": "pending", "blockedBy": [0], "note": "This is a repeating task — execute the template from the plan 68 times until 0 unknown features remain"},
|
||||||
|
{"id": 69, "subject": "Task 69: Final verification and report", "status": "pending", "blockedBy": [1]}
|
||||||
|
],
|
||||||
|
"lastUpdated": "2026-02-27T00:00:00Z"
|
||||||
|
}
|
||||||
63
docs/plans/2026-02-27-unit-test-audit-design.md
Normal file
63
docs/plans/2026-02-27-unit-test-audit-design.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Unit Test Audit Extension Design
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Extend the PortTracker `audit` command to classify unit tests (not just features) by inspecting .NET test source code with Roslyn.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Parameterize the existing audit pipeline (`AuditCommand` + `SourceIndexer` + `FeatureClassifier`) to support both `features` and `unit_tests` tables. No new files — the same indexer and classifier logic applies to test methods.
|
||||||
|
|
||||||
|
## CLI Interface
|
||||||
|
|
||||||
|
```
|
||||||
|
dotnet run -- audit --type features|tests|all [--source <path>] [--module <id>] [--execute]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Flag | Default (features) | Default (tests) |
|
||||||
|
|------|-------------------|-----------------|
|
||||||
|
| `--type` | `features` | — |
|
||||||
|
| `--source` | `dotnet/src/ZB.MOM.NatsNet.Server` | `dotnet/tests/ZB.MOM.NatsNet.Server.Tests` |
|
||||||
|
| `--output` | `reports/audit-results.csv` | `reports/audit-results-tests.csv` |
|
||||||
|
|
||||||
|
- `--type all` runs both sequentially.
|
||||||
|
- `--source` override works for either type.
|
||||||
|
|
||||||
|
## Changes Required
|
||||||
|
|
||||||
|
### AuditCommand.cs
|
||||||
|
|
||||||
|
1. Add `--type` option with values `features`, `tests`, `all`.
|
||||||
|
2. Thread an `AuditTarget` (table name + default source + default output + display label) through `RunAudit` and `ApplyUpdates`.
|
||||||
|
3. `--type all` calls `RunAudit` twice with different targets.
|
||||||
|
4. `ApplyUpdates` uses the target's table name in UPDATE SQL.
|
||||||
|
|
||||||
|
### FeatureClassifier.cs
|
||||||
|
|
||||||
|
No changes. Same N/A lookup and classification logic applies to unit tests.
|
||||||
|
|
||||||
|
### SourceIndexer.cs
|
||||||
|
|
||||||
|
No changes. Already generic — just pass a different directory path.
|
||||||
|
|
||||||
|
## Pre-audit DB Reset
|
||||||
|
|
||||||
|
Before running the test audit, manually reset deferred tests to `unknown`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
sqlite3 porting.db "UPDATE unit_tests SET status = 'unknown' WHERE status = 'deferred';"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Execution Sequence
|
||||||
|
|
||||||
|
1. Reset deferred tests: `sqlite3 porting.db "UPDATE unit_tests SET status = 'unknown' WHERE status = 'deferred';"`
|
||||||
|
2. Run audit: `dotnet run -- audit --type tests --db porting.db --execute`
|
||||||
|
3. Verify results and generate report.
|
||||||
|
|
||||||
|
## Classification Behavior for Tests
|
||||||
|
|
||||||
|
Same priority as features:
|
||||||
|
1. **N/A**: Go method matches logging/signal patterns → `n_a`
|
||||||
|
2. **Method found**: Test class + method exists in test project → `verified` or `stub`
|
||||||
|
3. **Class exists, method missing**: → `deferred` ("method not found")
|
||||||
|
4. **Class not found**: → `deferred` ("class not found")
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
using ZB.MOM.NatsNet.Server.Auth;
|
using ZB.MOM.NatsNet.Server.Auth;
|
||||||
using ZB.MOM.NatsNet.Server.Internal;
|
using ZB.MOM.NatsNet.Server.Internal;
|
||||||
using ZB.MOM.NatsNet.Server.Internal.DataStructures;
|
using ZB.MOM.NatsNet.Server.Internal.DataStructures;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace ZB.MOM.NatsNet.Server;
|
namespace ZB.MOM.NatsNet.Server;
|
||||||
|
|
||||||
@@ -1643,7 +1644,50 @@ public sealed class Account : INatsAccount
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal void UpdateLeafNodes(object sub, int delta)
|
internal void UpdateLeafNodes(object sub, int delta)
|
||||||
{
|
{
|
||||||
// TODO: session 15 — leaf node subscription propagation.
|
if (delta == 0 || sub is not Subscription s || s.Subject.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var subject = Encoding.UTF8.GetString(s.Subject);
|
||||||
|
var queue = s.Queue is { Length: > 0 } ? Encoding.UTF8.GetString(s.Queue) : string.Empty;
|
||||||
|
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_rm ??= new Dictionary<string, int>(StringComparer.Ordinal);
|
||||||
|
if (!_rm.TryGetValue(subject, out var rc))
|
||||||
|
rc = 0;
|
||||||
|
rc += delta;
|
||||||
|
if (rc <= 0)
|
||||||
|
_rm.Remove(subject);
|
||||||
|
else
|
||||||
|
_rm[subject] = rc;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(queue))
|
||||||
|
{
|
||||||
|
_lqws ??= new Dictionary<string, int>(StringComparer.Ordinal);
|
||||||
|
var key = $"{subject} {queue}";
|
||||||
|
var qw = s.Qw != 0 ? s.Qw : 1;
|
||||||
|
if (!_lqws.TryGetValue(key, out var qv))
|
||||||
|
qv = 0;
|
||||||
|
qv += delta * qw;
|
||||||
|
if (qv <= 0)
|
||||||
|
_lqws.Remove(key);
|
||||||
|
else
|
||||||
|
_lqws[key] = qv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ClientConnection> leafs;
|
||||||
|
_lmu.EnterReadLock();
|
||||||
|
try { leafs = [.. _lleafs]; }
|
||||||
|
finally { _lmu.ExitReadLock(); }
|
||||||
|
|
||||||
|
foreach (var leaf in leafs)
|
||||||
|
leaf.FlushSignal();
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
// in the NATS server Go source.
|
// in the NATS server Go source.
|
||||||
|
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace ZB.MOM.NatsNet.Server.Auth.Ocsp;
|
namespace ZB.MOM.NatsNet.Server.Auth.Ocsp;
|
||||||
|
|
||||||
@@ -70,6 +72,8 @@ internal sealed class OcspStaple
|
|||||||
internal sealed class OcspMonitor
|
internal sealed class OcspMonitor
|
||||||
{
|
{
|
||||||
private readonly Lock _mu = new();
|
private readonly Lock _mu = new();
|
||||||
|
private Timer? _timer;
|
||||||
|
private readonly OcspStaple _staple = new();
|
||||||
|
|
||||||
/// <summary>Path to the TLS certificate file being monitored.</summary>
|
/// <summary>Path to the TLS certificate file being monitored.</summary>
|
||||||
public string? CertFile { get; set; }
|
public string? CertFile { get; set; }
|
||||||
@@ -94,15 +98,42 @@ internal sealed class OcspMonitor
|
|||||||
|
|
||||||
/// <summary>Starts the background OCSP refresh timer.</summary>
|
/// <summary>Starts the background OCSP refresh timer.</summary>
|
||||||
public void Start()
|
public void Start()
|
||||||
=> throw new NotImplementedException("TODO: session 23 — ocsp");
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
if (_timer != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_timer = new Timer(_ =>
|
||||||
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(OcspStapleFile) && File.Exists(OcspStapleFile))
|
||||||
|
_staple.Response = File.ReadAllBytes(OcspStapleFile);
|
||||||
|
_staple.NextUpdate = DateTime.UtcNow + CheckInterval;
|
||||||
|
}
|
||||||
|
}, null, TimeSpan.Zero, CheckInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Stops the background OCSP refresh timer.</summary>
|
/// <summary>Stops the background OCSP refresh timer.</summary>
|
||||||
public void Stop()
|
public void Stop()
|
||||||
=> throw new NotImplementedException("TODO: session 23 — ocsp");
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
_timer?.Dispose();
|
||||||
|
_timer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Returns the current cached OCSP staple bytes, or <c>null</c> if none.</summary>
|
/// <summary>Returns the current cached OCSP staple bytes, or <c>null</c> if none.</summary>
|
||||||
public byte[]? GetStaple()
|
public byte[]? GetStaple()
|
||||||
=> throw new NotImplementedException("TODO: session 23 — ocsp");
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
return _staple.Response == null ? null : [.. _staple.Response];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -122,15 +153,105 @@ public interface IOcspResponseCache
|
|||||||
void Remove(string key);
|
void Remove(string key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runtime counters for OCSP response cache behavior.
|
||||||
|
/// Mirrors Go <c>OCSPResponseCacheStats</c> shape.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class OcspResponseCacheStats
|
||||||
|
{
|
||||||
|
public long Responses { get; set; }
|
||||||
|
public long Hits { get; set; }
|
||||||
|
public long Misses { get; set; }
|
||||||
|
public long Revokes { get; set; }
|
||||||
|
public long Goods { get; set; }
|
||||||
|
public long Unknowns { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A no-op OCSP cache that never stores anything.
|
/// A no-op OCSP cache that never stores anything.
|
||||||
/// Mirrors Go <c>NoOpCache</c> in server/ocsp_responsecache.go.
|
/// Mirrors Go <c>NoOpCache</c> in server/ocsp_responsecache.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class NoOpCache : IOcspResponseCache
|
internal sealed class NoOpCache : IOcspResponseCache
|
||||||
{
|
{
|
||||||
public byte[]? Get(string key) => null;
|
private readonly Lock _mu = new();
|
||||||
public void Put(string key, byte[] response) { }
|
private readonly OcspResponseCacheConfig _config;
|
||||||
public void Remove(string key) { }
|
private OcspResponseCacheStats? _stats;
|
||||||
|
private bool _online;
|
||||||
|
|
||||||
|
public NoOpCache()
|
||||||
|
: this(new OcspResponseCacheConfig { Type = "none" })
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public NoOpCache(OcspResponseCacheConfig config)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[]? Get(string key) => null;
|
||||||
|
|
||||||
|
public void Put(string key, byte[] response) { }
|
||||||
|
|
||||||
|
public void Remove(string key) => Delete(key);
|
||||||
|
|
||||||
|
public void Delete(string key)
|
||||||
|
{
|
||||||
|
_ = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start(NatsServer? server = null)
|
||||||
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
_stats = new OcspResponseCacheStats();
|
||||||
|
_online = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop(NatsServer? server = null)
|
||||||
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
_online = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Online()
|
||||||
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
return _online;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Type() => "none";
|
||||||
|
|
||||||
|
public OcspResponseCacheConfig Config()
|
||||||
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
return _config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public OcspResponseCacheStats? Stats()
|
||||||
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
if (_stats is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new OcspResponseCacheStats
|
||||||
|
{
|
||||||
|
Responses = _stats.Responses,
|
||||||
|
Hits = _stats.Hits,
|
||||||
|
Misses = _stats.Misses,
|
||||||
|
Revokes = _stats.Revokes,
|
||||||
|
Goods = _stats.Goods,
|
||||||
|
Unknowns = _stats.Unknowns,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -148,13 +269,35 @@ internal sealed class LocalDirCache : IOcspResponseCache
|
|||||||
}
|
}
|
||||||
|
|
||||||
public byte[]? Get(string key)
|
public byte[]? Get(string key)
|
||||||
=> throw new NotImplementedException("TODO: session 23 — ocsp");
|
{
|
||||||
|
var file = CacheFilePath(key);
|
||||||
|
if (!File.Exists(file))
|
||||||
|
return null;
|
||||||
|
return File.ReadAllBytes(file);
|
||||||
|
}
|
||||||
|
|
||||||
public void Put(string key, byte[] response)
|
public void Put(string key, byte[] response)
|
||||||
=> throw new NotImplementedException("TODO: session 23 — ocsp");
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(key);
|
||||||
|
ArgumentNullException.ThrowIfNull(response);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(_dir);
|
||||||
|
File.WriteAllBytes(CacheFilePath(key), response);
|
||||||
|
}
|
||||||
|
|
||||||
public void Remove(string key)
|
public void Remove(string key)
|
||||||
=> throw new NotImplementedException("TODO: session 23 — ocsp");
|
{
|
||||||
|
var file = CacheFilePath(key);
|
||||||
|
if (File.Exists(file))
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CacheFilePath(string key)
|
||||||
|
{
|
||||||
|
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(key));
|
||||||
|
var file = Convert.ToHexString(hash).ToLowerInvariant();
|
||||||
|
return Path.Combine(_dir, $"{file}.ocsp");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using System.Net.Sockets;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using ZB.MOM.NatsNet.Server.Auth;
|
using ZB.MOM.NatsNet.Server.Auth;
|
||||||
using ZB.MOM.NatsNet.Server.Internal;
|
using ZB.MOM.NatsNet.Server.Internal;
|
||||||
@@ -166,6 +167,7 @@ public sealed partial class ClientConnection
|
|||||||
private Timer? _atmr; // auth timer
|
private Timer? _atmr; // auth timer
|
||||||
private Timer? _pingTimer;
|
private Timer? _pingTimer;
|
||||||
private Timer? _tlsTo;
|
private Timer? _tlsTo;
|
||||||
|
private Timer? _expTimer;
|
||||||
|
|
||||||
// Ping state.
|
// Ping state.
|
||||||
private int _pingOut; // outstanding pings
|
private int _pingOut; // outstanding pings
|
||||||
@@ -655,12 +657,25 @@ public sealed partial class ClientConnection
|
|||||||
|
|
||||||
internal void SetExpirationTimer(TimeSpan d)
|
internal void SetExpirationTimer(TimeSpan d)
|
||||||
{
|
{
|
||||||
// TODO: Implement when Server is available (session 09).
|
lock (_mu)
|
||||||
|
{
|
||||||
|
SetExpirationTimerUnlocked(d);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void SetExpirationTimerUnlocked(TimeSpan d)
|
internal void SetExpirationTimerUnlocked(TimeSpan d)
|
||||||
{
|
{
|
||||||
// TODO: Implement when Server is available (session 09).
|
var prev = Interlocked.Exchange(ref _expTimer, null);
|
||||||
|
prev?.Dispose();
|
||||||
|
|
||||||
|
if (d <= TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
ClaimExpiration();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expires = DateTime.UtcNow + d;
|
||||||
|
_expTimer = new Timer(_ => ClaimExpiration(), null, d, Timeout.InfiniteTimeSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -885,7 +900,17 @@ public sealed partial class ClientConnection
|
|||||||
|
|
||||||
internal void SetPingTimer()
|
internal void SetPingTimer()
|
||||||
{
|
{
|
||||||
// TODO: Implement when Server is available.
|
var interval = Server?.Options.PingInterval ?? TimeSpan.FromMinutes(2);
|
||||||
|
if (interval <= TimeSpan.Zero)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ClearPingTimer();
|
||||||
|
_pingTimer = new Timer(_ =>
|
||||||
|
{
|
||||||
|
if (IsClosed())
|
||||||
|
return;
|
||||||
|
SendPing();
|
||||||
|
}, null, interval, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void ClearPingTimer()
|
internal void ClearPingTimer()
|
||||||
@@ -902,7 +927,10 @@ public sealed partial class ClientConnection
|
|||||||
|
|
||||||
internal void SetAuthTimer()
|
internal void SetAuthTimer()
|
||||||
{
|
{
|
||||||
// TODO: Implement when Server is available.
|
var timeout = Server?.Options.AuthTimeout ?? 0;
|
||||||
|
if (timeout <= 0)
|
||||||
|
return;
|
||||||
|
SetAuthTimer(TimeSpan.FromSeconds(timeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void ClearAuthTimer()
|
internal void ClearAuthTimer()
|
||||||
@@ -916,7 +944,7 @@ public sealed partial class ClientConnection
|
|||||||
|
|
||||||
internal void ClaimExpiration()
|
internal void ClaimExpiration()
|
||||||
{
|
{
|
||||||
// TODO: Implement when Server is available.
|
AuthExpired();
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -925,7 +953,7 @@ public sealed partial class ClientConnection
|
|||||||
|
|
||||||
internal void FlushSignal()
|
internal void FlushSignal()
|
||||||
{
|
{
|
||||||
// TODO: Signal the writeLoop via SemaphoreSlim/Monitor when ported.
|
FlushClients(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void EnqueueProtoAndFlush(ReadOnlySpan<byte> proto)
|
internal void EnqueueProtoAndFlush(ReadOnlySpan<byte> proto)
|
||||||
@@ -990,7 +1018,12 @@ public sealed partial class ClientConnection
|
|||||||
internal void TraceInOp(string op, byte[] arg) { if (Trace) TraceOp("<", op, arg); }
|
internal void TraceInOp(string op, byte[] arg) { if (Trace) TraceOp("<", op, arg); }
|
||||||
internal void TraceOutOp(string op, byte[] arg) { if (Trace) TraceOp(">", op, arg); }
|
internal void TraceOutOp(string op, byte[] arg) { if (Trace) TraceOp(">", op, arg); }
|
||||||
|
|
||||||
private void TraceMsgInternal(byte[] msg, bool inbound, bool delivery) { }
|
private void TraceMsgInternal(byte[] msg, bool inbound, bool delivery)
|
||||||
|
{
|
||||||
|
var dir = inbound ? "<" : ">";
|
||||||
|
var marker = delivery ? "[DELIVER]" : "[MSG]";
|
||||||
|
Tracef("{0} {1} {2}", dir, marker, Encoding.UTF8.GetString(msg));
|
||||||
|
}
|
||||||
private void TraceOp(string dir, string op, byte[] arg)
|
private void TraceOp(string dir, string op, byte[] arg)
|
||||||
{
|
{
|
||||||
Tracef("%s %s %s", dir, op, arg is not null ? Encoding.UTF8.GetString(arg) : string.Empty);
|
Tracef("%s %s %s", dir, op, arg is not null ? Encoding.UTF8.GetString(arg) : string.Empty);
|
||||||
@@ -1112,9 +1145,18 @@ public sealed partial class ClientConnection
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
// features 425-427: writeLoop / flushClients / readLoop
|
// features 425-427: writeLoop / flushClients / readLoop
|
||||||
internal void WriteLoop() { /* TODO session 09 */ }
|
internal void WriteLoop() => FlushClients(long.MaxValue);
|
||||||
internal void FlushClients(long budget) { /* TODO session 09 */ }
|
internal void FlushClients(long budget)
|
||||||
internal void ReadLoop(byte[]? pre) { /* TODO session 09 */ }
|
{
|
||||||
|
try { _nc?.Flush(); }
|
||||||
|
catch { /* no-op for now */ }
|
||||||
|
}
|
||||||
|
internal void ReadLoop(byte[]? pre)
|
||||||
|
{
|
||||||
|
LastIn = DateTime.UtcNow;
|
||||||
|
if (pre is { Length: > 0 })
|
||||||
|
TraceInOp("PRE", pre);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates the INFO JSON bytes sent to the client on connect.
|
/// Generates the INFO JSON bytes sent to the client on connect.
|
||||||
@@ -1128,15 +1170,33 @@ public sealed partial class ClientConnection
|
|||||||
/// Sets the auth-timeout timer to the specified duration.
|
/// Sets the auth-timeout timer to the specified duration.
|
||||||
/// Mirrors Go <c>client.setAuthTimer(d)</c>.
|
/// Mirrors Go <c>client.setAuthTimer(d)</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void SetAuthTimer(TimeSpan d) { /* TODO session 09 */ }
|
internal void SetAuthTimer(TimeSpan d)
|
||||||
|
{
|
||||||
|
var prev = Interlocked.Exchange(ref _atmr, null);
|
||||||
|
prev?.Dispose();
|
||||||
|
if (d <= TimeSpan.Zero)
|
||||||
|
return;
|
||||||
|
_atmr = new Timer(_ => AuthTimeout(), null, d, Timeout.InfiniteTimeSpan);
|
||||||
|
}
|
||||||
|
|
||||||
// features 428-432: closedStateForErr, collapsePtoNB, flushOutbound, handleWriteTimeout, markConnAsClosed
|
// features 428-432: closedStateForErr, collapsePtoNB, flushOutbound, handleWriteTimeout, markConnAsClosed
|
||||||
internal static ClosedState ClosedStateForErr(Exception err) =>
|
internal static ClosedState ClosedStateForErr(Exception err) =>
|
||||||
err is EndOfStreamException ? ClosedState.ClientClosed : ClosedState.ReadError;
|
err is EndOfStreamException ? ClosedState.ClientClosed : ClosedState.ReadError;
|
||||||
|
|
||||||
// features 440-441: processInfo, processErr
|
// features 440-441: processInfo, processErr
|
||||||
internal void ProcessInfo(string info) { /* TODO session 09 */ }
|
internal void ProcessInfo(string info)
|
||||||
internal void ProcessErr(string err) { /* TODO session 09 */ }
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(info))
|
||||||
|
return;
|
||||||
|
Debugf("INFO {0}", info);
|
||||||
|
}
|
||||||
|
internal void ProcessErr(string err)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(err))
|
||||||
|
return;
|
||||||
|
SetAuthError(new InvalidOperationException(err));
|
||||||
|
Errorf("-ERR {0}", err);
|
||||||
|
}
|
||||||
|
|
||||||
// features 442-443: removeSecretsFromTrace, redact
|
// features 442-443: removeSecretsFromTrace, redact
|
||||||
// Delegates to ServerLogging.RemoveSecretsFromTrace (the real implementation lives there).
|
// Delegates to ServerLogging.RemoveSecretsFromTrace (the real implementation lives there).
|
||||||
@@ -1147,7 +1207,31 @@ public sealed partial class ClientConnection
|
|||||||
internal static TimeSpan ComputeRtt(DateTime start) => DateTime.UtcNow - start;
|
internal static TimeSpan ComputeRtt(DateTime start) => DateTime.UtcNow - start;
|
||||||
|
|
||||||
// feature 445: processConnect
|
// feature 445: processConnect
|
||||||
internal void ProcessConnect(byte[] arg) { /* TODO session 09 */ }
|
internal void ProcessConnect(byte[] arg)
|
||||||
|
{
|
||||||
|
if (arg == null || arg.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var parsed = JsonSerializer.Deserialize<ClientOptions>(arg);
|
||||||
|
if (parsed != null)
|
||||||
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
Opts = parsed;
|
||||||
|
Echo = parsed.Echo;
|
||||||
|
Headers = parsed.Headers;
|
||||||
|
Flags |= ClientFlags.ConnectReceived;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
SetAuthError(ex);
|
||||||
|
Errorf("CONNECT parse failed: {0}", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// feature 467-468: processPing, processPong
|
// feature 467-468: processPing, processPong
|
||||||
internal void ProcessPing()
|
internal void ProcessPing()
|
||||||
@@ -1156,10 +1240,19 @@ public sealed partial class ClientConnection
|
|||||||
SendPong();
|
SendPong();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void ProcessPong() { /* TODO */ }
|
internal void ProcessPong()
|
||||||
|
{
|
||||||
|
Rtt = ComputeRtt(RttStart);
|
||||||
|
_pingOut = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// feature 469: updateS2AutoCompressionLevel
|
// feature 469: updateS2AutoCompressionLevel
|
||||||
internal void UpdateS2AutoCompressionLevel() { /* TODO */ }
|
internal void UpdateS2AutoCompressionLevel()
|
||||||
|
{
|
||||||
|
// Placeholder for adaptive compression tuning; keep no-op semantics for now.
|
||||||
|
if (_pingOut < 0)
|
||||||
|
_pingOut = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// features 471-486: processPub variants, parseSub, processSub, etc.
|
// features 471-486: processPub variants, parseSub, processSub, etc.
|
||||||
// Implemented in full when Server+Account sessions complete.
|
// Implemented in full when Server+Account sessions complete.
|
||||||
|
|||||||
@@ -25,6 +25,18 @@ public static class AccessTimeService
|
|||||||
// Mirror Go's init(): nothing to pre-allocate in .NET.
|
// Mirror Go's init(): nothing to pre-allocate in .NET.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Explicit init hook for Go parity.
|
||||||
|
/// Mirrors package <c>init()</c> in server/ats/ats.go.
|
||||||
|
/// This method is intentionally idempotent.
|
||||||
|
/// </summary>
|
||||||
|
public static void Init()
|
||||||
|
{
|
||||||
|
// Ensure a non-zero cached timestamp is present.
|
||||||
|
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L;
|
||||||
|
Interlocked.CompareExchange(ref _utime, now, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a user. Starts the background timer when the first registrant calls this.
|
/// Registers a user. Starts the background timer when the first registrant calls this.
|
||||||
/// Each call to <see cref="Register"/> must be paired with a call to <see cref="Unregister"/>.
|
/// Each call to <see cref="Register"/> must be paired with a call to <see cref="Unregister"/>.
|
||||||
|
|||||||
@@ -74,6 +74,21 @@ public sealed class StreamDeletionMeta
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to get the pending entry for <paramref name="seq"/>.
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetPending(ulong seq, out SdmBySeq entry) => _pending.TryGetValue(seq, out entry);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the pending entry for <paramref name="seq"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void SetPending(ulong seq, SdmBySeq entry) => _pending[seq] = entry;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the pending count for <paramref name="subj"/>, or 0 if not tracked.
|
||||||
|
/// </summary>
|
||||||
|
public ulong GetSubjectTotal(string subj) => _totals.TryGetValue(subj, out var cnt) ? cnt : 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears all tracked data.
|
/// Clears all tracked data.
|
||||||
/// Mirrors <c>SDMMeta.empty</c>.
|
/// Mirrors <c>SDMMeta.empty</c>.
|
||||||
|
|||||||
@@ -1096,6 +1096,14 @@ public sealed class SubscriptionIndex
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write lock must be held.
|
||||||
|
private Exception? AddInsertNotify(string subject, Action<bool> notify)
|
||||||
|
=> AddNotify(_notify!.Insert, subject, notify);
|
||||||
|
|
||||||
|
// Write lock must be held.
|
||||||
|
private Exception? AddRemoveNotify(string subject, Action<bool> notify)
|
||||||
|
=> AddNotify(_notify!.Remove, subject, notify);
|
||||||
|
|
||||||
private static Exception? AddNotify(Dictionary<string, List<Action<bool>>> m, string subject, Action<bool> notify)
|
private static Exception? AddNotify(Dictionary<string, List<Action<bool>>> m, string subject, Action<bool> notify)
|
||||||
{
|
{
|
||||||
if (m.TryGetValue(subject, out var chs))
|
if (m.TryGetValue(subject, out var chs))
|
||||||
@@ -1531,6 +1539,9 @@ public sealed class SubscriptionIndex
|
|||||||
public List<Subscription>? PList;
|
public List<Subscription>? PList;
|
||||||
public SublistLevel? Next;
|
public SublistLevel? Next;
|
||||||
|
|
||||||
|
/// <summary>Factory method matching Go's <c>newNode()</c>.</summary>
|
||||||
|
public static SublistNode NewNode() => new();
|
||||||
|
|
||||||
public bool IsEmpty()
|
public bool IsEmpty()
|
||||||
{
|
{
|
||||||
return PSubs.Count == 0 && (QSubs == null || QSubs.Count == 0) &&
|
return PSubs.Count == 0 && (QSubs == null || QSubs.Count == 0) &&
|
||||||
@@ -1544,6 +1555,9 @@ public sealed class SubscriptionIndex
|
|||||||
public SublistNode? Pwc;
|
public SublistNode? Pwc;
|
||||||
public SublistNode? Fwc;
|
public SublistNode? Fwc;
|
||||||
|
|
||||||
|
/// <summary>Factory method matching Go's <c>newLevel()</c>.</summary>
|
||||||
|
public static SublistLevel NewLevel() => new();
|
||||||
|
|
||||||
public int NumNodes()
|
public int NumNodes()
|
||||||
{
|
{
|
||||||
var num = Nodes.Count;
|
var num = Nodes.Count;
|
||||||
|
|||||||
@@ -40,6 +40,24 @@ public sealed class IpQueue<T>
|
|||||||
/// <summary>Default maximum size of the recycled backing-list capacity.</summary>
|
/// <summary>Default maximum size of the recycled backing-list capacity.</summary>
|
||||||
public const int DefaultMaxRecycleSize = 4 * 1024;
|
public const int DefaultMaxRecycleSize = 4 * 1024;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Functional option type used by <see cref="NewIPQueue"/>.
|
||||||
|
/// Mirrors Go <c>ipQueueOpt</c>.
|
||||||
|
/// </summary>
|
||||||
|
public delegate void IpQueueOption(IpQueueOptions options);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Option bag used by <see cref="NewIPQueue"/>.
|
||||||
|
/// Mirrors Go <c>ipQueueOpts</c>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class IpQueueOptions
|
||||||
|
{
|
||||||
|
public int MaxRecycleSize { get; set; } = DefaultMaxRecycleSize;
|
||||||
|
public Func<T, ulong>? SizeCalc { get; set; }
|
||||||
|
public ulong MaxSize { get; set; }
|
||||||
|
public int MaxLen { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
private long _inprogress;
|
private long _inprogress;
|
||||||
private readonly object _lock = new();
|
private readonly object _lock = new();
|
||||||
|
|
||||||
@@ -68,6 +86,56 @@ public sealed class IpQueue<T>
|
|||||||
/// <summary>Notification channel reader — wait on this to learn items were added.</summary>
|
/// <summary>Notification channel reader — wait on this to learn items were added.</summary>
|
||||||
public ChannelReader<bool> Ch => _ch.Reader;
|
public ChannelReader<bool> Ch => _ch.Reader;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Option helper that configures maximum recycled backing-list size.
|
||||||
|
/// Mirrors Go <c>ipqMaxRecycleSize</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static IpQueueOption IpqMaxRecycleSize(int max) =>
|
||||||
|
options => options.MaxRecycleSize = max;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Option helper that enables size accounting for queue elements.
|
||||||
|
/// Mirrors Go <c>ipqSizeCalculation</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static IpQueueOption IpqSizeCalculation(Func<T, ulong> calc) =>
|
||||||
|
options => options.SizeCalc = calc;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Option helper that limits queue pushes by total accounted size.
|
||||||
|
/// Mirrors Go <c>ipqLimitBySize</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static IpQueueOption IpqLimitBySize(ulong max) =>
|
||||||
|
options => options.MaxSize = max;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Option helper that limits queue pushes by element count.
|
||||||
|
/// Mirrors Go <c>ipqLimitByLen</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static IpQueueOption IpqLimitByLen(int max) =>
|
||||||
|
options => options.MaxLen = max;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Factory wrapper for Go parity.
|
||||||
|
/// Mirrors <c>newIPQueue</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static IpQueue<T> NewIPQueue(
|
||||||
|
string name,
|
||||||
|
ConcurrentDictionary<string, object>? registry = null,
|
||||||
|
params IpQueueOption[] options)
|
||||||
|
{
|
||||||
|
var opts = new IpQueueOptions();
|
||||||
|
foreach (var option in options)
|
||||||
|
option(opts);
|
||||||
|
|
||||||
|
return new IpQueue<T>(
|
||||||
|
name,
|
||||||
|
registry,
|
||||||
|
opts.MaxRecycleSize,
|
||||||
|
opts.SizeCalc,
|
||||||
|
opts.MaxSize,
|
||||||
|
opts.MaxLen);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new queue, optionally registering it in <paramref name="registry"/>.
|
/// Creates a new queue, optionally registering it in <paramref name="registry"/>.
|
||||||
/// Mirrors <c>newIPQueue</c>.
|
/// Mirrors <c>newIPQueue</c>.
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ public sealed class RateCounter
|
|||||||
Interval = TimeSpan.FromSeconds(1);
|
Interval = TimeSpan.FromSeconds(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Factory wrapper for Go parity.
|
||||||
|
/// Mirrors <c>newRateCounter</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static RateCounter NewRateCounter(long limit) => new(limit);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the event is within the rate limit for the current window.
|
/// Returns true if the event is within the rate limit for the current window.
|
||||||
/// Mirrors <c>rateCounter.allow</c>.
|
/// Mirrors <c>rateCounter.allow</c>.
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
// Adapted from server/util.go in the NATS server Go source.
|
// Adapted from server/util.go in the NATS server Go source.
|
||||||
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace ZB.MOM.NatsNet.Server.Internal;
|
namespace ZB.MOM.NatsNet.Server.Internal;
|
||||||
@@ -268,6 +270,25 @@ public static class ServerUtilities
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parity wrapper for Go <c>natsDialTimeout</c>.
|
||||||
|
/// Accepts a network label (tcp/tcp4/tcp6) and host:port address.
|
||||||
|
/// </summary>
|
||||||
|
public static Task<System.Net.Sockets.TcpClient> NatsDialTimeout(
|
||||||
|
string network, string address, TimeSpan timeout)
|
||||||
|
{
|
||||||
|
if (!string.Equals(network, "tcp", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
!string.Equals(network, "tcp4", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
!string.Equals(network, "tcp6", StringComparison.OrdinalIgnoreCase))
|
||||||
|
throw new NotSupportedException($"unsupported network: {network}");
|
||||||
|
|
||||||
|
var (host, port, err) = ParseHostPort(address, defaultPort: 0);
|
||||||
|
if (err != null || port <= 0)
|
||||||
|
throw new InvalidOperationException($"invalid dial address: {address}", err);
|
||||||
|
|
||||||
|
return NatsDialTimeoutAsync(host, port, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// URL redaction
|
// URL redaction
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -337,6 +358,54 @@ public static class ServerUtilities
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// RefCountedUrlSet wrappers (Go parity mapping)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parity wrapper for <see cref="RefCountedUrlSet.AddUrl"/>.
|
||||||
|
/// Mirrors <c>refCountedUrlSet.addUrl</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static bool AddUrl(RefCountedUrlSet urlSet, string urlStr)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(urlSet);
|
||||||
|
return urlSet.AddUrl(urlStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parity wrapper for <see cref="RefCountedUrlSet.RemoveUrl"/>.
|
||||||
|
/// Mirrors <c>refCountedUrlSet.removeUrl</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static bool RemoveUrl(RefCountedUrlSet urlSet, string urlStr)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(urlSet);
|
||||||
|
return urlSet.RemoveUrl(urlStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parity wrapper for <see cref="RefCountedUrlSet.GetAsStringSlice"/>.
|
||||||
|
/// Mirrors <c>refCountedUrlSet.getAsStringSlice</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static string[] GetAsStringSlice(RefCountedUrlSet urlSet)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(urlSet);
|
||||||
|
return urlSet.GetAsStringSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// INFO helpers
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serialises <paramref name="info"/> into an INFO line (<c>INFO {...}\r\n</c>).
|
||||||
|
/// Mirrors <c>generateInfoJSON</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static byte[] GenerateInfoJSON(global::ZB.MOM.NatsNet.Server.ServerInfo info)
|
||||||
|
{
|
||||||
|
var json = JsonSerializer.Serialize(info);
|
||||||
|
return Encoding.UTF8.GetBytes($"INFO {json}\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Copy helpers
|
// Copy helpers
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -391,6 +460,13 @@ public static class ServerUtilities
|
|||||||
|
|
||||||
return channel.Writer;
|
return channel.Writer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parity wrapper for <see cref="CreateParallelTaskQueue"/>.
|
||||||
|
/// Mirrors <c>parallelTaskQueue</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static System.Threading.Channels.ChannelWriter<Action> ParallelTaskQueue(int maxParallelism = 0) =>
|
||||||
|
CreateParallelTaskQueue(maxParallelism);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace ZB.MOM.NatsNet.Server.Internal;
|
namespace ZB.MOM.NatsNet.Server.Internal;
|
||||||
|
|
||||||
@@ -25,7 +26,16 @@ namespace ZB.MOM.NatsNet.Server.Internal;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class SignalHandler
|
public static class SignalHandler
|
||||||
{
|
{
|
||||||
|
private const string ResolvePidError = "unable to resolve pid, try providing one";
|
||||||
private static string _processName = "nats-server";
|
private static string _processName = "nats-server";
|
||||||
|
internal static Func<List<int>> ResolvePidsHandler { get; set; } = ResolvePids;
|
||||||
|
internal static Func<int, UnixSignal, Exception?> SendSignalHandler { get; set; } = SendSignal;
|
||||||
|
|
||||||
|
internal static void ResetTestHooks()
|
||||||
|
{
|
||||||
|
ResolvePidsHandler = ResolvePids;
|
||||||
|
SendSignalHandler = SendSignal;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the process name used for resolving PIDs.
|
/// Sets the process name used for resolving PIDs.
|
||||||
@@ -46,25 +56,67 @@ public static class SignalHandler
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
List<int> pids;
|
var pids = new List<int>(1);
|
||||||
if (string.IsNullOrEmpty(pidExpr))
|
var pidStr = pidExpr.TrimEnd('*');
|
||||||
|
var isGlob = pidExpr.EndsWith('*');
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(pidStr))
|
||||||
{
|
{
|
||||||
pids = ResolvePids();
|
if (!int.TryParse(pidStr, out var pid))
|
||||||
if (pids.Count == 0)
|
return new InvalidOperationException($"invalid pid: {pidStr}");
|
||||||
return new InvalidOperationException("no nats-server processes found");
|
pids.Add(pid);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (int.TryParse(pidExpr, out var pid))
|
|
||||||
pids = [pid];
|
|
||||||
else
|
|
||||||
return new InvalidOperationException($"invalid pid: {pidExpr}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var signal = CommandToUnixSignal(command);
|
if (string.IsNullOrEmpty(pidStr) || isGlob)
|
||||||
|
pids = ResolvePidsHandler();
|
||||||
|
|
||||||
|
if (pids.Count > 1 && !isGlob)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder($"multiple {_processName} processes running:");
|
||||||
|
foreach (var p in pids)
|
||||||
|
sb.Append('\n').Append(p);
|
||||||
|
return new InvalidOperationException(sb.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pids.Count == 0)
|
||||||
|
return new InvalidOperationException($"no {_processName} processes running");
|
||||||
|
|
||||||
|
UnixSignal signal;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
signal = CommandToUnixSignal(command);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
var errBuilder = new StringBuilder();
|
||||||
foreach (var pid in pids)
|
foreach (var pid in pids)
|
||||||
Process.GetProcessById(pid).Kill(signal == UnixSignal.SigKill);
|
{
|
||||||
|
var pidText = pid.ToString();
|
||||||
|
if (pidStr.Length > 0 && pidText != pidStr)
|
||||||
|
{
|
||||||
|
if (!isGlob || !pidText.StartsWith(pidStr, StringComparison.Ordinal))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var err = SendSignalHandler(pid, signal);
|
||||||
|
if (err != null)
|
||||||
|
{
|
||||||
|
errBuilder
|
||||||
|
.Append('\n')
|
||||||
|
.Append("signal \"")
|
||||||
|
.Append(CommandToString(command))
|
||||||
|
.Append("\" ")
|
||||||
|
.Append(pid)
|
||||||
|
.Append(": ")
|
||||||
|
.Append(err.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errBuilder.Length > 0)
|
||||||
|
return new InvalidOperationException(errBuilder.ToString());
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -80,7 +132,7 @@ public static class SignalHandler
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static List<int> ResolvePids()
|
public static List<int> ResolvePids()
|
||||||
{
|
{
|
||||||
var pids = new List<int>();
|
var pids = new List<int>(8);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var psi = new ProcessStartInfo("pgrep", _processName)
|
var psi = new ProcessStartInfo("pgrep", _processName)
|
||||||
@@ -90,22 +142,33 @@ public static class SignalHandler
|
|||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
};
|
};
|
||||||
using var proc = Process.Start(psi);
|
using var proc = Process.Start(psi);
|
||||||
if (proc == null) return pids;
|
if (proc == null)
|
||||||
|
throw new InvalidOperationException(ResolvePidError);
|
||||||
|
|
||||||
var output = proc.StandardOutput.ReadToEnd();
|
var output = proc.StandardOutput.ReadToEnd();
|
||||||
proc.WaitForExit();
|
proc.WaitForExit();
|
||||||
|
if (proc.ExitCode != 0)
|
||||||
|
return pids;
|
||||||
|
|
||||||
var currentPid = Environment.ProcessId;
|
var currentPid = Environment.ProcessId;
|
||||||
foreach (var line in output.Split('\n', StringSplitOptions.RemoveEmptyEntries))
|
foreach (var line in output.Split('\n', StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
if (int.TryParse(line.Trim(), out var pid) && pid != currentPid)
|
if (!int.TryParse(line.Trim(), out var pid))
|
||||||
|
throw new InvalidOperationException(ResolvePidError);
|
||||||
|
|
||||||
|
if (pid != currentPid)
|
||||||
pids.Add(pid);
|
pids.Add(pid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (InvalidOperationException ex) when (ex.Message == ResolvePidError)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// pgrep not available or failed
|
throw new InvalidOperationException(ResolvePidError);
|
||||||
}
|
}
|
||||||
|
|
||||||
return pids;
|
return pids;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +182,39 @@ public static class SignalHandler
|
|||||||
ServerCommand.Quit => UnixSignal.SigInt,
|
ServerCommand.Quit => UnixSignal.SigInt,
|
||||||
ServerCommand.Reopen => UnixSignal.SigUsr1,
|
ServerCommand.Reopen => UnixSignal.SigUsr1,
|
||||||
ServerCommand.Reload => UnixSignal.SigHup,
|
ServerCommand.Reload => UnixSignal.SigHup,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(command), $"unknown command: {command}"),
|
ServerCommand.LameDuckMode => UnixSignal.SigUsr2,
|
||||||
|
ServerCommand.Term => UnixSignal.SigTerm,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(command), $"unknown signal \"{CommandToString(command)}\""),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Go parity alias for <see cref="CommandToUnixSignal"/>.
|
||||||
|
/// Mirrors <c>CommandToSignal</c> in signal.go.
|
||||||
|
/// </summary>
|
||||||
|
public static UnixSignal CommandToSignal(ServerCommand command) => CommandToUnixSignal(command);
|
||||||
|
|
||||||
|
private static Exception? SendSignal(int pid, UnixSignal signal)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Process.GetProcessById(pid).Kill(signal == UnixSignal.SigKill);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CommandToString(ServerCommand command) => command switch
|
||||||
|
{
|
||||||
|
ServerCommand.Stop => "stop",
|
||||||
|
ServerCommand.Quit => "quit",
|
||||||
|
ServerCommand.Reopen => "reopen",
|
||||||
|
ServerCommand.Reload => "reload",
|
||||||
|
ServerCommand.LameDuckMode => "ldm",
|
||||||
|
ServerCommand.Term => "term",
|
||||||
|
_ => command.ToString().ToLowerInvariant(),
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -243,6 +243,51 @@ public sealed class SubjectTransform : ISubjectTransformer
|
|||||||
public static (SubjectTransform? transform, Exception? err) NewStrict(string src, string dest) =>
|
public static (SubjectTransform? transform, Exception? err) NewStrict(string src, string dest) =>
|
||||||
NewWithStrict(src, dest, true);
|
NewWithStrict(src, dest, true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates a subject mapping destination. Checks each token for valid syntax,
|
||||||
|
/// validates mustache-style mapping functions against known regexes, then verifies
|
||||||
|
/// the full transform can be created. Mirrors Go's <c>ValidateMapping</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static Exception? ValidateMapping(string src, string dest)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(dest))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
bool sfwc = false;
|
||||||
|
foreach (var t in dest.Split(SubjectTokens.Btsep))
|
||||||
|
{
|
||||||
|
var length = t.Length;
|
||||||
|
if (length == 0 || sfwc)
|
||||||
|
return new MappingDestinationException(t, ServerErrors.ErrInvalidMappingDestinationSubject);
|
||||||
|
|
||||||
|
// If it looks like a mapping function, validate against known patterns.
|
||||||
|
if (length > 4 && t[0] == '{' && t[1] == '{' && t[length - 2] == '}' && t[length - 1] == '}')
|
||||||
|
{
|
||||||
|
if (!PartitionRe.IsMatch(t) &&
|
||||||
|
!WildcardRe.IsMatch(t) &&
|
||||||
|
!SplitFromLeftRe.IsMatch(t) &&
|
||||||
|
!SplitFromRightRe.IsMatch(t) &&
|
||||||
|
!SliceFromLeftRe.IsMatch(t) &&
|
||||||
|
!SliceFromRightRe.IsMatch(t) &&
|
||||||
|
!SplitRe.IsMatch(t) &&
|
||||||
|
!RandomRe.IsMatch(t))
|
||||||
|
{
|
||||||
|
return new MappingDestinationException(t, ServerErrors.ErrUnknownMappingDestinationFunction);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length == 1 && t[0] == SubjectTokens.Fwc)
|
||||||
|
sfwc = true;
|
||||||
|
else if (t.AsSpan().ContainsAny("\t\n\f\r "))
|
||||||
|
return ServerErrors.ErrInvalidMappingDestinationSubject;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the transform can actually be created.
|
||||||
|
var (_, err) = New(src, dest);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to match a published subject against the source pattern.
|
/// Attempts to match a published subject against the source pattern.
|
||||||
/// Returns the transformed subject or an error.
|
/// Returns the transformed subject or an error.
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
//
|
//
|
||||||
// Adapted from server/filestore.go (fileStore struct and methods)
|
// Adapted from server/filestore.go (fileStore struct and methods)
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using ZB.MOM.NatsNet.Server.Internal.DataStructures;
|
using ZB.MOM.NatsNet.Server.Internal.DataStructures;
|
||||||
|
|
||||||
@@ -100,6 +101,10 @@ public sealed class JetStreamFileStore : IStreamStore, IDisposable
|
|||||||
// Last PurgeEx call time (for throttle logic)
|
// Last PurgeEx call time (for throttle logic)
|
||||||
private DateTime _lpex;
|
private DateTime _lpex;
|
||||||
|
|
||||||
|
// In this incremental port stage, file-store logic delegates core stream semantics
|
||||||
|
// to the memory store implementation while file-specific APIs are added on top.
|
||||||
|
private readonly JetStreamMemStore _memStore;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Constructor
|
// Constructor
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
@@ -135,6 +140,10 @@ public sealed class JetStreamFileStore : IStreamStore, IDisposable
|
|||||||
_bim = new Dictionary<uint, MessageBlock>();
|
_bim = new Dictionary<uint, MessageBlock>();
|
||||||
_qch = Channel.CreateUnbounded<byte>();
|
_qch = Channel.CreateUnbounded<byte>();
|
||||||
_fsld = Channel.CreateUnbounded<byte>();
|
_fsld = Channel.CreateUnbounded<byte>();
|
||||||
|
|
||||||
|
var memCfg = cfg.Config.Clone();
|
||||||
|
memCfg.Storage = StorageType.MemoryStorage;
|
||||||
|
_memStore = new JetStreamMemStore(memCfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
@@ -146,52 +155,11 @@ public sealed class JetStreamFileStore : IStreamStore, IDisposable
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public StreamState State()
|
public StreamState State()
|
||||||
{
|
=> _memStore.State();
|
||||||
_mu.EnterReadLock();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Return a shallow copy so callers cannot mutate internal state.
|
|
||||||
return new StreamState
|
|
||||||
{
|
|
||||||
Msgs = _state.Msgs,
|
|
||||||
Bytes = _state.Bytes,
|
|
||||||
FirstSeq = _state.FirstSeq,
|
|
||||||
FirstTime = _state.FirstTime,
|
|
||||||
LastSeq = _state.LastSeq,
|
|
||||||
LastTime = _state.LastTime,
|
|
||||||
NumSubjects = _state.NumSubjects,
|
|
||||||
NumDeleted = _state.NumDeleted,
|
|
||||||
Deleted = _state.Deleted,
|
|
||||||
Lost = _state.Lost,
|
|
||||||
Consumers = _state.Consumers,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_mu.ExitReadLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void FastState(StreamState state)
|
public void FastState(StreamState state)
|
||||||
{
|
=> _memStore.FastState(state);
|
||||||
_mu.EnterReadLock();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
state.Msgs = _state.Msgs;
|
|
||||||
state.Bytes = _state.Bytes;
|
|
||||||
state.FirstSeq = _state.FirstSeq;
|
|
||||||
state.FirstTime = _state.FirstTime;
|
|
||||||
state.LastSeq = _state.LastSeq;
|
|
||||||
state.LastTime = _state.LastTime;
|
|
||||||
state.NumDeleted = _state.NumDeleted;
|
|
||||||
state.Consumers = _state.Consumers;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_mu.ExitReadLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// IStreamStore — callback registration
|
// IStreamStore — callback registration
|
||||||
@@ -199,27 +167,15 @@ public sealed class JetStreamFileStore : IStreamStore, IDisposable
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void RegisterStorageUpdates(StorageUpdateHandler cb)
|
public void RegisterStorageUpdates(StorageUpdateHandler cb)
|
||||||
{
|
=> _memStore.RegisterStorageUpdates(cb);
|
||||||
_mu.EnterWriteLock();
|
|
||||||
try { _scb = cb; }
|
|
||||||
finally { _mu.ExitWriteLock(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void RegisterStorageRemoveMsg(StorageRemoveMsgHandler cb)
|
public void RegisterStorageRemoveMsg(StorageRemoveMsgHandler cb)
|
||||||
{
|
=> _memStore.RegisterStorageRemoveMsg(cb);
|
||||||
_mu.EnterWriteLock();
|
|
||||||
try { _rmcb = cb; }
|
|
||||||
finally { _mu.ExitWriteLock(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void RegisterProcessJetStreamMsg(ProcessJetStreamMsgHandler cb)
|
public void RegisterProcessJetStreamMsg(ProcessJetStreamMsgHandler cb)
|
||||||
{
|
=> _memStore.RegisterProcessJetStreamMsg(cb);
|
||||||
_mu.EnterWriteLock();
|
|
||||||
try { _pmsgcb = cb; }
|
|
||||||
finally { _mu.ExitWriteLock(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// IStreamStore — lifecycle
|
// IStreamStore — lifecycle
|
||||||
@@ -245,6 +201,7 @@ public sealed class JetStreamFileStore : IStreamStore, IDisposable
|
|||||||
_syncTmr = null;
|
_syncTmr = null;
|
||||||
|
|
||||||
_closed = true;
|
_closed = true;
|
||||||
|
_memStore.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -256,71 +213,71 @@ public sealed class JetStreamFileStore : IStreamStore, IDisposable
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (ulong Seq, long Ts) StoreMsg(string subject, byte[]? hdr, byte[]? msg, long ttl)
|
public (ulong Seq, long Ts) StoreMsg(string subject, byte[]? hdr, byte[]? msg, long ttl)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore StoreMsg");
|
=> _memStore.StoreMsg(subject, hdr, msg, ttl);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void StoreRawMsg(string subject, byte[]? hdr, byte[]? msg, ulong seq, long ts, long ttl, bool discardNewCheck)
|
public void StoreRawMsg(string subject, byte[]? hdr, byte[]? msg, ulong seq, long ts, long ttl, bool discardNewCheck)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore StoreRawMsg");
|
=> _memStore.StoreRawMsg(subject, hdr, msg, seq, ts, ttl, discardNewCheck);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (ulong Seq, Exception? Error) SkipMsg(ulong seq)
|
public (ulong Seq, Exception? Error) SkipMsg(ulong seq)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore SkipMsg");
|
=> _memStore.SkipMsg(seq);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SkipMsgs(ulong seq, ulong num)
|
public void SkipMsgs(ulong seq, ulong num)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore SkipMsgs");
|
=> _memStore.SkipMsgs(seq, num);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void FlushAllPending()
|
public void FlushAllPending()
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore FlushAllPending");
|
=> _memStore.FlushAllPending();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public StoreMsg? LoadMsg(ulong seq, StoreMsg? sm)
|
public StoreMsg? LoadMsg(ulong seq, StoreMsg? sm)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore LoadMsg");
|
=> _memStore.LoadMsg(seq, sm);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (StoreMsg? Sm, ulong Skip) LoadNextMsg(string filter, bool wc, ulong start, StoreMsg? smp)
|
public (StoreMsg? Sm, ulong Skip) LoadNextMsg(string filter, bool wc, ulong start, StoreMsg? smp)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore LoadNextMsg");
|
=> _memStore.LoadNextMsg(filter, wc, start, smp);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (StoreMsg? Sm, ulong Skip) LoadNextMsgMulti(object? sl, ulong start, StoreMsg? smp)
|
public (StoreMsg? Sm, ulong Skip) LoadNextMsgMulti(object? sl, ulong start, StoreMsg? smp)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore LoadNextMsgMulti");
|
=> _memStore.LoadNextMsgMulti(sl, start, smp);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public StoreMsg? LoadLastMsg(string subject, StoreMsg? sm)
|
public StoreMsg? LoadLastMsg(string subject, StoreMsg? sm)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore LoadLastMsg");
|
=> _memStore.LoadLastMsg(subject, sm);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (StoreMsg? Sm, Exception? Error) LoadPrevMsg(ulong start, StoreMsg? smp)
|
public (StoreMsg? Sm, Exception? Error) LoadPrevMsg(ulong start, StoreMsg? smp)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore LoadPrevMsg");
|
=> _memStore.LoadPrevMsg(start, smp);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (StoreMsg? Sm, ulong Skip, Exception? Error) LoadPrevMsgMulti(object? sl, ulong start, StoreMsg? smp)
|
public (StoreMsg? Sm, ulong Skip, Exception? Error) LoadPrevMsgMulti(object? sl, ulong start, StoreMsg? smp)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore LoadPrevMsgMulti");
|
=> _memStore.LoadPrevMsgMulti(sl, start, smp);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (bool Removed, Exception? Error) RemoveMsg(ulong seq)
|
public (bool Removed, Exception? Error) RemoveMsg(ulong seq)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore RemoveMsg");
|
=> _memStore.RemoveMsg(seq);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (bool Removed, Exception? Error) EraseMsg(ulong seq)
|
public (bool Removed, Exception? Error) EraseMsg(ulong seq)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore EraseMsg");
|
=> _memStore.EraseMsg(seq);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (ulong Purged, Exception? Error) Purge()
|
public (ulong Purged, Exception? Error) Purge()
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore Purge");
|
=> _memStore.Purge();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (ulong Purged, Exception? Error) PurgeEx(string subject, ulong seq, ulong keep)
|
public (ulong Purged, Exception? Error) PurgeEx(string subject, ulong seq, ulong keep)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore PurgeEx");
|
=> _memStore.PurgeEx(subject, seq, keep);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (ulong Purged, Exception? Error) Compact(ulong seq)
|
public (ulong Purged, Exception? Error) Compact(ulong seq)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore Compact");
|
=> _memStore.Compact(seq);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Truncate(ulong seq)
|
public void Truncate(ulong seq)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore Truncate");
|
=> _memStore.Truncate(seq);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// IStreamStore — query methods (all stubs)
|
// IStreamStore — query methods (all stubs)
|
||||||
@@ -328,39 +285,39 @@ public sealed class JetStreamFileStore : IStreamStore, IDisposable
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ulong GetSeqFromTime(DateTime t)
|
public ulong GetSeqFromTime(DateTime t)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore GetSeqFromTime");
|
=> _memStore.GetSeqFromTime(t);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public SimpleState FilteredState(ulong seq, string subject)
|
public SimpleState FilteredState(ulong seq, string subject)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore FilteredState");
|
=> _memStore.FilteredState(seq, subject);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Dictionary<string, SimpleState> SubjectsState(string filterSubject)
|
public Dictionary<string, SimpleState> SubjectsState(string filterSubject)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore SubjectsState");
|
=> _memStore.SubjectsState(filterSubject);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Dictionary<string, ulong> SubjectsTotals(string filterSubject)
|
public Dictionary<string, ulong> SubjectsTotals(string filterSubject)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore SubjectsTotals");
|
=> _memStore.SubjectsTotals(filterSubject);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (ulong[] Seqs, Exception? Error) AllLastSeqs()
|
public (ulong[] Seqs, Exception? Error) AllLastSeqs()
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore AllLastSeqs");
|
=> _memStore.AllLastSeqs();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (ulong[] Seqs, Exception? Error) MultiLastSeqs(string[] filters, ulong maxSeq, int maxAllowed)
|
public (ulong[] Seqs, Exception? Error) MultiLastSeqs(string[] filters, ulong maxSeq, int maxAllowed)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore MultiLastSeqs");
|
=> _memStore.MultiLastSeqs(filters, maxSeq, maxAllowed);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (string Subject, Exception? Error) SubjectForSeq(ulong seq)
|
public (string Subject, Exception? Error) SubjectForSeq(ulong seq)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore SubjectForSeq");
|
=> _memStore.SubjectForSeq(seq);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (ulong Total, ulong ValidThrough, Exception? Error) NumPending(ulong sseq, string filter, bool lastPerSubject)
|
public (ulong Total, ulong ValidThrough, Exception? Error) NumPending(ulong sseq, string filter, bool lastPerSubject)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore NumPending");
|
=> _memStore.NumPending(sseq, filter, lastPerSubject);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (ulong Total, ulong ValidThrough, Exception? Error) NumPendingMulti(ulong sseq, object? sl, bool lastPerSubject)
|
public (ulong Total, ulong ValidThrough, Exception? Error) NumPendingMulti(ulong sseq, object? sl, bool lastPerSubject)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore NumPendingMulti");
|
=> _memStore.NumPendingMulti(sseq, sl, lastPerSubject);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// IStreamStore — stream state encoding (stubs)
|
// IStreamStore — stream state encoding (stubs)
|
||||||
@@ -368,11 +325,11 @@ public sealed class JetStreamFileStore : IStreamStore, IDisposable
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (byte[] Enc, Exception? Error) EncodedStreamState(ulong failed)
|
public (byte[] Enc, Exception? Error) EncodedStreamState(ulong failed)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore EncodedStreamState");
|
=> _memStore.EncodedStreamState(failed);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SyncDeleted(DeleteBlocks dbs)
|
public void SyncDeleted(DeleteBlocks dbs)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore SyncDeleted");
|
=> _memStore.SyncDeleted(dbs);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// IStreamStore — config / admin (stubs)
|
// IStreamStore — config / admin (stubs)
|
||||||
@@ -380,15 +337,18 @@ public sealed class JetStreamFileStore : IStreamStore, IDisposable
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void UpdateConfig(StreamConfig cfg)
|
public void UpdateConfig(StreamConfig cfg)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore UpdateConfig");
|
{
|
||||||
|
_cfg.Config = cfg.Clone();
|
||||||
|
_memStore.UpdateConfig(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Delete(bool inline)
|
public void Delete(bool inline)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore Delete");
|
=> _memStore.Delete(inline);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void ResetState()
|
public void ResetState()
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ResetState");
|
=> _memStore.ResetState();
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// IStreamStore — consumer management (stubs)
|
// IStreamStore — consumer management (stubs)
|
||||||
@@ -396,13 +356,29 @@ public sealed class JetStreamFileStore : IStreamStore, IDisposable
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IConsumerStore ConsumerStore(string name, DateTime created, ConsumerConfig cfg)
|
public IConsumerStore ConsumerStore(string name, DateTime created, ConsumerConfig cfg)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ConsumerStore");
|
{
|
||||||
|
var cfi = new FileConsumerInfo
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Created = created,
|
||||||
|
Config = cfg,
|
||||||
|
};
|
||||||
|
var odir = Path.Combine(_fcfg.StoreDir, FileStoreDefaults.ConsumerDir, name);
|
||||||
|
Directory.CreateDirectory(odir);
|
||||||
|
var cs = new ConsumerFileStore(this, cfi, name, odir);
|
||||||
|
AddConsumer(cs);
|
||||||
|
return cs;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void AddConsumer(IConsumerStore o)
|
public void AddConsumer(IConsumerStore o)
|
||||||
{
|
{
|
||||||
_cmu.EnterWriteLock();
|
_cmu.EnterWriteLock();
|
||||||
try { _cfs.Add(o); }
|
try
|
||||||
|
{
|
||||||
|
_cfs.Add(o);
|
||||||
|
_memStore.AddConsumer(o);
|
||||||
|
}
|
||||||
finally { _cmu.ExitWriteLock(); }
|
finally { _cmu.ExitWriteLock(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,7 +386,11 @@ public sealed class JetStreamFileStore : IStreamStore, IDisposable
|
|||||||
public void RemoveConsumer(IConsumerStore o)
|
public void RemoveConsumer(IConsumerStore o)
|
||||||
{
|
{
|
||||||
_cmu.EnterWriteLock();
|
_cmu.EnterWriteLock();
|
||||||
try { _cfs.Remove(o); }
|
try
|
||||||
|
{
|
||||||
|
_cfs.Remove(o);
|
||||||
|
_memStore.RemoveConsumer(o);
|
||||||
|
}
|
||||||
finally { _cmu.ExitWriteLock(); }
|
finally { _cmu.ExitWriteLock(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,9 +400,14 @@ public sealed class JetStreamFileStore : IStreamStore, IDisposable
|
|||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (SnapshotResult? Result, Exception? Error) Snapshot(TimeSpan deadline, bool includeConsumers, bool checkMsgs)
|
public (SnapshotResult? Result, Exception? Error) Snapshot(TimeSpan deadline, bool includeConsumers, bool checkMsgs)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore Snapshot");
|
{
|
||||||
|
var state = _memStore.State();
|
||||||
|
var payload = JsonSerializer.SerializeToUtf8Bytes(state);
|
||||||
|
var reader = new MemoryStream(payload, writable: false);
|
||||||
|
return (new SnapshotResult { Reader = reader, State = state }, null);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (ulong Total, ulong Reported, Exception? Error) Utilization()
|
public (ulong Total, ulong Reported, Exception? Error) Utilization()
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore Utilization");
|
=> _memStore.Utilization();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,12 +183,19 @@ public sealed class CompressionInfo
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Serialises compression metadata as a compact binary prefix.
|
/// Serialises compression metadata as a compact binary prefix.
|
||||||
/// Format: 'c' 'm' 'p' <algorithmByte> <uvarint originalSize>
|
/// Format: 'c' 'm' 'p' <algorithmByte> <uvarint originalSize> <uvarint compressedSize>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte[] MarshalMetadata()
|
public byte[] MarshalMetadata()
|
||||||
{
|
{
|
||||||
// TODO: session 18 — implement varint encoding
|
Span<byte> scratch = stackalloc byte[32];
|
||||||
throw new NotImplementedException("TODO: session 18 — filestore CompressionInfo.MarshalMetadata");
|
var pos = 0;
|
||||||
|
scratch[pos++] = (byte)'c';
|
||||||
|
scratch[pos++] = (byte)'m';
|
||||||
|
scratch[pos++] = (byte)'p';
|
||||||
|
scratch[pos++] = (byte)Type;
|
||||||
|
pos += WriteUVarInt(scratch[pos..], Original);
|
||||||
|
pos += WriteUVarInt(scratch[pos..], Compressed);
|
||||||
|
return scratch[..pos].ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -197,8 +204,58 @@ public sealed class CompressionInfo
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int UnmarshalMetadata(byte[] b)
|
public int UnmarshalMetadata(byte[] b)
|
||||||
{
|
{
|
||||||
// TODO: session 18 — implement varint decoding
|
ArgumentNullException.ThrowIfNull(b);
|
||||||
throw new NotImplementedException("TODO: session 18 — filestore CompressionInfo.UnmarshalMetadata");
|
|
||||||
|
if (b.Length < 4 || b[0] != (byte)'c' || b[1] != (byte)'m' || b[2] != (byte)'p')
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
Type = (StoreCompression)b[3];
|
||||||
|
var pos = 4;
|
||||||
|
|
||||||
|
if (!TryReadUVarInt(b.AsSpan(pos), out var original, out var used1))
|
||||||
|
return 0;
|
||||||
|
pos += used1;
|
||||||
|
|
||||||
|
if (!TryReadUVarInt(b.AsSpan(pos), out var compressed, out var used2))
|
||||||
|
return 0;
|
||||||
|
pos += used2;
|
||||||
|
|
||||||
|
Original = original;
|
||||||
|
Compressed = compressed;
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int WriteUVarInt(Span<byte> dest, ulong value)
|
||||||
|
{
|
||||||
|
var i = 0;
|
||||||
|
while (value >= 0x80)
|
||||||
|
{
|
||||||
|
dest[i++] = (byte)(value | 0x80);
|
||||||
|
value >>= 7;
|
||||||
|
}
|
||||||
|
dest[i++] = (byte)value;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadUVarInt(ReadOnlySpan<byte> src, out ulong value, out int used)
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
used = 0;
|
||||||
|
var shift = 0;
|
||||||
|
foreach (var b in src)
|
||||||
|
{
|
||||||
|
value |= (ulong)(b & 0x7F) << shift;
|
||||||
|
used++;
|
||||||
|
if ((b & 0x80) == 0)
|
||||||
|
return true;
|
||||||
|
shift += 7;
|
||||||
|
if (shift > 63)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = 0;
|
||||||
|
used = 0;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,27 @@ namespace ZB.MOM.NatsNet.Server;
|
|||||||
/// <summary>Stub: stored message type — full definition in session 20.</summary>
|
/// <summary>Stub: stored message type — full definition in session 20.</summary>
|
||||||
public sealed class StoredMsg { }
|
public sealed class StoredMsg { }
|
||||||
|
|
||||||
/// <summary>Priority group for pull consumers — full definition in session 20.</summary>
|
/// <summary>
|
||||||
public sealed class PriorityGroup { }
|
/// Priority group for pull consumers.
|
||||||
|
/// Mirrors <c>PriorityGroup</c> in server/consumer.go.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PriorityGroup
|
||||||
|
{
|
||||||
|
[JsonPropertyName("group")]
|
||||||
|
public string Group { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("min_pending")]
|
||||||
|
public long MinPending { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("min_ack_pending")]
|
||||||
|
public long MinAckPending { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("priority")]
|
||||||
|
public int Priority { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// API subject constants
|
// API subject constants
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ public sealed class JsApiError
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class JsApiErrors
|
public static class JsApiErrors
|
||||||
{
|
{
|
||||||
|
public delegate object? ErrorOption();
|
||||||
|
|
||||||
// ---- Account ----
|
// ---- Account ----
|
||||||
public static readonly JsApiError AccountResourcesExceeded = new() { Code = 400, ErrCode = 10002, Description = "resource limits exceeded for account" };
|
public static readonly JsApiError AccountResourcesExceeded = new() { Code = 400, ErrCode = 10002, Description = "resource limits exceeded for account" };
|
||||||
|
|
||||||
@@ -315,9 +317,104 @@ public static class JsApiErrors
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool IsNatsError(JsApiError? err, params ushort[] errCodes)
|
public static bool IsNatsError(JsApiError? err, params ushort[] errCodes)
|
||||||
{
|
{
|
||||||
if (err is null) return false;
|
return IsNatsErr(err, errCodes);
|
||||||
foreach (var code in errCodes)
|
}
|
||||||
if (err.ErrCode == code) return true;
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if <paramref name="err"/> is a <see cref="JsApiError"/> and matches one of the supplied IDs.
|
||||||
|
/// Unknown IDs are ignored, matching Go's map-based lookup behavior.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsNatsErr(object? err, params ushort[] ids)
|
||||||
|
{
|
||||||
|
if (err is not JsApiError ce)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (var id in ids)
|
||||||
|
{
|
||||||
|
var ae = ForErrCode(id);
|
||||||
|
if (ae != null && ce.ErrCode == ae.ErrCode)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formats an API error string exactly as Go <c>ApiError.Error()</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static string Error(JsApiError? err) => err?.ToString() ?? string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an option that causes constructor helpers to return the provided
|
||||||
|
/// <see cref="JsApiError"/> when present.
|
||||||
|
/// Mirrors Go <c>Unless</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static ErrorOption Unless(object? err) => () => err;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors Go <c>NewJSRestoreSubscribeFailedError</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static JsApiError NewJSRestoreSubscribeFailedError(Exception err, string subject, params ErrorOption[] opts)
|
||||||
|
{
|
||||||
|
var overridden = ParseUnless(opts);
|
||||||
|
if (overridden != null)
|
||||||
|
return overridden;
|
||||||
|
|
||||||
|
return NewWithTags(
|
||||||
|
RestoreSubscribeFailed,
|
||||||
|
("{err}", err.Message),
|
||||||
|
("{subject}", subject));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors Go <c>NewJSStreamRestoreError</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static JsApiError NewJSStreamRestoreError(Exception err, params ErrorOption[] opts)
|
||||||
|
{
|
||||||
|
var overridden = ParseUnless(opts);
|
||||||
|
if (overridden != null)
|
||||||
|
return overridden;
|
||||||
|
|
||||||
|
return NewWithTags(StreamRestore, ("{err}", err.Message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors Go <c>NewJSPeerRemapError</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static JsApiError NewJSPeerRemapError(params ErrorOption[] opts)
|
||||||
|
{
|
||||||
|
var overridden = ParseUnless(opts);
|
||||||
|
return overridden ?? Clone(PeerRemap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsApiError? ParseUnless(ReadOnlySpan<ErrorOption> opts)
|
||||||
|
{
|
||||||
|
foreach (var opt in opts)
|
||||||
|
{
|
||||||
|
var value = opt();
|
||||||
|
if (value is JsApiError apiErr)
|
||||||
|
return Clone(apiErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsApiError Clone(JsApiError source) => new()
|
||||||
|
{
|
||||||
|
Code = source.Code,
|
||||||
|
ErrCode = source.ErrCode,
|
||||||
|
Description = source.Description,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static JsApiError NewWithTags(JsApiError source, params (string key, string value)[] replacements)
|
||||||
|
{
|
||||||
|
var clone = Clone(source);
|
||||||
|
var description = clone.Description ?? string.Empty;
|
||||||
|
|
||||||
|
foreach (var (key, value) in replacements)
|
||||||
|
description = description.Replace(key, value, StringComparison.Ordinal);
|
||||||
|
|
||||||
|
clone.Description = description;
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,3 +291,109 @@ internal sealed class JsaUsage
|
|||||||
/// Mirrors <c>keyGen</c> in server/jetstream.go.
|
/// Mirrors <c>keyGen</c> in server/jetstream.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public delegate byte[] KeyGen(byte[] context);
|
public delegate byte[] KeyGen(byte[] context);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// JetStream message header helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Static helpers for extracting TTL, scheduling, and scheduler information
|
||||||
|
/// from JetStream message headers.
|
||||||
|
/// Mirrors <c>getMessageTTL</c>, <c>nextMessageSchedule</c>, <c>getMessageScheduler</c>
|
||||||
|
/// in server/stream.go.
|
||||||
|
/// </summary>
|
||||||
|
public static class JetStreamHeaderHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts the TTL value (in seconds) from the message header.
|
||||||
|
/// Returns 0 if no TTL header is present. Returns -1 for "never".
|
||||||
|
/// Mirrors Go <c>getMessageTTL</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static (long Ttl, Exception? Error) GetMessageTtl(byte[] hdr)
|
||||||
|
{
|
||||||
|
var raw = NatsMessageHeaders.GetHeader(NatsHeaderConstants.JsMessageTtl, hdr);
|
||||||
|
if (raw == null || raw.Length == 0)
|
||||||
|
return (0, null);
|
||||||
|
|
||||||
|
return ParseMessageTtl(System.Text.Encoding.ASCII.GetString(raw));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses a TTL string value into seconds.
|
||||||
|
/// Supports "never" (-1), Go-style duration strings ("30s", "5m"), or plain integer seconds.
|
||||||
|
/// Mirrors Go <c>parseMessageTTL</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static (long Ttl, Exception? Error) ParseMessageTtl(string ttl)
|
||||||
|
{
|
||||||
|
if (string.Equals(ttl, "never", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return (-1, null);
|
||||||
|
|
||||||
|
// Try parsing as a Go-style duration.
|
||||||
|
if (TryParseDuration(ttl, out var dur))
|
||||||
|
{
|
||||||
|
if (dur.TotalSeconds < 1)
|
||||||
|
return (0, new InvalidOperationException("message TTL invalid"));
|
||||||
|
return ((long)dur.TotalSeconds, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try as plain integer (seconds).
|
||||||
|
if (long.TryParse(ttl, out var t))
|
||||||
|
{
|
||||||
|
if (t < 0)
|
||||||
|
return (0, new InvalidOperationException("message TTL invalid"));
|
||||||
|
return (t, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0, new InvalidOperationException("message TTL invalid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts the next scheduled fire time from the message header.
|
||||||
|
/// Returns (DateTime, true) if valid, (default, true) if no header, (default, false) on parse error.
|
||||||
|
/// Mirrors Go <c>nextMessageSchedule</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static (DateTime Schedule, bool Ok) NextMessageSchedule(byte[] hdr, long ts)
|
||||||
|
{
|
||||||
|
if (hdr.Length == 0)
|
||||||
|
return (default, true);
|
||||||
|
|
||||||
|
var slice = NatsMessageHeaders.SliceHeader(NatsHeaderConstants.JsSchedulePattern, hdr);
|
||||||
|
if (slice == null || slice.Value.Length == 0)
|
||||||
|
return (default, true);
|
||||||
|
|
||||||
|
var val = System.Text.Encoding.ASCII.GetString(slice.Value.Span);
|
||||||
|
var (schedule, _, ok) = Internal.MsgScheduling.ParseMsgSchedule(val, ts);
|
||||||
|
return (schedule, ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts the scheduler identifier from the message header.
|
||||||
|
/// Returns empty string if not present.
|
||||||
|
/// Mirrors Go <c>getMessageScheduler</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static string GetMessageScheduler(byte[] hdr)
|
||||||
|
{
|
||||||
|
if (hdr.Length == 0)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var raw = NatsMessageHeaders.GetHeader(NatsHeaderConstants.JsScheduler, hdr);
|
||||||
|
if (raw == null || raw.Length == 0)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return System.Text.Encoding.ASCII.GetString(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryParseDuration(string s, out TimeSpan result)
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
if (s.EndsWith("ms", StringComparison.Ordinal) && double.TryParse(s[..^2], out var ms))
|
||||||
|
{ result = TimeSpan.FromMilliseconds(ms); return true; }
|
||||||
|
if (s.EndsWith('s') && double.TryParse(s[..^1], out var sec))
|
||||||
|
{ result = TimeSpan.FromSeconds(sec); return true; }
|
||||||
|
if (s.EndsWith('m') && double.TryParse(s[..^1], out var min))
|
||||||
|
{ result = TimeSpan.FromMinutes(min); return true; }
|
||||||
|
if (s.EndsWith('h') && double.TryParse(s[..^1], out var hr))
|
||||||
|
{ result = TimeSpan.FromHours(hr); return true; }
|
||||||
|
return TimeSpan.TryParse(s, out result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// Adapted from server/memstore.go
|
// Adapted from server/memstore.go
|
||||||
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using ZB.MOM.NatsNet.Server.Internal;
|
||||||
using ZB.MOM.NatsNet.Server.Internal.DataStructures;
|
using ZB.MOM.NatsNet.Server.Internal.DataStructures;
|
||||||
|
|
||||||
namespace ZB.MOM.NatsNet.Server;
|
namespace ZB.MOM.NatsNet.Server;
|
||||||
@@ -57,6 +58,18 @@ public sealed class JetStreamMemStore : IStreamStore
|
|||||||
// Consumer count
|
// Consumer count
|
||||||
private int _consumers;
|
private int _consumers;
|
||||||
|
|
||||||
|
// TTL hash wheel (only created when cfg.AllowMsgTTL)
|
||||||
|
private HashWheel? _ttls;
|
||||||
|
|
||||||
|
// Message scheduling (only created when cfg.AllowMsgSchedules)
|
||||||
|
private MsgScheduling? _scheduling;
|
||||||
|
|
||||||
|
// Subject deletion metadata for cluster consensus
|
||||||
|
private StreamDeletionMeta _sdm = new();
|
||||||
|
|
||||||
|
// Guard against re-entrant age check
|
||||||
|
private bool _ageChkRun;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Constructor
|
// Constructor
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
@@ -83,6 +96,11 @@ public sealed class JetStreamMemStore : IStreamStore
|
|||||||
_state.LastSeq = cfg.FirstSeq - 1;
|
_state.LastSeq = cfg.FirstSeq - 1;
|
||||||
_state.FirstSeq = cfg.FirstSeq;
|
_state.FirstSeq = cfg.FirstSeq;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cfg.AllowMsgTTL)
|
||||||
|
_ttls = HashWheel.NewHashWheel();
|
||||||
|
if (cfg.AllowMsgSchedules)
|
||||||
|
_scheduling = new MsgScheduling(RunMsgScheduling);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
@@ -225,9 +243,52 @@ public sealed class JetStreamMemStore : IStreamStore
|
|||||||
EnforceMsgLimit();
|
EnforceMsgLimit();
|
||||||
EnforceBytesLimit();
|
EnforceBytesLimit();
|
||||||
|
|
||||||
// Age check
|
// Per-message TTL tracking.
|
||||||
if (_ageChk == null && _cfg.MaxAge != TimeSpan.Zero)
|
if (_ttls != null && ttl > 0)
|
||||||
|
{
|
||||||
|
var expires = ts + (ttl * 1_000_000_000L);
|
||||||
|
_ttls.Add(seq, expires);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Age check timer management.
|
||||||
|
if (_ttls != null && ttl > 0)
|
||||||
|
{
|
||||||
|
ResetAgeChk(0);
|
||||||
|
}
|
||||||
|
else if (_ageChk == null && (_cfg.MaxAge > TimeSpan.Zero || _ttls != null))
|
||||||
|
{
|
||||||
StartAgeChk();
|
StartAgeChk();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message scheduling.
|
||||||
|
if (_scheduling != null && hdr.Length > 0)
|
||||||
|
{
|
||||||
|
var (schedule, ok) = JetStreamHeaderHelpers.NextMessageSchedule(hdr, ts);
|
||||||
|
if (ok && schedule != default)
|
||||||
|
{
|
||||||
|
_scheduling.Add(seq, subject, schedule.Ticks * 100L);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_scheduling.RemoveSubject(subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a repeating schedule.
|
||||||
|
var scheduleNextSlice = NatsMessageHeaders.SliceHeader(NatsHeaderConstants.JsScheduleNext, hdr);
|
||||||
|
if (scheduleNextSlice != null && scheduleNextSlice.Value.Length > 0)
|
||||||
|
{
|
||||||
|
var scheduleNext = Encoding.ASCII.GetString(scheduleNextSlice.Value.Span);
|
||||||
|
if (scheduleNext != NatsHeaderConstants.JsScheduleNextPurge)
|
||||||
|
{
|
||||||
|
var scheduler = JetStreamHeaderHelpers.GetMessageScheduler(hdr);
|
||||||
|
if (DateTime.TryParse(scheduleNext, null, System.Globalization.DateTimeStyles.RoundtripKind, out var next)
|
||||||
|
&& !string.IsNullOrEmpty(scheduler))
|
||||||
|
{
|
||||||
|
_scheduling.Update(scheduler, next.ToUniversalTime().Ticks * 100L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -562,6 +623,17 @@ public sealed class JetStreamMemStore : IStreamStore
|
|||||||
UpdateFirstSeq(seq);
|
UpdateFirstSeq(seq);
|
||||||
RemoveSeqPerSubject(sm.Subject, seq);
|
RemoveSeqPerSubject(sm.Subject, seq);
|
||||||
|
|
||||||
|
// Remove TTL entry from hash wheel if applicable.
|
||||||
|
if (_ttls != null && sm.Hdr.Length > 0)
|
||||||
|
{
|
||||||
|
var (ttl, err) = JetStreamHeaderHelpers.GetMessageTtl(sm.Hdr);
|
||||||
|
if (err == null && ttl > 0)
|
||||||
|
{
|
||||||
|
var expires = sm.Ts + (ttl * 1_000_000_000L);
|
||||||
|
_ttls.Remove(seq, expires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (secure)
|
if (secure)
|
||||||
{
|
{
|
||||||
if (sm.Hdr.Length > 0)
|
if (sm.Hdr.Length > 0)
|
||||||
@@ -1325,7 +1397,15 @@ public sealed class JetStreamMemStore : IStreamStore
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void ResetState()
|
public void ResetState()
|
||||||
{
|
{
|
||||||
// For memory store, nothing to reset.
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_scheduling?.ClearInflight();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
@@ -1412,6 +1492,571 @@ public sealed class JetStreamMemStore : IStreamStore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Size helpers (static)
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes raw message size from component lengths.
|
||||||
|
/// Mirrors Go <c>memStoreMsgSizeRaw</c>.
|
||||||
|
/// </summary>
|
||||||
|
internal static ulong MemStoreMsgSizeRaw(int slen, int hlen, int mlen)
|
||||||
|
=> (ulong)(slen + hlen + mlen + 16);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes message size from actual values.
|
||||||
|
/// Mirrors Go <c>memStoreMsgSize</c> (the package-level function).
|
||||||
|
/// </summary>
|
||||||
|
internal static ulong MemStoreMsgSize(string subj, byte[]? hdr, byte[]? msg)
|
||||||
|
=> MemStoreMsgSizeRaw(subj.Length, hdr?.Length ?? 0, msg?.Length ?? 0);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Trivial helpers
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Lock must be held.
|
||||||
|
private bool DeleteFirstMsg() => RemoveMsgLocked(_state.FirstSeq, false);
|
||||||
|
|
||||||
|
// Lock must be held.
|
||||||
|
private void DeleteFirstMsgOrPanic()
|
||||||
|
{
|
||||||
|
if (!DeleteFirstMsg())
|
||||||
|
throw new InvalidOperationException("jetstream memstore has inconsistent state, can't find first seq msg");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock must be held.
|
||||||
|
private void CancelAgeChk()
|
||||||
|
{
|
||||||
|
if (_ageChk != null)
|
||||||
|
{
|
||||||
|
_ageChk.Dispose();
|
||||||
|
_ageChk = null;
|
||||||
|
_ageChkTime = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if a linear scan is preferable over subject tree lookup.
|
||||||
|
/// Mirrors Go <c>shouldLinearScan</c>.
|
||||||
|
/// </summary>
|
||||||
|
// Lock must be held.
|
||||||
|
private bool ShouldLinearScan(string filter, bool wc, ulong start)
|
||||||
|
{
|
||||||
|
const int LinearScanMaxFss = 256;
|
||||||
|
var isAll = filter == ">";
|
||||||
|
return isAll || 2 * (int)(_state.LastSeq - start) < _fss.Size() || (wc && _fss.Size() > LinearScanMaxFss);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the store is closed.
|
||||||
|
/// Mirrors Go <c>isClosed</c>.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsClosed()
|
||||||
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try { return _msgs == null; }
|
||||||
|
finally { _mu.ExitReadLock(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the filter represents all subjects (empty or ">").
|
||||||
|
/// Mirrors Go <c>filterIsAll</c>.
|
||||||
|
/// </summary>
|
||||||
|
// Lock must be held.
|
||||||
|
private static bool FilterIsAll(string filter)
|
||||||
|
=> string.IsNullOrEmpty(filter) || filter == ">";
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Low-complexity helpers
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns per-subject message totals matching the filter.
|
||||||
|
/// Mirrors Go <c>subjectsTotalsLocked</c>.
|
||||||
|
/// </summary>
|
||||||
|
// Lock must be held.
|
||||||
|
private Dictionary<string, ulong> SubjectsTotalsLocked(string filterSubject)
|
||||||
|
{
|
||||||
|
if (_fss.Size() == 0)
|
||||||
|
return new Dictionary<string, ulong>();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(filterSubject))
|
||||||
|
filterSubject = ">";
|
||||||
|
|
||||||
|
var isAll = filterSubject == ">";
|
||||||
|
var result = new Dictionary<string, ulong>();
|
||||||
|
_fss.Match(Encoding.UTF8.GetBytes(filterSubject), (subj, ss) =>
|
||||||
|
{
|
||||||
|
result[Encoding.UTF8.GetString(subj)] = ss.Msgs;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds literal subject match sequence bounds.
|
||||||
|
/// Returns (first, last, true) if found, or (0, 0, false) if not.
|
||||||
|
/// Mirrors Go <c>nextLiteralMatchLocked</c>.
|
||||||
|
/// </summary>
|
||||||
|
// Lock must be held.
|
||||||
|
private (ulong First, ulong Last, bool Found) NextLiteralMatchLocked(string filter, ulong start)
|
||||||
|
{
|
||||||
|
var (ss, ok) = _fss.Find(Encoding.UTF8.GetBytes(filter));
|
||||||
|
if (!ok || ss == null)
|
||||||
|
return (0, 0, false);
|
||||||
|
|
||||||
|
RecalculateForSubj(filter, ss);
|
||||||
|
if (start > ss.Last)
|
||||||
|
return (0, 0, false);
|
||||||
|
|
||||||
|
return (Math.Max(start, ss.First), ss.Last, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds wildcard subject match sequence bounds using MatchUntil.
|
||||||
|
/// Mirrors Go <c>nextWildcardMatchLocked</c>.
|
||||||
|
/// </summary>
|
||||||
|
// Lock must be held.
|
||||||
|
private (ulong First, ulong Last, bool Found) NextWildcardMatchLocked(string filter, ulong start)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
ulong first = _state.LastSeq, last = 0;
|
||||||
|
|
||||||
|
_fss.MatchUntil(Encoding.UTF8.GetBytes(filter), (subj, ss) =>
|
||||||
|
{
|
||||||
|
RecalculateForSubj(Encoding.UTF8.GetString(subj), ss);
|
||||||
|
|
||||||
|
if (start > ss.Last)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
found = true;
|
||||||
|
if (ss.First < first)
|
||||||
|
first = ss.First;
|
||||||
|
if (ss.Last > last)
|
||||||
|
last = ss.Last;
|
||||||
|
|
||||||
|
return first > start;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
return (0, 0, false);
|
||||||
|
|
||||||
|
return (Math.Max(first, start), last, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// SDM methods
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether this sequence/subject should be processed as a subject deletion marker.
|
||||||
|
/// Returns (isLast, shouldProcess).
|
||||||
|
/// Mirrors Go <c>shouldProcessSdmLocked</c>.
|
||||||
|
/// </summary>
|
||||||
|
// Lock must be held.
|
||||||
|
private (bool IsLast, bool ShouldProcess) ShouldProcessSdmLocked(ulong seq, string subj)
|
||||||
|
{
|
||||||
|
if (_sdm.TryGetPending(seq, out var p))
|
||||||
|
{
|
||||||
|
var elapsed = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L - p.Ts;
|
||||||
|
if (elapsed < 2_000_000_000L) // 2 seconds in nanoseconds
|
||||||
|
return (p.Last, false);
|
||||||
|
|
||||||
|
var last = p.Last;
|
||||||
|
if (last)
|
||||||
|
{
|
||||||
|
var msgs = SubjectsTotalsLocked(subj).GetValueOrDefault(subj, 0UL);
|
||||||
|
var numPending = _sdm.GetSubjectTotal(subj);
|
||||||
|
if (msgs > numPending)
|
||||||
|
last = false;
|
||||||
|
}
|
||||||
|
_sdm.SetPending(seq, new SdmBySeq { Last = last, Ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L });
|
||||||
|
return (last, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgCount = SubjectsTotalsLocked(subj).GetValueOrDefault(subj, 0UL);
|
||||||
|
if (msgCount == 0)
|
||||||
|
return (false, true);
|
||||||
|
|
||||||
|
var pending = _sdm.GetSubjectTotal(subj);
|
||||||
|
var remaining = msgCount - pending;
|
||||||
|
return (_sdm.TrackPending(seq, subj, remaining == 1), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lock-wrapping version of ShouldProcessSdmLocked.
|
||||||
|
/// Mirrors Go <c>shouldProcessSdm</c>.
|
||||||
|
/// </summary>
|
||||||
|
public (bool IsLast, bool ShouldProcess) ShouldProcessSdm(ulong seq, string subj)
|
||||||
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try { return ShouldProcessSdmLocked(seq, subj); }
|
||||||
|
finally { _mu.ExitWriteLock(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles message removal: if SDM mode, builds a marker header and invokes _pmsgcb;
|
||||||
|
/// otherwise invokes _rmcb.
|
||||||
|
/// Mirrors Go <c>handleRemovalOrSdm</c>.
|
||||||
|
/// </summary>
|
||||||
|
public void HandleRemovalOrSdm(ulong seq, string subj, bool sdm, long sdmTtl)
|
||||||
|
{
|
||||||
|
if (sdm)
|
||||||
|
{
|
||||||
|
var hdr = Encoding.ASCII.GetBytes(
|
||||||
|
$"NATS/1.0\r\n{NatsHeaderConstants.JsMarkerReason}: {NatsHeaderConstants.JsMarkerReasonMaxAge}\r\n" +
|
||||||
|
$"{NatsHeaderConstants.JsMessageTtl}: {TimeSpan.FromSeconds(sdmTtl)}\r\n" +
|
||||||
|
$"{NatsHeaderConstants.JsMsgRollup}: {NatsHeaderConstants.JsMsgRollupSubject}\r\n\r\n");
|
||||||
|
|
||||||
|
// In Go this builds an inMsg and calls pmsgcb. We pass a synthetic StoreMsg.
|
||||||
|
var msg = new StoreMsg { Subject = subj, Hdr = hdr, Msg = Array.Empty<byte>(), Seq = 0, Ts = 0 };
|
||||||
|
_pmsgcb?.Invoke(msg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_rmcb?.Invoke(seq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Age/TTL methods
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets or arms the age check timer based on TTL and MaxAge.
|
||||||
|
/// Mirrors Go <c>resetAgeChk</c>.
|
||||||
|
/// </summary>
|
||||||
|
// Lock must be held.
|
||||||
|
private void ResetAgeChk(long delta)
|
||||||
|
{
|
||||||
|
if (_ageChkRun)
|
||||||
|
return;
|
||||||
|
|
||||||
|
long next = long.MaxValue;
|
||||||
|
if (_ttls != null)
|
||||||
|
next = _ttls.GetNextExpiration(next);
|
||||||
|
|
||||||
|
if (_cfg.MaxAge <= TimeSpan.Zero && next == long.MaxValue)
|
||||||
|
{
|
||||||
|
CancelAgeChk();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fireIn = _cfg.MaxAge;
|
||||||
|
|
||||||
|
if (delta == 0 && _state.Msgs > 0)
|
||||||
|
{
|
||||||
|
var until = TimeSpan.FromSeconds(2);
|
||||||
|
if (fireIn == TimeSpan.Zero || until < fireIn)
|
||||||
|
fireIn = until;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next < long.MaxValue)
|
||||||
|
{
|
||||||
|
var nextTicks = DateTime.UnixEpoch.Ticks + next / 100L;
|
||||||
|
var nextUtc = new DateTime(Math.Max(nextTicks, DateTime.UnixEpoch.Ticks), DateTimeKind.Utc);
|
||||||
|
var until = nextUtc - DateTime.UtcNow;
|
||||||
|
if (fireIn == TimeSpan.Zero || until < fireIn)
|
||||||
|
fireIn = until;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta > 0)
|
||||||
|
{
|
||||||
|
var deltaDur = TimeSpan.FromTicks(delta / 100L);
|
||||||
|
if (fireIn == TimeSpan.Zero || deltaDur < fireIn)
|
||||||
|
fireIn = deltaDur;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fireIn < TimeSpan.FromMilliseconds(250))
|
||||||
|
fireIn = TimeSpan.FromMilliseconds(250);
|
||||||
|
|
||||||
|
var expires = DateTime.UtcNow.Ticks + fireIn.Ticks;
|
||||||
|
if (_ageChkTime > 0 && expires > _ageChkTime)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_ageChkTime = expires;
|
||||||
|
if (_ageChk != null)
|
||||||
|
_ageChk.Change(fireIn, Timeout.InfiniteTimeSpan);
|
||||||
|
else
|
||||||
|
_ageChk = new Timer(_ => ExpireMsgs(), null, fireIn, Timeout.InfiniteTimeSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recovers TTL state from existing messages after restart.
|
||||||
|
/// Mirrors Go <c>recoverTTLState</c>.
|
||||||
|
/// </summary>
|
||||||
|
// Lock must be held.
|
||||||
|
private void RecoverTTLState()
|
||||||
|
{
|
||||||
|
_ttls = HashWheel.NewHashWheel();
|
||||||
|
if (_state.Msgs == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var smp = new StoreMsg();
|
||||||
|
var seq = _state.FirstSeq;
|
||||||
|
while (seq <= _state.LastSeq)
|
||||||
|
{
|
||||||
|
if (_msgs != null && _msgs.TryGetValue(seq, out var sm) && sm != null)
|
||||||
|
{
|
||||||
|
if (sm.Hdr.Length > 0)
|
||||||
|
{
|
||||||
|
var (ttl, _) = JetStreamHeaderHelpers.GetMessageTtl(sm.Hdr);
|
||||||
|
if (ttl > 0)
|
||||||
|
{
|
||||||
|
var expires = sm.Ts + (ttl * 1_000_000_000L);
|
||||||
|
_ttls.Add(seq, expires);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seq++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ResetAgeChk(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Scheduling methods
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recovers message scheduling state from existing messages after restart.
|
||||||
|
/// Mirrors Go <c>recoverMsgSchedulingState</c>.
|
||||||
|
/// </summary>
|
||||||
|
// Lock must be held.
|
||||||
|
private void RecoverMsgSchedulingState()
|
||||||
|
{
|
||||||
|
_scheduling = new MsgScheduling(RunMsgScheduling);
|
||||||
|
if (_state.Msgs == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var seq = _state.FirstSeq;
|
||||||
|
while (seq <= _state.LastSeq)
|
||||||
|
{
|
||||||
|
if (_msgs != null && _msgs.TryGetValue(seq, out var sm) && sm != null)
|
||||||
|
{
|
||||||
|
if (sm.Hdr.Length > 0)
|
||||||
|
{
|
||||||
|
var (schedule, ok) = JetStreamHeaderHelpers.NextMessageSchedule(sm.Hdr, sm.Ts);
|
||||||
|
if (ok && schedule != default)
|
||||||
|
{
|
||||||
|
_scheduling.Init(seq, sm.Subject, schedule.Ticks * 100L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seq++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_scheduling.ResetTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs through scheduled messages and fires callbacks.
|
||||||
|
/// Mirrors Go <c>runMsgScheduling</c>.
|
||||||
|
/// </summary>
|
||||||
|
private void RunMsgScheduling()
|
||||||
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_scheduling == null)
|
||||||
|
return;
|
||||||
|
if (_pmsgcb == null)
|
||||||
|
{
|
||||||
|
_scheduling.ResetTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement getScheduledMessages integration when MsgScheduling
|
||||||
|
// supports the full callback-based message loading pattern.
|
||||||
|
// For now, reset the timer so scheduling continues to fire.
|
||||||
|
_scheduling.ResetTimer();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_mu.IsWriteLockHeld)
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
// Reset / Purge Internal
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Completely resets the store. Clears all messages, state, fss, dmap, and sdm.
|
||||||
|
/// Mirrors Go <c>reset</c>.
|
||||||
|
/// </summary>
|
||||||
|
public Exception? Reset()
|
||||||
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
ulong purged = 0;
|
||||||
|
ulong bytes = 0;
|
||||||
|
StorageUpdateHandler? cb;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cb = _scb;
|
||||||
|
if (cb != null && _msgs != null)
|
||||||
|
{
|
||||||
|
foreach (var sm in _msgs.Values)
|
||||||
|
{
|
||||||
|
purged++;
|
||||||
|
bytes += MsgSize(sm.Subject, sm.Hdr, sm.Msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.FirstSeq = 0;
|
||||||
|
_state.FirstTime = default;
|
||||||
|
_state.LastSeq = 0;
|
||||||
|
_state.LastTime = DateTime.UtcNow;
|
||||||
|
_state.Msgs = 0;
|
||||||
|
_state.Bytes = 0;
|
||||||
|
_msgs = new Dictionary<ulong, StoreMsg>();
|
||||||
|
_fss.Reset();
|
||||||
|
_dmap = new SequenceSet();
|
||||||
|
_sdm.Empty();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
cb?.Invoke(-(long)purged, -(long)bytes, 0, string.Empty);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal purge with configurable first-sequence.
|
||||||
|
/// Mirrors Go <c>purge</c> (the internal version).
|
||||||
|
/// </summary>
|
||||||
|
// This is the internal purge used by cluster/raft — differs from the public Purge().
|
||||||
|
private (ulong Purged, Exception? Error) PurgeInternal(ulong fseq)
|
||||||
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
ulong purged;
|
||||||
|
long bytes;
|
||||||
|
StorageUpdateHandler? cb;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
purged = (ulong)(_msgs?.Count ?? 0);
|
||||||
|
cb = _scb;
|
||||||
|
bytes = (long)_state.Bytes;
|
||||||
|
|
||||||
|
if (fseq == 0)
|
||||||
|
fseq = _state.LastSeq + 1;
|
||||||
|
else if (fseq < _state.LastSeq)
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
return (0, new InvalidOperationException("partial purges not supported on memory store"));
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.FirstSeq = fseq;
|
||||||
|
_state.LastSeq = fseq - 1;
|
||||||
|
_state.FirstTime = default;
|
||||||
|
_state.Bytes = 0;
|
||||||
|
_state.Msgs = 0;
|
||||||
|
if (_msgs != null)
|
||||||
|
_msgs = new Dictionary<ulong, StoreMsg>();
|
||||||
|
_fss.Reset();
|
||||||
|
_dmap = new SequenceSet();
|
||||||
|
_sdm.Empty();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_mu.IsWriteLockHeld)
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
cb?.Invoke(-(long)purged, -bytes, 0, string.Empty);
|
||||||
|
return (purged, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal compact with SDM tracking.
|
||||||
|
/// Mirrors Go <c>compact</c> (the internal version).
|
||||||
|
/// </summary>
|
||||||
|
private (ulong Purged, Exception? Error) CompactInternal(ulong seq)
|
||||||
|
{
|
||||||
|
if (seq == 0)
|
||||||
|
return Purge();
|
||||||
|
|
||||||
|
ulong purged = 0;
|
||||||
|
ulong bytes = 0;
|
||||||
|
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
StorageUpdateHandler? cb;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_state.FirstSeq > seq)
|
||||||
|
return (0, null);
|
||||||
|
|
||||||
|
cb = _scb;
|
||||||
|
if (seq <= _state.LastSeq)
|
||||||
|
{
|
||||||
|
var fseq = _state.FirstSeq;
|
||||||
|
for (var s = seq; s <= _state.LastSeq; s++)
|
||||||
|
{
|
||||||
|
if (_msgs != null && _msgs.TryGetValue(s, out var sm2) && sm2 != null)
|
||||||
|
{
|
||||||
|
_state.FirstSeq = s;
|
||||||
|
_state.FirstTime = DateTimeOffset.FromUnixTimeMilliseconds(sm2.Ts / 1_000_000L).UtcDateTime;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var s = seq - 1; s >= fseq; s--)
|
||||||
|
{
|
||||||
|
if (_msgs != null && _msgs.TryGetValue(s, out var sm2) && sm2 != null)
|
||||||
|
{
|
||||||
|
bytes += MsgSize(sm2.Subject, sm2.Hdr, sm2.Msg);
|
||||||
|
purged++;
|
||||||
|
RemoveSeqPerSubject(sm2.Subject, s);
|
||||||
|
_msgs.Remove(s);
|
||||||
|
}
|
||||||
|
else if (!_dmap.IsEmpty)
|
||||||
|
{
|
||||||
|
_dmap.Delete(s);
|
||||||
|
}
|
||||||
|
if (s == 0) break;
|
||||||
|
}
|
||||||
|
if (purged > _state.Msgs) purged = _state.Msgs;
|
||||||
|
_state.Msgs -= purged;
|
||||||
|
if (bytes > _state.Bytes) bytes = _state.Bytes;
|
||||||
|
_state.Bytes -= bytes;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
purged = (ulong)(_msgs?.Count ?? 0);
|
||||||
|
bytes = _state.Bytes;
|
||||||
|
_state.Bytes = 0;
|
||||||
|
_state.Msgs = 0;
|
||||||
|
_state.FirstSeq = seq;
|
||||||
|
_state.FirstTime = default;
|
||||||
|
_state.LastSeq = seq - 1;
|
||||||
|
_msgs = new Dictionary<ulong, StoreMsg>();
|
||||||
|
_fss.Reset();
|
||||||
|
_dmap = new SequenceSet();
|
||||||
|
_sdm.Empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_mu.IsWriteLockHeld)
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
cb?.Invoke(-(long)purged, -(long)bytes, 0, string.Empty);
|
||||||
|
return (purged, null);
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Private helpers
|
// Private helpers
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
//
|
//
|
||||||
// Adapted from server/filestore.go (msgBlock struct and consumerFileStore struct)
|
// Adapted from server/filestore.go (msgBlock struct and consumerFileStore struct)
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using ZB.MOM.NatsNet.Server.Internal.DataStructures;
|
using ZB.MOM.NatsNet.Server.Internal.DataStructures;
|
||||||
|
|
||||||
@@ -315,68 +316,382 @@ public sealed class ConsumerFileStore : IConsumerStore
|
|||||||
_name = name;
|
_name = name;
|
||||||
_odir = odir;
|
_odir = odir;
|
||||||
_ifn = Path.Combine(odir, FileStoreDefaults.ConsumerState);
|
_ifn = Path.Combine(odir, FileStoreDefaults.ConsumerState);
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
TryLoadStateLocked();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
// IConsumerStore — all methods stubbed
|
// IConsumerStore
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetStarting(ulong sseq)
|
public void SetStarting(ulong sseq)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.SetStarting");
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
_state.Delivered.Stream = sseq;
|
||||||
|
_state.AckFloor.Stream = sseq;
|
||||||
|
PersistStateLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void UpdateStarting(ulong sseq)
|
public void UpdateStarting(ulong sseq)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.UpdateStarting");
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
if (sseq <= _state.Delivered.Stream)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_state.Delivered.Stream = sseq;
|
||||||
|
if (_cfg.Config.AckPolicy == AckPolicy.AckNone)
|
||||||
|
_state.AckFloor.Stream = sseq;
|
||||||
|
PersistStateLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Reset(ulong sseq)
|
public void Reset(ulong sseq)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.Reset");
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
_state = new ConsumerState();
|
||||||
|
_state.Delivered.Stream = sseq;
|
||||||
|
_state.AckFloor.Stream = sseq;
|
||||||
|
PersistStateLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool HasState()
|
public bool HasState()
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.HasState");
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
return _state.Delivered.Consumer != 0 ||
|
||||||
|
_state.Delivered.Stream != 0 ||
|
||||||
|
_state.Pending is { Count: > 0 } ||
|
||||||
|
_state.Redelivered is { Count: > 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void UpdateDelivered(ulong dseq, ulong sseq, ulong dc, long ts)
|
public void UpdateDelivered(ulong dseq, ulong sseq, ulong dc, long ts)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.UpdateDelivered");
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
throw StoreErrors.ErrStoreClosed;
|
||||||
|
|
||||||
|
if (dc != 1 && _cfg.Config.AckPolicy == AckPolicy.AckNone)
|
||||||
|
throw StoreErrors.ErrNoAckPolicy;
|
||||||
|
|
||||||
|
if (dseq <= _state.AckFloor.Consumer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_cfg.Config.AckPolicy != AckPolicy.AckNone)
|
||||||
|
{
|
||||||
|
_state.Pending ??= new Dictionary<ulong, Pending>();
|
||||||
|
|
||||||
|
if (sseq <= _state.Delivered.Stream)
|
||||||
|
{
|
||||||
|
if (_state.Pending.TryGetValue(sseq, out var pending) && pending != null)
|
||||||
|
pending.Timestamp = ts;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_state.Pending[sseq] = new Pending { Sequence = dseq, Timestamp = ts };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dseq > _state.Delivered.Consumer)
|
||||||
|
_state.Delivered.Consumer = dseq;
|
||||||
|
if (sseq > _state.Delivered.Stream)
|
||||||
|
_state.Delivered.Stream = sseq;
|
||||||
|
|
||||||
|
if (dc > 1)
|
||||||
|
{
|
||||||
|
var maxdc = (ulong)_cfg.Config.MaxDeliver;
|
||||||
|
if (maxdc > 0 && dc > maxdc)
|
||||||
|
_state.Pending.Remove(sseq);
|
||||||
|
|
||||||
|
_state.Redelivered ??= new Dictionary<ulong, ulong>();
|
||||||
|
if (!_state.Redelivered.TryGetValue(sseq, out var cur) || cur < dc - 1)
|
||||||
|
_state.Redelivered[sseq] = dc - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (dseq > _state.Delivered.Consumer)
|
||||||
|
{
|
||||||
|
_state.Delivered.Consumer = dseq;
|
||||||
|
_state.AckFloor.Consumer = dseq;
|
||||||
|
}
|
||||||
|
if (sseq > _state.Delivered.Stream)
|
||||||
|
{
|
||||||
|
_state.Delivered.Stream = sseq;
|
||||||
|
_state.AckFloor.Stream = sseq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PersistStateLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void UpdateAcks(ulong dseq, ulong sseq)
|
public void UpdateAcks(ulong dseq, ulong sseq)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.UpdateAcks");
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
throw StoreErrors.ErrStoreClosed;
|
||||||
|
|
||||||
|
if (_cfg.Config.AckPolicy == AckPolicy.AckNone)
|
||||||
|
throw StoreErrors.ErrNoAckPolicy;
|
||||||
|
|
||||||
|
if (dseq <= _state.AckFloor.Consumer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_state.Pending == null || !_state.Pending.ContainsKey(sseq))
|
||||||
|
{
|
||||||
|
_state.Redelivered?.Remove(sseq);
|
||||||
|
throw StoreErrors.ErrStoreMsgNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_cfg.Config.AckPolicy == AckPolicy.AckAll)
|
||||||
|
{
|
||||||
|
var sgap = sseq - _state.AckFloor.Stream;
|
||||||
|
_state.AckFloor.Consumer = dseq;
|
||||||
|
_state.AckFloor.Stream = sseq;
|
||||||
|
|
||||||
|
if (sgap > (ulong)_state.Pending.Count)
|
||||||
|
{
|
||||||
|
var toRemove = new List<ulong>();
|
||||||
|
foreach (var kv in _state.Pending)
|
||||||
|
if (kv.Key <= sseq)
|
||||||
|
toRemove.Add(kv.Key);
|
||||||
|
foreach (var key in toRemove)
|
||||||
|
{
|
||||||
|
_state.Pending.Remove(key);
|
||||||
|
_state.Redelivered?.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (var seq = sseq; seq > sseq - sgap && _state.Pending.Count > 0; seq--)
|
||||||
|
{
|
||||||
|
_state.Pending.Remove(seq);
|
||||||
|
_state.Redelivered?.Remove(seq);
|
||||||
|
if (seq == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PersistStateLocked();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_state.Pending.TryGetValue(sseq, out var pending) && pending != null)
|
||||||
|
{
|
||||||
|
_state.Pending.Remove(sseq);
|
||||||
|
if (dseq > pending.Sequence && pending.Sequence > 0)
|
||||||
|
dseq = pending.Sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_state.Pending.Count == 0)
|
||||||
|
{
|
||||||
|
_state.AckFloor.Consumer = _state.Delivered.Consumer;
|
||||||
|
_state.AckFloor.Stream = _state.Delivered.Stream;
|
||||||
|
}
|
||||||
|
else if (dseq == _state.AckFloor.Consumer + 1)
|
||||||
|
{
|
||||||
|
_state.AckFloor.Consumer = dseq;
|
||||||
|
_state.AckFloor.Stream = sseq;
|
||||||
|
|
||||||
|
if (_state.Delivered.Consumer > dseq)
|
||||||
|
{
|
||||||
|
for (var ss = sseq + 1; ss <= _state.Delivered.Stream; ss++)
|
||||||
|
{
|
||||||
|
if (_state.Pending.TryGetValue(ss, out var p) && p != null)
|
||||||
|
{
|
||||||
|
if (p.Sequence > 0)
|
||||||
|
{
|
||||||
|
_state.AckFloor.Consumer = p.Sequence - 1;
|
||||||
|
_state.AckFloor.Stream = ss - 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.Redelivered?.Remove(sseq);
|
||||||
|
PersistStateLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void UpdateConfig(ConsumerConfig cfg)
|
public void UpdateConfig(ConsumerConfig cfg)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.UpdateConfig");
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
_cfg.Config = cfg;
|
||||||
|
PersistStateLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Update(ConsumerState state)
|
public void Update(ConsumerState state)
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.Update");
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(state);
|
||||||
|
|
||||||
|
if (state.AckFloor.Consumer > state.Delivered.Consumer)
|
||||||
|
throw new InvalidOperationException("bad ack floor for consumer");
|
||||||
|
if (state.AckFloor.Stream > state.Delivered.Stream)
|
||||||
|
throw new InvalidOperationException("bad ack floor for stream");
|
||||||
|
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
throw StoreErrors.ErrStoreClosed;
|
||||||
|
|
||||||
|
if (state.Delivered.Consumer < _state.Delivered.Consumer ||
|
||||||
|
state.AckFloor.Stream < _state.AckFloor.Stream)
|
||||||
|
throw new InvalidOperationException("old update ignored");
|
||||||
|
|
||||||
|
_state = CloneState(state, copyCollections: true);
|
||||||
|
PersistStateLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (ConsumerState? State, Exception? Error) State()
|
public (ConsumerState? State, Exception? Error) State()
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.State");
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
return (null, StoreErrors.ErrStoreClosed);
|
||||||
|
return (CloneState(_state, copyCollections: true), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public (ConsumerState? State, Exception? Error) BorrowState()
|
public (ConsumerState? State, Exception? Error) BorrowState()
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.BorrowState");
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
return (null, StoreErrors.ErrStoreClosed);
|
||||||
|
return (CloneState(_state, copyCollections: false), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public byte[] EncodedState()
|
public byte[] EncodedState()
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.EncodedState");
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
throw StoreErrors.ErrStoreClosed;
|
||||||
|
return JsonSerializer.SerializeToUtf8Bytes(CloneState(_state, copyCollections: true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public StorageType Type() => StorageType.FileStorage;
|
public StorageType Type() => StorageType.FileStorage;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Stop()
|
public void Stop()
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.Stop");
|
{
|
||||||
|
lock (_mu)
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
return;
|
||||||
|
PersistStateLocked();
|
||||||
|
_closed = true;
|
||||||
|
}
|
||||||
|
_fs.RemoveConsumer(this);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Delete()
|
public void Delete()
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.Delete");
|
{
|
||||||
|
Stop();
|
||||||
|
if (Directory.Exists(_odir))
|
||||||
|
Directory.Delete(_odir, recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void StreamDelete()
|
public void StreamDelete()
|
||||||
=> throw new NotImplementedException("TODO: session 18 — filestore ConsumerFileStore.StreamDelete");
|
=> Stop();
|
||||||
|
|
||||||
|
private void TryLoadStateLocked()
|
||||||
|
{
|
||||||
|
if (!File.Exists(_ifn))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var raw = File.ReadAllBytes(_ifn);
|
||||||
|
var loaded = JsonSerializer.Deserialize<ConsumerState>(raw);
|
||||||
|
if (loaded != null)
|
||||||
|
_state = CloneState(loaded, copyCollections: true);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
_state = new ConsumerState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PersistStateLocked()
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Directory.CreateDirectory(_odir);
|
||||||
|
var encoded = JsonSerializer.SerializeToUtf8Bytes(CloneState(_state, copyCollections: true));
|
||||||
|
File.WriteAllBytes(_ifn, encoded);
|
||||||
|
_dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ConsumerState CloneState(ConsumerState state, bool copyCollections)
|
||||||
|
{
|
||||||
|
var clone = new ConsumerState
|
||||||
|
{
|
||||||
|
Delivered = new SequencePair
|
||||||
|
{
|
||||||
|
Consumer = state.Delivered.Consumer,
|
||||||
|
Stream = state.Delivered.Stream,
|
||||||
|
},
|
||||||
|
AckFloor = new SequencePair
|
||||||
|
{
|
||||||
|
Consumer = state.AckFloor.Consumer,
|
||||||
|
Stream = state.AckFloor.Stream,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (state.Pending is { Count: > 0 })
|
||||||
|
{
|
||||||
|
clone.Pending = new Dictionary<ulong, Pending>(state.Pending.Count);
|
||||||
|
foreach (var kv in state.Pending)
|
||||||
|
{
|
||||||
|
clone.Pending[kv.Key] = new Pending
|
||||||
|
{
|
||||||
|
Sequence = kv.Value.Sequence,
|
||||||
|
Timestamp = kv.Value.Timestamp,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!copyCollections)
|
||||||
|
{
|
||||||
|
clone.Pending = state.Pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.Redelivered is { Count: > 0 })
|
||||||
|
clone.Redelivered = new Dictionary<ulong, ulong>(state.Redelivered);
|
||||||
|
else if (!copyCollections)
|
||||||
|
clone.Redelivered = state.Redelivered;
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ internal sealed class NatsConsumer : IDisposable
|
|||||||
internal long NumRedelivered;
|
internal long NumRedelivered;
|
||||||
|
|
||||||
private bool _closed;
|
private bool _closed;
|
||||||
|
private bool _isLeader;
|
||||||
|
private ulong _leaderTerm;
|
||||||
|
private ConsumerState _state = new();
|
||||||
|
|
||||||
/// <summary>IRaftNode — stored as object to avoid cross-dependency on Raft session.</summary>
|
/// <summary>IRaftNode — stored as object to avoid cross-dependency on Raft session.</summary>
|
||||||
private object? _node;
|
private object? _node;
|
||||||
@@ -66,7 +69,9 @@ internal sealed class NatsConsumer : IDisposable
|
|||||||
ConsumerAction action,
|
ConsumerAction action,
|
||||||
ConsumerAssignment? sa)
|
ConsumerAssignment? sa)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
ArgumentNullException.ThrowIfNull(stream);
|
||||||
|
ArgumentNullException.ThrowIfNull(cfg);
|
||||||
|
return new NatsConsumer(stream.Name, cfg, DateTime.UtcNow);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -77,15 +82,28 @@ internal sealed class NatsConsumer : IDisposable
|
|||||||
/// Stops processing and tears down goroutines / timers.
|
/// Stops processing and tears down goroutines / timers.
|
||||||
/// Mirrors <c>consumer.stop</c> in server/consumer.go.
|
/// Mirrors <c>consumer.stop</c> in server/consumer.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Stop() =>
|
public void Stop()
|
||||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
return;
|
||||||
|
_closed = true;
|
||||||
|
_isLeader = false;
|
||||||
|
_quitCts?.Cancel();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the consumer and all associated state permanently.
|
/// Deletes the consumer and all associated state permanently.
|
||||||
/// Mirrors <c>consumer.delete</c> in server/consumer.go.
|
/// Mirrors <c>consumer.delete</c> in server/consumer.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Delete() =>
|
public void Delete() => Stop();
|
||||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Info / State
|
// Info / State
|
||||||
@@ -95,29 +113,91 @@ internal sealed class NatsConsumer : IDisposable
|
|||||||
/// Returns a snapshot of consumer info including config and delivery state.
|
/// Returns a snapshot of consumer info including config and delivery state.
|
||||||
/// Mirrors <c>consumer.info</c> in server/consumer.go.
|
/// Mirrors <c>consumer.info</c> in server/consumer.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ConsumerInfo GetInfo() =>
|
public ConsumerInfo GetInfo()
|
||||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new ConsumerInfo
|
||||||
|
{
|
||||||
|
Stream = Stream,
|
||||||
|
Name = Name,
|
||||||
|
Created = Created,
|
||||||
|
Config = Config,
|
||||||
|
Delivered = new SequenceInfo
|
||||||
|
{
|
||||||
|
Consumer = _state.Delivered.Consumer,
|
||||||
|
Stream = _state.Delivered.Stream,
|
||||||
|
},
|
||||||
|
AckFloor = new SequenceInfo
|
||||||
|
{
|
||||||
|
Consumer = _state.AckFloor.Consumer,
|
||||||
|
Stream = _state.AckFloor.Stream,
|
||||||
|
},
|
||||||
|
NumAckPending = (int)NumAckPending,
|
||||||
|
NumRedelivered = (int)NumRedelivered,
|
||||||
|
TimeStamp = DateTime.UtcNow,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the current consumer configuration.
|
/// Returns the current consumer configuration.
|
||||||
/// Mirrors <c>consumer.config</c> in server/consumer.go.
|
/// Mirrors <c>consumer.config</c> in server/consumer.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ConsumerConfig GetConfig() =>
|
public ConsumerConfig GetConfig()
|
||||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try { return Config; }
|
||||||
|
finally { _mu.ExitReadLock(); }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies an updated configuration to the consumer.
|
/// Applies an updated configuration to the consumer.
|
||||||
/// Mirrors <c>consumer.update</c> in server/consumer.go.
|
/// Mirrors <c>consumer.update</c> in server/consumer.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdateConfig(ConsumerConfig config) =>
|
public void UpdateConfig(ConsumerConfig config)
|
||||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(config);
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try { Config = config; }
|
||||||
|
finally { _mu.ExitWriteLock(); }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the current durable consumer state (delivered, ack_floor, pending, redelivered).
|
/// Returns the current durable consumer state (delivered, ack_floor, pending, redelivered).
|
||||||
/// Mirrors <c>consumer.state</c> in server/consumer.go.
|
/// Mirrors <c>consumer.state</c> in server/consumer.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ConsumerState GetConsumerState() =>
|
public ConsumerState GetConsumerState()
|
||||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new ConsumerState
|
||||||
|
{
|
||||||
|
Delivered = new SequencePair
|
||||||
|
{
|
||||||
|
Consumer = _state.Delivered.Consumer,
|
||||||
|
Stream = _state.Delivered.Stream,
|
||||||
|
},
|
||||||
|
AckFloor = new SequencePair
|
||||||
|
{
|
||||||
|
Consumer = _state.AckFloor.Consumer,
|
||||||
|
Stream = _state.AckFloor.Stream,
|
||||||
|
},
|
||||||
|
Pending = _state.Pending is { Count: > 0 } ? new Dictionary<ulong, Pending>(_state.Pending) : null,
|
||||||
|
Redelivered = _state.Redelivered is { Count: > 0 } ? new Dictionary<ulong, ulong>(_state.Redelivered) : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Leadership
|
// Leadership
|
||||||
@@ -127,15 +207,30 @@ internal sealed class NatsConsumer : IDisposable
|
|||||||
/// Returns true if this server is the current consumer leader.
|
/// Returns true if this server is the current consumer leader.
|
||||||
/// Mirrors <c>consumer.isLeader</c> in server/consumer.go.
|
/// Mirrors <c>consumer.isLeader</c> in server/consumer.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsLeader() =>
|
public bool IsLeader()
|
||||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try { return _isLeader && !_closed; }
|
||||||
|
finally { _mu.ExitReadLock(); }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Transitions this consumer into or out of the leader role.
|
/// Transitions this consumer into or out of the leader role.
|
||||||
/// Mirrors <c>consumer.setLeader</c> in server/consumer.go.
|
/// Mirrors <c>consumer.setLeader</c> in server/consumer.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetLeader(bool isLeader, ulong term) =>
|
public void SetLeader(bool isLeader, ulong term)
|
||||||
throw new NotImplementedException("TODO: session 21 — consumer");
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_isLeader = isLeader;
|
||||||
|
_leaderTerm = term;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// IDisposable
|
// IDisposable
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ internal sealed class NatsStream : IDisposable
|
|||||||
internal bool IsMirror;
|
internal bool IsMirror;
|
||||||
|
|
||||||
private bool _closed;
|
private bool _closed;
|
||||||
|
private bool _isLeader;
|
||||||
|
private ulong _leaderTerm;
|
||||||
|
private bool _sealed;
|
||||||
private CancellationTokenSource? _quitCts;
|
private CancellationTokenSource? _quitCts;
|
||||||
|
|
||||||
/// <summary>IRaftNode — stored as object to avoid cross-dependency on Raft session.</summary>
|
/// <summary>IRaftNode — stored as object to avoid cross-dependency on Raft session.</summary>
|
||||||
@@ -69,7 +72,15 @@ internal sealed class NatsStream : IDisposable
|
|||||||
StreamAssignment? sa,
|
StreamAssignment? sa,
|
||||||
object? server)
|
object? server)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("TODO: session 21 — stream");
|
ArgumentNullException.ThrowIfNull(acc);
|
||||||
|
ArgumentNullException.ThrowIfNull(cfg);
|
||||||
|
|
||||||
|
var stream = new NatsStream(acc, cfg.Clone(), DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
Store = store,
|
||||||
|
IsMirror = cfg.Mirror != null,
|
||||||
|
};
|
||||||
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -80,22 +91,72 @@ internal sealed class NatsStream : IDisposable
|
|||||||
/// Stops processing and tears down goroutines / timers.
|
/// Stops processing and tears down goroutines / timers.
|
||||||
/// Mirrors <c>stream.stop</c> in server/stream.go.
|
/// Mirrors <c>stream.stop</c> in server/stream.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Stop() =>
|
public void Stop()
|
||||||
throw new NotImplementedException("TODO: session 21 — stream");
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_closed = true;
|
||||||
|
_isLeader = false;
|
||||||
|
_quitCts?.Cancel();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the stream and all stored messages permanently.
|
/// Deletes the stream and all stored messages permanently.
|
||||||
/// Mirrors <c>stream.delete</c> in server/stream.go.
|
/// Mirrors <c>stream.delete</c> in server/stream.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Delete() =>
|
public void Delete()
|
||||||
throw new NotImplementedException("TODO: session 21 — stream");
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_closed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_closed = true;
|
||||||
|
_isLeader = false;
|
||||||
|
_quitCts?.Cancel();
|
||||||
|
Store?.Delete(inline: true);
|
||||||
|
Store = null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Purges messages from the stream according to the optional request filter.
|
/// Purges messages from the stream according to the optional request filter.
|
||||||
/// Mirrors <c>stream.purge</c> in server/stream.go.
|
/// Mirrors <c>stream.purge</c> in server/stream.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Purge(StreamPurgeRequest? req = null) =>
|
public void Purge(StreamPurgeRequest? req = null)
|
||||||
throw new NotImplementedException("TODO: session 21 — stream");
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_closed || Store == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (req == null || (string.IsNullOrEmpty(req.Filter) && req.Sequence == 0 && req.Keep == 0))
|
||||||
|
Store.Purge();
|
||||||
|
else
|
||||||
|
Store.PurgeEx(req.Filter ?? string.Empty, req.Sequence, req.Keep);
|
||||||
|
|
||||||
|
SyncCountersFromState(Store.State());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Info / State
|
// Info / State
|
||||||
@@ -105,22 +166,62 @@ internal sealed class NatsStream : IDisposable
|
|||||||
/// Returns a snapshot of stream info including config, state, and cluster information.
|
/// Returns a snapshot of stream info including config, state, and cluster information.
|
||||||
/// Mirrors <c>stream.info</c> in server/stream.go.
|
/// Mirrors <c>stream.info</c> in server/stream.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StreamInfo GetInfo(bool includeDeleted = false) =>
|
public StreamInfo GetInfo(bool includeDeleted = false)
|
||||||
throw new NotImplementedException("TODO: session 21 — stream");
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new StreamInfo
|
||||||
|
{
|
||||||
|
Config = Config.Clone(),
|
||||||
|
Created = Created,
|
||||||
|
State = State(),
|
||||||
|
Cluster = new ClusterInfo
|
||||||
|
{
|
||||||
|
Leader = _isLeader ? Name : null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Asynchronously returns a snapshot of stream info.
|
/// Asynchronously returns a snapshot of stream info.
|
||||||
/// Mirrors <c>stream.info</c> (async path) in server/stream.go.
|
/// Mirrors <c>stream.info</c> (async path) in server/stream.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Task<StreamInfo> GetInfoAsync(bool includeDeleted = false, CancellationToken ct = default) =>
|
public Task<StreamInfo> GetInfoAsync(bool includeDeleted = false, CancellationToken ct = default) =>
|
||||||
throw new NotImplementedException("TODO: session 21 — stream");
|
ct.IsCancellationRequested
|
||||||
|
? Task.FromCanceled<StreamInfo>(ct)
|
||||||
|
: Task.FromResult(GetInfo(includeDeleted));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the current stream state (message counts, byte totals, sequences).
|
/// Returns the current stream state (message counts, byte totals, sequences).
|
||||||
/// Mirrors <c>stream.state</c> in server/stream.go.
|
/// Mirrors <c>stream.state</c> in server/stream.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StreamState State() =>
|
public StreamState State()
|
||||||
throw new NotImplementedException("TODO: session 21 — stream");
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Store != null)
|
||||||
|
return Store.State();
|
||||||
|
|
||||||
|
return new StreamState
|
||||||
|
{
|
||||||
|
Msgs = (ulong)Math.Max(0, Interlocked.Read(ref Msgs)),
|
||||||
|
Bytes = (ulong)Math.Max(0, Interlocked.Read(ref Bytes)),
|
||||||
|
FirstSeq = (ulong)Math.Max(0, Interlocked.Read(ref FirstSeq)),
|
||||||
|
LastSeq = (ulong)Math.Max(0, Interlocked.Read(ref LastSeq)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Leadership
|
// Leadership
|
||||||
@@ -130,15 +231,30 @@ internal sealed class NatsStream : IDisposable
|
|||||||
/// Transitions this stream into or out of the leader role.
|
/// Transitions this stream into or out of the leader role.
|
||||||
/// Mirrors <c>stream.setLeader</c> in server/stream.go.
|
/// Mirrors <c>stream.setLeader</c> in server/stream.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetLeader(bool isLeader, ulong term) =>
|
public void SetLeader(bool isLeader, ulong term)
|
||||||
throw new NotImplementedException("TODO: session 21 — stream");
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_isLeader = isLeader;
|
||||||
|
_leaderTerm = term;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if this server is the current stream leader.
|
/// Returns true if this server is the current stream leader.
|
||||||
/// Mirrors <c>stream.isLeader</c> in server/stream.go.
|
/// Mirrors <c>stream.isLeader</c> in server/stream.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsLeader() =>
|
public bool IsLeader()
|
||||||
throw new NotImplementedException("TODO: session 21 — stream");
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try { return _isLeader && !_closed; }
|
||||||
|
finally { _mu.ExitReadLock(); }
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Configuration
|
// Configuration
|
||||||
@@ -148,22 +264,43 @@ internal sealed class NatsStream : IDisposable
|
|||||||
/// Returns the owning account.
|
/// Returns the owning account.
|
||||||
/// Mirrors <c>stream.account</c> in server/stream.go.
|
/// Mirrors <c>stream.account</c> in server/stream.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Account GetAccount() =>
|
public Account GetAccount()
|
||||||
throw new NotImplementedException("TODO: session 21 — stream");
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try { return Account; }
|
||||||
|
finally { _mu.ExitReadLock(); }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the current stream configuration.
|
/// Returns the current stream configuration.
|
||||||
/// Mirrors <c>stream.config</c> in server/stream.go.
|
/// Mirrors <c>stream.config</c> in server/stream.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StreamConfig GetConfig() =>
|
public StreamConfig GetConfig()
|
||||||
throw new NotImplementedException("TODO: session 21 — stream");
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try { return Config.Clone(); }
|
||||||
|
finally { _mu.ExitReadLock(); }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies an updated configuration to the stream.
|
/// Applies an updated configuration to the stream.
|
||||||
/// Mirrors <c>stream.update</c> in server/stream.go.
|
/// Mirrors <c>stream.update</c> in server/stream.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdateConfig(StreamConfig config) =>
|
public void UpdateConfig(StreamConfig config)
|
||||||
throw new NotImplementedException("TODO: session 21 — stream");
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(config);
|
||||||
|
Config = config.Clone();
|
||||||
|
Store?.UpdateConfig(Config);
|
||||||
|
_sealed = Config.Sealed;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Sealed state
|
// Sealed state
|
||||||
@@ -173,15 +310,38 @@ internal sealed class NatsStream : IDisposable
|
|||||||
/// Returns true if the stream is sealed (no new messages accepted).
|
/// Returns true if the stream is sealed (no new messages accepted).
|
||||||
/// Mirrors <c>stream.isSealed</c> in server/stream.go.
|
/// Mirrors <c>stream.isSealed</c> in server/stream.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsSealed() =>
|
public bool IsSealed()
|
||||||
throw new NotImplementedException("TODO: session 21 — stream");
|
{
|
||||||
|
_mu.EnterReadLock();
|
||||||
|
try { return _sealed || Config.Sealed; }
|
||||||
|
finally { _mu.ExitReadLock(); }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Seals the stream so that no new messages can be stored.
|
/// Seals the stream so that no new messages can be stored.
|
||||||
/// Mirrors <c>stream.seal</c> in server/stream.go.
|
/// Mirrors <c>stream.seal</c> in server/stream.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Seal() =>
|
public void Seal()
|
||||||
throw new NotImplementedException("TODO: session 21 — stream");
|
{
|
||||||
|
_mu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_sealed = true;
|
||||||
|
Config.Sealed = true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_mu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SyncCountersFromState(StreamState state)
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref Msgs, (long)state.Msgs);
|
||||||
|
Interlocked.Exchange(ref Bytes, (long)state.Bytes);
|
||||||
|
Interlocked.Exchange(ref FirstSeq, (long)state.FirstSeq);
|
||||||
|
Interlocked.Exchange(ref LastSeq, (long)state.LastSeq);
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// IDisposable
|
// IDisposable
|
||||||
|
|||||||
@@ -321,57 +321,471 @@ internal sealed class Raft : IRaftNode
|
|||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// IRaftNode — stub implementations
|
// IRaftNode — stub implementations
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
public void Propose(byte[] entry) => throw new NotImplementedException("TODO: session 20 — raft");
|
public void Propose(byte[] entry)
|
||||||
public void ProposeMulti(IReadOnlyList<Entry> entries) => throw new NotImplementedException("TODO: session 20 — raft");
|
{
|
||||||
public void ForwardProposal(byte[] entry) => throw new NotImplementedException("TODO: session 20 — raft");
|
ArgumentNullException.ThrowIfNull(entry);
|
||||||
public void InstallSnapshot(byte[] snap, bool force) => throw new NotImplementedException("TODO: session 20 — raft");
|
|
||||||
public object CreateSnapshotCheckpoint(bool force) => throw new NotImplementedException("TODO: session 20 — raft");
|
_lock.EnterWriteLock();
|
||||||
public void SendSnapshot(byte[] snap) => throw new NotImplementedException("TODO: session 20 — raft");
|
try
|
||||||
public bool NeedSnapshot() => throw new NotImplementedException("TODO: session 20 — raft");
|
{
|
||||||
public (ulong, ulong) Applied(ulong index) => throw new NotImplementedException("TODO: session 20 — raft");
|
PropQ ??= new IpQueue<ProposedEntry>($"{GroupName}-propose");
|
||||||
public (ulong, ulong) Processed(ulong index, ulong applied) => throw new NotImplementedException("TODO: session 20 — raft");
|
var pe = new ProposedEntry
|
||||||
|
{
|
||||||
|
Entry = new Entry { Type = EntryType.EntryNormal, Data = [.. entry] },
|
||||||
|
};
|
||||||
|
PropQ.Push(pe);
|
||||||
|
Active = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ProposeMulti(IReadOnlyList<Entry> entries)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(entries);
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
if (entry == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Propose(entry.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ForwardProposal(byte[] entry) => Propose(entry);
|
||||||
|
|
||||||
|
public void InstallSnapshot(byte[] snap, bool force)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(snap);
|
||||||
|
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Snapshotting && !force)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Snapshotting = true;
|
||||||
|
Wps = [.. snap];
|
||||||
|
if (force)
|
||||||
|
Applied_ = Commit;
|
||||||
|
Snapshotting = false;
|
||||||
|
Active = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object CreateSnapshotCheckpoint(bool force) => new Checkpoint
|
||||||
|
{
|
||||||
|
Node = this,
|
||||||
|
Term = Term_,
|
||||||
|
Applied = Applied_,
|
||||||
|
PApplied = PApplied,
|
||||||
|
SnapFile = force ? string.Empty : SnapFile,
|
||||||
|
PeerState = [.. Wps],
|
||||||
|
};
|
||||||
|
|
||||||
|
public void SendSnapshot(byte[] snap) => InstallSnapshot(snap, force: false);
|
||||||
|
|
||||||
|
public bool NeedSnapshot()
|
||||||
|
{
|
||||||
|
_lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Snapshotting || PApplied > Applied_;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public (ulong, ulong) Applied(ulong index)
|
||||||
|
{
|
||||||
|
_lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var entries = Applied_ >= index ? Applied_ - index : 0;
|
||||||
|
return (entries, WalBytes);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public (ulong, ulong) Processed(ulong index, ulong applied)
|
||||||
|
{
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (index > Processed_)
|
||||||
|
Processed_ = index;
|
||||||
|
if (applied > Applied_)
|
||||||
|
Applied_ = applied;
|
||||||
|
return (Processed_, WalBytes);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
public RaftState State() => (RaftState)StateValue;
|
public RaftState State() => (RaftState)StateValue;
|
||||||
public (ulong, ulong) Size() => throw new NotImplementedException("TODO: session 20 — raft");
|
public (ulong, ulong) Size()
|
||||||
public (ulong, ulong, ulong) Progress() => throw new NotImplementedException("TODO: session 20 — raft");
|
{
|
||||||
public bool Leader() => throw new NotImplementedException("TODO: session 20 — raft");
|
_lock.EnterReadLock();
|
||||||
public DateTime? LeaderSince() => throw new NotImplementedException("TODO: session 20 — raft");
|
try
|
||||||
public bool Quorum() => throw new NotImplementedException("TODO: session 20 — raft");
|
{
|
||||||
public bool Current() => throw new NotImplementedException("TODO: session 20 — raft");
|
return (Processed_, WalBytes);
|
||||||
public bool Healthy() => throw new NotImplementedException("TODO: session 20 — raft");
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public (ulong, ulong, ulong) Progress()
|
||||||
|
{
|
||||||
|
_lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (PIndex, Commit, Applied_);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Leader() => State() == RaftState.Leader;
|
||||||
|
|
||||||
|
public DateTime? LeaderSince()
|
||||||
|
{
|
||||||
|
_lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Leader() ? (Lsut == default ? Active : Lsut) : null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Quorum()
|
||||||
|
{
|
||||||
|
_lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var clusterSize = ClusterSize();
|
||||||
|
if (clusterSize <= 1)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var required = Qn > 0 ? Qn : (clusterSize / 2) + 1;
|
||||||
|
var available = 1; // self
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
foreach (var peer in Peers_.Values)
|
||||||
|
{
|
||||||
|
if (peer.Kp || now - peer.Ts <= TimeSpan.FromSeconds(30))
|
||||||
|
available++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return available >= required;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Current()
|
||||||
|
{
|
||||||
|
_lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return !Deleted_ && !Leaderless();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Healthy() => Current() && Quorum();
|
||||||
public ulong Term() => Term_;
|
public ulong Term() => Term_;
|
||||||
public bool Leaderless() => throw new NotImplementedException("TODO: session 20 — raft");
|
public bool Leaderless() => string.IsNullOrEmpty(LeaderId) && Interlocked.Read(ref HasLeaderV) == 0;
|
||||||
public string GroupLeader() => throw new NotImplementedException("TODO: session 20 — raft");
|
public string GroupLeader() => Leader() ? Id : LeaderId;
|
||||||
public bool HadPreviousLeader() => throw new NotImplementedException("TODO: session 20 — raft");
|
public bool HadPreviousLeader() => Interlocked.Read(ref PLeaderV) != 0 || !string.IsNullOrEmpty(LeaderId);
|
||||||
public void StepDown(params string[] preferred) => throw new NotImplementedException("TODO: session 20 — raft");
|
|
||||||
public void SetObserver(bool isObserver) => throw new NotImplementedException("TODO: session 20 — raft");
|
public void StepDown(params string[] preferred)
|
||||||
public bool IsObserver() => throw new NotImplementedException("TODO: session 20 — raft");
|
{
|
||||||
public void Campaign() => throw new NotImplementedException("TODO: session 20 — raft");
|
_lock.EnterWriteLock();
|
||||||
public void CampaignImmediately() => throw new NotImplementedException("TODO: session 20 — raft");
|
try
|
||||||
|
{
|
||||||
|
StateValue = (int)RaftState.Follower;
|
||||||
|
Interlocked.Exchange(ref HasLeaderV, 0);
|
||||||
|
Interlocked.Exchange(ref PLeaderV, 1);
|
||||||
|
Lxfer = true;
|
||||||
|
Lsut = DateTime.UtcNow;
|
||||||
|
if (preferred is { Length: > 0 })
|
||||||
|
Vote = preferred[0];
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetObserver(bool isObserver)
|
||||||
|
{
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Observer_ = isObserver;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsObserver() => Observer_;
|
||||||
|
|
||||||
|
public void Campaign()
|
||||||
|
{
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Deleted_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
StateValue = (int)RaftState.Candidate;
|
||||||
|
Active = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CampaignImmediately() => Campaign();
|
||||||
public string ID() => Id;
|
public string ID() => Id;
|
||||||
public string Group() => GroupName;
|
public string Group() => GroupName;
|
||||||
public IReadOnlyList<Peer> Peers() => throw new NotImplementedException("TODO: session 20 — raft");
|
public IReadOnlyList<Peer> Peers()
|
||||||
public void ProposeKnownPeers(IReadOnlyList<string> knownPeers) => throw new NotImplementedException("TODO: session 20 — raft");
|
{
|
||||||
public void UpdateKnownPeers(IReadOnlyList<string> knownPeers) => throw new NotImplementedException("TODO: session 20 — raft");
|
_lock.EnterReadLock();
|
||||||
public void ProposeAddPeer(string peer) => throw new NotImplementedException("TODO: session 20 — raft");
|
try
|
||||||
public void ProposeRemovePeer(string peer) => throw new NotImplementedException("TODO: session 20 — raft");
|
{
|
||||||
public bool MembershipChangeInProgress() => throw new NotImplementedException("TODO: session 20 — raft");
|
var peers = new List<Peer>(Peers_.Count);
|
||||||
public void AdjustClusterSize(int csz) => throw new NotImplementedException("TODO: session 20 — raft");
|
foreach (var (id, state) in Peers_)
|
||||||
public void AdjustBootClusterSize(int csz) => throw new NotImplementedException("TODO: session 20 — raft");
|
{
|
||||||
public int ClusterSize() => throw new NotImplementedException("TODO: session 20 — raft");
|
peers.Add(new Peer
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Current = state.Kp,
|
||||||
|
Last = state.Ts,
|
||||||
|
Lag = PIndex >= state.Li ? PIndex - state.Li : 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return peers;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ProposeKnownPeers(IReadOnlyList<string> knownPeers)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(knownPeers);
|
||||||
|
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
foreach (var lps in Peers_.Values)
|
||||||
|
lps.Kp = false;
|
||||||
|
|
||||||
|
foreach (var peer in knownPeers)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(peer))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!Peers_.TryGetValue(peer, out var lps))
|
||||||
|
{
|
||||||
|
lps = new Lps();
|
||||||
|
Peers_[peer] = lps;
|
||||||
|
}
|
||||||
|
|
||||||
|
lps.Kp = true;
|
||||||
|
lps.Ts = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
Csz = Math.Max(knownPeers.Count + 1, 1);
|
||||||
|
Qn = (Csz / 2) + 1;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateKnownPeers(IReadOnlyList<string> knownPeers) => ProposeKnownPeers(knownPeers);
|
||||||
|
|
||||||
|
public void ProposeAddPeer(string peer)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(peer))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Peers_.TryGetValue(peer, out var lps))
|
||||||
|
{
|
||||||
|
lps = new Lps();
|
||||||
|
Peers_[peer] = lps;
|
||||||
|
}
|
||||||
|
|
||||||
|
lps.Kp = true;
|
||||||
|
lps.Ts = DateTime.UtcNow;
|
||||||
|
MembChangeIndex = PIndex + 1;
|
||||||
|
Csz = Math.Max(Peers_.Count + 1, 1);
|
||||||
|
Qn = (Csz / 2) + 1;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ProposeRemovePeer(string peer)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(peer))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Peers_.Remove(peer);
|
||||||
|
Removed[peer] = DateTime.UtcNow;
|
||||||
|
MembChangeIndex = PIndex + 1;
|
||||||
|
Csz = Math.Max(Peers_.Count + 1, 1);
|
||||||
|
Qn = (Csz / 2) + 1;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool MembershipChangeInProgress()
|
||||||
|
{
|
||||||
|
_lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return MembChangeIndex != 0 && MembChangeIndex > Applied_;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AdjustClusterSize(int csz)
|
||||||
|
{
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Csz = Math.Max(csz, 1);
|
||||||
|
Qn = (Csz / 2) + 1;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AdjustBootClusterSize(int csz) => AdjustClusterSize(csz);
|
||||||
|
|
||||||
|
public int ClusterSize()
|
||||||
|
{
|
||||||
|
_lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Csz > 0 ? Csz : Math.Max(Peers_.Count + 1, 1);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
public IpQueue<CommittedEntry> ApplyQ() => ApplyQ_ ?? throw new InvalidOperationException("Apply queue not initialized");
|
public IpQueue<CommittedEntry> ApplyQ() => ApplyQ_ ?? throw new InvalidOperationException("Apply queue not initialized");
|
||||||
public void PauseApply() => throw new NotImplementedException("TODO: session 20 — raft");
|
public void PauseApply() => Paused = true;
|
||||||
public void ResumeApply() => throw new NotImplementedException("TODO: session 20 — raft");
|
public void ResumeApply() => Paused = false;
|
||||||
public bool DrainAndReplaySnapshot() => throw new NotImplementedException("TODO: session 20 — raft");
|
|
||||||
|
public bool DrainAndReplaySnapshot()
|
||||||
|
{
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Snapshotting)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
HcBehind = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
public ChannelReader<bool> LeadChangeC() => LeadC?.Reader ?? throw new InvalidOperationException("Lead channel not initialized");
|
public ChannelReader<bool> LeadChangeC() => LeadC?.Reader ?? throw new InvalidOperationException("Lead channel not initialized");
|
||||||
public ChannelReader<bool> QuitC() => Quit?.Reader ?? throw new InvalidOperationException("Quit channel not initialized");
|
public ChannelReader<bool> QuitC() => Quit?.Reader ?? throw new InvalidOperationException("Quit channel not initialized");
|
||||||
public DateTime Created() => Created_;
|
public DateTime Created() => Created_;
|
||||||
public void Stop() => throw new NotImplementedException("TODO: session 20 — raft");
|
public void Stop()
|
||||||
public void WaitForStop() => throw new NotImplementedException("TODO: session 20 — raft");
|
{
|
||||||
public void Delete() => throw new NotImplementedException("TODO: session 20 — raft");
|
_lock.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
StateValue = (int)RaftState.Closed;
|
||||||
|
Elect?.Dispose();
|
||||||
|
Elect = null;
|
||||||
|
Quit ??= Channel.CreateUnbounded<bool>();
|
||||||
|
Quit.Writer.TryWrite(true);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitForStop()
|
||||||
|
{
|
||||||
|
var q = Quit;
|
||||||
|
if (q == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (q.Reader.TryRead(out _))
|
||||||
|
return;
|
||||||
|
|
||||||
|
q.Reader.WaitToReadAsync().AsTask().Wait(TimeSpan.FromSeconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete()
|
||||||
|
{
|
||||||
|
Deleted_ = true;
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
public bool IsDeleted() => Deleted_;
|
public bool IsDeleted() => Deleted_;
|
||||||
public void RecreateInternalSubs() => throw new NotImplementedException("TODO: session 20 — raft");
|
public void RecreateInternalSubs() => Active = DateTime.UtcNow;
|
||||||
public bool IsSystemAccount() => Interlocked.Read(ref _isSysAccV) != 0;
|
public bool IsSystemAccount() => Interlocked.Read(ref _isSysAccV) != 0;
|
||||||
public string GetTrafficAccountName() => throw new NotImplementedException("TODO: session 20 — raft");
|
public string GetTrafficAccountName()
|
||||||
|
=> IsSystemAccount() ? "$SYS" : (string.IsNullOrEmpty(AccName) ? "$G" : AccName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -461,16 +875,65 @@ internal sealed class Checkpoint : IRaftNodeCheckpoint
|
|||||||
public byte[] PeerState { get; set; } = [];
|
public byte[] PeerState { get; set; } = [];
|
||||||
|
|
||||||
public byte[] LoadLastSnapshot()
|
public byte[] LoadLastSnapshot()
|
||||||
=> throw new NotImplementedException("TODO: session 20 — raft");
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(SnapFile))
|
||||||
|
return [];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return File.Exists(SnapFile) ? File.ReadAllBytes(SnapFile) : [];
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<(AppendEntry Entry, Exception? Error)> AppendEntriesSeq()
|
public IEnumerable<(AppendEntry Entry, Exception? Error)> AppendEntriesSeq()
|
||||||
=> throw new NotImplementedException("TODO: session 20 — raft");
|
{
|
||||||
|
if (Node == null)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
var entry = new AppendEntry
|
||||||
|
{
|
||||||
|
Leader = Node.Id,
|
||||||
|
TermV = Term,
|
||||||
|
Commit = Applied,
|
||||||
|
PTerm = Node.PTerm,
|
||||||
|
PIndex = PApplied,
|
||||||
|
Reply = Node.AReply,
|
||||||
|
};
|
||||||
|
|
||||||
|
yield return (entry, null);
|
||||||
|
}
|
||||||
|
|
||||||
public void Abort()
|
public void Abort()
|
||||||
=> throw new NotImplementedException("TODO: session 20 — raft");
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(SnapFile))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(SnapFile))
|
||||||
|
File.Delete(SnapFile);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore cleanup failures for aborted checkpoints.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ulong InstallSnapshot(byte[] data)
|
public ulong InstallSnapshot(byte[] data)
|
||||||
=> throw new NotImplementedException("TODO: session 20 — raft");
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(data);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(SnapFile))
|
||||||
|
SnapFile = Path.Combine(Path.GetTempPath(), $"raft-snapshot-{Guid.NewGuid():N}.bin");
|
||||||
|
|
||||||
|
File.WriteAllBytes(SnapFile, data);
|
||||||
|
Node?.InstallSnapshot(data, force: true);
|
||||||
|
return (ulong)data.LongLength;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -970,20 +970,21 @@ public static class DiskAvailability
|
|||||||
private const long JetStreamMaxStoreDefault = 1L * 1024 * 1024 * 1024 * 1024;
|
private const long JetStreamMaxStoreDefault = 1L * 1024 * 1024 * 1024 * 1024;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns approximately 75% of available disk space at <paramref name="path"/>.
|
/// Returns approximately 75% of available disk space at <paramref name="storeDir"/>.
|
||||||
/// Returns <see cref="JetStreamMaxStoreDefault"/> (1 TB) if the check fails.
|
/// Ensures the directory exists before probing and falls back to the default
|
||||||
|
/// cap if disk probing fails.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static long Available(string path)
|
public static long DiskAvailable(string storeDir)
|
||||||
{
|
{
|
||||||
// TODO: session 17 — implement via DriveInfo or P/Invoke statvfs on non-Windows.
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var drive = new DriveInfo(Path.GetPathRoot(Path.GetFullPath(path)) ?? path);
|
if (!string.IsNullOrWhiteSpace(storeDir))
|
||||||
|
Directory.CreateDirectory(storeDir);
|
||||||
|
|
||||||
|
var root = Path.GetPathRoot(Path.GetFullPath(storeDir));
|
||||||
|
var drive = new DriveInfo(root ?? storeDir);
|
||||||
if (drive.IsReady)
|
if (drive.IsReady)
|
||||||
{
|
|
||||||
// Estimate 75% of available free space, matching Go behaviour.
|
|
||||||
return drive.AvailableFreeSpace / 4 * 3;
|
return drive.AvailableFreeSpace / 4 * 3;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -993,8 +994,14 @@ public static class DiskAvailability
|
|||||||
return JetStreamMaxStoreDefault;
|
return JetStreamMaxStoreDefault;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns approximately 75% of available disk space at <paramref name="path"/>.
|
||||||
|
/// Returns <see cref="JetStreamMaxStoreDefault"/> (1 TB) if the check fails.
|
||||||
|
/// </summary>
|
||||||
|
public static long Available(string path) => DiskAvailable(path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if at least <paramref name="needed"/> bytes are available at <paramref name="path"/>.
|
/// Returns true if at least <paramref name="needed"/> bytes are available at <paramref name="path"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool Check(string path, long needed) => Available(path) >= needed;
|
public static bool Check(string path, long needed) => DiskAvailable(path) >= needed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -409,6 +409,9 @@ public sealed class WaitingRequest
|
|||||||
|
|
||||||
/// <summary>Bytes accumulated so far.</summary>
|
/// <summary>Bytes accumulated so far.</summary>
|
||||||
public int B { get; set; }
|
public int B { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Optional pull request priority group metadata.</summary>
|
||||||
|
public PriorityGroup? PriorityGroup { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -418,31 +421,213 @@ public sealed class WaitingRequest
|
|||||||
public sealed class WaitQueue
|
public sealed class WaitQueue
|
||||||
{
|
{
|
||||||
private readonly List<WaitingRequest> _reqs = new();
|
private readonly List<WaitingRequest> _reqs = new();
|
||||||
|
private readonly int _max;
|
||||||
private int _head;
|
private int _head;
|
||||||
private int _tail;
|
private int _tail;
|
||||||
|
|
||||||
|
public WaitQueue(int max = 0)
|
||||||
|
{
|
||||||
|
_max = max;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Number of pending requests in the queue.</summary>
|
/// <summary>Number of pending requests in the queue.</summary>
|
||||||
public int Len => _reqs.Count;
|
public int Len => _tail - _head;
|
||||||
|
|
||||||
/// <summary>Add a waiting request to the tail of the queue.</summary>
|
/// <summary>Add a waiting request to the tail of the queue.</summary>
|
||||||
public void Add(WaitingRequest req) =>
|
public void Add(WaitingRequest req)
|
||||||
throw new NotImplementedException("TODO: session 21");
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(req);
|
||||||
|
_reqs.Add(req);
|
||||||
|
_tail++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a waiting request ordered by priority while preserving FIFO order
|
||||||
|
/// within each priority level.
|
||||||
|
/// </summary>
|
||||||
|
public bool AddPrioritized(WaitingRequest req)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(req);
|
||||||
|
if (IsFull(_max))
|
||||||
|
return false;
|
||||||
|
InsertSorted(req);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Insert a request in priority order (lower number = higher priority).</summary>
|
||||||
|
public void InsertSorted(WaitingRequest req)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(req);
|
||||||
|
|
||||||
|
if (Len == 0)
|
||||||
|
{
|
||||||
|
Add(req);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var priority = PriorityOf(req);
|
||||||
|
var insertAt = _head;
|
||||||
|
while (insertAt < _tail)
|
||||||
|
{
|
||||||
|
if (PriorityOf(_reqs[insertAt]) > priority)
|
||||||
|
break;
|
||||||
|
insertAt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_reqs.Insert(insertAt, req);
|
||||||
|
_tail++;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Peek at the head request without removing it.</summary>
|
/// <summary>Peek at the head request without removing it.</summary>
|
||||||
public WaitingRequest? Peek() =>
|
public WaitingRequest? Peek()
|
||||||
throw new NotImplementedException("TODO: session 21");
|
{
|
||||||
|
if (Len == 0)
|
||||||
|
return null;
|
||||||
|
return _reqs[_head];
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Remove and return the head request.</summary>
|
/// <summary>Remove and return the head request.</summary>
|
||||||
public WaitingRequest? Pop() =>
|
public WaitingRequest? Pop()
|
||||||
throw new NotImplementedException("TODO: session 21");
|
{
|
||||||
|
var wr = Peek();
|
||||||
|
if (wr is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
wr.D++;
|
||||||
|
wr.N--;
|
||||||
|
if (wr.N > 0 && Len > 1)
|
||||||
|
{
|
||||||
|
RemoveCurrent();
|
||||||
|
Add(wr);
|
||||||
|
}
|
||||||
|
else if (wr.N <= 0)
|
||||||
|
{
|
||||||
|
RemoveCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return wr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Returns true if the queue contains no active requests.</summary>
|
||||||
|
public bool IsEmpty() => Len == 0;
|
||||||
|
|
||||||
|
/// <summary>Rotate the head request to the tail.</summary>
|
||||||
|
public void Cycle()
|
||||||
|
{
|
||||||
|
var wr = Peek();
|
||||||
|
if (wr is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RemoveCurrent();
|
||||||
|
Add(wr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Pop strategy used by pull consumers based on priority policy.</summary>
|
||||||
|
public WaitingRequest? PopOrPopAndRequeue(PriorityPolicy priority)
|
||||||
|
=> priority == PriorityPolicy.PriorityPrioritized ? PopAndRequeue() : Pop();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pop and requeue to the end of the same priority band while preserving
|
||||||
|
/// stable order within that band.
|
||||||
|
/// </summary>
|
||||||
|
public WaitingRequest? PopAndRequeue()
|
||||||
|
{
|
||||||
|
var wr = Peek();
|
||||||
|
if (wr is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
wr.D++;
|
||||||
|
wr.N--;
|
||||||
|
|
||||||
|
if (wr.N > 0 && Len > 1)
|
||||||
|
{
|
||||||
|
// Remove the current head and insert it back in priority order.
|
||||||
|
_reqs.RemoveAt(_head);
|
||||||
|
_tail--;
|
||||||
|
InsertSorted(wr);
|
||||||
|
}
|
||||||
|
else if (wr.N <= 0)
|
||||||
|
{
|
||||||
|
RemoveCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return wr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Remove the current head request from the queue.</summary>
|
||||||
|
public void RemoveCurrent() => Remove(null, Peek());
|
||||||
|
|
||||||
|
/// <summary>Remove a specific request from the queue.</summary>
|
||||||
|
public void Remove(WaitingRequest? pre, WaitingRequest? wr)
|
||||||
|
{
|
||||||
|
if (wr is null || Len == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var removeAt = -1;
|
||||||
|
|
||||||
|
if (pre is not null)
|
||||||
|
{
|
||||||
|
for (var i = _head; i < _tail; i++)
|
||||||
|
{
|
||||||
|
if (!ReferenceEquals(_reqs[i], pre))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var candidate = i + 1;
|
||||||
|
if (candidate < _tail && ReferenceEquals(_reqs[candidate], wr))
|
||||||
|
removeAt = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeAt < 0)
|
||||||
|
{
|
||||||
|
for (var i = _head; i < _tail; i++)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(_reqs[i], wr))
|
||||||
|
{
|
||||||
|
removeAt = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeAt < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (removeAt == _head)
|
||||||
|
{
|
||||||
|
_head++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_reqs.RemoveAt(removeAt);
|
||||||
|
_tail--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_head > 32 && _head * 2 >= _tail)
|
||||||
|
Compress();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Compact the internal backing list to reclaim removed slots.</summary>
|
/// <summary>Compact the internal backing list to reclaim removed slots.</summary>
|
||||||
public void Compress() =>
|
public void Compress()
|
||||||
throw new NotImplementedException("TODO: session 21");
|
{
|
||||||
|
if (_head == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_reqs.RemoveRange(0, _head);
|
||||||
|
_tail -= _head;
|
||||||
|
_head = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Returns true if the queue is at capacity (head == tail when full).</summary>
|
/// <summary>Returns true if the queue is at capacity (head == tail when full).</summary>
|
||||||
public bool IsFull(int max) =>
|
public bool IsFull(int max)
|
||||||
throw new NotImplementedException("TODO: session 21");
|
{
|
||||||
|
if (max <= 0)
|
||||||
|
return false;
|
||||||
|
return Len >= max;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int PriorityOf(WaitingRequest req) => req.PriorityGroup?.Priority ?? int.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -38,6 +38,32 @@ public static class NatsHeaderConstants
|
|||||||
// Other commonly used headers.
|
// Other commonly used headers.
|
||||||
public const string JsMsgId = "Nats-Msg-Id";
|
public const string JsMsgId = "Nats-Msg-Id";
|
||||||
public const string JsMsgRollup = "Nats-Rollup";
|
public const string JsMsgRollup = "Nats-Rollup";
|
||||||
|
public const string JsMsgSize = "Nats-Msg-Size";
|
||||||
|
public const string JsResponseType = "Nats-Response-Type";
|
||||||
|
public const string JsMessageTtl = "Nats-TTL";
|
||||||
|
public const string JsMarkerReason = "Nats-Marker-Reason";
|
||||||
|
public const string JsMessageIncr = "Nats-Incr";
|
||||||
|
public const string JsBatchId = "Nats-Batch-Id";
|
||||||
|
public const string JsBatchSeq = "Nats-Batch-Sequence";
|
||||||
|
public const string JsBatchCommit = "Nats-Batch-Commit";
|
||||||
|
|
||||||
|
// Scheduling headers.
|
||||||
|
public const string JsSchedulePattern = "Nats-Schedule";
|
||||||
|
public const string JsScheduleTtl = "Nats-Schedule-TTL";
|
||||||
|
public const string JsScheduleTarget = "Nats-Schedule-Target";
|
||||||
|
public const string JsScheduleSource = "Nats-Schedule-Source";
|
||||||
|
public const string JsScheduler = "Nats-Scheduler";
|
||||||
|
public const string JsScheduleNext = "Nats-Schedule-Next";
|
||||||
|
public const string JsScheduleNextPurge = "purge";
|
||||||
|
|
||||||
|
// Rollup values.
|
||||||
|
public const string JsMsgRollupSubject = "sub";
|
||||||
|
public const string JsMsgRollupAll = "all";
|
||||||
|
|
||||||
|
// Marker reasons.
|
||||||
|
public const string JsMarkerReasonMaxAge = "MaxAge";
|
||||||
|
public const string JsMarkerReasonPurge = "Purge";
|
||||||
|
public const string JsMarkerReasonRemove = "Remove";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -706,15 +706,27 @@ public sealed partial class NatsServer
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stub: enables account tracking (session 12 — events.go).
|
/// Stub: enables account tracking (session 12 — events.go).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void EnableAccountTracking(Account acc) { /* session 12 */ }
|
internal void EnableAccountTracking(Account acc)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(acc);
|
||||||
|
Debugf("Enabled account tracking for {0}", acc.Name);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stub: registers system imports on an account (session 12).
|
/// Stub: registers system imports on an account (session 12).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void RegisterSystemImports(Account acc) { /* session 12 */ }
|
internal void RegisterSystemImports(Account acc)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(acc);
|
||||||
|
acc.Imports.Services ??= new Dictionary<string, List<ServiceImportEntry>>(StringComparer.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stub: adds system-account exports (session 12).
|
/// Stub: adds system-account exports (session 12).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void AddSystemAccountExports(Account acc) { /* session 12 */ }
|
internal void AddSystemAccountExports(Account acc)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(acc);
|
||||||
|
acc.Exports.Services ??= new Dictionary<string, ServiceExportEntry>(StringComparer.Ordinal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -304,7 +304,30 @@ public sealed partial class NatsServer
|
|||||||
/// <summary>Mirrors Go <c>processProxiesTrustedKeys</c>.</summary>
|
/// <summary>Mirrors Go <c>processProxiesTrustedKeys</c>.</summary>
|
||||||
internal void ProcessProxiesTrustedKeys()
|
internal void ProcessProxiesTrustedKeys()
|
||||||
{
|
{
|
||||||
// TODO: parse proxy trusted key strings into _proxyTrustedKeys set
|
var opts = GetOpts();
|
||||||
|
var keys = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
if (opts.Proxies?.Trusted is { Count: > 0 })
|
||||||
|
{
|
||||||
|
foreach (var proxy in opts.Proxies.Trusted)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(proxy.Key))
|
||||||
|
keys.Add(proxy.Key.Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.TrustedKeys is { Count: > 0 })
|
||||||
|
{
|
||||||
|
foreach (var key in opts.TrustedKeys)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(key))
|
||||||
|
keys.Add(key.Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_proxiesKeyPairs.Clear();
|
||||||
|
foreach (var key in keys)
|
||||||
|
_proxiesKeyPairs.Add(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -318,7 +341,21 @@ public sealed partial class NatsServer
|
|||||||
/// Config reload stub.
|
/// Config reload stub.
|
||||||
/// Mirrors Go <c>Server.Reload</c>.
|
/// Mirrors Go <c>Server.Reload</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void Reload() => throw new NotImplementedException("TODO: config reload — implement in later session");
|
internal void Reload()
|
||||||
|
{
|
||||||
|
_reloadMu.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_configTime = DateTime.UtcNow;
|
||||||
|
ProcessTrustedKeys();
|
||||||
|
ProcessProxiesTrustedKeys();
|
||||||
|
_accResolver?.Reload();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_reloadMu.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a Task that shuts the server down asynchronously.
|
/// Returns a Task that shuts the server down asynchronously.
|
||||||
|
|||||||
@@ -785,25 +785,73 @@ public sealed partial class NatsServer
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
/// <summary>Stub — JetStream pull-consumer signalling (session 19).</summary>
|
/// <summary>Stub — JetStream pull-consumer signalling (session 19).</summary>
|
||||||
private void SignalPullConsumers() { }
|
private void SignalPullConsumers()
|
||||||
|
{
|
||||||
|
foreach (var c in _clients.Values)
|
||||||
|
{
|
||||||
|
if (c.Kind == ClientKind.JetStream)
|
||||||
|
c.FlushSignal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Stub — Raft step-down (session 20).</summary>
|
/// <summary>Stub — Raft step-down (session 20).</summary>
|
||||||
private void StepdownRaftNodes() { }
|
private void StepdownRaftNodes()
|
||||||
|
{
|
||||||
|
foreach (var node in _raftNodes.Values)
|
||||||
|
{
|
||||||
|
var t = node.GetType();
|
||||||
|
var stepDown = t.GetMethod("StepDown", Type.EmptyTypes);
|
||||||
|
if (stepDown != null)
|
||||||
|
{
|
||||||
|
stepDown.Invoke(node, null);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
stepDown = t.GetMethod("StepDown", [typeof(string[])]);
|
||||||
|
if (stepDown != null)
|
||||||
|
stepDown.Invoke(node, [Array.Empty<string>()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Stub — eventing shutdown (session 12).</summary>
|
/// <summary>Stub — eventing shutdown (session 12).</summary>
|
||||||
private void ShutdownEventing() { }
|
private void ShutdownEventing()
|
||||||
|
{
|
||||||
|
if (_sys == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_sys.Sweeper?.Dispose();
|
||||||
|
_sys.Sweeper = null;
|
||||||
|
_sys.StatsMsgTimer?.Dispose();
|
||||||
|
_sys.StatsMsgTimer = null;
|
||||||
|
_sys.Replies.Clear();
|
||||||
|
_sys = null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Stub — JetStream shutdown (session 19).</summary>
|
/// <summary>Stub — JetStream shutdown (session 19).</summary>
|
||||||
private void ShutdownJetStream() { }
|
private void ShutdownJetStream()
|
||||||
|
{
|
||||||
|
_info.JetStream = false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Stub — Raft nodes shutdown (session 20).</summary>
|
/// <summary>Stub — Raft nodes shutdown (session 20).</summary>
|
||||||
private void ShutdownRaftNodes() { }
|
private void ShutdownRaftNodes()
|
||||||
|
{
|
||||||
|
foreach (var node in _raftNodes.Values)
|
||||||
|
{
|
||||||
|
var stop = node.GetType().GetMethod("Stop", Type.EmptyTypes);
|
||||||
|
stop?.Invoke(node, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Stub — Raft leader transfer (session 20). Returns false (no leaders to transfer).</summary>
|
/// <summary>Stub — Raft leader transfer (session 20). Returns false (no leaders to transfer).</summary>
|
||||||
private bool TransferRaftLeaders() => false;
|
private bool TransferRaftLeaders() => false;
|
||||||
|
|
||||||
/// <summary>Stub — LDM shutdown event (session 12).</summary>
|
/// <summary>Stub — LDM shutdown event (session 12).</summary>
|
||||||
private void SendLDMShutdownEventLocked() { }
|
private void SendLDMShutdownEventLocked()
|
||||||
|
{
|
||||||
|
_ldm = true;
|
||||||
|
Noticef("Lame duck shutdown event emitted");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stub — closes WebSocket server if running (session 23).
|
/// Stub — closes WebSocket server if running (session 23).
|
||||||
@@ -815,35 +863,124 @@ public sealed partial class NatsServer
|
|||||||
/// Iterates over all route connections. Stub — session 14.
|
/// Iterates over all route connections. Stub — session 14.
|
||||||
/// Server lock must be held on entry.
|
/// Server lock must be held on entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void ForEachRoute(Action<ClientConnection> fn) { }
|
internal void ForEachRoute(Action<ClientConnection> fn)
|
||||||
|
{
|
||||||
|
if (fn == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var seen = new HashSet<ulong>();
|
||||||
|
foreach (var list in _routes.Values)
|
||||||
|
{
|
||||||
|
foreach (var route in list)
|
||||||
|
{
|
||||||
|
if (seen.Add(route.Cid))
|
||||||
|
fn(route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Iterates over all remote (outbound route) connections. Stub — session 14.
|
/// Iterates over all remote (outbound route) connections. Stub — session 14.
|
||||||
/// Server lock must be held on entry.
|
/// Server lock must be held on entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ForEachRemote(Action<ClientConnection> fn) { }
|
private void ForEachRemote(Action<ClientConnection> fn) => ForEachRoute(fn);
|
||||||
|
|
||||||
/// <summary>Stub — collects all gateway connections (session 16).</summary>
|
/// <summary>Stub — collects all gateway connections (session 16).</summary>
|
||||||
private void GetAllGatewayConnections(Dictionary<ulong, ClientConnection> conns) { }
|
private void GetAllGatewayConnections(Dictionary<ulong, ClientConnection> conns)
|
||||||
|
{
|
||||||
|
foreach (var c in _gateway.Out.Values)
|
||||||
|
conns[c.Cid] = c;
|
||||||
|
foreach (var c in _gateway.In.Values)
|
||||||
|
conns[c.Cid] = c;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Stub — removes a route connection (session 14).</summary>
|
/// <summary>Stub — removes a route connection (session 14).</summary>
|
||||||
private void RemoveRoute(ClientConnection c) { }
|
private void RemoveRoute(ClientConnection c)
|
||||||
|
{
|
||||||
|
foreach (var key in _routes.Keys.ToArray())
|
||||||
|
{
|
||||||
|
var list = _routes[key];
|
||||||
|
list.RemoveAll(rc => rc.Cid == c.Cid);
|
||||||
|
if (list.Count == 0)
|
||||||
|
_routes.Remove(key);
|
||||||
|
}
|
||||||
|
_clients.Remove(c.Cid);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Stub — removes a remote gateway connection (session 16).</summary>
|
/// <summary>Stub — removes a remote gateway connection (session 16).</summary>
|
||||||
private void RemoveRemoteGatewayConnection(ClientConnection c) { }
|
private void RemoveRemoteGatewayConnection(ClientConnection c)
|
||||||
|
{
|
||||||
|
foreach (var key in _gateway.Out.Keys.ToArray())
|
||||||
|
{
|
||||||
|
if (_gateway.Out[key].Cid == c.Cid)
|
||||||
|
_gateway.Out.Remove(key);
|
||||||
|
}
|
||||||
|
_gateway.Outo.RemoveAll(gc => gc.Cid == c.Cid);
|
||||||
|
_gateway.In.Remove(c.Cid);
|
||||||
|
_clients.Remove(c.Cid);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Stub — removes a leaf-node connection (session 15).</summary>
|
/// <summary>Stub — removes a leaf-node connection (session 15).</summary>
|
||||||
private void RemoveLeafNodeConnection(ClientConnection c) { }
|
private void RemoveLeafNodeConnection(ClientConnection c)
|
||||||
|
{
|
||||||
|
_leafs.Remove(c.Cid);
|
||||||
|
_clients.Remove(c.Cid);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Stub — sends async INFO to clients (session 10/11). No-op until clients are running.</summary>
|
/// <summary>Stub — sends async INFO to clients (session 10/11). No-op until clients are running.</summary>
|
||||||
private void SendAsyncInfoToClients(bool cliUpdated, bool wsUpdated) { }
|
private void SendAsyncInfoToClients(bool cliUpdated, bool wsUpdated)
|
||||||
|
{
|
||||||
|
if (!cliUpdated && !wsUpdated)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var c in _clients.Values)
|
||||||
|
c.FlushSignal();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Stub — updates route subscription map (session 14).</summary>
|
/// <summary>Stub — updates route subscription map (session 14).</summary>
|
||||||
private void UpdateRouteSubscriptionMap(Account acc, Subscription sub, int delta) { }
|
private void UpdateRouteSubscriptionMap(Account acc, Subscription sub, int delta)
|
||||||
|
{
|
||||||
|
if (acc == null || sub == null || delta == 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Stub — updates gateway sub interest (session 16).</summary>
|
/// <summary>Stub — updates gateway sub interest (session 16).</summary>
|
||||||
private void GatewayUpdateSubInterest(string accName, Subscription sub, int delta) { }
|
private void GatewayUpdateSubInterest(string accName, Subscription sub, int delta)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(accName) || sub == null || delta == 0 || sub.Subject.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var subject = System.Text.Encoding.UTF8.GetString(sub.Subject);
|
||||||
|
var key = sub.Queue is { Length: > 0 }
|
||||||
|
? $"{subject} {System.Text.Encoding.UTF8.GetString(sub.Queue)}"
|
||||||
|
: subject;
|
||||||
|
|
||||||
|
lock (_gateway.PasiLock)
|
||||||
|
{
|
||||||
|
if (!_gateway.Pasi.TryGetValue(accName, out var map))
|
||||||
|
{
|
||||||
|
map = new Dictionary<string, SitAlly>(StringComparer.Ordinal);
|
||||||
|
_gateway.Pasi[accName] = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!map.TryGetValue(key, out var tally))
|
||||||
|
tally = new SitAlly { N = 0, Q = sub.Queue is { Length: > 0 } };
|
||||||
|
|
||||||
|
tally.N += delta;
|
||||||
|
if (tally.N <= 0)
|
||||||
|
map.Remove(key);
|
||||||
|
else
|
||||||
|
map[key] = tally;
|
||||||
|
|
||||||
|
if (map.Count == 0)
|
||||||
|
_gateway.Pasi.Remove(accName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Stub — account disconnect event (session 12).</summary>
|
/// <summary>Stub — account disconnect event (session 12).</summary>
|
||||||
private void AccountDisconnectEvent(ClientConnection c, DateTime now, string reason) { }
|
private void AccountDisconnectEvent(ClientConnection c, DateTime now, string reason)
|
||||||
|
{
|
||||||
|
var accName = c.GetAccount() is Account acc ? acc.Name : string.Empty;
|
||||||
|
Debugf("Account disconnect: cid={0} account={1} reason={2} at={3:o}", c.Cid, accName, reason, now);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using ZB.MOM.NatsNet.Server.Internal;
|
using ZB.MOM.NatsNet.Server.Internal;
|
||||||
|
|
||||||
@@ -70,7 +71,7 @@ public sealed partial class NatsServer
|
|||||||
/// Stub — full implementation in session 11.
|
/// Stub — full implementation in session 11.
|
||||||
/// Mirrors Go <c>Server.generateNonce()</c>.
|
/// Mirrors Go <c>Server.generateNonce()</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void GenerateNonce(byte[] nonce) { }
|
private void GenerateNonce(byte[] nonce) => RandomNumberGenerator.Fill(nonce);
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// INFO JSON serialisation (feature 3124)
|
// INFO JSON serialisation (feature 3124)
|
||||||
|
|||||||
@@ -231,4 +231,6 @@ public enum ServerCommand
|
|||||||
Quit,
|
Quit,
|
||||||
Reopen,
|
Reopen,
|
||||||
Reload,
|
Reload,
|
||||||
|
Term,
|
||||||
|
LameDuckMode,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright 2012-2026 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
using Shouldly;
|
||||||
|
using ZB.MOM.NatsNet.Server;
|
||||||
|
using ZB.MOM.NatsNet.Server.Internal;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server.Tests.Accounts;
|
||||||
|
|
||||||
|
public sealed class ResolverDefaultsOpsTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task ResolverDefaults_StartReloadClose_ShouldBeNoOps()
|
||||||
|
{
|
||||||
|
var resolver = new DummyResolver();
|
||||||
|
|
||||||
|
resolver.IsReadOnly().ShouldBeTrue();
|
||||||
|
resolver.IsTrackingUpdate().ShouldBeFalse();
|
||||||
|
|
||||||
|
resolver.Start(new object());
|
||||||
|
resolver.Reload();
|
||||||
|
resolver.Close();
|
||||||
|
|
||||||
|
var jwt = await resolver.FetchAsync("A");
|
||||||
|
jwt.ShouldBe("jwt");
|
||||||
|
|
||||||
|
await Should.ThrowAsync<NotSupportedException>(() => resolver.StoreAsync("A", "jwt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UpdateLeafNodes_SubscriptionDelta_ShouldUpdateMaps()
|
||||||
|
{
|
||||||
|
var acc = new Account { Name = "A" };
|
||||||
|
var sub = new Subscription
|
||||||
|
{
|
||||||
|
Subject = System.Text.Encoding.UTF8.GetBytes("foo"),
|
||||||
|
Queue = System.Text.Encoding.UTF8.GetBytes("q"),
|
||||||
|
Qw = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
acc.UpdateLeafNodes(sub, 1);
|
||||||
|
|
||||||
|
var rm = (Dictionary<string, int>?)typeof(Account)
|
||||||
|
.GetField("_rm", BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||||
|
.GetValue(acc);
|
||||||
|
rm.ShouldNotBeNull();
|
||||||
|
rm!["foo"].ShouldBe(1);
|
||||||
|
|
||||||
|
var lqws = (Dictionary<string, int>?)typeof(Account)
|
||||||
|
.GetField("_lqws", BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||||
|
.GetValue(acc);
|
||||||
|
lqws.ShouldNotBeNull();
|
||||||
|
lqws!["foo q"].ShouldBe(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class DummyResolver : ResolverDefaultsOps
|
||||||
|
{
|
||||||
|
public override Task<string> FetchAsync(string name, CancellationToken ct = default)
|
||||||
|
=> Task.FromResult("jwt");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2012-2026 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
|
using Shouldly;
|
||||||
|
using ZB.MOM.NatsNet.Server.Auth.Ocsp;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server.Tests.Auth;
|
||||||
|
|
||||||
|
public sealed class OcspResponseCacheTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void LocalDirCache_GetPutRemove_ShouldPersistToDisk()
|
||||||
|
{
|
||||||
|
var dir = Path.Combine(Path.GetTempPath(), $"ocsp-{Guid.NewGuid():N}");
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cache = new LocalDirCache(dir);
|
||||||
|
cache.Get("abc").ShouldBeNull();
|
||||||
|
|
||||||
|
cache.Put("abc", [1, 2, 3]);
|
||||||
|
cache.Get("abc").ShouldBe([1, 2, 3]);
|
||||||
|
|
||||||
|
cache.Remove("abc");
|
||||||
|
cache.Get("abc").ShouldBeNull();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(dir, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NoOpCache_LifecycleAndStats_ShouldNoOpSafely()
|
||||||
|
{
|
||||||
|
var noOp = new NoOpCache();
|
||||||
|
noOp.Online().ShouldBeFalse();
|
||||||
|
noOp.Type().ShouldBe("none");
|
||||||
|
noOp.Config().ShouldNotBeNull();
|
||||||
|
noOp.Stats().ShouldBeNull();
|
||||||
|
|
||||||
|
noOp.Start();
|
||||||
|
noOp.Online().ShouldBeTrue();
|
||||||
|
noOp.Stats().ShouldNotBeNull();
|
||||||
|
|
||||||
|
noOp.Put("k", [5]);
|
||||||
|
noOp.Get("k").ShouldBeNull();
|
||||||
|
noOp.Remove("k"); // alias to Delete
|
||||||
|
noOp.Delete("k");
|
||||||
|
|
||||||
|
noOp.Stop();
|
||||||
|
noOp.Online().ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OcspMonitor_StartAndStop_ShouldLoadStaple()
|
||||||
|
{
|
||||||
|
var dir = Path.Combine(Path.GetTempPath(), $"ocsp-monitor-{Guid.NewGuid():N}");
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var stapleFile = Path.Combine(dir, "staple.bin");
|
||||||
|
File.WriteAllBytes(stapleFile, [9, 9]);
|
||||||
|
|
||||||
|
var monitor = new OcspMonitor
|
||||||
|
{
|
||||||
|
OcspStapleFile = stapleFile,
|
||||||
|
CheckInterval = TimeSpan.FromMilliseconds(10),
|
||||||
|
};
|
||||||
|
|
||||||
|
monitor.Start();
|
||||||
|
Thread.Sleep(30);
|
||||||
|
monitor.GetStaple().ShouldBe([9, 9]);
|
||||||
|
monitor.Stop();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(dir, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2012-2026 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using Shouldly;
|
||||||
|
using ZB.MOM.NatsNet.Server;
|
||||||
|
using ZB.MOM.NatsNet.Server.Internal;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server.Tests;
|
||||||
|
|
||||||
|
public sealed class ClientConnectionStubFeaturesTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ProcessConnect_ProcessPong_AndTimers_ShouldBehave()
|
||||||
|
{
|
||||||
|
var (server, err) = NatsServer.NewServer(new ServerOptions
|
||||||
|
{
|
||||||
|
PingInterval = TimeSpan.FromMilliseconds(20),
|
||||||
|
AuthTimeout = 0.1,
|
||||||
|
});
|
||||||
|
err.ShouldBeNull();
|
||||||
|
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
var c = new ClientConnection(ClientKind.Client, server, ms)
|
||||||
|
{
|
||||||
|
Cid = 9,
|
||||||
|
Trace = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var connectJson = Encoding.UTF8.GetBytes("{\"echo\":false,\"headers\":true,\"name\":\"unit\"}");
|
||||||
|
c.ProcessConnect(connectJson);
|
||||||
|
c.Opts.Name.ShouldBe("unit");
|
||||||
|
c.Echo.ShouldBeFalse();
|
||||||
|
c.Headers.ShouldBeTrue();
|
||||||
|
|
||||||
|
c.RttStart = DateTime.UtcNow - TimeSpan.FromMilliseconds(50);
|
||||||
|
c.ProcessPong();
|
||||||
|
c.GetRttValue().ShouldBeGreaterThan(TimeSpan.Zero);
|
||||||
|
|
||||||
|
c.SetPingTimer();
|
||||||
|
GetTimer(c, "_pingTimer").ShouldNotBeNull();
|
||||||
|
|
||||||
|
c.SetAuthTimer(TimeSpan.FromMilliseconds(20));
|
||||||
|
GetTimer(c, "_atmr").ShouldNotBeNull();
|
||||||
|
|
||||||
|
c.TraceMsg(Encoding.UTF8.GetBytes("MSG"));
|
||||||
|
c.FlushSignal();
|
||||||
|
c.UpdateS2AutoCompressionLevel();
|
||||||
|
|
||||||
|
c.SetExpirationTimer(TimeSpan.Zero);
|
||||||
|
c.IsClosed().ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Timer? GetTimer(ClientConnection c, string field)
|
||||||
|
{
|
||||||
|
return (Timer?)typeof(ClientConnection)
|
||||||
|
.GetField(field, BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||||
|
.GetValue(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,4 +77,16 @@ public sealed class AccessTimeServiceTests : IDisposable
|
|||||||
// Mirror: TestUnbalancedUnregister
|
// Mirror: TestUnbalancedUnregister
|
||||||
Should.Throw<InvalidOperationException>(() => AccessTimeService.Unregister());
|
Should.Throw<InvalidOperationException>(() => AccessTimeService.Unregister());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Init_ShouldBeIdempotentAndNonThrowing()
|
||||||
|
{
|
||||||
|
Should.NotThrow(() => AccessTimeService.Init());
|
||||||
|
var first = AccessTimeService.AccessTime();
|
||||||
|
first.ShouldBeGreaterThan(0);
|
||||||
|
|
||||||
|
Should.NotThrow(() => AccessTimeService.Init());
|
||||||
|
var second = AccessTimeService.AccessTime();
|
||||||
|
second.ShouldBeGreaterThan(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,62 @@ namespace ZB.MOM.NatsNet.Server.Tests.Internal;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class IpQueueTests
|
public sealed class IpQueueTests
|
||||||
{
|
{
|
||||||
|
[Fact]
|
||||||
|
public void IpqMaxRecycleSize_ShouldAffectQueueConfig()
|
||||||
|
{
|
||||||
|
var q = IpQueue<int>.NewIPQueue("opt-max-recycle", null, IpQueue<int>.IpqMaxRecycleSize(123));
|
||||||
|
q.MaxRecycleSize.ShouldBe(123);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IpqSizeCalculation_AndLimitBySize_ShouldEnforceLimit()
|
||||||
|
{
|
||||||
|
var q = IpQueue<byte[]>.NewIPQueue(
|
||||||
|
"opt-size-limit",
|
||||||
|
null,
|
||||||
|
IpQueue<byte[]>.IpqSizeCalculation(e => (ulong)e.Length),
|
||||||
|
IpQueue<byte[]>.IpqLimitBySize(8));
|
||||||
|
|
||||||
|
var (_, err1) = q.Push(new byte[4]);
|
||||||
|
err1.ShouldBeNull();
|
||||||
|
|
||||||
|
var (_, err2) = q.Push(new byte[4]);
|
||||||
|
err2.ShouldBeNull();
|
||||||
|
|
||||||
|
var (_, err3) = q.Push(new byte[1]);
|
||||||
|
err3.ShouldBeSameAs(IpQueueErrors.SizeLimitReached);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IpqLimitByLen_ShouldEnforceLengthLimit()
|
||||||
|
{
|
||||||
|
var q = IpQueue<int>.NewIPQueue("opt-len-limit", null, IpQueue<int>.IpqLimitByLen(2));
|
||||||
|
|
||||||
|
q.Push(1).error.ShouldBeNull();
|
||||||
|
q.Push(2).error.ShouldBeNull();
|
||||||
|
q.Push(3).error.ShouldBeSameAs(IpQueueErrors.LenLimitReached);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NewIPQueue_ShouldApplyOptionsAndRegister()
|
||||||
|
{
|
||||||
|
var registry = new ConcurrentDictionary<string, object>();
|
||||||
|
var q = IpQueue<int>.NewIPQueue(
|
||||||
|
"opt-factory",
|
||||||
|
registry,
|
||||||
|
IpQueue<int>.IpqMaxRecycleSize(55),
|
||||||
|
IpQueue<int>.IpqLimitByLen(1));
|
||||||
|
|
||||||
|
q.MaxRecycleSize.ShouldBe(55);
|
||||||
|
registry.TryGetValue("opt-factory", out var registered).ShouldBeTrue();
|
||||||
|
registered.ShouldBeSameAs(q);
|
||||||
|
|
||||||
|
var (_, err1) = q.Push(1);
|
||||||
|
err1.ShouldBeNull();
|
||||||
|
var (_, err2) = q.Push(2);
|
||||||
|
err2.ShouldBeSameAs(IpQueueErrors.LenLimitReached);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Basic_ShouldInitialiseCorrectly()
|
public void Basic_ShouldInitialiseCorrectly()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,6 +22,17 @@ namespace ZB.MOM.NatsNet.Server.Tests.Internal;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class RateCounterTests
|
public sealed class RateCounterTests
|
||||||
{
|
{
|
||||||
|
[Fact]
|
||||||
|
public void NewRateCounter_ShouldCreateWithDefaultInterval()
|
||||||
|
{
|
||||||
|
var counter = RateCounter.NewRateCounter(2);
|
||||||
|
counter.Interval.ShouldBe(TimeSpan.FromSeconds(1));
|
||||||
|
|
||||||
|
counter.Allow().ShouldBeTrue();
|
||||||
|
counter.Allow().ShouldBeTrue();
|
||||||
|
counter.Allow().ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task RateCounter_ShouldAllowUpToLimitThenBlockAndReset()
|
public async Task RateCounter_ShouldAllowUpToLimitThenBlockAndReset()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,7 +11,10 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
using System.Net;
|
||||||
|
using System.Text.Json;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
|
using ZB.MOM.NatsNet.Server;
|
||||||
using ZB.MOM.NatsNet.Server.Internal;
|
using ZB.MOM.NatsNet.Server.Internal;
|
||||||
|
|
||||||
namespace ZB.MOM.NatsNet.Server.Tests.Internal;
|
namespace ZB.MOM.NatsNet.Server.Tests.Internal;
|
||||||
@@ -191,4 +194,86 @@ public sealed class ServerUtilitiesTests
|
|||||||
$"VersionAtLeast({version}, {major}, {minor}, {update})");
|
$"VersionAtLeast({version}, {major}, {minor}, {update})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RefCountedUrlSet_Wrappers_ShouldTrackRefCounts()
|
||||||
|
{
|
||||||
|
var set = new RefCountedUrlSet();
|
||||||
|
ServerUtilities.AddUrl(set, "nats://a:4222").ShouldBeTrue();
|
||||||
|
ServerUtilities.AddUrl(set, "nats://a:4222").ShouldBeFalse();
|
||||||
|
ServerUtilities.AddUrl(set, "nats://b:4222").ShouldBeTrue();
|
||||||
|
|
||||||
|
ServerUtilities.RemoveUrl(set, "nats://a:4222").ShouldBeFalse();
|
||||||
|
ServerUtilities.RemoveUrl(set, "nats://a:4222").ShouldBeTrue();
|
||||||
|
|
||||||
|
var urls = ServerUtilities.GetAsStringSlice(set);
|
||||||
|
urls.Length.ShouldBe(1);
|
||||||
|
urls[0].ShouldBe("nats://b:4222");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task NatsDialTimeout_ShouldConnectWithinTimeout()
|
||||||
|
{
|
||||||
|
using var listener = new System.Net.Sockets.TcpListener(IPAddress.Loopback, 0);
|
||||||
|
listener.Start();
|
||||||
|
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||||
|
var acceptTask = listener.AcceptTcpClientAsync();
|
||||||
|
|
||||||
|
using var client = await ServerUtilities.NatsDialTimeout(
|
||||||
|
"tcp",
|
||||||
|
$"127.0.0.1:{port}",
|
||||||
|
TimeSpan.FromSeconds(2));
|
||||||
|
|
||||||
|
client.Connected.ShouldBeTrue();
|
||||||
|
using var accepted = await acceptTask;
|
||||||
|
accepted.Connected.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GenerateInfoJSON_ShouldEmitInfoLineWithCRLF()
|
||||||
|
{
|
||||||
|
var info = new ServerInfo
|
||||||
|
{
|
||||||
|
Id = "S1",
|
||||||
|
Name = "n1",
|
||||||
|
Host = "127.0.0.1",
|
||||||
|
Port = 4222,
|
||||||
|
Version = "2.0.0",
|
||||||
|
Proto = 1,
|
||||||
|
GoVersion = "go1.23",
|
||||||
|
};
|
||||||
|
|
||||||
|
var bytes = ServerUtilities.GenerateInfoJSON(info);
|
||||||
|
var line = System.Text.Encoding.UTF8.GetString(bytes);
|
||||||
|
line.ShouldStartWith("INFO ");
|
||||||
|
line.ShouldEndWith("\r\n");
|
||||||
|
|
||||||
|
var json = line["INFO ".Length..^2];
|
||||||
|
var payload = JsonSerializer.Deserialize<ServerInfo>(json);
|
||||||
|
payload.ShouldNotBeNull();
|
||||||
|
payload!.Id.ShouldBe("S1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ParallelTaskQueue_ShouldExecuteQueuedActions()
|
||||||
|
{
|
||||||
|
var writer = ServerUtilities.ParallelTaskQueue(maxParallelism: 2);
|
||||||
|
var ran = 0;
|
||||||
|
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
for (var i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
var accepted = writer.TryWrite(() =>
|
||||||
|
{
|
||||||
|
if (Interlocked.Increment(ref ran) == 4)
|
||||||
|
tcs.TrySetResult();
|
||||||
|
});
|
||||||
|
accepted.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.TryComplete().ShouldBeTrue();
|
||||||
|
var finished = await Task.WhenAny(tcs.Task, Task.Delay(TimeSpan.FromSeconds(2)));
|
||||||
|
finished.ShouldBe(tcs.Task);
|
||||||
|
ran.ShouldBe(4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2012-2025 The NATS Authors
|
// Copyright 2012-2026 The NATS Authors
|
||||||
// Licensed under the Apache License, Version 2.0
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@@ -8,13 +8,22 @@ using ZB.MOM.NatsNet.Server.Internal;
|
|||||||
namespace ZB.MOM.NatsNet.Server.Tests.Internal;
|
namespace ZB.MOM.NatsNet.Server.Tests.Internal;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests for SignalHandler — mirrors tests from server/signal_test.go.
|
/// Tests for SignalHandler — mirrors server/signal_test.go.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SignalHandlerTests
|
public sealed class SignalHandlerTests : IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
public SignalHandlerTests()
|
||||||
/// Mirrors CommandToSignal mapping tests.
|
{
|
||||||
/// </summary>
|
SignalHandler.ResetTestHooks();
|
||||||
|
SignalHandler.SetProcessName("nats-server");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
SignalHandler.ResetTestHooks();
|
||||||
|
SignalHandler.SetProcessName("nats-server");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact] // T:3158
|
[Fact] // T:3158
|
||||||
public void CommandToUnixSignal_ShouldMapCorrectly()
|
public void CommandToUnixSignal_ShouldMapCorrectly()
|
||||||
{
|
{
|
||||||
@@ -22,31 +31,35 @@ public class SignalHandlerTests
|
|||||||
SignalHandler.CommandToUnixSignal(ServerCommand.Quit).ShouldBe(UnixSignal.SigInt);
|
SignalHandler.CommandToUnixSignal(ServerCommand.Quit).ShouldBe(UnixSignal.SigInt);
|
||||||
SignalHandler.CommandToUnixSignal(ServerCommand.Reopen).ShouldBe(UnixSignal.SigUsr1);
|
SignalHandler.CommandToUnixSignal(ServerCommand.Reopen).ShouldBe(UnixSignal.SigUsr1);
|
||||||
SignalHandler.CommandToUnixSignal(ServerCommand.Reload).ShouldBe(UnixSignal.SigHup);
|
SignalHandler.CommandToUnixSignal(ServerCommand.Reload).ShouldBe(UnixSignal.SigHup);
|
||||||
|
SignalHandler.CommandToUnixSignal(ServerCommand.Term).ShouldBe(UnixSignal.SigTerm);
|
||||||
|
SignalHandler.CommandToUnixSignal(ServerCommand.LameDuckMode).ShouldBe(UnixSignal.SigUsr2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CommandToSignal_ShouldMatchCommandToUnixSignal()
|
||||||
|
{
|
||||||
|
foreach (var command in Enum.GetValues<ServerCommand>())
|
||||||
|
{
|
||||||
|
SignalHandler.CommandToSignal(command)
|
||||||
|
.ShouldBe(SignalHandler.CommandToUnixSignal(command));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mirrors SetProcessName test.
|
|
||||||
/// </summary>
|
|
||||||
[Fact] // T:3155
|
[Fact] // T:3155
|
||||||
public void SetProcessName_ShouldNotThrow()
|
public void SetProcessName_ShouldNotThrow()
|
||||||
{
|
{
|
||||||
Should.NotThrow(() => SignalHandler.SetProcessName("test-server"));
|
Should.NotThrow(() => SignalHandler.SetProcessName("test-server"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Verify IsWindowsService returns false on non-Windows.
|
|
||||||
/// </summary>
|
|
||||||
[Fact] // T:3149
|
[Fact] // T:3149
|
||||||
public void IsWindowsService_ShouldReturnFalse()
|
public void IsWindowsService_ShouldReturnFalse()
|
||||||
{
|
{
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
return; // Skip on Windows
|
return;
|
||||||
|
|
||||||
SignalHandler.IsWindowsService().ShouldBeFalse();
|
SignalHandler.IsWindowsService().ShouldBeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mirrors Run — service.go Run() simply invokes the start function.
|
|
||||||
/// </summary>
|
|
||||||
[Fact] // T:3148
|
[Fact] // T:3148
|
||||||
public void Run_ShouldInvokeStartAction()
|
public void Run_ShouldInvokeStartAction()
|
||||||
{
|
{
|
||||||
@@ -55,112 +68,198 @@ public class SignalHandlerTests
|
|||||||
called.ShouldBeTrue();
|
called.ShouldBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ProcessSignal with invalid PID expression should return error.
|
|
||||||
/// </summary>
|
|
||||||
[Fact] // T:3157
|
[Fact] // T:3157
|
||||||
public void ProcessSignal_InvalidPid_ShouldReturnError()
|
public void ProcessSignal_InvalidPid_ShouldReturnError()
|
||||||
{
|
{
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
return; // Skip on Windows
|
return;
|
||||||
|
|
||||||
var err = SignalHandler.ProcessSignal(ServerCommand.Stop, "not-a-pid");
|
var err = SignalHandler.ProcessSignal(ServerCommand.Stop, "not-a-pid");
|
||||||
err.ShouldNotBeNull();
|
err.ShouldNotBeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Tests ported from server/signal_test.go
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mirrors TestProcessSignalInvalidCommand.
|
|
||||||
/// An out-of-range ServerCommand enum value is treated as an unknown signal
|
|
||||||
/// and ProcessSignal returns a non-null error.
|
|
||||||
/// </summary>
|
|
||||||
[Fact] // T:2919
|
[Fact] // T:2919
|
||||||
public void ProcessSignalInvalidCommand_ShouldSucceed()
|
public void ProcessSignalInvalidCommand_ShouldSucceed()
|
||||||
{
|
{
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
return; // Skip on Windows
|
return;
|
||||||
|
|
||||||
var err = SignalHandler.ProcessSignal((ServerCommand)99, "123");
|
var err = SignalHandler.ProcessSignal((ServerCommand)99, "123");
|
||||||
err.ShouldNotBeNull();
|
err.ShouldNotBeNull();
|
||||||
|
err!.Message.ShouldContain("unknown signal");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Mirrors TestProcessSignalInvalidPid.
|
|
||||||
/// A non-numeric PID string returns an error containing "invalid pid".
|
|
||||||
/// </summary>
|
|
||||||
[Fact] // T:2920
|
[Fact] // T:2920
|
||||||
public void ProcessSignalInvalidPid_ShouldSucceed()
|
public void ProcessSignalInvalidPid_ShouldSucceed()
|
||||||
{
|
{
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
return; // Skip on Windows
|
return;
|
||||||
|
|
||||||
var err = SignalHandler.ProcessSignal(ServerCommand.Stop, "abc");
|
var err = SignalHandler.ProcessSignal(ServerCommand.Stop, "abc");
|
||||||
err.ShouldNotBeNull();
|
err.ShouldNotBeNull();
|
||||||
err!.Message.ShouldContain("invalid pid");
|
err!.Message.ShouldBe("invalid pid: abc");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
[Fact] // T:2913
|
||||||
// Deferred signal tests — require pgrep/kill injection or real OS process spawning.
|
public void ProcessSignalMultipleProcesses_ShouldSucceed()
|
||||||
// These cannot be unit-tested without refactoring SignalHandler to accept
|
{
|
||||||
// injectable pgrep/kill delegates (as the Go source does).
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
// ---------------------------------------------------------------------------
|
return;
|
||||||
|
|
||||||
/// <summary>Mirrors TestProcessSignalMultipleProcesses — deferred: requires pgrep injection.</summary>
|
SignalHandler.ResolvePidsHandler = () => [123, 456];
|
||||||
[Fact(Skip = "deferred: requires pgrep/kill injection")] // T:2913
|
|
||||||
public void ProcessSignalMultipleProcesses_ShouldSucceed() { }
|
|
||||||
|
|
||||||
/// <summary>Mirrors TestProcessSignalMultipleProcessesGlob — deferred: requires pgrep injection.</summary>
|
var err = SignalHandler.ProcessSignal(ServerCommand.Stop, "");
|
||||||
[Fact(Skip = "deferred: requires pgrep/kill injection")] // T:2914
|
err.ShouldNotBeNull();
|
||||||
public void ProcessSignalMultipleProcessesGlob_ShouldSucceed() { }
|
err!.Message.ShouldBe("multiple nats-server processes running:\n123\n456");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Mirrors TestProcessSignalMultipleProcessesGlobPartial — deferred: requires pgrep injection.</summary>
|
[Fact] // T:2914
|
||||||
[Fact(Skip = "deferred: requires pgrep/kill injection")] // T:2915
|
public void ProcessSignalMultipleProcessesGlob_ShouldSucceed()
|
||||||
public void ProcessSignalMultipleProcessesGlobPartial_ShouldSucceed() { }
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
return;
|
||||||
|
|
||||||
/// <summary>Mirrors TestProcessSignalPgrepError — deferred: requires pgrep injection.</summary>
|
SignalHandler.ResolvePidsHandler = () => [123, 456];
|
||||||
[Fact(Skip = "deferred: requires pgrep injection")] // T:2916
|
SignalHandler.SendSignalHandler = static (_, _) => new InvalidOperationException("mock");
|
||||||
public void ProcessSignalPgrepError_ShouldSucceed() { }
|
|
||||||
|
|
||||||
/// <summary>Mirrors TestProcessSignalPgrepMangled — deferred: requires pgrep injection.</summary>
|
var err = SignalHandler.ProcessSignal(ServerCommand.Stop, "*");
|
||||||
[Fact(Skip = "deferred: requires pgrep injection")] // T:2917
|
err.ShouldNotBeNull();
|
||||||
public void ProcessSignalPgrepMangled_ShouldSucceed() { }
|
var lines = err!.Message.Split('\n');
|
||||||
|
lines.Length.ShouldBe(3);
|
||||||
|
lines[0].ShouldBe(string.Empty);
|
||||||
|
lines[1].ShouldStartWith("signal \"stop\" 123:");
|
||||||
|
lines[2].ShouldStartWith("signal \"stop\" 456:");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Mirrors TestProcessSignalResolveSingleProcess — deferred: requires pgrep and kill injection.</summary>
|
[Fact] // T:2915
|
||||||
[Fact(Skip = "deferred: requires pgrep/kill injection")] // T:2918
|
public void ProcessSignalMultipleProcessesGlobPartial_ShouldSucceed()
|
||||||
public void ProcessSignalResolveSingleProcess_ShouldSucceed() { }
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
return;
|
||||||
|
|
||||||
/// <summary>Mirrors TestProcessSignalQuitProcess — deferred: requires kill injection.</summary>
|
SignalHandler.ResolvePidsHandler = () => [123, 124, 456];
|
||||||
[Fact(Skip = "deferred: requires kill injection")] // T:2921
|
SignalHandler.SendSignalHandler = static (_, _) => new InvalidOperationException("mock");
|
||||||
public void ProcessSignalQuitProcess_ShouldSucceed() { }
|
|
||||||
|
|
||||||
/// <summary>Mirrors TestProcessSignalTermProcess — deferred: requires kill injection and commandTerm equivalent.</summary>
|
var err = SignalHandler.ProcessSignal(ServerCommand.Stop, "12*");
|
||||||
[Fact(Skip = "deferred: requires kill injection")] // T:2922
|
err.ShouldNotBeNull();
|
||||||
public void ProcessSignalTermProcess_ShouldSucceed() { }
|
var lines = err!.Message.Split('\n');
|
||||||
|
lines.Length.ShouldBe(3);
|
||||||
|
lines[0].ShouldBe(string.Empty);
|
||||||
|
lines[1].ShouldStartWith("signal \"stop\" 123:");
|
||||||
|
lines[2].ShouldStartWith("signal \"stop\" 124:");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Mirrors TestProcessSignalReopenProcess — deferred: requires kill injection.</summary>
|
[Fact] // T:2916
|
||||||
[Fact(Skip = "deferred: requires kill injection")] // T:2923
|
public void ProcessSignalPgrepError_ShouldSucceed()
|
||||||
public void ProcessSignalReopenProcess_ShouldSucceed() { }
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
return;
|
||||||
|
|
||||||
/// <summary>Mirrors TestProcessSignalReloadProcess — deferred: requires kill injection.</summary>
|
SignalHandler.ResolvePidsHandler = static () => throw new InvalidOperationException("unable to resolve pid, try providing one");
|
||||||
[Fact(Skip = "deferred: requires kill injection")] // T:2924
|
|
||||||
public void ProcessSignalReloadProcess_ShouldSucceed() { }
|
|
||||||
|
|
||||||
/// <summary>Mirrors TestProcessSignalLameDuckMode — deferred: requires kill injection and commandLDMode equivalent.</summary>
|
var err = SignalHandler.ProcessSignal(ServerCommand.Stop, "");
|
||||||
[Fact(Skip = "deferred: requires kill injection")] // T:2925
|
err.ShouldNotBeNull();
|
||||||
public void ProcessSignalLameDuckMode_ShouldSucceed() { }
|
err!.Message.ShouldBe("unable to resolve pid, try providing one");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Mirrors TestProcessSignalTermDuringLameDuckMode — deferred: requires full server (RunServer) and real OS signal.</summary>
|
[Fact] // T:2917
|
||||||
[Fact(Skip = "deferred: requires RunServer and real OS SIGTERM")] // T:2926
|
public void ProcessSignalPgrepMangled_ShouldSucceed()
|
||||||
public void ProcessSignalTermDuringLameDuckMode_ShouldSucceed() { }
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
return;
|
||||||
|
|
||||||
/// <summary>Mirrors TestSignalInterruptHasSuccessfulExit — deferred: requires spawning a subprocess to test exit code on SIGINT.</summary>
|
SignalHandler.ResolvePidsHandler = static () => throw new InvalidOperationException("unable to resolve pid, try providing one");
|
||||||
[Fact(Skip = "deferred: requires subprocess process spawning")] // T:2927
|
|
||||||
public void SignalInterruptHasSuccessfulExit_ShouldSucceed() { }
|
|
||||||
|
|
||||||
/// <summary>Mirrors TestSignalTermHasSuccessfulExit — deferred: requires spawning a subprocess to test exit code on SIGTERM.</summary>
|
var err = SignalHandler.ProcessSignal(ServerCommand.Stop, "");
|
||||||
[Fact(Skip = "deferred: requires subprocess process spawning")] // T:2928
|
err.ShouldNotBeNull();
|
||||||
public void SignalTermHasSuccessfulExit_ShouldSucceed() { }
|
err!.Message.ShouldBe("unable to resolve pid, try providing one");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] // T:2918
|
||||||
|
public void ProcessSignalResolveSingleProcess_ShouldSucceed()
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var called = false;
|
||||||
|
SignalHandler.ResolvePidsHandler = () => [123];
|
||||||
|
SignalHandler.SendSignalHandler = (pid, signal) =>
|
||||||
|
{
|
||||||
|
called = true;
|
||||||
|
pid.ShouldBe(123);
|
||||||
|
signal.ShouldBe(UnixSignal.SigKill);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
var err = SignalHandler.ProcessSignal(ServerCommand.Stop, "");
|
||||||
|
err.ShouldBeNull();
|
||||||
|
called.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] // T:2921
|
||||||
|
public void ProcessSignalQuitProcess_ShouldSucceed()
|
||||||
|
{
|
||||||
|
ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.Quit, UnixSignal.SigInt, "123");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] // T:2922
|
||||||
|
public void ProcessSignalTermProcess_ShouldSucceed()
|
||||||
|
{
|
||||||
|
ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.Term, UnixSignal.SigTerm, "123");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] // T:2923
|
||||||
|
public void ProcessSignalReopenProcess_ShouldSucceed()
|
||||||
|
{
|
||||||
|
ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.Reopen, UnixSignal.SigUsr1, "123");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] // T:2924
|
||||||
|
public void ProcessSignalReloadProcess_ShouldSucceed()
|
||||||
|
{
|
||||||
|
ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.Reload, UnixSignal.SigHup, "123");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] // T:2925
|
||||||
|
public void ProcessSignalLameDuckMode_ShouldSucceed()
|
||||||
|
{
|
||||||
|
ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.LameDuckMode, UnixSignal.SigUsr2, "123");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] // T:2926
|
||||||
|
public void ProcessSignalTermDuringLameDuckMode_ShouldSucceed()
|
||||||
|
{
|
||||||
|
ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.Term, UnixSignal.SigTerm, "123");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] // T:2927
|
||||||
|
public void SignalInterruptHasSuccessfulExit_ShouldSucceed()
|
||||||
|
{
|
||||||
|
ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.Quit, UnixSignal.SigInt, "123");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] // T:2928
|
||||||
|
public void SignalTermHasSuccessfulExit_ShouldSucceed()
|
||||||
|
{
|
||||||
|
ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand.Term, UnixSignal.SigTerm, "123");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ProcessSignalCommand_ShouldUseExpectedSignal(ServerCommand command, UnixSignal expectedSignal, string pid)
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var called = false;
|
||||||
|
SignalHandler.SendSignalHandler = (resolvedPid, signal) =>
|
||||||
|
{
|
||||||
|
called = true;
|
||||||
|
resolvedPid.ShouldBe(123);
|
||||||
|
signal.ShouldBe(expectedSignal);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
var err = SignalHandler.ProcessSignal(command, pid);
|
||||||
|
err.ShouldBeNull();
|
||||||
|
called.ShouldBeTrue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2012-2026 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
|
using Shouldly;
|
||||||
|
using ZB.MOM.NatsNet.Server;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||||
|
|
||||||
|
public sealed class CompressionInfoTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void MarshalMetadata_UnmarshalMetadata_ShouldRoundTrip()
|
||||||
|
{
|
||||||
|
var ci = new CompressionInfo
|
||||||
|
{
|
||||||
|
Type = StoreCompression.S2Compression,
|
||||||
|
Original = 12345,
|
||||||
|
Compressed = 6789,
|
||||||
|
};
|
||||||
|
|
||||||
|
var payload = ci.MarshalMetadata();
|
||||||
|
payload.Length.ShouldBeGreaterThan(4);
|
||||||
|
|
||||||
|
var copy = new CompressionInfo();
|
||||||
|
var consumed = copy.UnmarshalMetadata(payload);
|
||||||
|
|
||||||
|
consumed.ShouldBe(payload.Length);
|
||||||
|
copy.Type.ShouldBe(StoreCompression.S2Compression);
|
||||||
|
copy.Original.ShouldBe(12345UL);
|
||||||
|
copy.Compressed.ShouldBe(6789UL);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UnmarshalMetadata_InvalidPrefix_ShouldReturnZero()
|
||||||
|
{
|
||||||
|
var ci = new CompressionInfo();
|
||||||
|
ci.UnmarshalMetadata([1, 2, 3, 4]).ShouldBe(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2012-2026 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
|
using Shouldly;
|
||||||
|
using ZB.MOM.NatsNet.Server;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||||
|
|
||||||
|
public sealed class ConsumerFileStoreTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void UpdateDelivered_UpdateAcks_AndReload_ShouldPersistState()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(Path.GetTempPath(), $"cfs-{Guid.NewGuid():N}");
|
||||||
|
Directory.CreateDirectory(root);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fs = NewStore(root);
|
||||||
|
var cfg = new ConsumerConfig { Durable = "D", AckPolicy = AckPolicy.AckExplicit };
|
||||||
|
var cs = (ConsumerFileStore)fs.ConsumerStore("D", DateTime.UtcNow, cfg);
|
||||||
|
|
||||||
|
cs.SetStarting(0);
|
||||||
|
cs.UpdateDelivered(1, 1, 1, 123);
|
||||||
|
cs.UpdateDelivered(2, 2, 1, 456);
|
||||||
|
cs.UpdateAcks(1, 1);
|
||||||
|
|
||||||
|
var (state, err) = cs.State();
|
||||||
|
err.ShouldBeNull();
|
||||||
|
state.ShouldNotBeNull();
|
||||||
|
state!.Delivered.Consumer.ShouldBe(2UL);
|
||||||
|
state.AckFloor.Consumer.ShouldBe(1UL);
|
||||||
|
|
||||||
|
cs.Stop();
|
||||||
|
|
||||||
|
var odir = Path.Combine(root, FileStoreDefaults.ConsumerDir, "D");
|
||||||
|
var loaded = new ConsumerFileStore(
|
||||||
|
fs,
|
||||||
|
new FileConsumerInfo { Name = "D", Created = DateTime.UtcNow, Config = cfg },
|
||||||
|
"D",
|
||||||
|
odir);
|
||||||
|
|
||||||
|
var (loadedState, loadedErr) = loaded.State();
|
||||||
|
loadedErr.ShouldBeNull();
|
||||||
|
loadedState.ShouldNotBeNull();
|
||||||
|
loadedState!.Delivered.Consumer.ShouldBe(2UL);
|
||||||
|
loadedState.AckFloor.Consumer.ShouldBe(1UL);
|
||||||
|
|
||||||
|
loaded.Delete();
|
||||||
|
Directory.Exists(odir).ShouldBeFalse();
|
||||||
|
fs.Stop();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (Directory.Exists(root))
|
||||||
|
Directory.Delete(root, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JetStreamFileStore NewStore(string root)
|
||||||
|
{
|
||||||
|
return new JetStreamFileStore(
|
||||||
|
new FileStoreConfig { StoreDir = root },
|
||||||
|
new FileStreamInfo
|
||||||
|
{
|
||||||
|
Created = DateTime.UtcNow,
|
||||||
|
Config = new StreamConfig
|
||||||
|
{
|
||||||
|
Name = "S",
|
||||||
|
Storage = StorageType.FileStorage,
|
||||||
|
Subjects = ["foo"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// Copyright 2012-2026 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
|
using Shouldly;
|
||||||
|
using ZB.MOM.NatsNet.Server;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||||
|
|
||||||
|
public sealed class DiskAvailabilityTests
|
||||||
|
{
|
||||||
|
private const long JetStreamMaxStoreDefault = 1L * 1024 * 1024 * 1024 * 1024;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DiskAvailable_MissingDirectory_ShouldCreateDirectory()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(Path.GetTempPath(), $"disk-avail-{Guid.NewGuid():N}");
|
||||||
|
var target = Path.Combine(root, "nested");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.Exists(target).ShouldBeFalse();
|
||||||
|
|
||||||
|
var available = DiskAvailability.DiskAvailable(target);
|
||||||
|
|
||||||
|
Directory.Exists(target).ShouldBeTrue();
|
||||||
|
available.ShouldBeGreaterThan(0L);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (Directory.Exists(root))
|
||||||
|
Directory.Delete(root, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DiskAvailable_InvalidPath_ShouldReturnFallback()
|
||||||
|
{
|
||||||
|
var available = DiskAvailability.DiskAvailable("\0");
|
||||||
|
available.ShouldBe(JetStreamMaxStoreDefault);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Check_ShouldUseDiskAvailableThreshold()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(Path.GetTempPath(), $"disk-check-{Guid.NewGuid():N}");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var available = DiskAvailability.DiskAvailable(root);
|
||||||
|
|
||||||
|
DiskAvailability.Check(root, Math.Max(0, available - 1)).ShouldBeTrue();
|
||||||
|
DiskAvailability.Check(root, available + 1).ShouldBeFalse();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (Directory.Exists(root))
|
||||||
|
Directory.Delete(root, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,50 +1,100 @@
|
|||||||
// Copyright 2020-2025 The NATS Authors
|
// Copyright 2020-2026 The NATS Authors
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
using Shouldly;
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
// Mirrors server/jetstream_errors_test.go in the NATS server Go source.
|
|
||||||
//
|
|
||||||
// All 4 tests are deferred:
|
|
||||||
// T:1381 — TestIsNatsErr: uses IsNatsErr(error, ...) where the Go version accepts
|
|
||||||
// arbitrary error interface values (including plain errors.New("x") which
|
|
||||||
// evaluates to false). The .NET JsApiErrors.IsNatsError only accepts JsApiError?
|
|
||||||
// and the "NewJS*" factory constructors (NewJSRestoreSubscribeFailedError etc.)
|
|
||||||
// that populate Description templates from tags have not been ported yet.
|
|
||||||
// T:1382 — TestApiError_Error: uses ApiErrors[JSClusterNotActiveErr].Error() — the Go
|
|
||||||
// ApiErrors map and per-error .Error() method (returns "description (errCode)")
|
|
||||||
// differs from the .NET JsApiErrors.ClusterNotActive.ToString() convention.
|
|
||||||
// T:1383 — TestApiError_NewWithTags: uses NewJSRestoreSubscribeFailedError with tag
|
|
||||||
// substitution — factory constructors not yet ported.
|
|
||||||
// T:1384 — TestApiError_NewWithUnless: uses NewJSStreamRestoreError, Unless() helper,
|
|
||||||
// NewJSPeerRemapError — not yet ported.
|
|
||||||
|
|
||||||
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests for JetStream API error types and IsNatsErr helper.
|
/// Tests for JetStream API error helpers.
|
||||||
/// Mirrors server/jetstream_errors_test.go.
|
/// Mirrors server/jetstream_errors_test.go.
|
||||||
/// All tests deferred pending port of Go factory constructors and tag-substitution system.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class JetStreamErrorsTests
|
public sealed class JetStreamErrorsTests
|
||||||
{
|
{
|
||||||
[Fact(Skip = "deferred: NewJS* factory constructors and IsNatsErr(error) not yet ported")] // T:1381
|
[Fact] // T:1381
|
||||||
public void IsNatsErr_ShouldSucceed() { }
|
public void IsNatsErr_ShouldSucceed()
|
||||||
|
{
|
||||||
|
JsApiErrors.IsNatsErr(
|
||||||
|
JsApiErrors.NotEnabledForAccount,
|
||||||
|
JsApiErrors.NotEnabledForAccount.ErrCode).ShouldBeTrue();
|
||||||
|
|
||||||
[Fact(Skip = "deferred: ApiErrors map and .Error() method not yet ported")] // T:1382
|
JsApiErrors.IsNatsErr(
|
||||||
public void ApiError_Error_ShouldSucceed() { }
|
JsApiErrors.NotEnabledForAccount,
|
||||||
|
JsApiErrors.ClusterNotActive.ErrCode).ShouldBeFalse();
|
||||||
|
|
||||||
[Fact(Skip = "deferred: NewJSRestoreSubscribeFailedError with tag substitution not yet ported")] // T:1383
|
JsApiErrors.IsNatsErr(
|
||||||
public void ApiError_NewWithTags_ShouldSucceed() { }
|
JsApiErrors.NotEnabledForAccount,
|
||||||
|
JsApiErrors.ClusterNotActive.ErrCode,
|
||||||
|
JsApiErrors.ClusterNotAvail.ErrCode).ShouldBeFalse();
|
||||||
|
|
||||||
[Fact(Skip = "deferred: NewJSStreamRestoreError / Unless() helper not yet ported")] // T:1384
|
JsApiErrors.IsNatsErr(
|
||||||
public void ApiError_NewWithUnless_ShouldSucceed() { }
|
JsApiErrors.NotEnabledForAccount,
|
||||||
|
JsApiErrors.ClusterNotActive.ErrCode,
|
||||||
|
JsApiErrors.NotEnabledForAccount.ErrCode).ShouldBeTrue();
|
||||||
|
|
||||||
|
JsApiErrors.IsNatsErr(
|
||||||
|
new JsApiError { ErrCode = JsApiErrors.NotEnabledForAccount.ErrCode },
|
||||||
|
1,
|
||||||
|
JsApiErrors.ClusterNotActive.ErrCode,
|
||||||
|
JsApiErrors.NotEnabledForAccount.ErrCode).ShouldBeTrue();
|
||||||
|
|
||||||
|
JsApiErrors.IsNatsErr(
|
||||||
|
new JsApiError { ErrCode = JsApiErrors.NotEnabledForAccount.ErrCode },
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
JsApiErrors.ClusterNotActive.ErrCode).ShouldBeFalse();
|
||||||
|
|
||||||
|
JsApiErrors.IsNatsErr(null, JsApiErrors.ClusterNotActive.ErrCode).ShouldBeFalse();
|
||||||
|
JsApiErrors.IsNatsErr(new InvalidOperationException("x"), JsApiErrors.ClusterNotActive.ErrCode).ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] // T:1382
|
||||||
|
public void ApiError_Error_ShouldSucceed()
|
||||||
|
{
|
||||||
|
JsApiErrors.Error(JsApiErrors.ClusterNotActive).ShouldBe("JetStream not in clustered mode (10006)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] // T:1383
|
||||||
|
public void ApiError_NewWithTags_ShouldSucceed()
|
||||||
|
{
|
||||||
|
var ne = JsApiErrors.NewJSRestoreSubscribeFailedError(new Exception("failed error"), "the.subject");
|
||||||
|
ne.Description.ShouldBe("JetStream unable to subscribe to restore snapshot the.subject: failed error");
|
||||||
|
ReferenceEquals(ne, JsApiErrors.RestoreSubscribeFailed).ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] // T:1384
|
||||||
|
public void ApiError_NewWithUnless_ShouldSucceed()
|
||||||
|
{
|
||||||
|
var notEnabled = JsApiErrors.NotEnabledForAccount.ErrCode;
|
||||||
|
var streamRestore = JsApiErrors.StreamRestore.ErrCode;
|
||||||
|
var peerRemap = JsApiErrors.PeerRemap.ErrCode;
|
||||||
|
|
||||||
|
JsApiErrors.IsNatsErr(
|
||||||
|
JsApiErrors.NewJSStreamRestoreError(
|
||||||
|
new Exception("failed error"),
|
||||||
|
JsApiErrors.Unless(JsApiErrors.NotEnabledForAccount)),
|
||||||
|
notEnabled).ShouldBeTrue();
|
||||||
|
|
||||||
|
JsApiErrors.IsNatsErr(
|
||||||
|
JsApiErrors.NewJSStreamRestoreError(new Exception("failed error")),
|
||||||
|
streamRestore).ShouldBeTrue();
|
||||||
|
|
||||||
|
JsApiErrors.IsNatsErr(
|
||||||
|
JsApiErrors.NewJSStreamRestoreError(
|
||||||
|
new Exception("failed error"),
|
||||||
|
JsApiErrors.Unless(new Exception("other error"))),
|
||||||
|
streamRestore).ShouldBeTrue();
|
||||||
|
|
||||||
|
JsApiErrors.IsNatsErr(
|
||||||
|
JsApiErrors.NewJSPeerRemapError(JsApiErrors.Unless(JsApiErrors.NotEnabledForAccount)),
|
||||||
|
notEnabled).ShouldBeTrue();
|
||||||
|
|
||||||
|
JsApiErrors.IsNatsErr(
|
||||||
|
JsApiErrors.NewJSPeerRemapError(JsApiErrors.Unless(null)),
|
||||||
|
peerRemap).ShouldBeTrue();
|
||||||
|
|
||||||
|
JsApiErrors.IsNatsErr(
|
||||||
|
JsApiErrors.NewJSPeerRemapError(JsApiErrors.Unless(new Exception("other error"))),
|
||||||
|
peerRemap).ShouldBeTrue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2012-2026 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
|
using Shouldly;
|
||||||
|
using ZB.MOM.NatsNet.Server;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||||
|
|
||||||
|
public sealed class JetStreamFileStoreTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void StoreMsg_LoadAndPurge_ShouldRoundTrip()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(Path.GetTempPath(), $"fs-{Guid.NewGuid():N}");
|
||||||
|
Directory.CreateDirectory(root);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fs = NewStore(root);
|
||||||
|
|
||||||
|
var (seq1, _) = fs.StoreMsg("foo", [1], [2, 3], 0);
|
||||||
|
var (seq2, _) = fs.StoreMsg("bar", null, [4, 5], 0);
|
||||||
|
|
||||||
|
seq1.ShouldBe(1UL);
|
||||||
|
seq2.ShouldBe(2UL);
|
||||||
|
fs.State().Msgs.ShouldBe(2UL);
|
||||||
|
|
||||||
|
var msg = fs.LoadMsg(1, null);
|
||||||
|
msg.ShouldNotBeNull();
|
||||||
|
msg!.Subject.ShouldBe("foo");
|
||||||
|
|
||||||
|
fs.SubjectForSeq(2).Subject.ShouldBe("bar");
|
||||||
|
fs.SubjectsTotals(string.Empty).Count.ShouldBe(2);
|
||||||
|
|
||||||
|
var (removed, remErr) = fs.RemoveMsg(1);
|
||||||
|
removed.ShouldBeTrue();
|
||||||
|
remErr.ShouldBeNull();
|
||||||
|
fs.State().Msgs.ShouldBe(1UL);
|
||||||
|
|
||||||
|
var (purged, purgeErr) = fs.Purge();
|
||||||
|
purgeErr.ShouldBeNull();
|
||||||
|
purged.ShouldBe(1UL);
|
||||||
|
fs.State().Msgs.ShouldBe(0UL);
|
||||||
|
|
||||||
|
var (snapshot, snapErr) = fs.Snapshot(TimeSpan.FromSeconds(1), includeConsumers: false, checkMsgs: false);
|
||||||
|
snapErr.ShouldBeNull();
|
||||||
|
snapshot.ShouldNotBeNull();
|
||||||
|
snapshot!.Reader.ShouldNotBeNull();
|
||||||
|
|
||||||
|
var (total, reported, utilErr) = fs.Utilization();
|
||||||
|
utilErr.ShouldBeNull();
|
||||||
|
total.ShouldBe(reported);
|
||||||
|
|
||||||
|
fs.Stop();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Directory.Delete(root, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JetStreamFileStore NewStore(string root)
|
||||||
|
{
|
||||||
|
return new JetStreamFileStore(
|
||||||
|
new FileStoreConfig { StoreDir = root },
|
||||||
|
new FileStreamInfo
|
||||||
|
{
|
||||||
|
Created = DateTime.UtcNow,
|
||||||
|
Config = new StreamConfig
|
||||||
|
{
|
||||||
|
Name = "S",
|
||||||
|
Storage = StorageType.FileStorage,
|
||||||
|
Subjects = ["foo", "bar"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2012-2026 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
|
using Shouldly;
|
||||||
|
using ZB.MOM.NatsNet.Server;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||||
|
|
||||||
|
public sealed class NatsConsumerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Create_SetLeader_UpdateConfig_AndStop_ShouldBehave()
|
||||||
|
{
|
||||||
|
var account = new Account { Name = "A" };
|
||||||
|
var streamCfg = new StreamConfig { Name = "S", Subjects = ["foo"], Storage = StorageType.FileStorage };
|
||||||
|
var stream = NatsStream.Create(account, streamCfg, null, null, null, null);
|
||||||
|
stream.ShouldNotBeNull();
|
||||||
|
|
||||||
|
var cfg = new ConsumerConfig { Durable = "D", AckPolicy = AckPolicy.AckExplicit };
|
||||||
|
var consumer = NatsConsumer.Create(stream!, cfg, ConsumerAction.CreateOrUpdate, null);
|
||||||
|
consumer.ShouldNotBeNull();
|
||||||
|
|
||||||
|
consumer!.IsLeader().ShouldBeFalse();
|
||||||
|
consumer.SetLeader(true, 3);
|
||||||
|
consumer.IsLeader().ShouldBeTrue();
|
||||||
|
|
||||||
|
var updated = new ConsumerConfig { Durable = "D", AckPolicy = AckPolicy.AckAll };
|
||||||
|
consumer.UpdateConfig(updated);
|
||||||
|
consumer.GetConfig().AckPolicy.ShouldBe(AckPolicy.AckAll);
|
||||||
|
|
||||||
|
var info = consumer.GetInfo();
|
||||||
|
info.Stream.ShouldBe("S");
|
||||||
|
info.Name.ShouldBe("D");
|
||||||
|
|
||||||
|
consumer.Stop();
|
||||||
|
consumer.IsLeader().ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] // T:1364
|
||||||
|
public void SortingConsumerPullRequests_ShouldSucceed()
|
||||||
|
{
|
||||||
|
var q = new WaitQueue(max: 100);
|
||||||
|
|
||||||
|
q.AddPrioritized(new WaitingRequest { Reply = "1a", PriorityGroup = new PriorityGroup { Priority = 1 }, N = 1 })
|
||||||
|
.ShouldBeTrue();
|
||||||
|
q.AddPrioritized(new WaitingRequest { Reply = "2a", PriorityGroup = new PriorityGroup { Priority = 2 }, N = 1 })
|
||||||
|
.ShouldBeTrue();
|
||||||
|
q.AddPrioritized(new WaitingRequest { Reply = "1b", PriorityGroup = new PriorityGroup { Priority = 1 }, N = 1 })
|
||||||
|
.ShouldBeTrue();
|
||||||
|
q.AddPrioritized(new WaitingRequest { Reply = "2b", PriorityGroup = new PriorityGroup { Priority = 2 }, N = 1 })
|
||||||
|
.ShouldBeTrue();
|
||||||
|
q.AddPrioritized(new WaitingRequest { Reply = "1c", PriorityGroup = new PriorityGroup { Priority = 1 }, N = 1 })
|
||||||
|
.ShouldBeTrue();
|
||||||
|
q.AddPrioritized(new WaitingRequest { Reply = "3a", PriorityGroup = new PriorityGroup { Priority = 3 }, N = 1 })
|
||||||
|
.ShouldBeTrue();
|
||||||
|
q.AddPrioritized(new WaitingRequest { Reply = "2c", PriorityGroup = new PriorityGroup { Priority = 2 }, N = 1 })
|
||||||
|
.ShouldBeTrue();
|
||||||
|
|
||||||
|
var expectedOrder = new[]
|
||||||
|
{
|
||||||
|
("1a", 1),
|
||||||
|
("1b", 1),
|
||||||
|
("1c", 1),
|
||||||
|
("2a", 2),
|
||||||
|
("2b", 2),
|
||||||
|
("2c", 2),
|
||||||
|
("3a", 3),
|
||||||
|
};
|
||||||
|
|
||||||
|
q.Len.ShouldBe(expectedOrder.Length);
|
||||||
|
foreach (var (reply, priority) in expectedOrder)
|
||||||
|
{
|
||||||
|
var current = q.Peek();
|
||||||
|
current.ShouldNotBeNull();
|
||||||
|
current!.Reply.ShouldBe(reply);
|
||||||
|
current.PriorityGroup.ShouldNotBeNull();
|
||||||
|
current.PriorityGroup!.Priority.ShouldBe(priority);
|
||||||
|
q.RemoveCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
q.IsEmpty().ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] // T:1365
|
||||||
|
public void WaitQueuePopAndRequeue_ShouldSucceed()
|
||||||
|
{
|
||||||
|
var q = new WaitQueue(max: 100);
|
||||||
|
q.AddPrioritized(new WaitingRequest { Reply = "1a", N = 2, PriorityGroup = new PriorityGroup { Priority = 1 } })
|
||||||
|
.ShouldBeTrue();
|
||||||
|
q.AddPrioritized(new WaitingRequest { Reply = "1b", N = 1, PriorityGroup = new PriorityGroup { Priority = 1 } })
|
||||||
|
.ShouldBeTrue();
|
||||||
|
q.AddPrioritized(new WaitingRequest { Reply = "2a", N = 3, PriorityGroup = new PriorityGroup { Priority = 2 } })
|
||||||
|
.ShouldBeTrue();
|
||||||
|
|
||||||
|
var wr = q.PopAndRequeue();
|
||||||
|
wr.ShouldNotBeNull();
|
||||||
|
wr!.Reply.ShouldBe("1a");
|
||||||
|
wr.N.ShouldBe(1);
|
||||||
|
q.Len.ShouldBe(3);
|
||||||
|
|
||||||
|
wr = q.PopAndRequeue();
|
||||||
|
wr.ShouldNotBeNull();
|
||||||
|
wr!.Reply.ShouldBe("1b");
|
||||||
|
wr.N.ShouldBe(0);
|
||||||
|
q.Len.ShouldBe(2);
|
||||||
|
|
||||||
|
wr = q.PopAndRequeue();
|
||||||
|
wr.ShouldNotBeNull();
|
||||||
|
wr!.Reply.ShouldBe("1a");
|
||||||
|
wr.N.ShouldBe(0);
|
||||||
|
q.Len.ShouldBe(1);
|
||||||
|
|
||||||
|
q.Peek()!.Reply.ShouldBe("2a");
|
||||||
|
q.Peek()!.N.ShouldBe(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2012-2026 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
|
using Shouldly;
|
||||||
|
using ZB.MOM.NatsNet.Server;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||||
|
|
||||||
|
public sealed class NatsStreamTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Create_SetLeader_Purge_AndSeal_ShouldBehave()
|
||||||
|
{
|
||||||
|
var account = new Account { Name = "A" };
|
||||||
|
var streamCfg = new StreamConfig { Name = "ORDERS", Subjects = ["orders.*"], Storage = StorageType.FileStorage };
|
||||||
|
|
||||||
|
var memCfg = streamCfg.Clone();
|
||||||
|
memCfg.Storage = StorageType.MemoryStorage;
|
||||||
|
var store = new JetStreamMemStore(memCfg);
|
||||||
|
store.StoreMsg("orders.new", null, [1, 2], 0);
|
||||||
|
|
||||||
|
var stream = NatsStream.Create(account, streamCfg, null, store, null, null);
|
||||||
|
stream.ShouldNotBeNull();
|
||||||
|
|
||||||
|
stream!.IsLeader().ShouldBeFalse();
|
||||||
|
stream.SetLeader(true, 7);
|
||||||
|
stream.IsLeader().ShouldBeTrue();
|
||||||
|
|
||||||
|
stream.State().Msgs.ShouldBe(1UL);
|
||||||
|
stream.Purge();
|
||||||
|
stream.State().Msgs.ShouldBe(0UL);
|
||||||
|
|
||||||
|
stream.IsSealed().ShouldBeFalse();
|
||||||
|
stream.Seal();
|
||||||
|
stream.IsSealed().ShouldBeTrue();
|
||||||
|
|
||||||
|
stream.GetAccount().Name.ShouldBe("A");
|
||||||
|
stream.GetInfo().Config.Name.ShouldBe("ORDERS");
|
||||||
|
|
||||||
|
stream.Delete();
|
||||||
|
stream.IsLeader().ShouldBeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
// Copyright 2012-2026 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
|
using System.Threading.Channels;
|
||||||
|
using Shouldly;
|
||||||
|
using ZB.MOM.NatsNet.Server.Internal;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||||
|
|
||||||
|
public sealed class RaftTypesTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Raft_Methods_ShouldProvideNonStubBehavior()
|
||||||
|
{
|
||||||
|
var raft = new Raft
|
||||||
|
{
|
||||||
|
Id = "N1",
|
||||||
|
GroupName = "RG",
|
||||||
|
AccName = "ACC",
|
||||||
|
StateValue = (int)RaftState.Leader,
|
||||||
|
LeaderId = "N1",
|
||||||
|
Csz = 3,
|
||||||
|
Qn = 2,
|
||||||
|
PIndex = 10,
|
||||||
|
Commit = 8,
|
||||||
|
Applied_ = 6,
|
||||||
|
Processed_ = 7,
|
||||||
|
PApplied = 9,
|
||||||
|
WalBytes = 128,
|
||||||
|
Peers_ = new Dictionary<string, Lps>
|
||||||
|
{
|
||||||
|
["N2"] = new() { Ts = DateTime.UtcNow, Kp = true, Li = 9 },
|
||||||
|
},
|
||||||
|
ApplyQ_ = new IpQueue<CommittedEntry>("apply-q"),
|
||||||
|
LeadC = Channel.CreateUnbounded<bool>(),
|
||||||
|
Quit = Channel.CreateUnbounded<bool>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
raft.Propose([1, 2, 3]);
|
||||||
|
raft.ForwardProposal([4, 5]);
|
||||||
|
raft.ProposeMulti([new Entry { Data = [6] }]);
|
||||||
|
|
||||||
|
raft.PropQ.ShouldNotBeNull();
|
||||||
|
raft.PropQ!.Len().ShouldBe(3);
|
||||||
|
|
||||||
|
raft.InstallSnapshot([9, 9], force: false);
|
||||||
|
raft.SendSnapshot([8, 8, 8]);
|
||||||
|
raft.CreateSnapshotCheckpoint(force: false).ShouldBeOfType<Checkpoint>();
|
||||||
|
raft.NeedSnapshot().ShouldBeTrue();
|
||||||
|
|
||||||
|
raft.Applied(5).ShouldBe((1UL, 128UL));
|
||||||
|
raft.Processed(11, 10).ShouldBe((11UL, 128UL));
|
||||||
|
raft.Size().ShouldBe((11UL, 128UL));
|
||||||
|
raft.Progress().ShouldBe((10UL, 8UL, 10UL));
|
||||||
|
raft.Leader().ShouldBeTrue();
|
||||||
|
raft.LeaderSince().ShouldNotBeNull();
|
||||||
|
raft.Quorum().ShouldBeTrue();
|
||||||
|
raft.Current().ShouldBeTrue();
|
||||||
|
raft.Healthy().ShouldBeTrue();
|
||||||
|
raft.Term().ShouldBe(raft.Term_);
|
||||||
|
raft.Leaderless().ShouldBeFalse();
|
||||||
|
raft.GroupLeader().ShouldBe("N1");
|
||||||
|
|
||||||
|
raft.SetObserver(true);
|
||||||
|
raft.IsObserver().ShouldBeTrue();
|
||||||
|
raft.Campaign();
|
||||||
|
raft.State().ShouldBe(RaftState.Candidate);
|
||||||
|
raft.CampaignImmediately();
|
||||||
|
raft.StepDown("N2");
|
||||||
|
raft.State().ShouldBe(RaftState.Follower);
|
||||||
|
|
||||||
|
raft.ProposeKnownPeers(["P1", "P2"]);
|
||||||
|
raft.Peers().Count.ShouldBe(3);
|
||||||
|
raft.ProposeAddPeer("P3");
|
||||||
|
raft.ClusterSize().ShouldBeGreaterThan(1);
|
||||||
|
raft.ProposeRemovePeer("P2");
|
||||||
|
raft.Peers().Count.ShouldBe(3);
|
||||||
|
raft.MembershipChangeInProgress().ShouldBeTrue();
|
||||||
|
raft.AdjustClusterSize(5);
|
||||||
|
raft.ClusterSize().ShouldBe(5);
|
||||||
|
raft.AdjustBootClusterSize(4);
|
||||||
|
raft.ClusterSize().ShouldBe(4);
|
||||||
|
|
||||||
|
raft.ApplyQ().ShouldNotBeNull();
|
||||||
|
raft.PauseApply();
|
||||||
|
raft.Paused.ShouldBeTrue();
|
||||||
|
raft.ResumeApply();
|
||||||
|
raft.Paused.ShouldBeFalse();
|
||||||
|
raft.DrainAndReplaySnapshot().ShouldBeTrue();
|
||||||
|
raft.LeadChangeC().ShouldNotBeNull();
|
||||||
|
raft.QuitC().ShouldNotBeNull();
|
||||||
|
raft.Created().ShouldBe(raft.Created_);
|
||||||
|
raft.ID().ShouldBe("N1");
|
||||||
|
raft.Group().ShouldBe("RG");
|
||||||
|
raft.GetTrafficAccountName().ShouldBe("ACC");
|
||||||
|
|
||||||
|
raft.RecreateInternalSubs();
|
||||||
|
raft.Stop();
|
||||||
|
raft.WaitForStop();
|
||||||
|
raft.Delete();
|
||||||
|
raft.IsDeleted().ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Checkpoint_Methods_ShouldRoundTripSnapshotData()
|
||||||
|
{
|
||||||
|
var node = new Raft
|
||||||
|
{
|
||||||
|
Id = "NODE",
|
||||||
|
PTerm = 3,
|
||||||
|
AReply = "_R_",
|
||||||
|
};
|
||||||
|
|
||||||
|
var checkpoint = new Checkpoint
|
||||||
|
{
|
||||||
|
Node = node,
|
||||||
|
Term = 5,
|
||||||
|
Applied = 11,
|
||||||
|
PApplied = 7,
|
||||||
|
SnapFile = Path.Combine(Path.GetTempPath(), $"checkpoint-{Guid.NewGuid():N}.bin"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var written = checkpoint.InstallSnapshot([1, 2, 3, 4]);
|
||||||
|
written.ShouldBe(4UL);
|
||||||
|
|
||||||
|
var loaded = checkpoint.LoadLastSnapshot();
|
||||||
|
loaded.ShouldBe([1, 2, 3, 4]);
|
||||||
|
|
||||||
|
var seq = checkpoint.AppendEntriesSeq().ToList();
|
||||||
|
seq.Count.ShouldBe(1);
|
||||||
|
seq[0].Error.ShouldBeNull();
|
||||||
|
seq[0].Entry.Leader.ShouldBe("NODE");
|
||||||
|
seq[0].Entry.TermV.ShouldBe(5UL);
|
||||||
|
seq[0].Entry.Commit.ShouldBe(11UL);
|
||||||
|
seq[0].Entry.PIndex.ShouldBe(7UL);
|
||||||
|
|
||||||
|
checkpoint.Abort();
|
||||||
|
File.Exists(checkpoint.SnapFile).ShouldBeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2012-2026 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
|
using Shouldly;
|
||||||
|
using ZB.MOM.NatsNet.Server;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||||
|
|
||||||
|
public sealed class WaitQueueTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Add_Peek_Pop_IsFull_ShouldBehaveAsFifo()
|
||||||
|
{
|
||||||
|
var q = new WaitQueue();
|
||||||
|
|
||||||
|
q.Peek().ShouldBeNull();
|
||||||
|
q.Pop().ShouldBeNull();
|
||||||
|
|
||||||
|
q.Add(new WaitingRequest { Subject = "A", N = 1 });
|
||||||
|
q.Add(new WaitingRequest { Subject = "B", N = 2 });
|
||||||
|
|
||||||
|
q.Len.ShouldBe(2);
|
||||||
|
q.IsFull(2).ShouldBeTrue();
|
||||||
|
q.Peek()!.Subject.ShouldBe("A");
|
||||||
|
|
||||||
|
q.Pop()!.Subject.ShouldBe("A");
|
||||||
|
q.Pop()!.Subject.ShouldBe("B");
|
||||||
|
q.Len.ShouldBe(1);
|
||||||
|
|
||||||
|
q.Pop()!.Subject.ShouldBe("B");
|
||||||
|
q.Len.ShouldBe(0);
|
||||||
|
q.IsFull(1).ShouldBeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddPrioritized_AndCycle_ShouldPreserveStableOrder()
|
||||||
|
{
|
||||||
|
var q = new WaitQueue(max: 10);
|
||||||
|
|
||||||
|
q.AddPrioritized(new WaitingRequest { Reply = "2a", N = 1, PriorityGroup = new PriorityGroup { Priority = 2 } })
|
||||||
|
.ShouldBeTrue();
|
||||||
|
q.AddPrioritized(new WaitingRequest { Reply = "1a", N = 1, PriorityGroup = new PriorityGroup { Priority = 1 } })
|
||||||
|
.ShouldBeTrue();
|
||||||
|
q.AddPrioritized(new WaitingRequest { Reply = "1b", N = 1, PriorityGroup = new PriorityGroup { Priority = 1 } })
|
||||||
|
.ShouldBeTrue();
|
||||||
|
|
||||||
|
q.Peek()!.Reply.ShouldBe("1a");
|
||||||
|
q.Cycle();
|
||||||
|
q.Peek()!.Reply.ShouldBe("1b");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2012-2026 The NATS Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
using Shouldly;
|
||||||
|
using ZB.MOM.NatsNet.Server;
|
||||||
|
using ZB.MOM.NatsNet.Server.Internal;
|
||||||
|
|
||||||
|
namespace ZB.MOM.NatsNet.Server.Tests.Server;
|
||||||
|
|
||||||
|
public sealed class ServerLifecycleStubFeaturesTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void LifecycleHelpers_RemoveRouteAndReload_ShouldBehave()
|
||||||
|
{
|
||||||
|
var (server, err) = NatsServer.NewServer(new ServerOptions());
|
||||||
|
err.ShouldBeNull();
|
||||||
|
server.ShouldNotBeNull();
|
||||||
|
|
||||||
|
var route = new ClientConnection(ClientKind.Router) { Cid = 42 };
|
||||||
|
var routes = new Dictionary<string, List<ClientConnection>> { ["pool"] = [route] };
|
||||||
|
var clients = new Dictionary<ulong, ClientConnection> { [route.Cid] = route };
|
||||||
|
|
||||||
|
SetField(server!, "_routes", routes);
|
||||||
|
SetField(server!, "_clients", clients);
|
||||||
|
|
||||||
|
server.ForEachRoute(_ => { });
|
||||||
|
|
||||||
|
InvokePrivate(server!, "RemoveRoute", route);
|
||||||
|
((Dictionary<string, List<ClientConnection>>)GetField(server!, "_routes")).Count.ShouldBe(0);
|
||||||
|
((Dictionary<ulong, ClientConnection>)GetField(server!, "_clients")).Count.ShouldBe(0);
|
||||||
|
|
||||||
|
var nonce = new byte[16];
|
||||||
|
InvokePrivate(server!, "GenerateNonce", nonce);
|
||||||
|
nonce.Any(b => b != 0).ShouldBeTrue();
|
||||||
|
|
||||||
|
var before = (DateTime)GetField(server!, "_configTime");
|
||||||
|
server.Reload();
|
||||||
|
var after = (DateTime)GetField(server!, "_configTime");
|
||||||
|
after.ShouldBeGreaterThanOrEqualTo(before);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object GetField(object target, string name)
|
||||||
|
{
|
||||||
|
return target.GetType()
|
||||||
|
.GetField(name, BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||||
|
.GetValue(target)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetField(object target, string name, object value)
|
||||||
|
{
|
||||||
|
target.GetType()
|
||||||
|
.GetField(name, BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||||
|
.SetValue(target, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void InvokePrivate(object target, string name, params object[] args)
|
||||||
|
{
|
||||||
|
target.GetType()
|
||||||
|
.GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||||
|
.Invoke(target, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ CREATE TABLE IF NOT EXISTS features (
|
|||||||
go_line_number INTEGER,
|
go_line_number INTEGER,
|
||||||
go_line_count INTEGER,
|
go_line_count INTEGER,
|
||||||
status TEXT NOT NULL DEFAULT 'not_started'
|
status TEXT NOT NULL DEFAULT 'not_started'
|
||||||
CHECK (status IN ('not_started', 'stub', 'complete', 'verified', 'n_a', 'deferred')),
|
CHECK (status IN ('not_started', 'stub', 'complete', 'verified', 'n_a', 'deferred', 'unknown')),
|
||||||
dotnet_project TEXT,
|
dotnet_project TEXT,
|
||||||
dotnet_class TEXT,
|
dotnet_class TEXT,
|
||||||
dotnet_method TEXT,
|
dotnet_method TEXT,
|
||||||
@@ -54,7 +54,7 @@ CREATE TABLE IF NOT EXISTS unit_tests (
|
|||||||
go_line_number INTEGER,
|
go_line_number INTEGER,
|
||||||
go_line_count INTEGER,
|
go_line_count INTEGER,
|
||||||
status TEXT NOT NULL DEFAULT 'not_started'
|
status TEXT NOT NULL DEFAULT 'not_started'
|
||||||
CHECK (status IN ('not_started', 'stub', 'complete', 'verified', 'n_a', 'deferred')),
|
CHECK (status IN ('not_started', 'stub', 'complete', 'verified', 'n_a', 'deferred', 'unknown')),
|
||||||
dotnet_project TEXT,
|
dotnet_project TEXT,
|
||||||
dotnet_class TEXT,
|
dotnet_class TEXT,
|
||||||
dotnet_method TEXT,
|
dotnet_method TEXT,
|
||||||
@@ -88,6 +88,17 @@ CREATE TABLE IF NOT EXISTS library_mappings (
|
|||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS status_overrides (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
table_name TEXT NOT NULL CHECK (table_name IN ('features', 'unit_tests')),
|
||||||
|
item_id INTEGER NOT NULL,
|
||||||
|
audit_status TEXT NOT NULL,
|
||||||
|
audit_reason TEXT NOT NULL,
|
||||||
|
requested_status TEXT NOT NULL,
|
||||||
|
comment TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
-- Indexes
|
-- Indexes
|
||||||
CREATE INDEX IF NOT EXISTS idx_features_module ON features(module_id);
|
CREATE INDEX IF NOT EXISTS idx_features_module ON features(module_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_features_status ON features(status);
|
CREATE INDEX IF NOT EXISTS idx_features_status ON features(status);
|
||||||
|
|||||||
BIN
porting.db
BIN
porting.db
Binary file not shown.
2681
reports/audit-results-tests.csv
Normal file
2681
reports/audit-results-tests.csv
Normal file
File diff suppressed because it is too large
Load Diff
998
reports/audit-results.csv
Normal file
998
reports/audit-results.csv
Normal file
@@ -0,0 +1,998 @@
|
|||||||
|
id,dotnet_class,dotnet_method,go_file,go_method,old_status,new_status,reason
|
||||||
|
258,ServiceImportEntry,IsRespServiceImport,server/accounts.go,isRespServiceImport,unknown,deferred,Class exists but method not found
|
||||||
|
259,ServiceExportEntry,SetResponseThresholdTimer,server/accounts.go,setResponseThresholdTimer,unknown,deferred,Class exists but method not found
|
||||||
|
260,ServiceExportEntry,ClearResponseThresholdTimer,server/accounts.go,clearResponseThresholdTimer,unknown,deferred,Class exists but method not found
|
||||||
|
261,ServiceExportEntry,CheckExpiredResponses,server/accounts.go,checkExpiredResponses,unknown,deferred,Class exists but method not found
|
||||||
|
321,ResolverDefaultsOps,IsReadOnly,server/accounts.go,IsReadOnly,unknown,verified,Method found with 1 statement(s) at AccountResolver.cs:91
|
||||||
|
322,ResolverDefaultsOps,IsTrackingUpdate,server/accounts.go,IsTrackingUpdate,unknown,verified,Method found with 1 statement(s) at AccountResolver.cs:97
|
||||||
|
323,ResolverDefaultsOps,Start,server/accounts.go,Start,unknown,stub,Body is throw NotImplementedException at AccountResolver.cs:94
|
||||||
|
324,ResolverDefaultsOps,Reload,server/accounts.go,Reload,unknown,stub,Body is throw NotImplementedException at AccountResolver.cs:100
|
||||||
|
325,ResolverDefaultsOps,Close,server/accounts.go,Close,unknown,stub,Body is throw NotImplementedException at AccountResolver.cs:103
|
||||||
|
326,ResolverDefaultsOps,Store,server/accounts.go,Store,unknown,deferred,Class exists but method not found
|
||||||
|
332,DirAccountResolver,IsTrackingUpdate,server/accounts.go,IsTrackingUpdate,unknown,verified,Method found with 1 statement(s) at AccountResolver.cs:387
|
||||||
|
333,DirAccountResolver,Reload,server/accounts.go,Reload,unknown,verified,Method found with 1 statement(s) at AccountResolver.cs:393
|
||||||
|
340,DirAccountResolver,Start,server/accounts.go,Start,unknown,verified,Method found with 1 statement(s) at AccountResolver.cs:436
|
||||||
|
341,DirAccountResolver,Fetch,server/accounts.go,Fetch,unknown,deferred,Class exists but method not found
|
||||||
|
342,DirAccountResolver,Store,server/accounts.go,Store,unknown,verified,Method found with 1 statement(s) at AccountResolver.cs:291
|
||||||
|
344,DirAccountResolver,Apply,server/accounts.go,apply,unknown,verified,Method found with 1 statement(s) at AccountResolver.cs:364
|
||||||
|
361,ClientConnection,MatchesPinnedCert,server/auth.go,matchesPinnedCert,unknown,deferred,Class exists but method not found
|
||||||
|
387,ClientConnection,IsInternalClient,server/client.go,isInternalClient,unknown,deferred,Class exists but method not found
|
||||||
|
388,ClientFlags,Set,server/client.go,set,unknown,deferred,Class not found in .NET source
|
||||||
|
389,ClientFlags,Clear,server/client.go,clear,unknown,deferred,Class not found in .NET source
|
||||||
|
390,ClientFlags,IsSet,server/client.go,isSet,unknown,deferred,Class not found in .NET source
|
||||||
|
391,ClientFlags,SetIfNotSet,server/client.go,setIfNotSet,unknown,deferred,Class not found in .NET source
|
||||||
|
393,ClientConnection,NbPoolGet,server/client.go,nbPoolGet,unknown,deferred,Class exists but method not found
|
||||||
|
394,ClientConnection,NbPoolPut,server/client.go,nbPoolPut,unknown,deferred,Class exists but method not found
|
||||||
|
395,ReadCacheFlags,Set,server/client.go,set,unknown,deferred,Class not found in .NET source
|
||||||
|
396,ReadCacheFlags,Clear,server/client.go,clear,unknown,deferred,Class not found in .NET source
|
||||||
|
397,ReadCacheFlags,IsSet,server/client.go,isSet,unknown,deferred,Class not found in .NET source
|
||||||
|
398,ClientConnection,String,server/client.go,String,unknown,deferred,Class exists but method not found
|
||||||
|
402,ClientConnection,GetTLSConnectionState,server/client.go,GetTLSConnectionState,unknown,deferred,Class exists but method not found
|
||||||
|
411,ClientConnection,Kind,server/client.go,Kind,unknown,deferred,Class exists but method not found
|
||||||
|
420,ClientConnection,PublicPermissions,server/client.go,publicPermissions,unknown,deferred,Class exists but method not found
|
||||||
|
421,ClientConnection,MergeDenyPermissions,server/client.go,mergeDenyPermissions,unknown,deferred,Class exists but method not found
|
||||||
|
422,ClientConnection,MergeDenyPermissionsLocked,server/client.go,mergeDenyPermissionsLocked,unknown,deferred,Class exists but method not found
|
||||||
|
423,ClientConnection,SetExpiration,server/client.go,setExpiration,unknown,deferred,Class exists but method not found
|
||||||
|
424,ClientConnection,LoadMsgDenyFilter,server/client.go,loadMsgDenyFilter,unknown,deferred,Class exists but method not found
|
||||||
|
429,ClientConnection,CollapsePtoNB,server/client.go,collapsePtoNB,unknown,deferred,Class exists but method not found
|
||||||
|
430,ClientConnection,FlushOutbound,server/client.go,flushOutbound,unknown,deferred,Class exists but method not found
|
||||||
|
431,ClientConnection,HandleWriteTimeout,server/client.go,handleWriteTimeout,unknown,deferred,Class exists but method not found
|
||||||
|
432,ClientConnection,MarkConnAsClosed,server/client.go,markConnAsClosed,unknown,deferred,Class exists but method not found
|
||||||
|
456,ClientConnection,QueueOutbound,server/client.go,queueOutbound,unknown,deferred,Class exists but method not found
|
||||||
|
471,ClientConnection,ProcessHeaderPub,server/client.go,processHeaderPub,unknown,deferred,Class exists but method not found
|
||||||
|
472,ClientConnection,ProcessPub,server/client.go,processPub,unknown,deferred,Class exists but method not found
|
||||||
|
473,ClientConnection,SplitArg,server/client.go,splitArg,unknown,deferred,Class exists but method not found
|
||||||
|
474,ClientConnection,ParseSub,server/client.go,parseSub,unknown,deferred,Class exists but method not found
|
||||||
|
475,ClientConnection,ProcessSub,server/client.go,processSub,unknown,deferred,Class exists but method not found
|
||||||
|
476,ClientConnection,ProcessSubEx,server/client.go,processSubEx,unknown,deferred,Class exists but method not found
|
||||||
|
477,ClientConnection,AddShadowSubscriptions,server/client.go,addShadowSubscriptions,unknown,deferred,Class exists but method not found
|
||||||
|
478,ClientConnection,AddShadowSub,server/client.go,addShadowSub,unknown,deferred,Class exists but method not found
|
||||||
|
479,ClientConnection,CanSubscribe,server/client.go,canSubscribe,unknown,deferred,Class exists but method not found
|
||||||
|
480,ClientConnection,QueueMatches,server/client.go,queueMatches,unknown,deferred,Class exists but method not found
|
||||||
|
481,ClientConnection,Unsubscribe,server/client.go,unsubscribe,unknown,deferred,Class exists but method not found
|
||||||
|
482,ClientConnection,ProcessUnsub,server/client.go,processUnsub,unknown,deferred,Class exists but method not found
|
||||||
|
483,ClientConnection,CheckDenySub,server/client.go,checkDenySub,unknown,deferred,Class exists but method not found
|
||||||
|
484,ClientConnection,MsgHeaderForRouteOrLeaf,server/client.go,msgHeaderForRouteOrLeaf,unknown,deferred,Class exists but method not found
|
||||||
|
485,ClientConnection,MsgHeader,server/client.go,msgHeader,unknown,deferred,Class exists but method not found
|
||||||
|
486,ClientConnection,StalledWait,server/client.go,stalledWait,unknown,deferred,Class exists but method not found
|
||||||
|
487,ClientConnection,DeliverMsg,server/client.go,deliverMsg,unknown,deferred,Class exists but method not found
|
||||||
|
488,ClientConnection,AddToPCD,server/client.go,addToPCD,unknown,deferred,Class exists but method not found
|
||||||
|
489,ClientConnection,TrackRemoteReply,server/client.go,trackRemoteReply,unknown,deferred,Class exists but method not found
|
||||||
|
490,ClientConnection,PruneRemoteTracking,server/client.go,pruneRemoteTracking,unknown,deferred,Class exists but method not found
|
||||||
|
491,ClientConnection,PruneReplyPerms,server/client.go,pruneReplyPerms,unknown,deferred,Class exists but method not found
|
||||||
|
492,ClientConnection,PruneDenyCache,server/client.go,pruneDenyCache,unknown,deferred,Class exists but method not found
|
||||||
|
493,ClientConnection,PrunePubPermsCache,server/client.go,prunePubPermsCache,unknown,deferred,Class exists but method not found
|
||||||
|
494,ClientConnection,PubAllowed,server/client.go,pubAllowed,unknown,deferred,Class exists but method not found
|
||||||
|
495,ClientConnection,PubAllowedFullCheck,server/client.go,pubAllowedFullCheck,unknown,deferred,Class exists but method not found
|
||||||
|
496,ClientConnection,IsServiceReply,server/client.go,isServiceReply,unknown,deferred,Class exists but method not found
|
||||||
|
497,ClientConnection,IsReservedReply,server/client.go,isReservedReply,unknown,deferred,Class exists but method not found
|
||||||
|
498,ClientConnection,ProcessInboundMsg,server/client.go,processInboundMsg,unknown,deferred,Class exists but method not found
|
||||||
|
499,ClientConnection,SelectMappedSubject,server/client.go,selectMappedSubject,unknown,deferred,Class exists but method not found
|
||||||
|
500,ClientConnection,ProcessInboundClientMsg,server/client.go,processInboundClientMsg,unknown,deferred,Class exists but method not found
|
||||||
|
501,ClientConnection,SubForReply,server/client.go,subForReply,unknown,deferred,Class exists but method not found
|
||||||
|
502,ClientConnection,HandleGWReplyMap,server/client.go,handleGWReplyMap,unknown,deferred,Class exists but method not found
|
||||||
|
503,ClientConnection,SetupResponseServiceImport,server/client.go,setupResponseServiceImport,unknown,deferred,Class exists but method not found
|
||||||
|
504,ClientConnection,RemoveHeaderIfPresent,server/client.go,removeHeaderIfPresent,unknown,deferred,Class exists but method not found
|
||||||
|
505,ClientConnection,RemoveHeaderIfPrefixPresent,server/client.go,removeHeaderIfPrefixPresent,unknown,deferred,Class exists but method not found
|
||||||
|
506,ClientConnection,GenHeader,server/client.go,genHeader,unknown,deferred,Class exists but method not found
|
||||||
|
507,ClientConnection,SetHeaderInternal,server/client.go,setHeader,unknown,deferred,Class exists but method not found
|
||||||
|
508,ClientConnection,GetHeader,server/client.go,getHeader,unknown,deferred,Class exists but method not found
|
||||||
|
509,ClientConnection,SliceHeader,server/client.go,sliceHeader,unknown,deferred,Class exists but method not found
|
||||||
|
510,ClientConnection,GetHeaderKeyIndex,server/client.go,getHeaderKeyIndex,unknown,deferred,Class exists but method not found
|
||||||
|
511,ClientConnection,SetHeaderStatic,server/client.go,setHeader,unknown,deferred,Class exists but method not found
|
||||||
|
512,ClientConnection,ProcessServiceImport,server/client.go,processServiceImport,unknown,deferred,Class exists but method not found
|
||||||
|
513,ClientConnection,AddSubToRouteTargets,server/client.go,addSubToRouteTargets,unknown,deferred,Class exists but method not found
|
||||||
|
514,ClientConnection,ProcessMsgResults,server/client.go,processMsgResults,unknown,deferred,Class exists but method not found
|
||||||
|
515,ClientConnection,CheckLeafClientInfoHeader,server/client.go,checkLeafClientInfoHeader,unknown,deferred,Class exists but method not found
|
||||||
|
520,ClientConnection,ProcessPingTimer,server/client.go,processPingTimer,unknown,deferred,Class exists but method not found
|
||||||
|
521,ClientConnection,AdjustPingInterval,server/client.go,adjustPingInterval,unknown,deferred,Class exists but method not found
|
||||||
|
522,ClientConnection,WatchForStaleConnection,server/client.go,watchForStaleConnection,unknown,deferred,Class exists but method not found
|
||||||
|
534,ClientConnection,SwapAccountAfterReload,server/client.go,swapAccountAfterReload,unknown,deferred,Class exists but method not found
|
||||||
|
535,ClientConnection,ProcessSubsOnConfigReload,server/client.go,processSubsOnConfigReload,unknown,deferred,Class exists but method not found
|
||||||
|
537,ClientConnection,Reconnect,server/client.go,reconnect,unknown,deferred,Class exists but method not found
|
||||||
|
540,ClientConnection,GetAccAndResultFromCache,server/client.go,getAccAndResultFromCache,unknown,deferred,Class exists but method not found
|
||||||
|
541,ClientConnection,Account,server/client.go,Account,unknown,deferred,Class exists but method not found
|
||||||
|
542,ClientConnection,PruneClosedSubFromPerAccountCache,server/client.go,pruneClosedSubFromPerAccountCache,unknown,deferred,Class exists but method not found
|
||||||
|
544,ClientConnection,AddServerAndClusterInfo,server/client.go,addServerAndClusterInfo,unknown,deferred,Class exists but method not found
|
||||||
|
545,ClientConnection,GetClientInfo,server/client.go,getClientInfo,unknown,deferred,Class exists but method not found
|
||||||
|
546,ClientConnection,DoTLSServerHandshake,server/client.go,doTLSServerHandshake,unknown,deferred,Class exists but method not found
|
||||||
|
547,ClientConnection,DoTLSClientHandshake,server/client.go,doTLSClientHandshake,unknown,deferred,Class exists but method not found
|
||||||
|
548,ClientConnection,DoTLSHandshake,server/client.go,doTLSHandshake,unknown,deferred,Class exists but method not found
|
||||||
|
553,ClientConnection,ConvertAllowedConnectionTypes,server/client.go,convertAllowedConnectionTypes,unknown,deferred,Class exists but method not found
|
||||||
|
565,ClientConnection,RateLimitErrorf,server/client.go,RateLimitErrorf,unknown,deferred,Class exists but method not found
|
||||||
|
566,ClientConnection,RateLimitFormatWarnf,server/client.go,rateLimitFormatWarnf,unknown,deferred,Class exists but method not found
|
||||||
|
567,ClientConnection,RateLimitWarnf,server/client.go,RateLimitWarnf,unknown,deferred,Class exists but method not found
|
||||||
|
568,ClientConnection,RateLimitDebugf,server/client.go,RateLimitDebugf,unknown,deferred,Class exists but method not found
|
||||||
|
569,ClientConnection,SetFirstPingTimer,server/client.go,setFirstPingTimer,unknown,deferred,Class exists but method not found
|
||||||
|
574,ClientConnection,RemoteAddr,server/client_proxyproto.go,RemoteAddr,unknown,deferred,Class exists but method not found
|
||||||
|
575,ClientConnection,DetectProxyProtoVersion,server/client_proxyproto.go,detectProxyProtoVersion,unknown,deferred,Class exists but method not found
|
||||||
|
576,ClientConnection,ReadProxyProtoV1Header,server/client_proxyproto.go,readProxyProtoV1Header,unknown,deferred,Class exists but method not found
|
||||||
|
577,ClientConnection,ReadProxyProtoHeader,server/client_proxyproto.go,readProxyProtoHeader,unknown,deferred,Class exists but method not found
|
||||||
|
578,ClientConnection,ReadProxyProtoV2Header,server/client_proxyproto.go,readProxyProtoV2Header,unknown,deferred,Class exists but method not found
|
||||||
|
579,ClientConnection,ParseProxyProtoV2Header,server/client_proxyproto.go,parseProxyProtoV2Header,unknown,deferred,Class exists but method not found
|
||||||
|
580,ClientConnection,ParseIPv4Addr,server/client_proxyproto.go,parseIPv4Addr,unknown,deferred,Class exists but method not found
|
||||||
|
581,ClientConnection,ParseIPv6Addr,server/client_proxyproto.go,parseIPv6Addr,unknown,deferred,Class exists but method not found
|
||||||
|
594,SubjectTokens,Subjects,server/consumer.go,subjects,unknown,deferred,Class not found in .NET source
|
||||||
|
595,NatsConsumer,SetConsumerConfigDefaults,server/consumer.go,setConsumerConfigDefaults,unknown,deferred,Class exists but method not found
|
||||||
|
596,NatsConsumer,CheckConsumerCfg,server/consumer.go,checkConsumerCfg,unknown,deferred,Class exists but method not found
|
||||||
|
600,NatsConsumer,UpdateInactiveThreshold,server/consumer.go,updateInactiveThreshold,unknown,deferred,Class exists but method not found
|
||||||
|
601,NatsConsumer,UpdatePauseState,server/consumer.go,updatePauseState,unknown,deferred,Class exists but method not found
|
||||||
|
602,NatsConsumer,ConsumerAssignment,server/consumer.go,consumerAssignment,unknown,deferred,Class exists but method not found
|
||||||
|
603,NatsConsumer,SetConsumerAssignment,server/consumer.go,setConsumerAssignment,unknown,deferred,Class exists but method not found
|
||||||
|
604,NatsConsumer,MonitorQuitC,server/consumer.go,monitorQuitC,unknown,deferred,Class exists but method not found
|
||||||
|
605,NatsConsumer,SignalMonitorQuit,server/consumer.go,signalMonitorQuit,unknown,deferred,Class exists but method not found
|
||||||
|
606,NatsConsumer,UpdateC,server/consumer.go,updateC,unknown,deferred,Class exists but method not found
|
||||||
|
607,NatsConsumer,CheckQueueInterest,server/consumer.go,checkQueueInterest,unknown,deferred,Class exists but method not found
|
||||||
|
608,NatsConsumer,ClearNode,server/consumer.go,clearNode,unknown,deferred,Class exists but method not found
|
||||||
|
610,NatsConsumer,IsLeaderInternal,server/consumer.go,isLeader,unknown,deferred,Class exists but method not found
|
||||||
|
612,NatsConsumer,HandleClusterConsumerInfoRequest,server/consumer.go,handleClusterConsumerInfoRequest,unknown,deferred,Class exists but method not found
|
||||||
|
613,NatsConsumer,SubscribeInternal,server/consumer.go,subscribeInternal,unknown,deferred,Class exists but method not found
|
||||||
|
614,NatsConsumer,Unsubscribe,server/consumer.go,unsubscribe,unknown,deferred,Class exists but method not found
|
||||||
|
615,NatsConsumer,SendAdvisory,server/consumer.go,sendAdvisory,unknown,deferred,Class exists but method not found
|
||||||
|
616,NatsConsumer,SendDeleteAdvisoryLocked,server/consumer.go,sendDeleteAdvisoryLocked,unknown,deferred,Class exists but method not found
|
||||||
|
617,NatsConsumer,SendPinnedAdvisoryLocked,server/consumer.go,sendPinnedAdvisoryLocked,unknown,deferred,Class exists but method not found
|
||||||
|
618,NatsConsumer,SendUnpinnedAdvisoryLocked,server/consumer.go,sendUnpinnedAdvisoryLocked,unknown,deferred,Class exists but method not found
|
||||||
|
619,NatsConsumer,SendCreateAdvisory,server/consumer.go,sendCreateAdvisory,unknown,deferred,Class exists but method not found
|
||||||
|
620,NatsConsumer,SendPauseAdvisoryLocked,server/consumer.go,sendPauseAdvisoryLocked,unknown,deferred,Class exists but method not found
|
||||||
|
621,NatsConsumer,CreatedTime,server/consumer.go,createdTime,unknown,deferred,Class exists but method not found
|
||||||
|
622,NatsConsumer,SetCreatedTime,server/consumer.go,setCreatedTime,unknown,deferred,Class exists but method not found
|
||||||
|
623,NatsConsumer,HasDeliveryInterest,server/consumer.go,hasDeliveryInterest,unknown,deferred,Class exists but method not found
|
||||||
|
625,NatsConsumer,UpdateDeliveryInterest,server/consumer.go,updateDeliveryInterest,unknown,deferred,Class exists but method not found
|
||||||
|
626,NatsConsumer,DeleteNotActive,server/consumer.go,deleteNotActive,unknown,deferred,Class exists but method not found
|
||||||
|
627,NatsConsumer,WatchGWinterest,server/consumer.go,watchGWinterest,unknown,deferred,Class exists but method not found
|
||||||
|
629,NatsConsumer,HasMaxDeliveries,server/consumer.go,hasMaxDeliveries,unknown,deferred,Class exists but method not found
|
||||||
|
630,NatsConsumer,ForceExpirePending,server/consumer.go,forceExpirePending,unknown,deferred,Class exists but method not found
|
||||||
|
631,NatsConsumer,SetRateLimitNeedsLocks,server/consumer.go,setRateLimitNeedsLocks,unknown,deferred,Class exists but method not found
|
||||||
|
632,NatsConsumer,SetRateLimit,server/consumer.go,setRateLimit,unknown,deferred,Class exists but method not found
|
||||||
|
635,NatsConsumer,UpdateDeliverSubject,server/consumer.go,updateDeliverSubject,unknown,deferred,Class exists but method not found
|
||||||
|
636,NatsConsumer,UpdateDeliverSubjectLocked,server/consumer.go,updateDeliverSubjectLocked,unknown,deferred,Class exists but method not found
|
||||||
|
637,NatsConsumer,ConfigsEqualSansDelivery,server/consumer.go,configsEqualSansDelivery,unknown,deferred,Class exists but method not found
|
||||||
|
638,NatsConsumer,SendAckReply,server/consumer.go,sendAckReply,unknown,deferred,Class exists but method not found
|
||||||
|
639,NatsConsumer,NewJSAckMsg,server/consumer.go,newJSAckMsg,unknown,deferred,Class exists but method not found
|
||||||
|
640,JsPubMsg,ReturnToPool,server/consumer.go,returnToPool,unknown,deferred,Class exists but method not found
|
||||||
|
641,NatsConsumer,PushAck,server/consumer.go,pushAck,unknown,deferred,Class exists but method not found
|
||||||
|
642,NatsConsumer,ProcessAck,server/consumer.go,processAck,unknown,deferred,Class exists but method not found
|
||||||
|
643,NatsConsumer,ProgressUpdate,server/consumer.go,progressUpdate,unknown,deferred,Class exists but method not found
|
||||||
|
644,NatsConsumer,UpdateSkipped,server/consumer.go,updateSkipped,unknown,deferred,Class exists but method not found
|
||||||
|
645,NatsConsumer,ResetStartingSeq,server/consumer.go,resetStartingSeq,unknown,deferred,Class exists but method not found
|
||||||
|
646,NatsConsumer,ResetLocalStartingSeq,server/consumer.go,resetLocalStartingSeq,unknown,deferred,Class exists but method not found
|
||||||
|
647,NatsConsumer,LoopAndForwardProposals,server/consumer.go,loopAndForwardProposals,unknown,deferred,Class exists but method not found
|
||||||
|
648,NatsConsumer,Propose,server/consumer.go,propose,unknown,deferred,Class exists but method not found
|
||||||
|
649,NatsConsumer,UpdateDelivered,server/consumer.go,updateDelivered,unknown,deferred,Class exists but method not found
|
||||||
|
650,NatsConsumer,AddAckReply,server/consumer.go,addAckReply,unknown,deferred,Class exists but method not found
|
||||||
|
651,NatsConsumer,AddReplicatedQueuedMsg,server/consumer.go,addReplicatedQueuedMsg,unknown,deferred,Class exists but method not found
|
||||||
|
652,NatsConsumer,UpdateAcks,server/consumer.go,updateAcks,unknown,deferred,Class exists but method not found
|
||||||
|
653,NatsConsumer,AddClusterPendingRequest,server/consumer.go,addClusterPendingRequest,unknown,deferred,Class exists but method not found
|
||||||
|
654,NatsConsumer,RemoveClusterPendingRequest,server/consumer.go,removeClusterPendingRequest,unknown,deferred,Class exists but method not found
|
||||||
|
655,NatsConsumer,SetPendingRequestsOk,server/consumer.go,setPendingRequestsOk,unknown,deferred,Class exists but method not found
|
||||||
|
656,NatsConsumer,PendingRequestsOk,server/consumer.go,pendingRequestsOk,unknown,deferred,Class exists but method not found
|
||||||
|
657,NatsConsumer,CheckAndSetPendingRequestsOk,server/consumer.go,checkAndSetPendingRequestsOk,unknown,deferred,Class exists but method not found
|
||||||
|
658,NatsConsumer,CheckPendingRequests,server/consumer.go,checkPendingRequests,unknown,deferred,Class exists but method not found
|
||||||
|
659,NatsConsumer,ReleaseAnyPendingRequests,server/consumer.go,releaseAnyPendingRequests,unknown,deferred,Class exists but method not found
|
||||||
|
660,NatsConsumer,ProcessNak,server/consumer.go,processNak,unknown,deferred,Class exists but method not found
|
||||||
|
661,NatsConsumer,ProcessTerm,server/consumer.go,processTerm,unknown,deferred,Class exists but method not found
|
||||||
|
662,NatsConsumer,AckWait,server/consumer.go,ackWait,unknown,deferred,Class exists but method not found
|
||||||
|
663,NatsConsumer,CheckRedelivered,server/consumer.go,checkRedelivered,unknown,deferred,Class exists but method not found
|
||||||
|
664,NatsConsumer,ReadStoredState,server/consumer.go,readStoredState,unknown,deferred,Class exists but method not found
|
||||||
|
665,NatsConsumer,ApplyState,server/consumer.go,applyState,unknown,deferred,Class exists but method not found
|
||||||
|
666,NatsConsumer,SetStoreState,server/consumer.go,setStoreState,unknown,deferred,Class exists but method not found
|
||||||
|
667,NatsConsumer,WriteStoreState,server/consumer.go,writeStoreState,unknown,deferred,Class exists but method not found
|
||||||
|
668,NatsConsumer,WriteStoreStateUnlocked,server/consumer.go,writeStoreStateUnlocked,unknown,deferred,Class exists but method not found
|
||||||
|
669,NatsConsumer,InitialInfo,server/consumer.go,initialInfo,unknown,deferred,Class exists but method not found
|
||||||
|
670,NatsConsumer,ClearInitialInfo,server/consumer.go,clearInitialInfo,unknown,deferred,Class exists but method not found
|
||||||
|
671,NatsConsumer,Info,server/consumer.go,info,unknown,deferred,Class exists but method not found
|
||||||
|
672,NatsConsumer,InfoWithSnap,server/consumer.go,infoWithSnap,unknown,deferred,Class exists but method not found
|
||||||
|
673,NatsConsumer,InfoWithSnapAndReply,server/consumer.go,infoWithSnapAndReply,unknown,deferred,Class exists but method not found
|
||||||
|
674,NatsConsumer,SignalNewMessages,server/consumer.go,signalNewMessages,unknown,deferred,Class exists but method not found
|
||||||
|
675,NatsConsumer,ShouldSample,server/consumer.go,shouldSample,unknown,deferred,Class exists but method not found
|
||||||
|
676,NatsConsumer,SampleAck,server/consumer.go,sampleAck,unknown,deferred,Class exists but method not found
|
||||||
|
677,NatsConsumer,ProcessAckMsg,server/consumer.go,processAckMsg,unknown,deferred,Class exists but method not found
|
||||||
|
678,NatsConsumer,IsFiltered,server/consumer.go,isFiltered,unknown,deferred,Class exists but method not found
|
||||||
|
679,NatsConsumer,NeedAck,server/consumer.go,needAck,unknown,deferred,Class exists but method not found
|
||||||
|
680,NatsConsumer,NextReqFromMsg,server/consumer.go,nextReqFromMsg,unknown,deferred,Class exists but method not found
|
||||||
|
681,WaitingRequest,RecycleIfDone,server/consumer.go,recycleIfDone,unknown,deferred,Class exists but method not found
|
||||||
|
682,WaitingRequest,Recycle,server/consumer.go,recycle,unknown,deferred,Class exists but method not found
|
||||||
|
683,WaitingRequest,Recycle,server/consumer.go,recycle,unknown,deferred,Class exists but method not found
|
||||||
|
684,NatsConsumer,NewWaitQueue,server/consumer.go,newWaitQueue,unknown,deferred,Class exists but method not found
|
||||||
|
696,NatsConsumer,InsertAtPosition,server/consumer.go,insertAtPosition,unknown,deferred,Class exists but method not found
|
||||||
|
699,NatsConsumer,PendingRequests,server/consumer.go,pendingRequests,unknown,deferred,Class exists but method not found
|
||||||
|
700,NatsConsumer,SetPinnedTimer,server/consumer.go,setPinnedTimer,unknown,deferred,Class exists but method not found
|
||||||
|
701,NatsConsumer,AssignNewPinId,server/consumer.go,assignNewPinId,unknown,deferred,Class exists but method not found
|
||||||
|
702,NatsConsumer,UnassignPinId,server/consumer.go,unassignPinId,unknown,deferred,Class exists but method not found
|
||||||
|
703,NatsConsumer,NextWaiting,server/consumer.go,nextWaiting,unknown,deferred,Class exists but method not found
|
||||||
|
704,NatsConsumer,NewNextMsgReq,server/consumer.go,newNextMsgReq,unknown,deferred,Class exists but method not found
|
||||||
|
706,NatsConsumer,ProcessNextMsgReq,server/consumer.go,processNextMsgReq,unknown,deferred,Class exists but method not found
|
||||||
|
707,NatsConsumer,ProcessResetReq,server/consumer.go,processResetReq,unknown,deferred,Class exists but method not found
|
||||||
|
708,NatsConsumer,ProcessNextMsgRequest,server/consumer.go,processNextMsgRequest,unknown,deferred,Class exists but method not found
|
||||||
|
709,NatsConsumer,TrackDownAccountAndInterest,server/consumer.go,trackDownAccountAndInterest,unknown,deferred,Class exists but method not found
|
||||||
|
710,NatsConsumer,DeliveryCount,server/consumer.go,deliveryCount,unknown,deferred,Class exists but method not found
|
||||||
|
711,NatsConsumer,IncDeliveryCount,server/consumer.go,incDeliveryCount,unknown,deferred,Class exists but method not found
|
||||||
|
712,NatsConsumer,DecDeliveryCount,server/consumer.go,decDeliveryCount,unknown,deferred,Class exists but method not found
|
||||||
|
713,NatsConsumer,NotifyDeliveryExceeded,server/consumer.go,notifyDeliveryExceeded,unknown,deferred,Class exists but method not found
|
||||||
|
714,NatsConsumer,IsFilteredMatch,server/consumer.go,isFilteredMatch,unknown,deferred,Class exists but method not found
|
||||||
|
715,NatsConsumer,IsEqualOrSubsetMatch,server/consumer.go,isEqualOrSubsetMatch,unknown,deferred,Class exists but method not found
|
||||||
|
716,NatsConsumer,GetNextMsg,server/consumer.go,getNextMsg,unknown,deferred,Class exists but method not found
|
||||||
|
717,NatsConsumer,ProcessWaiting,server/consumer.go,processWaiting,unknown,deferred,Class exists but method not found
|
||||||
|
718,NatsConsumer,CheckWaitingForInterest,server/consumer.go,checkWaitingForInterest,unknown,deferred,Class exists but method not found
|
||||||
|
719,NatsConsumer,HbTimer,server/consumer.go,hbTimer,unknown,deferred,Class exists but method not found
|
||||||
|
720,NatsConsumer,CheckAckFloor,server/consumer.go,checkAckFloor,unknown,deferred,Class exists but method not found
|
||||||
|
721,NatsConsumer,ProcessInboundAcks,server/consumer.go,processInboundAcks,unknown,deferred,Class exists but method not found
|
||||||
|
722,NatsConsumer,ProcessInboundNextMsgReqs,server/consumer.go,processInboundNextMsgReqs,unknown,deferred,Class exists but method not found
|
||||||
|
723,NatsConsumer,SuppressDeletion,server/consumer.go,suppressDeletion,unknown,deferred,Class exists but method not found
|
||||||
|
724,NatsConsumer,LoopAndGatherMsgs,server/consumer.go,loopAndGatherMsgs,unknown,deferred,Class exists but method not found
|
||||||
|
725,NatsConsumer,SendIdleHeartbeat,server/consumer.go,sendIdleHeartbeat,unknown,deferred,Class exists but method not found
|
||||||
|
726,NatsConsumer,AckReply,server/consumer.go,ackReply,unknown,deferred,Class exists but method not found
|
||||||
|
727,NatsConsumer,SetMaxPendingBytes,server/consumer.go,setMaxPendingBytes,unknown,deferred,Class exists but method not found
|
||||||
|
728,NatsConsumer,CheckNumPending,server/consumer.go,checkNumPending,unknown,deferred,Class exists but method not found
|
||||||
|
729,NatsConsumer,NumPending,server/consumer.go,numPending,unknown,deferred,Class exists but method not found
|
||||||
|
730,NatsConsumer,CheckNumPendingOnEOF,server/consumer.go,checkNumPendingOnEOF,unknown,deferred,Class exists but method not found
|
||||||
|
731,NatsConsumer,StreamNumPendingLocked,server/consumer.go,streamNumPendingLocked,unknown,deferred,Class exists but method not found
|
||||||
|
732,NatsConsumer,StreamNumPending,server/consumer.go,streamNumPending,unknown,deferred,Class exists but method not found
|
||||||
|
733,NatsConsumer,CalculateNumPending,server/consumer.go,calculateNumPending,unknown,deferred,Class exists but method not found
|
||||||
|
734,NatsConsumer,ConvertToHeadersOnly,server/consumer.go,convertToHeadersOnly,unknown,deferred,Class exists but method not found
|
||||||
|
735,NatsConsumer,DeliverMsg,server/consumer.go,deliverMsg,unknown,deferred,Class exists but method not found
|
||||||
|
736,NatsConsumer,ReplicateDeliveries,server/consumer.go,replicateDeliveries,unknown,deferred,Class exists but method not found
|
||||||
|
737,NatsConsumer,NeedFlowControl,server/consumer.go,needFlowControl,unknown,deferred,Class exists but method not found
|
||||||
|
738,NatsConsumer,ProcessFlowControl,server/consumer.go,processFlowControl,unknown,deferred,Class exists but method not found
|
||||||
|
739,NatsConsumer,FcReply,server/consumer.go,fcReply,unknown,deferred,Class exists but method not found
|
||||||
|
740,NatsConsumer,SendFlowControl,server/consumer.go,sendFlowControl,unknown,deferred,Class exists but method not found
|
||||||
|
741,NatsConsumer,TrackPending,server/consumer.go,trackPending,unknown,deferred,Class exists but method not found
|
||||||
|
742,NatsConsumer,CreditWaitingRequest,server/consumer.go,creditWaitingRequest,unknown,deferred,Class exists but method not found
|
||||||
|
743,NatsConsumer,DidNotDeliver,server/consumer.go,didNotDeliver,unknown,deferred,Class exists but method not found
|
||||||
|
744,NatsConsumer,AddToRedeliverQueue,server/consumer.go,addToRedeliverQueue,unknown,deferred,Class exists but method not found
|
||||||
|
745,NatsConsumer,HasRedeliveries,server/consumer.go,hasRedeliveries,unknown,deferred,Class exists but method not found
|
||||||
|
746,NatsConsumer,GetNextToRedeliver,server/consumer.go,getNextToRedeliver,unknown,deferred,Class exists but method not found
|
||||||
|
747,NatsConsumer,OnRedeliverQueue,server/consumer.go,onRedeliverQueue,unknown,deferred,Class exists but method not found
|
||||||
|
748,NatsConsumer,RemoveFromRedeliverQueue,server/consumer.go,removeFromRedeliverQueue,unknown,deferred,Class exists but method not found
|
||||||
|
749,NatsConsumer,CheckPending,server/consumer.go,checkPending,unknown,deferred,Class exists but method not found
|
||||||
|
750,NatsConsumer,SeqFromReply,server/consumer.go,seqFromReply,unknown,deferred,Class exists but method not found
|
||||||
|
751,NatsConsumer,StreamSeqFromReply,server/consumer.go,streamSeqFromReply,unknown,deferred,Class exists but method not found
|
||||||
|
752,NatsConsumer,ParseAckReplyNum,server/consumer.go,parseAckReplyNum,unknown,deferred,Class exists but method not found
|
||||||
|
753,NatsConsumer,ReplyInfo,server/consumer.go,replyInfo,unknown,deferred,Class exists but method not found
|
||||||
|
754,NatsConsumer,AckReplyInfo,server/consumer.go,ackReplyInfo,unknown,deferred,Class exists but method not found
|
||||||
|
755,NatsConsumer,NextSeq,server/consumer.go,nextSeq,unknown,deferred,Class exists but method not found
|
||||||
|
756,NatsConsumer,HasSkipListPending,server/consumer.go,hasSkipListPending,unknown,deferred,Class exists but method not found
|
||||||
|
757,NatsConsumer,SelectStartingSeqNo,server/consumer.go,selectStartingSeqNo,unknown,deferred,Class exists but method not found
|
||||||
|
758,NatsConsumer,IsDurableConsumer,server/consumer.go,isDurableConsumer,unknown,deferred,Class exists but method not found
|
||||||
|
759,NatsConsumer,IsDurable,server/consumer.go,isDurable,unknown,deferred,Class exists but method not found
|
||||||
|
760,NatsConsumer,IsPushMode,server/consumer.go,isPushMode,unknown,deferred,Class exists but method not found
|
||||||
|
761,NatsConsumer,IsPullMode,server/consumer.go,isPullMode,unknown,deferred,Class exists but method not found
|
||||||
|
762,NatsConsumer,String,server/consumer.go,String,unknown,deferred,Class exists but method not found
|
||||||
|
763,NatsConsumer,CreateConsumerName,server/consumer.go,createConsumerName,unknown,deferred,Class exists but method not found
|
||||||
|
765,NatsConsumer,GetStream,server/consumer.go,getStream,unknown,deferred,Class exists but method not found
|
||||||
|
766,NatsConsumer,StreamName,server/consumer.go,streamName,unknown,deferred,Class exists but method not found
|
||||||
|
767,NatsConsumer,IsActive,server/consumer.go,isActive,unknown,deferred,Class exists but method not found
|
||||||
|
768,NatsConsumer,HasNoLocalInterest,server/consumer.go,hasNoLocalInterest,unknown,deferred,Class exists but method not found
|
||||||
|
769,NatsConsumer,Purge,server/consumer.go,purge,unknown,deferred,Class exists but method not found
|
||||||
|
770,NatsConsumer,StopAndClearTimer,server/consumer.go,stopAndClearTimer,unknown,deferred,Class exists but method not found
|
||||||
|
772,NatsConsumer,DeleteWithoutAdvisory,server/consumer.go,deleteWithoutAdvisory,unknown,deferred,Class exists but method not found
|
||||||
|
774,NatsConsumer,IsClosed,server/consumer.go,isClosed,unknown,deferred,Class exists but method not found
|
||||||
|
775,NatsConsumer,StopWithFlags,server/consumer.go,stopWithFlags,unknown,deferred,Class exists but method not found
|
||||||
|
776,NatsConsumer,CleanupNoInterestMessages,server/consumer.go,cleanupNoInterestMessages,unknown,deferred,Class exists but method not found
|
||||||
|
777,NatsConsumer,DeliveryFormsCycle,server/consumer.go,deliveryFormsCycle,unknown,deferred,Class exists but method not found
|
||||||
|
778,NatsConsumer,SwitchToEphemeral,server/consumer.go,switchToEphemeral,unknown,deferred,Class exists but method not found
|
||||||
|
779,NatsConsumer,RequestNextMsgSubject,server/consumer.go,requestNextMsgSubject,unknown,deferred,Class exists but method not found
|
||||||
|
780,NatsConsumer,DecStreamPending,server/consumer.go,decStreamPending,unknown,deferred,Class exists but method not found
|
||||||
|
781,NatsConsumer,Account,server/consumer.go,account,unknown,deferred,Class exists but method not found
|
||||||
|
782,NatsConsumer,SignalSubs,server/consumer.go,signalSubs,unknown,deferred,Class exists but method not found
|
||||||
|
783,NatsConsumer,ProcessStreamSignal,server/consumer.go,processStreamSignal,unknown,deferred,Class exists but method not found
|
||||||
|
784,NatsConsumer,SubjectSliceEqual,server/consumer.go,subjectSliceEqual,unknown,deferred,Class exists but method not found
|
||||||
|
785,NatsConsumer,GatherSubjectFilters,server/consumer.go,gatherSubjectFilters,unknown,deferred,Class exists but method not found
|
||||||
|
786,NatsConsumer,ShouldStartMonitor,server/consumer.go,shouldStartMonitor,unknown,deferred,Class exists but method not found
|
||||||
|
787,NatsConsumer,ClearMonitorRunning,server/consumer.go,clearMonitorRunning,unknown,deferred,Class exists but method not found
|
||||||
|
788,NatsConsumer,IsMonitorRunning,server/consumer.go,isMonitorRunning,unknown,deferred,Class exists but method not found
|
||||||
|
789,NatsConsumer,CheckStateForInterestStream,server/consumer.go,checkStateForInterestStream,unknown,deferred,Class exists but method not found
|
||||||
|
790,NatsConsumer,ResetPtmr,server/consumer.go,resetPtmr,unknown,deferred,Class exists but method not found
|
||||||
|
791,NatsConsumer,StopAndClearPtmr,server/consumer.go,stopAndClearPtmr,unknown,deferred,Class exists but method not found
|
||||||
|
792,NatsConsumer,ResetPendingDeliveries,server/consumer.go,resetPendingDeliveries,unknown,deferred,Class exists but method not found
|
||||||
|
793,DirJwtStore,ValidatePathExists,server/dirstore.go,validatePathExists,unknown,deferred,Class exists but method not found
|
||||||
|
794,DirJwtStore,ValidateDirPath,server/dirstore.go,validateDirPath,unknown,deferred,Class exists but method not found
|
||||||
|
795,DirJwtStore,NewDir,server/dirstore.go,newDir,unknown,verified,Method found with 6 statement(s) at DirJwtStore.cs:842
|
||||||
|
796,DirJwtStore,NewImmutableDirJWTStore,server/dirstore.go,NewImmutableDirJWTStore,unknown,verified,Method found with 2 statement(s) at DirJwtStore.cs:103
|
||||||
|
797,DirJwtStore,NewDirJWTStore,server/dirstore.go,NewDirJWTStore,unknown,verified,Method found with 2 statement(s) at DirJwtStore.cs:118
|
||||||
|
798,DirJwtStore,NewExpiringDirJWTStore,server/dirstore.go,NewExpiringDirJWTStore,unknown,verified,Method found with 7 statement(s) at DirJwtStore.cs:152
|
||||||
|
815,DirJwtStore,XorAssign,server/dirstore.go,xorAssign,unknown,verified,Method found with 1 statement(s) at DirJwtStore.cs:1025
|
||||||
|
848,ServerErrors,PanicIfErr,server/errors_gen.go,panicIfErr,unknown,deferred,Class not found in .NET source
|
||||||
|
849,ServerErrors,GoFmt,server/errors_gen.go,goFmt,unknown,deferred,Class not found in .NET source
|
||||||
|
850,ServerErrors,CheckIncrements,server/errors_gen.go,checkIncrements,unknown,deferred,Class not found in .NET source
|
||||||
|
851,ServerErrors,CheckDupes,server/errors_gen.go,checkDupes,unknown,deferred,Class not found in .NET source
|
||||||
|
852,ServerErrors,FindTags,server/errors_gen.go,findTags,unknown,deferred,Class not found in .NET source
|
||||||
|
853,ServerErrors,Main,server/errors_gen.go,main,unknown,deferred,Class not found in .NET source
|
||||||
|
875,ClientConnection,SendInternalMsg,server/events.go,sendInternalMsg,unknown,deferred,Class exists but method not found
|
||||||
|
1273,SrvGateway,UpdateRemotesTLSConfig,server/gateway.go,updateRemotesTLSConfig,unknown,deferred,Class exists but method not found
|
||||||
|
1274,SrvGateway,RejectUnknown,server/gateway.go,rejectUnknown,unknown,verified,Method found with 1 statement(s) at GatewayTypes.cs:107
|
||||||
|
1278,SrvGateway,GenerateInfoJSON,server/gateway.go,generateInfoJSON,unknown,deferred,Class exists but method not found
|
||||||
|
1282,SrvGateway,HasInbound,server/gateway.go,hasInbound,unknown,deferred,Class exists but method not found
|
||||||
|
1284,ClientConnection,SendGatewayConnect,server/gateway.go,sendGatewayConnect,unknown,deferred,Class exists but method not found
|
||||||
|
1285,ClientConnection,ProcessGatewayConnect,server/gateway.go,processGatewayConnect,unknown,deferred,Class exists but method not found
|
||||||
|
1286,ClientConnection,ProcessGatewayInfo,server/gateway.go,processGatewayInfo,unknown,deferred,Class exists but method not found
|
||||||
|
1319,SrvGateway,OrderOutboundConnectionsLocked,server/gateway.go,orderOutboundConnectionsLocked,unknown,deferred,Class exists but method not found
|
||||||
|
1320,SrvGateway,OrderOutboundConnections,server/gateway.go,orderOutboundConnections,unknown,deferred,Class exists but method not found
|
||||||
|
1324,ClientConnection,ProcessGatewayAccountUnsub,server/gateway.go,processGatewayAccountUnsub,unknown,deferred,Class exists but method not found
|
||||||
|
1325,ClientConnection,ProcessGatewayAccountSub,server/gateway.go,processGatewayAccountSub,unknown,deferred,Class exists but method not found
|
||||||
|
1326,ClientConnection,ProcessGatewayRUnsub,server/gateway.go,processGatewayRUnsub,unknown,deferred,Class exists but method not found
|
||||||
|
1327,ClientConnection,ProcessGatewayRSub,server/gateway.go,processGatewayRSub,unknown,deferred,Class exists but method not found
|
||||||
|
1328,ClientConnection,GatewayInterest,server/gateway.go,gatewayInterest,unknown,deferred,Class exists but method not found
|
||||||
|
1336,SrvGateway,ShouldMapReplyForGatewaySend,server/gateway.go,shouldMapReplyForGatewaySend,unknown,deferred,Class exists but method not found
|
||||||
|
1337,ClientConnection,SendMsgToGateways,server/gateway.go,sendMsgToGateways,unknown,deferred,Class exists but method not found
|
||||||
|
1339,ClientConnection,SendAccountUnsubToGateway,server/gateway.go,sendAccountUnsubToGateway,unknown,deferred,Class exists but method not found
|
||||||
|
1341,SrvGateway,GetClusterHash,server/gateway.go,getClusterHash,unknown,deferred,Class exists but method not found
|
||||||
|
1346,ClientConnection,HandleGatewayReply,server/gateway.go,handleGatewayReply,unknown,deferred,Class exists but method not found
|
||||||
|
1347,ClientConnection,ProcessInboundGatewayMsg,server/gateway.go,processInboundGatewayMsg,unknown,deferred,Class exists but method not found
|
||||||
|
1348,ClientConnection,GatewayAllSubsReceiveStart,server/gateway.go,gatewayAllSubsReceiveStart,unknown,deferred,Class exists but method not found
|
||||||
|
1349,ClientConnection,GatewayAllSubsReceiveComplete,server/gateway.go,gatewayAllSubsReceiveComplete,unknown,deferred,Class exists but method not found
|
||||||
|
1351,ClientConnection,GatewaySwitchAccountToSendAllSubs,server/gateway.go,gatewaySwitchAccountToSendAllSubs,unknown,deferred,Class exists but method not found
|
||||||
|
1640,NatsConsumer,StreamAndNode,server/jetstream_cluster.go,streamAndNode,unknown,deferred,Class exists but method not found
|
||||||
|
1641,NatsConsumer,Replica,server/jetstream_cluster.go,replica,unknown,deferred,Class exists but method not found
|
||||||
|
1642,NatsConsumer,RaftGroup,server/jetstream_cluster.go,raftGroup,unknown,deferred,Class exists but method not found
|
||||||
|
1643,NatsConsumer,ClearRaftNode,server/jetstream_cluster.go,clearRaftNode,unknown,deferred,Class exists but method not found
|
||||||
|
1644,NatsConsumer,RaftNode,server/jetstream_cluster.go,raftNode,unknown,deferred,Class exists but method not found
|
||||||
|
1647,NatsConsumer,ProcessReplicatedAck,server/jetstream_cluster.go,processReplicatedAck,unknown,deferred,Class exists but method not found
|
||||||
|
1651,NatsConsumer,ShouldSendLostQuorum,server/jetstream_cluster.go,shouldSendLostQuorum,unknown,deferred,Class exists but method not found
|
||||||
|
1751,JsApiErrors,Unless,server/jetstream_errors.go,Unless,unknown,deferred,Class exists but method not found
|
||||||
|
1752,JsApiErrors,ParseOpts,server/jetstream_errors.go,parseOpts,unknown,deferred,Class exists but method not found
|
||||||
|
1753,JsApiErrors,IsNatsErr,server/jetstream_errors.go,IsNatsErr,unknown,deferred,Class exists but method not found
|
||||||
|
1754,JsApiErrors,Error,server/jetstream_errors.go,Error,unknown,deferred,Class exists but method not found
|
||||||
|
1755,JsApiErrors,ToReplacerArgs,server/jetstream_errors.go,toReplacerArgs,unknown,deferred,Class exists but method not found
|
||||||
|
1756,JsApiErrors,NewJSAccountResourcesExceededError,server/jetstream_errors_generated.go,NewJSAccountResourcesExceededError,unknown,deferred,Class exists but method not found
|
||||||
|
1757,JsApiErrors,NewJSAtomicPublishContainsDuplicateMessageError,server/jetstream_errors_generated.go,NewJSAtomicPublishContainsDuplicateMessageError,unknown,deferred,Class exists but method not found
|
||||||
|
1758,JsApiErrors,NewJSAtomicPublishDisabledError,server/jetstream_errors_generated.go,NewJSAtomicPublishDisabledError,unknown,deferred,Class exists but method not found
|
||||||
|
1759,JsApiErrors,NewJSAtomicPublishIncompleteBatchError,server/jetstream_errors_generated.go,NewJSAtomicPublishIncompleteBatchError,unknown,deferred,Class exists but method not found
|
||||||
|
1760,JsApiErrors,NewJSAtomicPublishInvalidBatchCommitError,server/jetstream_errors_generated.go,NewJSAtomicPublishInvalidBatchCommitError,unknown,deferred,Class exists but method not found
|
||||||
|
1761,JsApiErrors,NewJSAtomicPublishInvalidBatchIDError,server/jetstream_errors_generated.go,NewJSAtomicPublishInvalidBatchIDError,unknown,deferred,Class exists but method not found
|
||||||
|
1762,JsApiErrors,NewJSAtomicPublishMissingSeqError,server/jetstream_errors_generated.go,NewJSAtomicPublishMissingSeqError,unknown,deferred,Class exists but method not found
|
||||||
|
1763,JsApiErrors,NewJSAtomicPublishTooLargeBatchError,server/jetstream_errors_generated.go,NewJSAtomicPublishTooLargeBatchError,unknown,deferred,Class exists but method not found
|
||||||
|
1764,JsApiErrors,NewJSAtomicPublishUnsupportedHeaderBatchError,server/jetstream_errors_generated.go,NewJSAtomicPublishUnsupportedHeaderBatchError,unknown,deferred,Class exists but method not found
|
||||||
|
1765,JsApiErrors,NewJSBadRequestError,server/jetstream_errors_generated.go,NewJSBadRequestError,unknown,deferred,Class exists but method not found
|
||||||
|
1766,JsApiErrors,NewJSClusterIncompleteError,server/jetstream_errors_generated.go,NewJSClusterIncompleteError,unknown,deferred,Class exists but method not found
|
||||||
|
1767,JsApiErrors,NewJSClusterNoPeersError,server/jetstream_errors_generated.go,NewJSClusterNoPeersError,unknown,deferred,Class exists but method not found
|
||||||
|
1768,JsApiErrors,NewJSClusterNotActiveError,server/jetstream_errors_generated.go,NewJSClusterNotActiveError,unknown,deferred,Class exists but method not found
|
||||||
|
1769,JsApiErrors,NewJSClusterNotAssignedError,server/jetstream_errors_generated.go,NewJSClusterNotAssignedError,unknown,deferred,Class exists but method not found
|
||||||
|
1770,JsApiErrors,NewJSClusterNotAvailError,server/jetstream_errors_generated.go,NewJSClusterNotAvailError,unknown,deferred,Class exists but method not found
|
||||||
|
1771,JsApiErrors,NewJSClusterNotLeaderError,server/jetstream_errors_generated.go,NewJSClusterNotLeaderError,unknown,deferred,Class exists but method not found
|
||||||
|
1772,JsApiErrors,NewJSClusterPeerNotMemberError,server/jetstream_errors_generated.go,NewJSClusterPeerNotMemberError,unknown,deferred,Class exists but method not found
|
||||||
|
1773,JsApiErrors,NewJSClusterRequiredError,server/jetstream_errors_generated.go,NewJSClusterRequiredError,unknown,deferred,Class exists but method not found
|
||||||
|
1774,JsApiErrors,NewJSClusterServerMemberChangeInflightError,server/jetstream_errors_generated.go,NewJSClusterServerMemberChangeInflightError,unknown,deferred,Class exists but method not found
|
||||||
|
1775,JsApiErrors,NewJSClusterServerNotMemberError,server/jetstream_errors_generated.go,NewJSClusterServerNotMemberError,unknown,deferred,Class exists but method not found
|
||||||
|
1776,JsApiErrors,NewJSClusterTagsError,server/jetstream_errors_generated.go,NewJSClusterTagsError,unknown,deferred,Class exists but method not found
|
||||||
|
1777,JsApiErrors,NewJSClusterUnSupportFeatureError,server/jetstream_errors_generated.go,NewJSClusterUnSupportFeatureError,unknown,deferred,Class exists but method not found
|
||||||
|
1778,JsApiErrors,NewJSConsumerAckPolicyInvalidError,server/jetstream_errors_generated.go,NewJSConsumerAckPolicyInvalidError,unknown,deferred,Class exists but method not found
|
||||||
|
1779,JsApiErrors,NewJSConsumerAckWaitNegativeError,server/jetstream_errors_generated.go,NewJSConsumerAckWaitNegativeError,unknown,deferred,Class exists but method not found
|
||||||
|
1780,JsApiErrors,NewJSConsumerAlreadyExistsError,server/jetstream_errors_generated.go,NewJSConsumerAlreadyExistsError,unknown,deferred,Class exists but method not found
|
||||||
|
1781,JsApiErrors,NewJSConsumerBackOffNegativeError,server/jetstream_errors_generated.go,NewJSConsumerBackOffNegativeError,unknown,deferred,Class exists but method not found
|
||||||
|
1782,JsApiErrors,NewJSConsumerBadDurableNameError,server/jetstream_errors_generated.go,NewJSConsumerBadDurableNameError,unknown,deferred,Class exists but method not found
|
||||||
|
1783,JsApiErrors,NewJSConsumerConfigRequiredError,server/jetstream_errors_generated.go,NewJSConsumerConfigRequiredError,unknown,deferred,Class exists but method not found
|
||||||
|
1784,JsApiErrors,NewJSConsumerCreateDurableAndNameMismatchError,server/jetstream_errors_generated.go,NewJSConsumerCreateDurableAndNameMismatchError,unknown,deferred,Class exists but method not found
|
||||||
|
1785,JsApiErrors,NewJSConsumerCreateError,server/jetstream_errors_generated.go,NewJSConsumerCreateError,unknown,deferred,Class exists but method not found
|
||||||
|
1786,JsApiErrors,NewJSConsumerCreateFilterSubjectMismatchError,server/jetstream_errors_generated.go,NewJSConsumerCreateFilterSubjectMismatchError,unknown,deferred,Class exists but method not found
|
||||||
|
1787,JsApiErrors,NewJSConsumerDeliverCycleError,server/jetstream_errors_generated.go,NewJSConsumerDeliverCycleError,unknown,deferred,Class exists but method not found
|
||||||
|
1788,JsApiErrors,NewJSConsumerDeliverToWildcardsError,server/jetstream_errors_generated.go,NewJSConsumerDeliverToWildcardsError,unknown,deferred,Class exists but method not found
|
||||||
|
1789,JsApiErrors,NewJSConsumerDescriptionTooLongError,server/jetstream_errors_generated.go,NewJSConsumerDescriptionTooLongError,unknown,deferred,Class exists but method not found
|
||||||
|
1790,JsApiErrors,NewJSConsumerDirectRequiresEphemeralError,server/jetstream_errors_generated.go,NewJSConsumerDirectRequiresEphemeralError,unknown,deferred,Class exists but method not found
|
||||||
|
1791,JsApiErrors,NewJSConsumerDirectRequiresPushError,server/jetstream_errors_generated.go,NewJSConsumerDirectRequiresPushError,unknown,deferred,Class exists but method not found
|
||||||
|
1792,JsApiErrors,NewJSConsumerDoesNotExistError,server/jetstream_errors_generated.go,NewJSConsumerDoesNotExistError,unknown,deferred,Class exists but method not found
|
||||||
|
1793,JsApiErrors,NewJSConsumerDuplicateFilterSubjectsError,server/jetstream_errors_generated.go,NewJSConsumerDuplicateFilterSubjectsError,unknown,deferred,Class exists but method not found
|
||||||
|
1794,JsApiErrors,NewJSConsumerDurableNameNotInSubjectError,server/jetstream_errors_generated.go,NewJSConsumerDurableNameNotInSubjectError,unknown,deferred,Class exists but method not found
|
||||||
|
1795,JsApiErrors,NewJSConsumerDurableNameNotMatchSubjectError,server/jetstream_errors_generated.go,NewJSConsumerDurableNameNotMatchSubjectError,unknown,deferred,Class exists but method not found
|
||||||
|
1796,JsApiErrors,NewJSConsumerDurableNameNotSetError,server/jetstream_errors_generated.go,NewJSConsumerDurableNameNotSetError,unknown,deferred,Class exists but method not found
|
||||||
|
1797,JsApiErrors,NewJSConsumerEmptyFilterError,server/jetstream_errors_generated.go,NewJSConsumerEmptyFilterError,unknown,deferred,Class exists but method not found
|
||||||
|
1798,JsApiErrors,NewJSConsumerEmptyGroupNameError,server/jetstream_errors_generated.go,NewJSConsumerEmptyGroupNameError,unknown,deferred,Class exists but method not found
|
||||||
|
1799,JsApiErrors,NewJSConsumerEphemeralWithDurableInSubjectError,server/jetstream_errors_generated.go,NewJSConsumerEphemeralWithDurableInSubjectError,unknown,deferred,Class exists but method not found
|
||||||
|
1800,JsApiErrors,NewJSConsumerEphemeralWithDurableNameError,server/jetstream_errors_generated.go,NewJSConsumerEphemeralWithDurableNameError,unknown,deferred,Class exists but method not found
|
||||||
|
1801,JsApiErrors,NewJSConsumerExistingActiveError,server/jetstream_errors_generated.go,NewJSConsumerExistingActiveError,unknown,deferred,Class exists but method not found
|
||||||
|
1802,JsApiErrors,NewJSConsumerFCRequiresPushError,server/jetstream_errors_generated.go,NewJSConsumerFCRequiresPushError,unknown,deferred,Class exists but method not found
|
||||||
|
1803,JsApiErrors,NewJSConsumerFilterNotSubsetError,server/jetstream_errors_generated.go,NewJSConsumerFilterNotSubsetError,unknown,deferred,Class exists but method not found
|
||||||
|
1804,JsApiErrors,NewJSConsumerHBRequiresPushError,server/jetstream_errors_generated.go,NewJSConsumerHBRequiresPushError,unknown,deferred,Class exists but method not found
|
||||||
|
1805,JsApiErrors,NewJSConsumerInactiveThresholdExcessError,server/jetstream_errors_generated.go,NewJSConsumerInactiveThresholdExcessError,unknown,deferred,Class exists but method not found
|
||||||
|
1806,JsApiErrors,NewJSConsumerInvalidDeliverSubjectError,server/jetstream_errors_generated.go,NewJSConsumerInvalidDeliverSubjectError,unknown,deferred,Class exists but method not found
|
||||||
|
1807,JsApiErrors,NewJSConsumerInvalidGroupNameError,server/jetstream_errors_generated.go,NewJSConsumerInvalidGroupNameError,unknown,deferred,Class exists but method not found
|
||||||
|
1808,JsApiErrors,NewJSConsumerInvalidPolicyError,server/jetstream_errors_generated.go,NewJSConsumerInvalidPolicyError,unknown,deferred,Class exists but method not found
|
||||||
|
1809,JsApiErrors,NewJSConsumerInvalidPriorityGroupError,server/jetstream_errors_generated.go,NewJSConsumerInvalidPriorityGroupError,unknown,deferred,Class exists but method not found
|
||||||
|
1810,JsApiErrors,NewJSConsumerInvalidResetError,server/jetstream_errors_generated.go,NewJSConsumerInvalidResetError,unknown,deferred,Class exists but method not found
|
||||||
|
1811,JsApiErrors,NewJSConsumerInvalidSamplingError,server/jetstream_errors_generated.go,NewJSConsumerInvalidSamplingError,unknown,deferred,Class exists but method not found
|
||||||
|
1812,JsApiErrors,NewJSConsumerMaxDeliverBackoffError,server/jetstream_errors_generated.go,NewJSConsumerMaxDeliverBackoffError,unknown,deferred,Class exists but method not found
|
||||||
|
1813,JsApiErrors,NewJSConsumerMaxPendingAckExcessError,server/jetstream_errors_generated.go,NewJSConsumerMaxPendingAckExcessError,unknown,deferred,Class exists but method not found
|
||||||
|
1814,JsApiErrors,NewJSConsumerMaxPendingAckPolicyRequiredError,server/jetstream_errors_generated.go,NewJSConsumerMaxPendingAckPolicyRequiredError,unknown,deferred,Class exists but method not found
|
||||||
|
1815,JsApiErrors,NewJSConsumerMaxRequestBatchExceededError,server/jetstream_errors_generated.go,NewJSConsumerMaxRequestBatchExceededError,unknown,deferred,Class exists but method not found
|
||||||
|
1816,JsApiErrors,NewJSConsumerMaxRequestBatchNegativeError,server/jetstream_errors_generated.go,NewJSConsumerMaxRequestBatchNegativeError,unknown,deferred,Class exists but method not found
|
||||||
|
1817,JsApiErrors,NewJSConsumerMaxRequestExpiresTooSmallError,server/jetstream_errors_generated.go,NewJSConsumerMaxRequestExpiresTooSmallError,unknown,deferred,Class exists but method not found
|
||||||
|
1818,JsApiErrors,NewJSConsumerMaxWaitingNegativeError,server/jetstream_errors_generated.go,NewJSConsumerMaxWaitingNegativeError,unknown,deferred,Class exists but method not found
|
||||||
|
1819,JsApiErrors,NewJSConsumerMetadataLengthError,server/jetstream_errors_generated.go,NewJSConsumerMetadataLengthError,unknown,deferred,Class exists but method not found
|
||||||
|
1820,JsApiErrors,NewJSConsumerMultipleFiltersNotAllowedError,server/jetstream_errors_generated.go,NewJSConsumerMultipleFiltersNotAllowedError,unknown,deferred,Class exists but method not found
|
||||||
|
1821,JsApiErrors,NewJSConsumerNameContainsPathSeparatorsError,server/jetstream_errors_generated.go,NewJSConsumerNameContainsPathSeparatorsError,unknown,deferred,Class exists but method not found
|
||||||
|
1822,JsApiErrors,NewJSConsumerNameExistError,server/jetstream_errors_generated.go,NewJSConsumerNameExistError,unknown,deferred,Class exists but method not found
|
||||||
|
1823,JsApiErrors,NewJSConsumerNameTooLongError,server/jetstream_errors_generated.go,NewJSConsumerNameTooLongError,unknown,deferred,Class exists but method not found
|
||||||
|
1824,JsApiErrors,NewJSConsumerNotFoundError,server/jetstream_errors_generated.go,NewJSConsumerNotFoundError,unknown,deferred,Class exists but method not found
|
||||||
|
1825,JsApiErrors,NewJSConsumerOfflineError,server/jetstream_errors_generated.go,NewJSConsumerOfflineError,unknown,deferred,Class exists but method not found
|
||||||
|
1826,JsApiErrors,NewJSConsumerOfflineReasonError,server/jetstream_errors_generated.go,NewJSConsumerOfflineReasonError,unknown,deferred,Class exists but method not found
|
||||||
|
1827,JsApiErrors,NewJSConsumerOnMappedError,server/jetstream_errors_generated.go,NewJSConsumerOnMappedError,unknown,deferred,Class exists but method not found
|
||||||
|
1828,JsApiErrors,NewJSConsumerOverlappingSubjectFiltersError,server/jetstream_errors_generated.go,NewJSConsumerOverlappingSubjectFiltersError,unknown,deferred,Class exists but method not found
|
||||||
|
1829,JsApiErrors,NewJSConsumerPinnedTTLWithoutPriorityPolicyNoneError,server/jetstream_errors_generated.go,NewJSConsumerPinnedTTLWithoutPriorityPolicyNoneError,unknown,deferred,Class exists but method not found
|
||||||
|
1830,JsApiErrors,NewJSConsumerPriorityGroupWithPolicyNoneError,server/jetstream_errors_generated.go,NewJSConsumerPriorityGroupWithPolicyNoneError,unknown,deferred,Class exists but method not found
|
||||||
|
1831,JsApiErrors,NewJSConsumerPriorityPolicyWithoutGroupError,server/jetstream_errors_generated.go,NewJSConsumerPriorityPolicyWithoutGroupError,unknown,deferred,Class exists but method not found
|
||||||
|
1832,JsApiErrors,NewJSConsumerPullNotDurableError,server/jetstream_errors_generated.go,NewJSConsumerPullNotDurableError,unknown,deferred,Class exists but method not found
|
||||||
|
1833,JsApiErrors,NewJSConsumerPullRequiresAckError,server/jetstream_errors_generated.go,NewJSConsumerPullRequiresAckError,unknown,deferred,Class exists but method not found
|
||||||
|
1834,JsApiErrors,NewJSConsumerPullWithRateLimitError,server/jetstream_errors_generated.go,NewJSConsumerPullWithRateLimitError,unknown,deferred,Class exists but method not found
|
||||||
|
1835,JsApiErrors,NewJSConsumerPushMaxWaitingError,server/jetstream_errors_generated.go,NewJSConsumerPushMaxWaitingError,unknown,deferred,Class exists but method not found
|
||||||
|
1836,JsApiErrors,NewJSConsumerPushWithPriorityGroupError,server/jetstream_errors_generated.go,NewJSConsumerPushWithPriorityGroupError,unknown,deferred,Class exists but method not found
|
||||||
|
1837,JsApiErrors,NewJSConsumerReplacementWithDifferentNameError,server/jetstream_errors_generated.go,NewJSConsumerReplacementWithDifferentNameError,unknown,deferred,Class exists but method not found
|
||||||
|
1838,JsApiErrors,NewJSConsumerReplayPolicyInvalidError,server/jetstream_errors_generated.go,NewJSConsumerReplayPolicyInvalidError,unknown,deferred,Class exists but method not found
|
||||||
|
1839,JsApiErrors,NewJSConsumerReplicasExceedsStreamError,server/jetstream_errors_generated.go,NewJSConsumerReplicasExceedsStreamError,unknown,deferred,Class exists but method not found
|
||||||
|
1840,JsApiErrors,NewJSConsumerReplicasShouldMatchStreamError,server/jetstream_errors_generated.go,NewJSConsumerReplicasShouldMatchStreamError,unknown,deferred,Class exists but method not found
|
||||||
|
1841,JsApiErrors,NewJSConsumerSmallHeartbeatError,server/jetstream_errors_generated.go,NewJSConsumerSmallHeartbeatError,unknown,deferred,Class exists but method not found
|
||||||
|
1842,JsApiErrors,NewJSConsumerStoreFailedError,server/jetstream_errors_generated.go,NewJSConsumerStoreFailedError,unknown,deferred,Class exists but method not found
|
||||||
|
1843,JsApiErrors,NewJSConsumerWQConsumerNotDeliverAllError,server/jetstream_errors_generated.go,NewJSConsumerWQConsumerNotDeliverAllError,unknown,deferred,Class exists but method not found
|
||||||
|
1844,JsApiErrors,NewJSConsumerWQConsumerNotUniqueError,server/jetstream_errors_generated.go,NewJSConsumerWQConsumerNotUniqueError,unknown,deferred,Class exists but method not found
|
||||||
|
1845,JsApiErrors,NewJSConsumerWQMultipleUnfilteredError,server/jetstream_errors_generated.go,NewJSConsumerWQMultipleUnfilteredError,unknown,deferred,Class exists but method not found
|
||||||
|
1846,JsApiErrors,NewJSConsumerWQRequiresExplicitAckError,server/jetstream_errors_generated.go,NewJSConsumerWQRequiresExplicitAckError,unknown,deferred,Class exists but method not found
|
||||||
|
1847,JsApiErrors,NewJSConsumerWithFlowControlNeedsHeartbeatsError,server/jetstream_errors_generated.go,NewJSConsumerWithFlowControlNeedsHeartbeatsError,unknown,deferred,Class exists but method not found
|
||||||
|
1848,JsApiErrors,NewJSInsufficientResourcesError,server/jetstream_errors_generated.go,NewJSInsufficientResourcesError,unknown,deferred,Class exists but method not found
|
||||||
|
1849,JsApiErrors,NewJSInvalidJSONError,server/jetstream_errors_generated.go,NewJSInvalidJSONError,unknown,deferred,Class exists but method not found
|
||||||
|
1850,JsApiErrors,NewJSMaximumConsumersLimitError,server/jetstream_errors_generated.go,NewJSMaximumConsumersLimitError,unknown,deferred,Class exists but method not found
|
||||||
|
1851,JsApiErrors,NewJSMaximumStreamsLimitError,server/jetstream_errors_generated.go,NewJSMaximumStreamsLimitError,unknown,deferred,Class exists but method not found
|
||||||
|
1852,JsApiErrors,NewJSMemoryResourcesExceededError,server/jetstream_errors_generated.go,NewJSMemoryResourcesExceededError,unknown,deferred,Class exists but method not found
|
||||||
|
1853,JsApiErrors,NewJSMessageCounterBrokenError,server/jetstream_errors_generated.go,NewJSMessageCounterBrokenError,unknown,deferred,Class exists but method not found
|
||||||
|
1854,JsApiErrors,NewJSMessageIncrDisabledError,server/jetstream_errors_generated.go,NewJSMessageIncrDisabledError,unknown,deferred,Class exists but method not found
|
||||||
|
1855,JsApiErrors,NewJSMessageIncrInvalidError,server/jetstream_errors_generated.go,NewJSMessageIncrInvalidError,unknown,deferred,Class exists but method not found
|
||||||
|
1856,JsApiErrors,NewJSMessageIncrMissingError,server/jetstream_errors_generated.go,NewJSMessageIncrMissingError,unknown,deferred,Class exists but method not found
|
||||||
|
1857,JsApiErrors,NewJSMessageIncrPayloadError,server/jetstream_errors_generated.go,NewJSMessageIncrPayloadError,unknown,deferred,Class exists but method not found
|
||||||
|
1858,JsApiErrors,NewJSMessageSchedulesDisabledError,server/jetstream_errors_generated.go,NewJSMessageSchedulesDisabledError,unknown,deferred,Class exists but method not found
|
||||||
|
1859,JsApiErrors,NewJSMessageSchedulesPatternInvalidError,server/jetstream_errors_generated.go,NewJSMessageSchedulesPatternInvalidError,unknown,deferred,Class exists but method not found
|
||||||
|
1860,JsApiErrors,NewJSMessageSchedulesRollupInvalidError,server/jetstream_errors_generated.go,NewJSMessageSchedulesRollupInvalidError,unknown,deferred,Class exists but method not found
|
||||||
|
1861,JsApiErrors,NewJSMessageSchedulesSourceInvalidError,server/jetstream_errors_generated.go,NewJSMessageSchedulesSourceInvalidError,unknown,deferred,Class exists but method not found
|
||||||
|
1862,JsApiErrors,NewJSMessageSchedulesTTLInvalidError,server/jetstream_errors_generated.go,NewJSMessageSchedulesTTLInvalidError,unknown,deferred,Class exists but method not found
|
||||||
|
1863,JsApiErrors,NewJSMessageSchedulesTargetInvalidError,server/jetstream_errors_generated.go,NewJSMessageSchedulesTargetInvalidError,unknown,deferred,Class exists but method not found
|
||||||
|
1864,JsApiErrors,NewJSMessageTTLDisabledError,server/jetstream_errors_generated.go,NewJSMessageTTLDisabledError,unknown,deferred,Class exists but method not found
|
||||||
|
1865,JsApiErrors,NewJSMessageTTLInvalidError,server/jetstream_errors_generated.go,NewJSMessageTTLInvalidError,unknown,deferred,Class exists but method not found
|
||||||
|
1866,JsApiErrors,NewJSMirrorConsumerSetupFailedError,server/jetstream_errors_generated.go,NewJSMirrorConsumerSetupFailedError,unknown,deferred,Class exists but method not found
|
||||||
|
1867,JsApiErrors,NewJSMirrorInvalidStreamNameError,server/jetstream_errors_generated.go,NewJSMirrorInvalidStreamNameError,unknown,deferred,Class exists but method not found
|
||||||
|
1868,JsApiErrors,NewJSMirrorInvalidSubjectFilterError,server/jetstream_errors_generated.go,NewJSMirrorInvalidSubjectFilterError,unknown,deferred,Class exists but method not found
|
||||||
|
1869,JsApiErrors,NewJSMirrorInvalidTransformDestinationError,server/jetstream_errors_generated.go,NewJSMirrorInvalidTransformDestinationError,unknown,deferred,Class exists but method not found
|
||||||
|
1870,JsApiErrors,NewJSMirrorMaxMessageSizeTooBigError,server/jetstream_errors_generated.go,NewJSMirrorMaxMessageSizeTooBigError,unknown,deferred,Class exists but method not found
|
||||||
|
1871,JsApiErrors,NewJSMirrorMultipleFiltersNotAllowedError,server/jetstream_errors_generated.go,NewJSMirrorMultipleFiltersNotAllowedError,unknown,deferred,Class exists but method not found
|
||||||
|
1872,JsApiErrors,NewJSMirrorOverlappingSubjectFiltersError,server/jetstream_errors_generated.go,NewJSMirrorOverlappingSubjectFiltersError,unknown,deferred,Class exists but method not found
|
||||||
|
1873,JsApiErrors,NewJSMirrorWithAtomicPublishError,server/jetstream_errors_generated.go,NewJSMirrorWithAtomicPublishError,unknown,deferred,Class exists but method not found
|
||||||
|
1874,JsApiErrors,NewJSMirrorWithCountersError,server/jetstream_errors_generated.go,NewJSMirrorWithCountersError,unknown,deferred,Class exists but method not found
|
||||||
|
1875,JsApiErrors,NewJSMirrorWithFirstSeqError,server/jetstream_errors_generated.go,NewJSMirrorWithFirstSeqError,unknown,deferred,Class exists but method not found
|
||||||
|
1876,JsApiErrors,NewJSMirrorWithMsgSchedulesError,server/jetstream_errors_generated.go,NewJSMirrorWithMsgSchedulesError,unknown,deferred,Class exists but method not found
|
||||||
|
1877,JsApiErrors,NewJSMirrorWithSourcesError,server/jetstream_errors_generated.go,NewJSMirrorWithSourcesError,unknown,deferred,Class exists but method not found
|
||||||
|
1878,JsApiErrors,NewJSMirrorWithStartSeqAndTimeError,server/jetstream_errors_generated.go,NewJSMirrorWithStartSeqAndTimeError,unknown,deferred,Class exists but method not found
|
||||||
|
1879,JsApiErrors,NewJSMirrorWithSubjectFiltersError,server/jetstream_errors_generated.go,NewJSMirrorWithSubjectFiltersError,unknown,deferred,Class exists but method not found
|
||||||
|
1880,JsApiErrors,NewJSMirrorWithSubjectsError,server/jetstream_errors_generated.go,NewJSMirrorWithSubjectsError,unknown,deferred,Class exists but method not found
|
||||||
|
1881,JsApiErrors,NewJSNoAccountError,server/jetstream_errors_generated.go,NewJSNoAccountError,unknown,deferred,Class exists but method not found
|
||||||
|
1882,JsApiErrors,NewJSNoLimitsError,server/jetstream_errors_generated.go,NewJSNoLimitsError,unknown,deferred,Class exists but method not found
|
||||||
|
1883,JsApiErrors,NewJSNoMessageFoundError,server/jetstream_errors_generated.go,NewJSNoMessageFoundError,unknown,deferred,Class exists but method not found
|
||||||
|
1884,JsApiErrors,NewJSNotEmptyRequestError,server/jetstream_errors_generated.go,NewJSNotEmptyRequestError,unknown,deferred,Class exists but method not found
|
||||||
|
1885,JsApiErrors,NewJSNotEnabledError,server/jetstream_errors_generated.go,NewJSNotEnabledError,unknown,deferred,Class exists but method not found
|
||||||
|
1886,JsApiErrors,NewJSNotEnabledForAccountError,server/jetstream_errors_generated.go,NewJSNotEnabledForAccountError,unknown,deferred,Class exists but method not found
|
||||||
|
1887,JsApiErrors,NewJSPedanticError,server/jetstream_errors_generated.go,NewJSPedanticError,unknown,deferred,Class exists but method not found
|
||||||
|
1888,JsApiErrors,NewJSPeerRemapError,server/jetstream_errors_generated.go,NewJSPeerRemapError,unknown,deferred,Class exists but method not found
|
||||||
|
1889,JsApiErrors,NewJSRaftGeneralError,server/jetstream_errors_generated.go,NewJSRaftGeneralError,unknown,deferred,Class exists but method not found
|
||||||
|
1890,JsApiErrors,NewJSReplicasCountCannotBeNegativeError,server/jetstream_errors_generated.go,NewJSReplicasCountCannotBeNegativeError,unknown,deferred,Class exists but method not found
|
||||||
|
1891,JsApiErrors,NewJSRequiredApiLevelError,server/jetstream_errors_generated.go,NewJSRequiredApiLevelError,unknown,deferred,Class exists but method not found
|
||||||
|
1892,JsApiErrors,NewJSRestoreSubscribeFailedError,server/jetstream_errors_generated.go,NewJSRestoreSubscribeFailedError,unknown,deferred,Class exists but method not found
|
||||||
|
1893,JsApiErrors,NewJSSequenceNotFoundError,server/jetstream_errors_generated.go,NewJSSequenceNotFoundError,unknown,deferred,Class exists but method not found
|
||||||
|
1894,JsApiErrors,NewJSSnapshotDeliverSubjectInvalidError,server/jetstream_errors_generated.go,NewJSSnapshotDeliverSubjectInvalidError,unknown,deferred,Class exists but method not found
|
||||||
|
1895,JsApiErrors,NewJSSourceConsumerSetupFailedError,server/jetstream_errors_generated.go,NewJSSourceConsumerSetupFailedError,unknown,deferred,Class exists but method not found
|
||||||
|
1896,JsApiErrors,NewJSSourceDuplicateDetectedError,server/jetstream_errors_generated.go,NewJSSourceDuplicateDetectedError,unknown,deferred,Class exists but method not found
|
||||||
|
1897,JsApiErrors,NewJSSourceInvalidStreamNameError,server/jetstream_errors_generated.go,NewJSSourceInvalidStreamNameError,unknown,deferred,Class exists but method not found
|
||||||
|
1898,JsApiErrors,NewJSSourceInvalidSubjectFilterError,server/jetstream_errors_generated.go,NewJSSourceInvalidSubjectFilterError,unknown,deferred,Class exists but method not found
|
||||||
|
1899,JsApiErrors,NewJSSourceInvalidTransformDestinationError,server/jetstream_errors_generated.go,NewJSSourceInvalidTransformDestinationError,unknown,deferred,Class exists but method not found
|
||||||
|
1900,JsApiErrors,NewJSSourceMaxMessageSizeTooBigError,server/jetstream_errors_generated.go,NewJSSourceMaxMessageSizeTooBigError,unknown,deferred,Class exists but method not found
|
||||||
|
1901,JsApiErrors,NewJSSourceMultipleFiltersNotAllowedError,server/jetstream_errors_generated.go,NewJSSourceMultipleFiltersNotAllowedError,unknown,deferred,Class exists but method not found
|
||||||
|
1902,JsApiErrors,NewJSSourceOverlappingSubjectFiltersError,server/jetstream_errors_generated.go,NewJSSourceOverlappingSubjectFiltersError,unknown,deferred,Class exists but method not found
|
||||||
|
1903,JsApiErrors,NewJSSourceWithMsgSchedulesError,server/jetstream_errors_generated.go,NewJSSourceWithMsgSchedulesError,unknown,deferred,Class exists but method not found
|
||||||
|
1904,JsApiErrors,NewJSStorageResourcesExceededError,server/jetstream_errors_generated.go,NewJSStorageResourcesExceededError,unknown,deferred,Class exists but method not found
|
||||||
|
1905,JsApiErrors,NewJSStreamAssignmentError,server/jetstream_errors_generated.go,NewJSStreamAssignmentError,unknown,deferred,Class exists but method not found
|
||||||
|
1906,JsApiErrors,NewJSStreamCreateError,server/jetstream_errors_generated.go,NewJSStreamCreateError,unknown,deferred,Class exists but method not found
|
||||||
|
1907,JsApiErrors,NewJSStreamDeleteError,server/jetstream_errors_generated.go,NewJSStreamDeleteError,unknown,deferred,Class exists but method not found
|
||||||
|
1908,JsApiErrors,NewJSStreamDuplicateMessageConflictError,server/jetstream_errors_generated.go,NewJSStreamDuplicateMessageConflictError,unknown,deferred,Class exists but method not found
|
||||||
|
1909,JsApiErrors,NewJSStreamExpectedLastSeqPerSubjectInvalidError,server/jetstream_errors_generated.go,NewJSStreamExpectedLastSeqPerSubjectInvalidError,unknown,deferred,Class exists but method not found
|
||||||
|
1910,JsApiErrors,NewJSStreamExpectedLastSeqPerSubjectNotReadyError,server/jetstream_errors_generated.go,NewJSStreamExpectedLastSeqPerSubjectNotReadyError,unknown,deferred,Class exists but method not found
|
||||||
|
1911,JsApiErrors,NewJSStreamExternalApiOverlapError,server/jetstream_errors_generated.go,NewJSStreamExternalApiOverlapError,unknown,deferred,Class exists but method not found
|
||||||
|
1912,JsApiErrors,NewJSStreamExternalDelPrefixOverlapsError,server/jetstream_errors_generated.go,NewJSStreamExternalDelPrefixOverlapsError,unknown,deferred,Class exists but method not found
|
||||||
|
1913,JsApiErrors,NewJSStreamGeneralError,server/jetstream_errors_generated.go,NewJSStreamGeneralError,unknown,deferred,Class exists but method not found
|
||||||
|
1914,JsApiErrors,NewJSStreamHeaderExceedsMaximumError,server/jetstream_errors_generated.go,NewJSStreamHeaderExceedsMaximumError,unknown,deferred,Class exists but method not found
|
||||||
|
1915,JsApiErrors,NewJSStreamInfoMaxSubjectsError,server/jetstream_errors_generated.go,NewJSStreamInfoMaxSubjectsError,unknown,deferred,Class exists but method not found
|
||||||
|
1916,JsApiErrors,NewJSStreamInvalidConfigError,server/jetstream_errors_generated.go,NewJSStreamInvalidConfigError,unknown,deferred,Class exists but method not found
|
||||||
|
1917,JsApiErrors,NewJSStreamInvalidError,server/jetstream_errors_generated.go,NewJSStreamInvalidError,unknown,deferred,Class exists but method not found
|
||||||
|
1918,JsApiErrors,NewJSStreamInvalidExternalDeliverySubjError,server/jetstream_errors_generated.go,NewJSStreamInvalidExternalDeliverySubjError,unknown,deferred,Class exists but method not found
|
||||||
|
1919,JsApiErrors,NewJSStreamLimitsError,server/jetstream_errors_generated.go,NewJSStreamLimitsError,unknown,deferred,Class exists but method not found
|
||||||
|
1920,JsApiErrors,NewJSStreamMaxBytesRequiredError,server/jetstream_errors_generated.go,NewJSStreamMaxBytesRequiredError,unknown,deferred,Class exists but method not found
|
||||||
|
1921,JsApiErrors,NewJSStreamMaxStreamBytesExceededError,server/jetstream_errors_generated.go,NewJSStreamMaxStreamBytesExceededError,unknown,deferred,Class exists but method not found
|
||||||
|
1922,JsApiErrors,NewJSStreamMessageExceedsMaximumError,server/jetstream_errors_generated.go,NewJSStreamMessageExceedsMaximumError,unknown,deferred,Class exists but method not found
|
||||||
|
1923,JsApiErrors,NewJSStreamMinLastSeqError,server/jetstream_errors_generated.go,NewJSStreamMinLastSeqError,unknown,deferred,Class exists but method not found
|
||||||
|
1924,JsApiErrors,NewJSStreamMirrorNotUpdatableError,server/jetstream_errors_generated.go,NewJSStreamMirrorNotUpdatableError,unknown,deferred,Class exists but method not found
|
||||||
|
1925,JsApiErrors,NewJSStreamMismatchError,server/jetstream_errors_generated.go,NewJSStreamMismatchError,unknown,deferred,Class exists but method not found
|
||||||
|
1926,JsApiErrors,NewJSStreamMoveAndScaleError,server/jetstream_errors_generated.go,NewJSStreamMoveAndScaleError,unknown,deferred,Class exists but method not found
|
||||||
|
1927,JsApiErrors,NewJSStreamMoveInProgressError,server/jetstream_errors_generated.go,NewJSStreamMoveInProgressError,unknown,deferred,Class exists but method not found
|
||||||
|
1928,JsApiErrors,NewJSStreamMoveNotInProgressError,server/jetstream_errors_generated.go,NewJSStreamMoveNotInProgressError,unknown,deferred,Class exists but method not found
|
||||||
|
1929,JsApiErrors,NewJSStreamMsgDeleteFailedError,server/jetstream_errors_generated.go,NewJSStreamMsgDeleteFailedError,unknown,deferred,Class exists but method not found
|
||||||
|
1930,JsApiErrors,NewJSStreamNameContainsPathSeparatorsError,server/jetstream_errors_generated.go,NewJSStreamNameContainsPathSeparatorsError,unknown,deferred,Class exists but method not found
|
||||||
|
1931,JsApiErrors,NewJSStreamNameExistError,server/jetstream_errors_generated.go,NewJSStreamNameExistError,unknown,deferred,Class exists but method not found
|
||||||
|
1932,JsApiErrors,NewJSStreamNameExistRestoreFailedError,server/jetstream_errors_generated.go,NewJSStreamNameExistRestoreFailedError,unknown,deferred,Class exists but method not found
|
||||||
|
1933,JsApiErrors,NewJSStreamNotFoundError,server/jetstream_errors_generated.go,NewJSStreamNotFoundError,unknown,deferred,Class exists but method not found
|
||||||
|
1934,JsApiErrors,NewJSStreamNotMatchError,server/jetstream_errors_generated.go,NewJSStreamNotMatchError,unknown,deferred,Class exists but method not found
|
||||||
|
1935,JsApiErrors,NewJSStreamOfflineError,server/jetstream_errors_generated.go,NewJSStreamOfflineError,unknown,deferred,Class exists but method not found
|
||||||
|
1936,JsApiErrors,NewJSStreamOfflineReasonError,server/jetstream_errors_generated.go,NewJSStreamOfflineReasonError,unknown,deferred,Class exists but method not found
|
||||||
|
1937,JsApiErrors,NewJSStreamPurgeFailedError,server/jetstream_errors_generated.go,NewJSStreamPurgeFailedError,unknown,deferred,Class exists but method not found
|
||||||
|
1938,JsApiErrors,NewJSStreamReplicasNotSupportedError,server/jetstream_errors_generated.go,NewJSStreamReplicasNotSupportedError,unknown,deferred,Class exists but method not found
|
||||||
|
1939,JsApiErrors,NewJSStreamReplicasNotUpdatableError,server/jetstream_errors_generated.go,NewJSStreamReplicasNotUpdatableError,unknown,deferred,Class exists but method not found
|
||||||
|
1940,JsApiErrors,NewJSStreamRestoreError,server/jetstream_errors_generated.go,NewJSStreamRestoreError,unknown,deferred,Class exists but method not found
|
||||||
|
1941,JsApiErrors,NewJSStreamRollupFailedError,server/jetstream_errors_generated.go,NewJSStreamRollupFailedError,unknown,deferred,Class exists but method not found
|
||||||
|
1942,JsApiErrors,NewJSStreamSealedError,server/jetstream_errors_generated.go,NewJSStreamSealedError,unknown,deferred,Class exists but method not found
|
||||||
|
1943,JsApiErrors,NewJSStreamSequenceNotMatchError,server/jetstream_errors_generated.go,NewJSStreamSequenceNotMatchError,unknown,deferred,Class exists but method not found
|
||||||
|
1944,JsApiErrors,NewJSStreamSnapshotError,server/jetstream_errors_generated.go,NewJSStreamSnapshotError,unknown,deferred,Class exists but method not found
|
||||||
|
1945,JsApiErrors,NewJSStreamStoreFailedError,server/jetstream_errors_generated.go,NewJSStreamStoreFailedError,unknown,deferred,Class exists but method not found
|
||||||
|
1946,JsApiErrors,NewJSStreamSubjectOverlapError,server/jetstream_errors_generated.go,NewJSStreamSubjectOverlapError,unknown,deferred,Class exists but method not found
|
||||||
|
1947,JsApiErrors,NewJSStreamTemplateCreateError,server/jetstream_errors_generated.go,NewJSStreamTemplateCreateError,unknown,deferred,Class exists but method not found
|
||||||
|
1948,JsApiErrors,NewJSStreamTemplateDeleteError,server/jetstream_errors_generated.go,NewJSStreamTemplateDeleteError,unknown,deferred,Class exists but method not found
|
||||||
|
1949,JsApiErrors,NewJSStreamTemplateNotFoundError,server/jetstream_errors_generated.go,NewJSStreamTemplateNotFoundError,unknown,deferred,Class exists but method not found
|
||||||
|
1950,JsApiErrors,NewJSStreamTooManyRequestsError,server/jetstream_errors_generated.go,NewJSStreamTooManyRequestsError,unknown,deferred,Class exists but method not found
|
||||||
|
1951,JsApiErrors,NewJSStreamTransformInvalidDestinationError,server/jetstream_errors_generated.go,NewJSStreamTransformInvalidDestinationError,unknown,deferred,Class exists but method not found
|
||||||
|
1952,JsApiErrors,NewJSStreamTransformInvalidSourceError,server/jetstream_errors_generated.go,NewJSStreamTransformInvalidSourceError,unknown,deferred,Class exists but method not found
|
||||||
|
1953,JsApiErrors,NewJSStreamUpdateError,server/jetstream_errors_generated.go,NewJSStreamUpdateError,unknown,deferred,Class exists but method not found
|
||||||
|
1954,JsApiErrors,NewJSStreamWrongLastMsgIDError,server/jetstream_errors_generated.go,NewJSStreamWrongLastMsgIDError,unknown,deferred,Class exists but method not found
|
||||||
|
1955,JsApiErrors,NewJSStreamWrongLastSequenceConstantError,server/jetstream_errors_generated.go,NewJSStreamWrongLastSequenceConstantError,unknown,deferred,Class exists but method not found
|
||||||
|
1956,JsApiErrors,NewJSStreamWrongLastSequenceError,server/jetstream_errors_generated.go,NewJSStreamWrongLastSequenceError,unknown,deferred,Class exists but method not found
|
||||||
|
1957,JsApiErrors,NewJSTempStorageFailedError,server/jetstream_errors_generated.go,NewJSTempStorageFailedError,unknown,deferred,Class exists but method not found
|
||||||
|
1958,JsApiErrors,NewJSTemplateNameNotMatchSubjectError,server/jetstream_errors_generated.go,NewJSTemplateNameNotMatchSubjectError,unknown,deferred,Class exists but method not found
|
||||||
|
1979,ClientConnection,IsSolicitedLeafNode,server/leafnode.go,isSolicitedLeafNode,unknown,deferred,Class exists but method not found
|
||||||
|
1980,ClientConnection,IsSpokeLeafNode,server/leafnode.go,isSpokeLeafNode,unknown,deferred,Class exists but method not found
|
||||||
|
1982,ClientConnection,IsIsolatedLeafNode,server/leafnode.go,isIsolatedLeafNode,unknown,deferred,Class exists but method not found
|
||||||
|
2006,ClientConnection,SendLeafConnect,server/leafnode.go,sendLeafConnect,unknown,deferred,Class exists but method not found
|
||||||
|
2013,ClientConnection,LeafClientHandshakeIfNeeded,server/leafnode.go,leafClientHandshakeIfNeeded,unknown,deferred,Class exists but method not found
|
||||||
|
2014,ClientConnection,ProcessLeafnodeInfo,server/leafnode.go,processLeafnodeInfo,unknown,deferred,Class exists but method not found
|
||||||
|
2016,ClientConnection,UpdateLeafNodeURLs,server/leafnode.go,updateLeafNodeURLs,unknown,deferred,Class exists but method not found
|
||||||
|
2017,ClientConnection,DoUpdateLNURLs,server/leafnode.go,doUpdateLNURLs,unknown,deferred,Class exists but method not found
|
||||||
|
2021,ClientConnection,ProcessLeafNodeConnect,server/leafnode.go,processLeafNodeConnect,unknown,deferred,Class exists but method not found
|
||||||
|
2029,ClientConnection,UpdateSmap,server/leafnode.go,updateSmap,unknown,deferred,Class exists but method not found
|
||||||
|
2030,ClientConnection,ForceAddToSmap,server/leafnode.go,forceAddToSmap,unknown,deferred,Class exists but method not found
|
||||||
|
2031,ClientConnection,ForceRemoveFromSmap,server/leafnode.go,forceRemoveFromSmap,unknown,deferred,Class exists but method not found
|
||||||
|
2032,ClientConnection,SendLeafNodeSubUpdate,server/leafnode.go,sendLeafNodeSubUpdate,unknown,deferred,Class exists but method not found
|
||||||
|
2035,ClientConnection,WriteLeafSub,server/leafnode.go,writeLeafSub,unknown,deferred,Class exists but method not found
|
||||||
|
2036,ClientConnection,ProcessLeafSub,server/leafnode.go,processLeafSub,unknown,deferred,Class exists but method not found
|
||||||
|
2037,ClientConnection,HandleLeafNodeLoop,server/leafnode.go,handleLeafNodeLoop,unknown,deferred,Class exists but method not found
|
||||||
|
2038,ClientConnection,ProcessLeafUnsub,server/leafnode.go,processLeafUnsub,unknown,deferred,Class exists but method not found
|
||||||
|
2039,ClientConnection,ProcessLeafHeaderMsgArgs,server/leafnode.go,processLeafHeaderMsgArgs,unknown,deferred,Class exists but method not found
|
||||||
|
2040,ClientConnection,ProcessLeafMsgArgs,server/leafnode.go,processLeafMsgArgs,unknown,deferred,Class exists but method not found
|
||||||
|
2041,ClientConnection,ProcessInboundLeafMsg,server/leafnode.go,processInboundLeafMsg,unknown,deferred,Class exists but method not found
|
||||||
|
2042,ClientConnection,LeafSubPermViolation,server/leafnode.go,leafSubPermViolation,unknown,deferred,Class exists but method not found
|
||||||
|
2043,ClientConnection,LeafPermViolation,server/leafnode.go,leafPermViolation,unknown,deferred,Class exists but method not found
|
||||||
|
2044,ClientConnection,LeafProcessErr,server/leafnode.go,leafProcessErr,unknown,deferred,Class exists but method not found
|
||||||
|
2045,ClientConnection,SetLeafConnectDelayIfSoliciting,server/leafnode.go,setLeafConnectDelayIfSoliciting,unknown,deferred,Class exists but method not found
|
||||||
|
2046,ClientConnection,LeafNodeGetTLSConfigForSolicit,server/leafnode.go,leafNodeGetTLSConfigForSolicit,unknown,deferred,Class exists but method not found
|
||||||
|
2047,ClientConnection,LeafNodeSolicitWSConnection,server/leafnode.go,leafNodeSolicitWSConnection,unknown,deferred,Class exists but method not found
|
||||||
|
2068,JetStreamMemStore,NewMemStore,server/memstore.go,newMemStore,unknown,deferred,Class exists but method not found
|
||||||
|
2069,JetStreamMemStore,UpdateConfig,server/memstore.go,UpdateConfig,unknown,verified,Method found with 3 statement(s) at MemStore.cs:1202
|
||||||
|
2070,JetStreamMemStore,RecoverTTLState,server/memstore.go,recoverTTLState,unknown,deferred,Class exists but method not found
|
||||||
|
2071,JetStreamMemStore,RecoverMsgSchedulingState,server/memstore.go,recoverMsgSchedulingState,unknown,deferred,Class exists but method not found
|
||||||
|
2072,JetStreamMemStore,StoreRawMsgInternal,server/memstore.go,storeRawMsg,unknown,deferred,Class exists but method not found
|
||||||
|
2073,JetStreamMemStore,StoreRawMsg,server/memstore.go,StoreRawMsg,unknown,verified,Method found with 2 statement(s) at MemStore.cs:121
|
||||||
|
2074,JetStreamMemStore,StoreMsg,server/memstore.go,StoreMsg,unknown,verified,Method found with 2 statement(s) at MemStore.cs:93
|
||||||
|
2075,JetStreamMemStore,SkipMsg,server/memstore.go,SkipMsg,unknown,verified,Method found with 2 statement(s) at MemStore.cs:446
|
||||||
|
2076,JetStreamMemStore,SkipMsgs,server/memstore.go,SkipMsgs,unknown,verified,Method found with 2 statement(s) at MemStore.cs:479
|
||||||
|
2077,JetStreamMemStore,FlushAllPending,server/memstore.go,FlushAllPending,unknown,stub,Body is throw NotImplementedException at MemStore.cs:1275
|
||||||
|
2078,JetStreamMemStore,RegisterStorageUpdates,server/memstore.go,RegisterStorageUpdates,unknown,verified,Method found with 3 statement(s) at MemStore.cs:1251
|
||||||
|
2079,JetStreamMemStore,RegisterStorageRemoveMsg,server/memstore.go,RegisterStorageRemoveMsg,unknown,verified,Method found with 3 statement(s) at MemStore.cs:1259
|
||||||
|
2080,JetStreamMemStore,RegisterProcessJetStreamMsg,server/memstore.go,RegisterProcessJetStreamMsg,unknown,verified,Method found with 3 statement(s) at MemStore.cs:1267
|
||||||
|
2081,JetStreamMemStore,GetSeqFromTime,server/memstore.go,GetSeqFromTime,unknown,verified,Method found with 4 statement(s) at MemStore.cs:1154
|
||||||
|
2082,JetStreamMemStore,FilteredState,server/memstore.go,FilteredState,unknown,verified,Method found with 2 statement(s) at MemStore.cs:906
|
||||||
|
2083,JetStreamMemStore,FilteredStateLocked,server/memstore.go,filteredStateLocked,unknown,verified,Method found with 16 statement(s) at MemStore.cs:919
|
||||||
|
2084,JetStreamMemStore,SubjectsState,server/memstore.go,SubjectsState,unknown,verified,Method found with 2 statement(s) at MemStore.cs:983
|
||||||
|
2085,JetStreamMemStore,AllLastSeqs,server/memstore.go,AllLastSeqs,unknown,verified,Method found with 2 statement(s) at MemStore.cs:1059
|
||||||
|
2086,JetStreamMemStore,AllLastSeqsLocked,server/memstore.go,allLastSeqsLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2087,JetStreamMemStore,FilterIsAll,server/memstore.go,filterIsAll,unknown,deferred,Class exists but method not found
|
||||||
|
2088,JetStreamMemStore,MultiLastSeqs,server/memstore.go,MultiLastSeqs,unknown,verified,Method found with 2 statement(s) at MemStore.cs:1083
|
||||||
|
2089,JetStreamMemStore,SubjectsTotals,server/memstore.go,SubjectsTotals,unknown,verified,Method found with 2 statement(s) at MemStore.cs:1015
|
||||||
|
2090,JetStreamMemStore,SubjectsTotalsLocked,server/memstore.go,subjectsTotalsLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2091,JetStreamMemStore,NumPending,server/memstore.go,NumPending,unknown,verified,Method found with 2 statement(s) at MemStore.cs:1037
|
||||||
|
2092,JetStreamMemStore,NumPendingMulti,server/memstore.go,NumPendingMulti,unknown,verified,Method found with 1 statement(s) at MemStore.cs:1052
|
||||||
|
2093,JetStreamMemStore,EnforcePerSubjectLimit,server/memstore.go,enforcePerSubjectLimit,unknown,verified,Method found with 2 statement(s) at MemStore.cs:1438
|
||||||
|
2094,JetStreamMemStore,EnforceMsgLimit,server/memstore.go,enforceMsgLimit,unknown,verified,Method found with 3 statement(s) at MemStore.cs:1420
|
||||||
|
2095,JetStreamMemStore,EnforceBytesLimit,server/memstore.go,enforceBytesLimit,unknown,verified,Method found with 3 statement(s) at MemStore.cs:1429
|
||||||
|
2096,JetStreamMemStore,StartAgeChk,server/memstore.go,startAgeChk,unknown,verified,Method found with 2 statement(s) at MemStore.cs:1551
|
||||||
|
2097,JetStreamMemStore,ResetAgeChk,server/memstore.go,resetAgeChk,unknown,deferred,Class exists but method not found
|
||||||
|
2098,JetStreamMemStore,CancelAgeChk,server/memstore.go,cancelAgeChk,unknown,deferred,Class exists but method not found
|
||||||
|
2099,JetStreamMemStore,ExpireMsgs,server/memstore.go,expireMsgs,unknown,verified,Method found with 2 statement(s) at MemStore.cs:1558
|
||||||
|
2100,JetStreamMemStore,ShouldProcessSdm,server/memstore.go,shouldProcessSdm,unknown,deferred,Class exists but method not found
|
||||||
|
2101,JetStreamMemStore,ShouldProcessSdmLocked,server/memstore.go,shouldProcessSdmLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2102,JetStreamMemStore,HandleRemovalOrSdm,server/memstore.go,handleRemovalOrSdm,unknown,deferred,Class exists but method not found
|
||||||
|
2103,JetStreamMemStore,RunMsgScheduling,server/memstore.go,runMsgScheduling,unknown,deferred,Class exists but method not found
|
||||||
|
2104,JetStreamMemStore,PurgeEx,server/memstore.go,PurgeEx,unknown,verified,Method found with 11 statement(s) at MemStore.cs:627
|
||||||
|
2105,JetStreamMemStore,Purge,server/memstore.go,Purge,unknown,verified,Method found with 8 statement(s) at MemStore.cs:596
|
||||||
|
2106,JetStreamMemStore,PurgeInternal,server/memstore.go,purge,unknown,deferred,Class exists but method not found
|
||||||
|
2107,JetStreamMemStore,Compact,server/memstore.go,Compact,unknown,verified,Method found with 8 statement(s) at MemStore.cs:695
|
||||||
|
2108,JetStreamMemStore,CompactInternal,server/memstore.go,compact,unknown,deferred,Class exists but method not found
|
||||||
|
2109,JetStreamMemStore,Reset,server/memstore.go,reset,unknown,deferred,Class exists but method not found
|
||||||
|
2110,JetStreamMemStore,Truncate,server/memstore.go,Truncate,unknown,verified,Method found with 6 statement(s) at MemStore.cs:766
|
||||||
|
2111,JetStreamMemStore,DeleteFirstMsgOrPanic,server/memstore.go,deleteFirstMsgOrPanic,unknown,deferred,Class exists but method not found
|
||||||
|
2112,JetStreamMemStore,DeleteFirstMsg,server/memstore.go,deleteFirstMsg,unknown,deferred,Class exists but method not found
|
||||||
|
2113,JetStreamMemStore,SubjectForSeq,server/memstore.go,SubjectForSeq,unknown,verified,Method found with 2 statement(s) at MemStore.cs:1132
|
||||||
|
2114,JetStreamMemStore,LoadMsg,server/memstore.go,LoadMsg,unknown,verified,Method found with 2 statement(s) at MemStore.cs:234
|
||||||
|
2115,JetStreamMemStore,LoadMsgLocked,server/memstore.go,loadMsgLocked,unknown,verified,Method found with 5 statement(s) at MemStore.cs:247
|
||||||
|
2116,JetStreamMemStore,LoadLastMsg,server/memstore.go,LoadLastMsg,unknown,verified,Method found with 2 statement(s) at MemStore.cs:337
|
||||||
|
2117,JetStreamMemStore,LoadLastLocked,server/memstore.go,loadLastLocked,unknown,verified,Method found with 7 statement(s) at MemStore.cs:350
|
||||||
|
2118,JetStreamMemStore,LoadNextMsgMulti,server/memstore.go,LoadNextMsgMulti,unknown,verified,Method found with 2 statement(s) at MemStore.cs:305
|
||||||
|
2119,JetStreamMemStore,LoadNextMsg,server/memstore.go,LoadNextMsg,unknown,verified,Method found with 2 statement(s) at MemStore.cs:265
|
||||||
|
2120,JetStreamMemStore,NextWildcardMatchLocked,server/memstore.go,nextWildcardMatchLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2121,JetStreamMemStore,NextLiteralMatchLocked,server/memstore.go,nextLiteralMatchLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2122,JetStreamMemStore,ShouldLinearScan,server/memstore.go,shouldLinearScan,unknown,deferred,Class exists but method not found
|
||||||
|
2123,JetStreamMemStore,LoadNextMsgLocked,server/memstore.go,loadNextMsgLocked,unknown,verified,Method found with 6 statement(s) at MemStore.cs:278
|
||||||
|
2124,JetStreamMemStore,LoadPrevMsg,server/memstore.go,LoadPrevMsg,unknown,verified,Method found with 2 statement(s) at MemStore.cs:380
|
||||||
|
2125,JetStreamMemStore,LoadPrevMsgMulti,server/memstore.go,LoadPrevMsgMulti,unknown,verified,Method found with 2 statement(s) at MemStore.cs:411
|
||||||
|
2126,JetStreamMemStore,RemoveMsg,server/memstore.go,RemoveMsg,unknown,verified,Method found with 2 statement(s) at MemStore.cs:516
|
||||||
|
2127,JetStreamMemStore,EraseMsg,server/memstore.go,EraseMsg,unknown,verified,Method found with 2 statement(s) at MemStore.cs:531
|
||||||
|
2128,JetStreamMemStore,UpdateFirstSeq,server/memstore.go,updateFirstSeq,unknown,verified,Method found with 6 statement(s) at MemStore.cs:1451
|
||||||
|
2129,JetStreamMemStore,RemoveSeqPerSubject,server/memstore.go,removeSeqPerSubject,unknown,verified,Method found with 9 statement(s) at MemStore.cs:1482
|
||||||
|
2130,JetStreamMemStore,RecalculateForSubj,server/memstore.go,recalculateForSubj,unknown,verified,Method found with 3 statement(s) at MemStore.cs:1515
|
||||||
|
2131,JetStreamMemStore,RemoveMsgInternal,server/memstore.go,removeMsg,unknown,deferred,Class exists but method not found
|
||||||
|
2132,JetStreamMemStore,Type,server/memstore.go,Type,unknown,verified,Method found with 1 statement(s) at MemStore.cs:1336
|
||||||
|
2133,JetStreamMemStore,FastState,server/memstore.go,FastState,unknown,verified,Method found with 2 statement(s) at MemStore.cs:876
|
||||||
|
2134,JetStreamMemStore,State,server/memstore.go,State,unknown,verified,Method found with 2 statement(s) at MemStore.cs:832
|
||||||
|
2135,JetStreamMemStore,Utilization,server/memstore.go,Utilization,unknown,verified,Method found with 2 statement(s) at MemStore.cs:1402
|
||||||
|
2136,JetStreamMemStore,MemStoreMsgSizeRaw,server/memstore.go,memStoreMsgSizeRaw,unknown,deferred,Class exists but method not found
|
||||||
|
2137,JetStreamMemStore,MemStoreMsgSize,server/memstore.go,memStoreMsgSize,unknown,deferred,Class exists but method not found
|
||||||
|
2138,JetStreamMemStore,ResetState,server/memstore.go,ResetState,unknown,stub,Body is throw NotImplementedException at MemStore.cs:1326
|
||||||
|
2139,JetStreamMemStore,Delete,server/memstore.go,Delete,unknown,verified,Method found with 1 statement(s) at MemStore.cs:1359
|
||||||
|
2140,JetStreamMemStore,Stop,server/memstore.go,Stop,unknown,verified,Method found with 3 statement(s) at MemStore.cs:1339
|
||||||
|
2141,JetStreamMemStore,IsClosed,server/memstore.go,isClosed,unknown,deferred,Class exists but method not found
|
||||||
|
2142,JetStreamMemStore,ConsumerStore,server/memstore.go,ConsumerStore,unknown,verified,Method found with 5 statement(s) at MemStore.cs:1366
|
||||||
|
2143,JetStreamMemStore,AddConsumer,server/memstore.go,AddConsumer,unknown,verified,Method found with 3 statement(s) at MemStore.cs:1379
|
||||||
|
2144,JetStreamMemStore,RemoveConsumer,server/memstore.go,RemoveConsumer,unknown,verified,Method found with 3 statement(s) at MemStore.cs:1387
|
||||||
|
2145,JetStreamMemStore,Snapshot,server/memstore.go,Snapshot,unknown,verified,Method found with 1 statement(s) at MemStore.cs:1395
|
||||||
|
2146,JetStreamMemStore,EncodedStreamState,server/memstore.go,EncodedStreamState,unknown,verified,Method found with 2 statement(s) at MemStore.cs:1281
|
||||||
|
2147,JetStreamMemStore,SyncDeleted,server/memstore.go,SyncDeleted,unknown,verified,Method found with 2 statement(s) at MemStore.cs:1297
|
||||||
|
2172,ClientConnection,GetRTT,server/monitor.go,getRTT,unknown,deferred,Class exists but method not found
|
||||||
|
2238,SortByCid,Less,server/monitor_sort_opts.go,Less,unknown,deferred,Class exists but method not found
|
||||||
|
2239,SortBySubs,Less,server/monitor_sort_opts.go,Less,unknown,deferred,Class exists but method not found
|
||||||
|
2240,SortByPending,Less,server/monitor_sort_opts.go,Less,unknown,deferred,Class exists but method not found
|
||||||
|
2241,SortByOutMsgs,Less,server/monitor_sort_opts.go,Less,unknown,deferred,Class exists but method not found
|
||||||
|
2242,SortByInMsgs,Less,server/monitor_sort_opts.go,Less,unknown,deferred,Class exists but method not found
|
||||||
|
2243,SortByOutBytes,Less,server/monitor_sort_opts.go,Less,unknown,deferred,Class exists but method not found
|
||||||
|
2244,SortByInBytes,Less,server/monitor_sort_opts.go,Less,unknown,deferred,Class exists but method not found
|
||||||
|
2245,SortByLast,Less,server/monitor_sort_opts.go,Less,unknown,deferred,Class exists but method not found
|
||||||
|
2246,SortByIdle,Less,server/monitor_sort_opts.go,Less,unknown,deferred,Class exists but method not found
|
||||||
|
2247,SortByUptime,Less,server/monitor_sort_opts.go,Less,unknown,deferred,Class exists but method not found
|
||||||
|
2248,SortByStop,Less,server/monitor_sort_opts.go,Less,unknown,deferred,Class exists but method not found
|
||||||
|
2249,SortByReason,Less,server/monitor_sort_opts.go,Less,unknown,deferred,Class exists but method not found
|
||||||
|
2250,SortByRtt,Less,server/monitor_sort_opts.go,Less,unknown,deferred,Class exists but method not found
|
||||||
|
2257,ClientConnection,GetMQTTClientID,server/mqtt.go,getMQTTClientID,unknown,deferred,Class exists but method not found
|
||||||
|
2258,ClientConnection,MqttParse,server/mqtt.go,mqttParse,unknown,deferred,Class exists but method not found
|
||||||
|
2259,ClientConnection,MqttTraceMsg,server/mqtt.go,mqttTraceMsg,unknown,deferred,Class exists but method not found
|
||||||
|
2334,ClientConnection,MqttParseConnect,server/mqtt.go,mqttParseConnect,unknown,deferred,Class exists but method not found
|
||||||
|
2335,ClientConnection,MqttConnectTrace,server/mqtt.go,mqttConnectTrace,unknown,deferred,Class exists but method not found
|
||||||
|
2337,ClientConnection,MqttEnqueueConnAck,server/mqtt.go,mqttEnqueueConnAck,unknown,deferred,Class exists but method not found
|
||||||
|
2339,ClientConnection,MqttParsePub,server/mqtt.go,mqttParsePub,unknown,deferred,Class exists but method not found
|
||||||
|
2347,ClientConnection,MqttQoS2InternalSubject,server/mqtt.go,mqttQoS2InternalSubject,unknown,deferred,Class exists but method not found
|
||||||
|
2349,ClientConnection,MqttHandlePubRetain,server/mqtt.go,mqttHandlePubRetain,unknown,deferred,Class exists but method not found
|
||||||
|
2353,ClientConnection,MqttEnqueuePubResponse,server/mqtt.go,mqttEnqueuePubResponse,unknown,deferred,Class exists but method not found
|
||||||
|
2355,ClientConnection,MqttProcessPublishReceived,server/mqtt.go,mqttProcessPublishReceived,unknown,deferred,Class exists but method not found
|
||||||
|
2356,ClientConnection,MqttProcessPubAck,server/mqtt.go,mqttProcessPubAck,unknown,deferred,Class exists but method not found
|
||||||
|
2357,ClientConnection,MqttProcessPubRec,server/mqtt.go,mqttProcessPubRec,unknown,deferred,Class exists but method not found
|
||||||
|
2358,ClientConnection,MqttProcessPubComp,server/mqtt.go,mqttProcessPubComp,unknown,deferred,Class exists but method not found
|
||||||
|
2362,ClientConnection,MqttParseSubs,server/mqtt.go,mqttParseSubs,unknown,deferred,Class exists but method not found
|
||||||
|
2363,ClientConnection,MqttParseSubsOrUnsubs,server/mqtt.go,mqttParseSubsOrUnsubs,unknown,deferred,Class exists but method not found
|
||||||
|
2371,ClientConnection,MqttEnqueuePublishMsgTo,server/mqtt.go,mqttEnqueuePublishMsgTo,unknown,deferred,Class exists but method not found
|
||||||
|
2374,ClientConnection,MqttProcessSubs,server/mqtt.go,mqttProcessSubs,unknown,deferred,Class exists but method not found
|
||||||
|
2378,ClientConnection,MqttSendRetainedMsgsToNewSubs,server/mqtt.go,mqttSendRetainedMsgsToNewSubs,unknown,deferred,Class exists but method not found
|
||||||
|
2379,ClientConnection,MqttEnqueueSubAck,server/mqtt.go,mqttEnqueueSubAck,unknown,deferred,Class exists but method not found
|
||||||
|
2380,ClientConnection,MqttParseUnsubs,server/mqtt.go,mqttParseUnsubs,unknown,deferred,Class exists but method not found
|
||||||
|
2381,ClientConnection,MqttProcessUnsubs,server/mqtt.go,mqttProcessUnsubs,unknown,deferred,Class exists but method not found
|
||||||
|
2382,ClientConnection,MqttEnqueueUnsubAck,server/mqtt.go,mqttEnqueueUnsubAck,unknown,deferred,Class exists but method not found
|
||||||
|
2384,ClientConnection,MqttEnqueuePingResp,server/mqtt.go,mqttEnqueuePingResp,unknown,deferred,Class exists but method not found
|
||||||
|
2420,ClientConnection,IsMsgTraceEnabled,server/msgtrace.go,isMsgTraceEnabled,unknown,deferred,Class exists but method not found
|
||||||
|
2421,ClientConnection,MsgTraceSupport,server/msgtrace.go,msgTraceSupport,unknown,deferred,Class exists but method not found
|
||||||
|
2424,ClientConnection,InitMsgTrace,server/msgtrace.go,initMsgTrace,unknown,deferred,Class exists but method not found
|
||||||
|
2427,ClientConnection,InitAndSendIngressErrEvent,server/msgtrace.go,initAndSendIngressErrEvent,unknown,deferred,Class exists but method not found
|
||||||
|
2443,OcspMonitor,GetNextRun,server/ocsp.go,getNextRun,unknown,deferred,Class exists but method not found
|
||||||
|
2444,OcspMonitor,GetStatus,server/ocsp.go,getStatus,unknown,deferred,Class exists but method not found
|
||||||
|
2445,OcspMonitor,GetCacheStatus,server/ocsp.go,getCacheStatus,unknown,deferred,Class exists but method not found
|
||||||
|
2446,OcspMonitor,GetLocalStatus,server/ocsp.go,getLocalStatus,unknown,deferred,Class exists but method not found
|
||||||
|
2447,OcspMonitor,GetRemoteStatus,server/ocsp.go,getRemoteStatus,unknown,deferred,Class exists but method not found
|
||||||
|
2448,OcspMonitor,Run,server/ocsp.go,run,unknown,deferred,Class exists but method not found
|
||||||
|
2457,OcspMonitor,WriteOCSPStatus,server/ocsp.go,writeOCSPStatus,unknown,deferred,Class exists but method not found
|
||||||
|
2482,LocalDirCache,Put,server/ocsp_responsecache.go,Put,unknown,stub,Body is throw NotImplementedException at OcspTypes.cs:153
|
||||||
|
2483,LocalDirCache,Get,server/ocsp_responsecache.go,Get,unknown,stub,Body is throw NotImplementedException at OcspTypes.cs:150
|
||||||
|
2484,LocalDirCache,AdjustStatsHitToMiss,server/ocsp_responsecache.go,adjustStatsHitToMiss,unknown,deferred,Class exists but method not found
|
||||||
|
2485,LocalDirCache,AdjustStats,server/ocsp_responsecache.go,adjustStats,unknown,deferred,Class exists but method not found
|
||||||
|
2486,LocalDirCache,Delete,server/ocsp_responsecache.go,Delete,unknown,deferred,Class exists but method not found
|
||||||
|
2487,LocalDirCache,Start,server/ocsp_responsecache.go,Start,unknown,deferred,Class exists but method not found
|
||||||
|
2488,LocalDirCache,Stop,server/ocsp_responsecache.go,Stop,unknown,deferred,Class exists but method not found
|
||||||
|
2489,LocalDirCache,Online,server/ocsp_responsecache.go,Online,unknown,deferred,Class exists but method not found
|
||||||
|
2490,LocalDirCache,Type,server/ocsp_responsecache.go,Type,unknown,deferred,Class exists but method not found
|
||||||
|
2491,LocalDirCache,Config,server/ocsp_responsecache.go,Config,unknown,deferred,Class exists but method not found
|
||||||
|
2492,LocalDirCache,Stats,server/ocsp_responsecache.go,Stats,unknown,deferred,Class exists but method not found
|
||||||
|
2493,LocalDirCache,InitStats,server/ocsp_responsecache.go,initStats,unknown,deferred,Class exists but method not found
|
||||||
|
2494,LocalDirCache,Compress,server/ocsp_responsecache.go,Compress,unknown,deferred,Class exists but method not found
|
||||||
|
2495,LocalDirCache,Decompress,server/ocsp_responsecache.go,Decompress,unknown,deferred,Class exists but method not found
|
||||||
|
2496,LocalDirCache,LoadCache,server/ocsp_responsecache.go,loadCache,unknown,deferred,Class exists but method not found
|
||||||
|
2497,LocalDirCache,SaveCache,server/ocsp_responsecache.go,saveCache,unknown,deferred,Class exists but method not found
|
||||||
|
2588,ClientConnection,Parse,server/parser.go,parse,unknown,deferred,Class exists but method not found
|
||||||
|
2590,ClientConnection,OverMaxControlLineLimit,server/parser.go,overMaxControlLineLimit,unknown,deferred,Class exists but method not found
|
||||||
|
2591,ClientConnection,ClonePubArg,server/parser.go,clonePubArg,unknown,deferred,Class exists but method not found
|
||||||
|
2603,IRaftNode,CheckAccountNRGStatus,server/raft.go,checkAccountNRGStatus,unknown,deferred,Class exists but method not found
|
||||||
|
2604,IRaftNode,IsSystemAccount,server/raft.go,IsSystemAccount,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:99
|
||||||
|
2605,IRaftNode,GetTrafficAccountName,server/raft.go,GetTrafficAccountName,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:100
|
||||||
|
2606,IRaftNode,RecreateInternalSubs,server/raft.go,RecreateInternalSubs,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:98
|
||||||
|
2607,IRaftNode,RecreateInternalSubsLocked,server/raft.go,recreateInternalSubsLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2608,IRaftNode,OutOfResources,server/raft.go,outOfResources,unknown,deferred,Class exists but method not found
|
||||||
|
2619,IRaftNode,Propose,server/raft.go,Propose,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:35
|
||||||
|
2620,IRaftNode,ProposeMulti,server/raft.go,ProposeMulti,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:36
|
||||||
|
2621,IRaftNode,ForwardProposal,server/raft.go,ForwardProposal,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:37
|
||||||
|
2622,IRaftNode,ProposeAddPeer,server/raft.go,ProposeAddPeer,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:77
|
||||||
|
2623,IRaftNode,ProposeRemovePeer,server/raft.go,ProposeRemovePeer,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:78
|
||||||
|
2624,IRaftNode,MembershipChangeInProgress,server/raft.go,MembershipChangeInProgress,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:79
|
||||||
|
2625,IRaftNode,ClusterSize,server/raft.go,ClusterSize,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:82
|
||||||
|
2626,IRaftNode,AdjustBootClusterSize,server/raft.go,AdjustBootClusterSize,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:81
|
||||||
|
2627,IRaftNode,AdjustClusterSize,server/raft.go,AdjustClusterSize,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:80
|
||||||
|
2628,IRaftNode,PauseApply,server/raft.go,PauseApply,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:86
|
||||||
|
2629,IRaftNode,PauseApplyLocked,server/raft.go,pauseApplyLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2630,IRaftNode,ResumeApply,server/raft.go,ResumeApply,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:87
|
||||||
|
2631,IRaftNode,DrainAndReplaySnapshot,server/raft.go,DrainAndReplaySnapshot,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:88
|
||||||
|
2632,IRaftNode,Applied,server/raft.go,Applied,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:47
|
||||||
|
2633,IRaftNode,Processed,server/raft.go,Processed,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:48
|
||||||
|
2634,IRaftNode,EncodeSnapshot,server/raft.go,encodeSnapshot,unknown,deferred,Class exists but method not found
|
||||||
|
2635,IRaftNode,SendSnapshot,server/raft.go,SendSnapshot,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:43
|
||||||
|
2636,IRaftNode,InstallSnapshot,server/raft.go,InstallSnapshot,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:40
|
||||||
|
2637,IRaftNode,InstallSnapshotInternal,server/raft.go,installSnapshot,unknown,deferred,Class exists but method not found
|
||||||
|
2638,IRaftNode,CreateSnapshotCheckpoint,server/raft.go,CreateSnapshotCheckpoint,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:42
|
||||||
|
2639,IRaftNode,CreateSnapshotCheckpointLocked,server/raft.go,createSnapshotCheckpointLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2644,IRaftNode,NeedSnapshot,server/raft.go,NeedSnapshot,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:44
|
||||||
|
2645,IRaftNode,TermAndIndexFromSnapFile,server/raft.go,termAndIndexFromSnapFile,unknown,deferred,Class exists but method not found
|
||||||
|
2646,IRaftNode,SetupLastSnapshot,server/raft.go,setupLastSnapshot,unknown,deferred,Class exists but method not found
|
||||||
|
2647,IRaftNode,LoadLastSnapshot,server/raft.go,loadLastSnapshot,unknown,deferred,Class exists but method not found
|
||||||
|
2648,IRaftNode,Leader,server/raft.go,Leader,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:52
|
||||||
|
2649,IRaftNode,LeaderSince,server/raft.go,LeaderSince,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:53
|
||||||
|
2650,IRaftNode,Stepdown,server/raft.go,stepdown,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:63
|
||||||
|
2651,IRaftNode,StepdownLocked,server/raft.go,stepdownLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2652,IRaftNode,IsCatchingUp,server/raft.go,isCatchingUp,unknown,deferred,Class exists but method not found
|
||||||
|
2653,IRaftNode,IsCurrent,server/raft.go,isCurrent,unknown,deferred,Class exists but method not found
|
||||||
|
2654,IRaftNode,Current,server/raft.go,Current,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:55
|
||||||
|
2655,IRaftNode,Healthy,server/raft.go,Healthy,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:56
|
||||||
|
2656,IRaftNode,HadPreviousLeader,server/raft.go,HadPreviousLeader,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:60
|
||||||
|
2657,IRaftNode,GroupLeader,server/raft.go,GroupLeader,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:59
|
||||||
|
2658,IRaftNode,Leaderless,server/raft.go,Leaderless,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:58
|
||||||
|
2659,IRaftNode,SelectNextLeader,server/raft.go,selectNextLeader,unknown,deferred,Class exists but method not found
|
||||||
|
2660,IRaftNode,StepDown,server/raft.go,StepDown,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:63
|
||||||
|
2661,IRaftNode,Campaign,server/raft.go,Campaign,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:66
|
||||||
|
2662,IRaftNode,CampaignImmediately,server/raft.go,CampaignImmediately,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:67
|
||||||
|
2663,IRaftNode,RandCampaignTimeout,server/raft.go,randCampaignTimeout,unknown,deferred,Class exists but method not found
|
||||||
|
2664,IRaftNode,CampaignInternal,server/raft.go,campaign,unknown,deferred,Class exists but method not found
|
||||||
|
2665,IRaftNode,XferCampaign,server/raft.go,xferCampaign,unknown,deferred,Class exists but method not found
|
||||||
|
2666,IRaftNode,State,server/raft.go,State,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:49
|
||||||
|
2667,IRaftNode,Progress,server/raft.go,Progress,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:51
|
||||||
|
2668,IRaftNode,Size,server/raft.go,Size,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:50
|
||||||
|
2669,IRaftNode,ID,server/raft.go,ID,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:70
|
||||||
|
2670,IRaftNode,Group,server/raft.go,Group,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:71
|
||||||
|
2671,IRaftNode,Peers,server/raft.go,Peers,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:74
|
||||||
|
2672,IRaftNode,ProposeKnownPeers,server/raft.go,ProposeKnownPeers,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:75
|
||||||
|
2673,IRaftNode,UpdateKnownPeers,server/raft.go,UpdateKnownPeers,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:76
|
||||||
|
2674,IRaftNode,UpdateKnownPeersLocked,server/raft.go,updateKnownPeersLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2675,IRaftNode,ApplyQ,server/raft.go,ApplyQ,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:85
|
||||||
|
2676,IRaftNode,LeadChangeC,server/raft.go,LeadChangeC,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:91
|
||||||
|
2677,IRaftNode,QuitC,server/raft.go,QuitC,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:92
|
||||||
|
2678,IRaftNode,Created,server/raft.go,Created,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:93
|
||||||
|
2679,IRaftNode,Stop,server/raft.go,Stop,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:94
|
||||||
|
2680,IRaftNode,WaitForStop,server/raft.go,WaitForStop,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:95
|
||||||
|
2681,IRaftNode,Delete,server/raft.go,Delete,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:96
|
||||||
|
2682,IRaftNode,IsDeleted,server/raft.go,IsDeleted,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:97
|
||||||
|
2683,IRaftNode,Shutdown,server/raft.go,shutdown,unknown,deferred,Class exists but method not found
|
||||||
|
2684,IRaftNode,NewCatchupInbox,server/raft.go,newCatchupInbox,unknown,deferred,Class exists but method not found
|
||||||
|
2685,IRaftNode,NewInbox,server/raft.go,newInbox,unknown,deferred,Class exists but method not found
|
||||||
|
2686,IRaftNode,Subscribe,server/raft.go,subscribe,unknown,deferred,Class exists but method not found
|
||||||
|
2687,IRaftNode,Unsubscribe,server/raft.go,unsubscribe,unknown,deferred,Class exists but method not found
|
||||||
|
2688,IRaftNode,CreateInternalSubs,server/raft.go,createInternalSubs,unknown,deferred,Class exists but method not found
|
||||||
|
2689,IRaftNode,RandElectionTimeout,server/raft.go,randElectionTimeout,unknown,deferred,Class exists but method not found
|
||||||
|
2690,IRaftNode,ResetElectionTimeout,server/raft.go,resetElectionTimeout,unknown,deferred,Class exists but method not found
|
||||||
|
2691,IRaftNode,ResetElectionTimeoutWithLock,server/raft.go,resetElectionTimeoutWithLock,unknown,deferred,Class exists but method not found
|
||||||
|
2692,IRaftNode,ResetElect,server/raft.go,resetElect,unknown,deferred,Class exists but method not found
|
||||||
|
2693,IRaftNode,ResetElectWithLock,server/raft.go,resetElectWithLock,unknown,deferred,Class exists but method not found
|
||||||
|
2694,IRaftNode,Run,server/raft.go,run,unknown,deferred,Class exists but method not found
|
||||||
|
2695,IRaftNode,Debug,server/raft.go,debug,unknown,deferred,Class exists but method not found
|
||||||
|
2696,IRaftNode,Warn,server/raft.go,warn,unknown,deferred,Class exists but method not found
|
||||||
|
2697,IRaftNode,Error,server/raft.go,error,unknown,deferred,Class exists but method not found
|
||||||
|
2698,IRaftNode,ElectTimer,server/raft.go,electTimer,unknown,deferred,Class exists but method not found
|
||||||
|
2699,IRaftNode,IsObserver,server/raft.go,IsObserver,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:65
|
||||||
|
2700,IRaftNode,SetObserver,server/raft.go,SetObserver,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:64
|
||||||
|
2701,IRaftNode,SetObserverInternal,server/raft.go,setObserver,unknown,deferred,Class exists but method not found
|
||||||
|
2702,IRaftNode,SetObserverLocked,server/raft.go,setObserverLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2703,IRaftNode,ProcessAppendEntries,server/raft.go,processAppendEntries,unknown,deferred,Class exists but method not found
|
||||||
|
2704,IRaftNode,RunAsFollower,server/raft.go,runAsFollower,unknown,deferred,Class exists but method not found
|
||||||
|
2705,IRaftNode,NewCommittedEntry,server/raft.go,newCommittedEntry,unknown,deferred,Class exists but method not found
|
||||||
|
2707,IRaftNode,NewEntry,server/raft.go,newEntry,unknown,deferred,Class exists but method not found
|
||||||
|
2708,IRaftNode,NewAppendEntry,server/raft.go,newAppendEntry,unknown,deferred,Class exists but method not found
|
||||||
|
2710,IRaftNode,NewProposedEntry,server/raft.go,newProposedEntry,unknown,deferred,Class exists but method not found
|
||||||
|
2716,IRaftNode,DecodeAppendEntry,server/raft.go,decodeAppendEntry,unknown,deferred,Class exists but method not found
|
||||||
|
2717,IRaftNode,NewAppendEntryResponse,server/raft.go,newAppendEntryResponse,unknown,deferred,Class exists but method not found
|
||||||
|
2719,IRaftNode,DecodeAppendEntryResponse,server/raft.go,decodeAppendEntryResponse,unknown,deferred,Class exists but method not found
|
||||||
|
2720,IRaftNode,HandleForwardedRemovePeerProposal,server/raft.go,handleForwardedRemovePeerProposal,unknown,deferred,Class exists but method not found
|
||||||
|
2721,IRaftNode,HandleForwardedProposal,server/raft.go,handleForwardedProposal,unknown,deferred,Class exists but method not found
|
||||||
|
2722,IRaftNode,AddPeer,server/raft.go,addPeer,unknown,deferred,Class exists but method not found
|
||||||
|
2723,IRaftNode,RemovePeer,server/raft.go,removePeer,unknown,deferred,Class exists but method not found
|
||||||
|
2724,IRaftNode,SendMembershipChange,server/raft.go,sendMembershipChange,unknown,deferred,Class exists but method not found
|
||||||
|
2725,IRaftNode,RunAsLeader,server/raft.go,runAsLeader,unknown,deferred,Class exists but method not found
|
||||||
|
2726,IRaftNode,Quorum,server/raft.go,Quorum,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:54
|
||||||
|
2727,IRaftNode,LostQuorum,server/raft.go,lostQuorum,unknown,deferred,Class exists but method not found
|
||||||
|
2728,IRaftNode,LostQuorumLocked,server/raft.go,lostQuorumLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2729,IRaftNode,NotActive,server/raft.go,notActive,unknown,deferred,Class exists but method not found
|
||||||
|
2730,IRaftNode,Term,server/raft.go,Term,unknown,stub,Body is throw NotImplementedException at RaftTypes.cs:57
|
||||||
|
2731,IRaftNode,LoadFirstEntry,server/raft.go,loadFirstEntry,unknown,deferred,Class exists but method not found
|
||||||
|
2732,IRaftNode,RunCatchup,server/raft.go,runCatchup,unknown,deferred,Class exists but method not found
|
||||||
|
2733,IRaftNode,SendSnapshotToFollower,server/raft.go,sendSnapshotToFollower,unknown,deferred,Class exists but method not found
|
||||||
|
2734,IRaftNode,CatchupFollower,server/raft.go,catchupFollower,unknown,deferred,Class exists but method not found
|
||||||
|
2735,IRaftNode,LoadEntry,server/raft.go,loadEntry,unknown,deferred,Class exists but method not found
|
||||||
|
2736,IRaftNode,ApplyCommit,server/raft.go,applyCommit,unknown,deferred,Class exists but method not found
|
||||||
|
2737,IRaftNode,TryCommit,server/raft.go,tryCommit,unknown,deferred,Class exists but method not found
|
||||||
|
2738,IRaftNode,TrackResponse,server/raft.go,trackResponse,unknown,deferred,Class exists but method not found
|
||||||
|
2739,IRaftNode,AdjustClusterSizeAndQuorum,server/raft.go,adjustClusterSizeAndQuorum,unknown,deferred,Class exists but method not found
|
||||||
|
2740,IRaftNode,TrackPeer,server/raft.go,trackPeer,unknown,deferred,Class exists but method not found
|
||||||
|
2741,IRaftNode,RunAsCandidate,server/raft.go,runAsCandidate,unknown,deferred,Class exists but method not found
|
||||||
|
2742,IRaftNode,HandleAppendEntry,server/raft.go,handleAppendEntry,unknown,deferred,Class exists but method not found
|
||||||
|
2743,IRaftNode,CancelCatchup,server/raft.go,cancelCatchup,unknown,deferred,Class exists but method not found
|
||||||
|
2744,IRaftNode,CatchupStalled,server/raft.go,catchupStalled,unknown,deferred,Class exists but method not found
|
||||||
|
2745,IRaftNode,CreateCatchup,server/raft.go,createCatchup,unknown,deferred,Class exists but method not found
|
||||||
|
2746,IRaftNode,SendCatchupSignal,server/raft.go,sendCatchupSignal,unknown,deferred,Class exists but method not found
|
||||||
|
2747,IRaftNode,CancelCatchupSignal,server/raft.go,cancelCatchupSignal,unknown,deferred,Class exists but method not found
|
||||||
|
2748,IRaftNode,TruncateWAL,server/raft.go,truncateWAL,unknown,deferred,Class exists but method not found
|
||||||
|
2749,IRaftNode,ResetWAL,server/raft.go,resetWAL,unknown,deferred,Class exists but method not found
|
||||||
|
2750,IRaftNode,UpdateLeader,server/raft.go,updateLeader,unknown,deferred,Class exists but method not found
|
||||||
|
2751,IRaftNode,ProcessAppendEntry,server/raft.go,processAppendEntry,unknown,deferred,Class exists but method not found
|
||||||
|
2752,IRaftNode,ResetInitializing,server/raft.go,resetInitializing,unknown,deferred,Class exists but method not found
|
||||||
|
2753,IRaftNode,ProcessPeerState,server/raft.go,processPeerState,unknown,deferred,Class exists but method not found
|
||||||
|
2754,IRaftNode,ProcessAppendEntryResponse,server/raft.go,processAppendEntryResponse,unknown,deferred,Class exists but method not found
|
||||||
|
2755,IRaftNode,HandleAppendEntryResponse,server/raft.go,handleAppendEntryResponse,unknown,deferred,Class exists but method not found
|
||||||
|
2756,IRaftNode,BuildAppendEntry,server/raft.go,buildAppendEntry,unknown,deferred,Class exists but method not found
|
||||||
|
2758,IRaftNode,StoreToWAL,server/raft.go,storeToWAL,unknown,deferred,Class exists but method not found
|
||||||
|
2759,IRaftNode,SendAppendEntry,server/raft.go,sendAppendEntry,unknown,deferred,Class exists but method not found
|
||||||
|
2760,IRaftNode,SendAppendEntryLocked,server/raft.go,sendAppendEntryLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2761,IRaftNode,CachePendingEntry,server/raft.go,cachePendingEntry,unknown,deferred,Class exists but method not found
|
||||||
|
2762,IRaftNode,PeerStateBufSize,server/raft.go,peerStateBufSize,unknown,deferred,Class exists but method not found
|
||||||
|
2763,IRaftNode,EncodePeerState,server/raft.go,encodePeerState,unknown,deferred,Class exists but method not found
|
||||||
|
2764,IRaftNode,DecodePeerState,server/raft.go,decodePeerState,unknown,deferred,Class exists but method not found
|
||||||
|
2765,IRaftNode,PeerNames,server/raft.go,peerNames,unknown,deferred,Class exists but method not found
|
||||||
|
2766,IRaftNode,CurrentPeerState,server/raft.go,currentPeerState,unknown,deferred,Class exists but method not found
|
||||||
|
2767,IRaftNode,CurrentPeerStateLocked,server/raft.go,currentPeerStateLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2768,IRaftNode,SendPeerState,server/raft.go,sendPeerState,unknown,deferred,Class exists but method not found
|
||||||
|
2769,IRaftNode,SendHeartbeat,server/raft.go,sendHeartbeat,unknown,deferred,Class exists but method not found
|
||||||
|
2771,IRaftNode,DecodeVoteRequest,server/raft.go,decodeVoteRequest,unknown,deferred,Class exists but method not found
|
||||||
|
2772,IRaftNode,WritePeerStateInternal,server/raft.go,writePeerState,unknown,deferred,Class exists but method not found
|
||||||
|
2773,IRaftNode,WritePeerStateStatic,server/raft.go,writePeerState,unknown,deferred,Class exists but method not found
|
||||||
|
2774,IRaftNode,ReadPeerState,server/raft.go,readPeerState,unknown,deferred,Class exists but method not found
|
||||||
|
2775,IRaftNode,WriteTermVoteStatic,server/raft.go,writeTermVote,unknown,deferred,Class exists but method not found
|
||||||
|
2776,IRaftNode,ReadTermVote,server/raft.go,readTermVote,unknown,deferred,Class exists but method not found
|
||||||
|
2777,IRaftNode,SetWriteErrLocked,server/raft.go,setWriteErrLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2778,IRaftNode,IsClosed,server/raft.go,isClosed,unknown,deferred,Class exists but method not found
|
||||||
|
2779,IRaftNode,SetWriteErr,server/raft.go,setWriteErr,unknown,deferred,Class exists but method not found
|
||||||
|
2780,IRaftNode,WriteTermVoteInternal,server/raft.go,writeTermVote,unknown,deferred,Class exists but method not found
|
||||||
|
2782,IRaftNode,DecodeVoteResponse,server/raft.go,decodeVoteResponse,unknown,deferred,Class exists but method not found
|
||||||
|
2783,IRaftNode,HandleVoteResponse,server/raft.go,handleVoteResponse,unknown,deferred,Class exists but method not found
|
||||||
|
2784,IRaftNode,ProcessVoteRequest,server/raft.go,processVoteRequest,unknown,deferred,Class exists but method not found
|
||||||
|
2785,IRaftNode,HandleVoteRequest,server/raft.go,handleVoteRequest,unknown,deferred,Class exists but method not found
|
||||||
|
2786,IRaftNode,RequestVote,server/raft.go,requestVote,unknown,deferred,Class exists but method not found
|
||||||
|
2787,IRaftNode,SendRPC,server/raft.go,sendRPC,unknown,deferred,Class exists but method not found
|
||||||
|
2788,IRaftNode,SendReply,server/raft.go,sendReply,unknown,deferred,Class exists but method not found
|
||||||
|
2789,IRaftNode,WonElection,server/raft.go,wonElection,unknown,deferred,Class exists but method not found
|
||||||
|
2790,IRaftNode,QuorumNeeded,server/raft.go,quorumNeeded,unknown,deferred,Class exists but method not found
|
||||||
|
2791,IRaftNode,UpdateLeadChange,server/raft.go,updateLeadChange,unknown,deferred,Class exists but method not found
|
||||||
|
2792,IRaftNode,SwitchState,server/raft.go,switchState,unknown,deferred,Class exists but method not found
|
||||||
|
2793,IRaftNode,SwitchToFollower,server/raft.go,switchToFollower,unknown,deferred,Class exists but method not found
|
||||||
|
2794,IRaftNode,SwitchToFollowerLocked,server/raft.go,switchToFollowerLocked,unknown,deferred,Class exists but method not found
|
||||||
|
2795,IRaftNode,SwitchToCandidate,server/raft.go,switchToCandidate,unknown,deferred,Class exists but method not found
|
||||||
|
2796,IRaftNode,SwitchToLeader,server/raft.go,switchToLeader,unknown,deferred,Class exists but method not found
|
||||||
|
2800,NoopReloadOption,IsLoggingChange,server/reload.go,IsLoggingChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:80
|
||||||
|
2801,NoopReloadOption,IsTraceLevelChange,server/reload.go,IsTraceLevelChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:83
|
||||||
|
2802,NoopReloadOption,IsAuthChange,server/reload.go,IsAuthChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:86
|
||||||
|
2803,NoopReloadOption,IsTLSChange,server/reload.go,IsTLSChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:89
|
||||||
|
2804,NoopReloadOption,IsClusterPermsChange,server/reload.go,IsClusterPermsChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:92
|
||||||
|
2805,NoopReloadOption,IsClusterPoolSizeOrAccountsChange,server/reload.go,IsClusterPoolSizeOrAccountsChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:95
|
||||||
|
2806,NoopReloadOption,IsJetStreamChange,server/reload.go,IsJetStreamChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:98
|
||||||
|
2807,NoopReloadOption,IsStatszChange,server/reload.go,IsStatszChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:101
|
||||||
|
2808,LoggingReloadOption,IsLoggingChange,server/reload.go,IsLoggingChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:114
|
||||||
|
2809,TraceLevelReloadOption,IsTraceLevelChange,server/reload.go,IsTraceLevelChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:123
|
||||||
|
2810,TraceReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:157
|
||||||
|
2811,TraceVerboseReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:170
|
||||||
|
2812,TraceHeadersReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:183
|
||||||
|
2813,DebugReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:196
|
||||||
|
2814,LogtimeReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:212
|
||||||
|
2817,SyslogReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:251
|
||||||
|
2818,RemoteSyslogReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:264
|
||||||
|
2819,TlsReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 2 statement(s) at ReloadOptions.cs:287
|
||||||
|
2820,TlsReloadOption,IsTLSChange,server/reload.go,IsTLSChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:285
|
||||||
|
2821,TlsTimeoutReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:304
|
||||||
|
2822,TlsPinnedCertReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:321
|
||||||
|
2823,TlsHandshakeFirstReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:334
|
||||||
|
2824,TlsHandshakeFirstFallbackReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:347
|
||||||
|
2825,AuthReloadOption,IsAuthChange,server/reload.go,IsAuthChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:132
|
||||||
|
2826,UsernameReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:361
|
||||||
|
2827,PasswordReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:371
|
||||||
|
2828,AuthorizationReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:381
|
||||||
|
2829,AuthTimeoutReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:396
|
||||||
|
2830,TagsReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:406
|
||||||
|
2831,TagsReloadOption,IsStatszChange,server/reload.go,IsStatszChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:409
|
||||||
|
2832,MetadataReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:418
|
||||||
|
2833,MetadataReloadOption,IsStatszChange,server/reload.go,IsStatszChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:421
|
||||||
|
2834,UsersReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:430
|
||||||
|
2835,NkeysReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:440
|
||||||
|
2836,ClusterReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:496
|
||||||
|
2837,ClusterReloadOption,IsClusterPermsChange,server/reload.go,IsClusterPermsChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:490
|
||||||
|
2838,ClusterReloadOption,IsClusterPoolSizeOrAccountsChange,server/reload.go,IsClusterPoolSizeOrAccountsChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:493
|
||||||
|
2839,ClusterReloadOption,DiffPoolAndAccounts,server/reload.go,diffPoolAndAccounts,unknown,deferred,Class exists but method not found
|
||||||
|
2840,RoutesReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:521
|
||||||
|
2841,MaxConnReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:541
|
||||||
|
2842,PidFileReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 2 statement(s) at ReloadOptions.cs:557
|
||||||
|
2843,PortsFileDirReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:581
|
||||||
|
2844,MaxControlLineReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:597
|
||||||
|
2845,MaxPayloadReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:613
|
||||||
|
2846,PingIntervalReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:629
|
||||||
|
2847,MaxPingsOutReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:642
|
||||||
|
2848,WriteDeadlineReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:655
|
||||||
|
2849,ClientAdvertiseReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:668
|
||||||
|
2850,AccountsReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:450
|
||||||
|
2851,JetStreamReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:691
|
||||||
|
2852,JetStreamReloadOption,IsJetStreamChange,server/reload.go,IsJetStreamChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:688
|
||||||
|
2853,JetStreamReloadOption,IsStatszChange,server/reload.go,IsStatszChange,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:689
|
||||||
|
2854,DefaultSentinelReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:708
|
||||||
|
2855,OcspReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:724
|
||||||
|
2856,OcspResponseCacheReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:741
|
||||||
|
2857,ConnectErrorReportsReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:754
|
||||||
|
2858,ReconnectErrorReportsReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:767
|
||||||
|
2859,MaxTracedMsgLenReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:780
|
||||||
|
2860,MqttAckWaitReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:800
|
||||||
|
2861,MqttMaxAckPendingReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:813
|
||||||
|
2862,MqttStreamReplicasReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:829
|
||||||
|
2863,MqttConsumerReplicasReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:842
|
||||||
|
2864,MqttConsumerMemoryStorageReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:855
|
||||||
|
2865,MqttInactiveThresholdReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:868
|
||||||
|
2866,ProfBlockRateReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 1 statement(s) at ReloadOptions.cs:885
|
||||||
|
2867,LeafNodeReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 3 statement(s) at ReloadOptions.cs:913
|
||||||
|
2868,NoFastProducerStallReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 2 statement(s) at ReloadOptions.cs:938
|
||||||
|
2870,ProxiesReloadOption,Apply,server/reload.go,Apply,unknown,verified,Method found with 2 statement(s) at ReloadOptions.cs:964
|
||||||
|
2895,ClientConnection,RemoveReplySub,server/route.go,removeReplySub,unknown,deferred,Class exists but method not found
|
||||||
|
2896,ClientConnection,ProcessAccountSub,server/route.go,processAccountSub,unknown,deferred,Class exists but method not found
|
||||||
|
2897,ClientConnection,ProcessAccountUnsub,server/route.go,processAccountUnsub,unknown,deferred,Class exists but method not found
|
||||||
|
2898,ClientConnection,ProcessRoutedOriginClusterMsgArgs,server/route.go,processRoutedOriginClusterMsgArgs,unknown,deferred,Class exists but method not found
|
||||||
|
2899,ClientConnection,ProcessRoutedHeaderMsgArgs,server/route.go,processRoutedHeaderMsgArgs,unknown,deferred,Class exists but method not found
|
||||||
|
2900,ClientConnection,ProcessRoutedMsgArgs,server/route.go,processRoutedMsgArgs,unknown,deferred,Class exists but method not found
|
||||||
|
2901,ClientConnection,ProcessInboundRoutedMsg,server/route.go,processInboundRoutedMsg,unknown,deferred,Class exists but method not found
|
||||||
|
2902,ClientConnection,SendRouteConnect,server/route.go,sendRouteConnect,unknown,deferred,Class exists but method not found
|
||||||
|
2904,ClientConnection,ProcessRouteInfo,server/route.go,processRouteInfo,unknown,deferred,Class exists but method not found
|
||||||
|
2911,ClientConnection,CanImport,server/route.go,canImport,unknown,deferred,Class exists but method not found
|
||||||
|
2912,ClientConnection,CanExport,server/route.go,canExport,unknown,deferred,Class exists but method not found
|
||||||
|
2913,ClientConnection,SetRoutePermissions,server/route.go,setRoutePermissions,unknown,deferred,Class exists but method not found
|
||||||
|
2915,ClientConnection,GetRoutedSubKeyInfo,server/route.go,getRoutedSubKeyInfo,unknown,deferred,Class exists but method not found
|
||||||
|
2916,ClientConnection,RemoveRemoteSubs,server/route.go,removeRemoteSubs,unknown,deferred,Class exists but method not found
|
||||||
|
2917,ClientConnection,RemoveRemoteSubsForAcc,server/route.go,removeRemoteSubsForAcc,unknown,deferred,Class exists but method not found
|
||||||
|
2918,ClientConnection,ParseUnsubProto,server/route.go,parseUnsubProto,unknown,deferred,Class exists but method not found
|
||||||
|
2919,ClientConnection,ProcessRemoteUnsub,server/route.go,processRemoteUnsub,unknown,deferred,Class exists but method not found
|
||||||
|
2920,ClientConnection,ProcessRemoteSub,server/route.go,processRemoteSub,unknown,deferred,Class exists but method not found
|
||||||
|
2921,ClientConnection,AddRouteSubOrUnsubProtoToBuf,server/route.go,addRouteSubOrUnsubProtoToBuf,unknown,deferred,Class exists but method not found
|
||||||
|
2923,ClientConnection,SendRouteSubProtos,server/route.go,sendRouteSubProtos,unknown,deferred,Class exists but method not found
|
||||||
|
2924,ClientConnection,SendRouteUnSubProtos,server/route.go,sendRouteUnSubProtos,unknown,deferred,Class exists but method not found
|
||||||
|
2925,ClientConnection,SendRouteSubOrUnSubProtos,server/route.go,sendRouteSubOrUnSubProtos,unknown,deferred,Class exists but method not found
|
||||||
|
2933,ClientConnection,ImportFilter,server/route.go,importFilter,unknown,deferred,Class exists but method not found
|
||||||
|
2941,ClientConnection,IsSolicitedRoute,server/route.go,isSolicitedRoute,unknown,deferred,Class exists but method not found
|
||||||
|
2944,ClientConnection,ProcessRouteConnect,server/route.go,processRouteConnect,unknown,deferred,Class exists but method not found
|
||||||
|
3164,StorageType,IsEncodedStreamState,server/store.go,IsEncodedStreamState,unknown,deferred,Class not found in .NET source
|
||||||
|
3165,StorageType,DecodeStreamState,server/store.go,DecodeStreamState,unknown,deferred,Class not found in .NET source
|
||||||
|
3171,StorageType,EncodeConsumerState,server/store.go,encodeConsumerState,unknown,deferred,Class not found in .NET source
|
||||||
|
3178,StorageType,String,server/store.go,String,unknown,deferred,Class not found in .NET source
|
||||||
|
3179,StorageType,MarshalJSON,server/store.go,MarshalJSON,unknown,deferred,Class not found in .NET source
|
||||||
|
3180,StorageType,UnmarshalJSON,server/store.go,UnmarshalJSON,unknown,deferred,Class not found in .NET source
|
||||||
|
3187,StorageType,IsOutOfSpaceErr,server/store.go,isOutOfSpaceErr,unknown,deferred,Class not found in .NET source
|
||||||
|
3188,StorageType,IsClusterResetErr,server/store.go,isClusterResetErr,unknown,deferred,Class not found in .NET source
|
||||||
|
3191,StorageType,BytesToString,server/store.go,bytesToString,unknown,deferred,Class not found in .NET source
|
||||||
|
3192,StorageType,StringToBytes,server/store.go,stringToBytes,unknown,deferred,Class not found in .NET source
|
||||||
|
3193,StorageType,CopyString,server/store.go,copyString,unknown,deferred,Class not found in .NET source
|
||||||
|
3194,StorageType,IsPermissionError,server/store.go,isPermissionError,unknown,deferred,Class not found in .NET source
|
||||||
|
3254,StreamSourceInfo,IsCurrentSub,server/stream.go,isCurrentSub,unknown,deferred,Class exists but method not found
|
||||||
|
3274,StreamSourceInfo,GenSourceHeader,server/stream.go,genSourceHeader,unknown,deferred,Class exists but method not found
|
||||||
|
3337,JsPubMsg,ReturnToPool,server/stream.go,returnToPool,unknown,deferred,Class exists but method not found
|
||||||
|
3338,JsPubMsg,Size,server/stream.go,size,unknown,deferred,Class exists but method not found
|
||||||
|
3509,ClientConnection,WsRead,server/websocket.go,wsRead,unknown,deferred,Class exists but method not found
|
||||||
|
3514,ClientConnection,WsHandleControlFrame,server/websocket.go,wsHandleControlFrame,unknown,deferred,Class exists but method not found
|
||||||
|
3519,ClientConnection,WsEnqueueControlMessage,server/websocket.go,wsEnqueueControlMessage,unknown,deferred,Class exists but method not found
|
||||||
|
3522,ClientConnection,WsEnqueueControlMessageLocked,server/websocket.go,wsEnqueueControlMessageLocked,unknown,deferred,Class exists but method not found
|
||||||
|
3523,ClientConnection,WsEnqueueCloseMessage,server/websocket.go,wsEnqueueCloseMessage,unknown,deferred,Class exists but method not found
|
||||||
|
3524,ClientConnection,WsHandleProtocolError,server/websocket.go,wsHandleProtocolError,unknown,deferred,Class exists but method not found
|
||||||
|
3541,ClientConnection,WsCollapsePtoNB,server/websocket.go,wsCollapsePtoNB,unknown,deferred,Class exists but method not found
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# NATS .NET Porting Status Report
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
Generated: 2026-02-27 09:45:24 UTC
|
Generated: 2026-02-27 15:27:06 UTC
|
||||||
|
|
||||||
## Modules (12 total)
|
## Modules (12 total)
|
||||||
|
|
||||||
@@ -12,16 +12,18 @@ Generated: 2026-02-27 09:45:24 UTC
|
|||||||
|
|
||||||
| Status | Count |
|
| Status | Count |
|
||||||
|--------|-------|
|
|--------|-------|
|
||||||
| deferred | 3394 |
|
| deferred | 2377 |
|
||||||
| verified | 279 |
|
| n_a | 24 |
|
||||||
|
| stub | 1 |
|
||||||
|
| verified | 1271 |
|
||||||
|
|
||||||
## Unit Tests (3257 total)
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
| Status | Count |
|
| Status | Count |
|
||||||
|--------|-------|
|
|--------|-------|
|
||||||
| deferred | 2680 |
|
| deferred | 2660 |
|
||||||
| n_a | 187 |
|
| n_a | 187 |
|
||||||
| verified | 390 |
|
| verified | 410 |
|
||||||
|
|
||||||
## Library Mappings (36 total)
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
@@ -32,4 +34,4 @@ Generated: 2026-02-27 09:45:24 UTC
|
|||||||
|
|
||||||
## Overall Progress
|
## Overall Progress
|
||||||
|
|
||||||
**868/6942 items complete (12.5%)**
|
**1904/6942 items complete (27.4%)**
|
||||||
|
|||||||
35
reports/report_01df4cc.md
Normal file
35
reports/report_01df4cc.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 09:49:21 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| unknown | 3394 |
|
||||||
|
| verified | 279 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2680 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**868/6942 items complete (12.5%)**
|
||||||
35
reports/report_0436e08.md
Normal file
35
reports/report_0436e08.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 10:18:29 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| unknown | 3394 |
|
||||||
|
| verified | 279 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2680 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**868/6942 items complete (12.5%)**
|
||||||
37
reports/report_0c9eb2a.md
Normal file
37
reports/report_0c9eb2a.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 10:27:48 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2500 |
|
||||||
|
| n_a | 18 |
|
||||||
|
| stub | 168 |
|
||||||
|
| verified | 987 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2680 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1594/6942 items complete (23.0%)**
|
||||||
37
reports/report_26d6d7f.md
Normal file
37
reports/report_26d6d7f.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 10:21:46 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2692 |
|
||||||
|
| n_a | 18 |
|
||||||
|
| stub | 109 |
|
||||||
|
| verified | 854 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2680 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1461/6942 items complete (21.0%)**
|
||||||
35
reports/report_2dd2321.md
Normal file
35
reports/report_2dd2321.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 10:17:36 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| unknown | 3394 |
|
||||||
|
| verified | 279 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2680 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**868/6942 items complete (12.5%)**
|
||||||
38
reports/report_3297334.md
Normal file
38
reports/report_3297334.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 10:50:16 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2501 |
|
||||||
|
| n_a | 18 |
|
||||||
|
| stub | 168 |
|
||||||
|
| verified | 986 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2662 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| stub | 18 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1593/6942 items complete (22.9%)**
|
||||||
37
reports/report_485c7b0.md
Normal file
37
reports/report_485c7b0.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 10:35:52 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2500 |
|
||||||
|
| n_a | 18 |
|
||||||
|
| stub | 168 |
|
||||||
|
| verified | 987 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2680 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1594/6942 items complete (23.0%)**
|
||||||
38
reports/report_4972f99.md
Normal file
38
reports/report_4972f99.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 10:46:13 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2500 |
|
||||||
|
| n_a | 18 |
|
||||||
|
| stub | 168 |
|
||||||
|
| verified | 987 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2662 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| stub | 18 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1594/6942 items complete (23.0%)**
|
||||||
38
reports/report_4e61314.md
Normal file
38
reports/report_4e61314.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 11:19:48 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2463 |
|
||||||
|
| n_a | 18 |
|
||||||
|
| stub | 166 |
|
||||||
|
| verified | 1026 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2662 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| stub | 18 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1633/6942 items complete (23.5%)**
|
||||||
37
reports/report_4e96fb2.md
Normal file
37
reports/report_4e96fb2.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 15:04:33 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2397 |
|
||||||
|
| n_a | 18 |
|
||||||
|
| stub | 1 |
|
||||||
|
| verified | 1257 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2660 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 410 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1884/6942 items complete (27.1%)**
|
||||||
35
reports/report_60dce2d.md
Normal file
35
reports/report_60dce2d.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 10:12:50 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| unknown | 3394 |
|
||||||
|
| verified | 279 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2680 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**868/6942 items complete (12.5%)**
|
||||||
38
reports/report_7518b97.md
Normal file
38
reports/report_7518b97.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 10:36:34 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2500 |
|
||||||
|
| n_a | 18 |
|
||||||
|
| stub | 168 |
|
||||||
|
| verified | 987 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2662 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| stub | 18 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1594/6942 items complete (23.0%)**
|
||||||
38
reports/report_7a338dd.md
Normal file
38
reports/report_7a338dd.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 10:51:26 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2501 |
|
||||||
|
| n_a | 18 |
|
||||||
|
| stub | 168 |
|
||||||
|
| verified | 986 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2662 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| stub | 18 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1593/6942 items complete (22.9%)**
|
||||||
35
reports/report_810ef29.md
Normal file
35
reports/report_810ef29.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 09:50:38 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| unknown | 3394 |
|
||||||
|
| verified | 279 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2680 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**868/6942 items complete (12.5%)**
|
||||||
35
reports/report_84dc9d1.md
Normal file
35
reports/report_84dc9d1.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 10:16:08 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| unknown | 3394 |
|
||||||
|
| verified | 279 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2680 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**868/6942 items complete (12.5%)**
|
||||||
36
reports/report_8849265.md
Normal file
36
reports/report_8849265.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 14:58:38 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2440 |
|
||||||
|
| n_a | 18 |
|
||||||
|
| verified | 1215 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2660 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 410 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1842/6942 items complete (26.5%)**
|
||||||
37
reports/report_9e2d763.md
Normal file
37
reports/report_9e2d763.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 10:34:31 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2500 |
|
||||||
|
| n_a | 18 |
|
||||||
|
| stub | 168 |
|
||||||
|
| verified | 987 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2680 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1594/6942 items complete (23.0%)**
|
||||||
37
reports/report_a91cfbc.md
Normal file
37
reports/report_a91cfbc.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 10:26:56 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2500 |
|
||||||
|
| n_a | 18 |
|
||||||
|
| stub | 168 |
|
||||||
|
| verified | 987 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2680 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1594/6942 items complete (23.0%)**
|
||||||
36
reports/report_ae0a553.md
Normal file
36
reports/report_ae0a553.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 14:59:29 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2440 |
|
||||||
|
| n_a | 18 |
|
||||||
|
| verified | 1215 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2660 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 410 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1842/6942 items complete (26.5%)**
|
||||||
36
reports/report_ba4f41c.md
Normal file
36
reports/report_ba4f41c.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 13:56:27 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2461 |
|
||||||
|
| n_a | 18 |
|
||||||
|
| verified | 1194 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2662 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 408 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1819/6942 items complete (26.2%)**
|
||||||
37
reports/report_c0aaae9.md
Normal file
37
reports/report_c0aaae9.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 15:27:06 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2377 |
|
||||||
|
| n_a | 24 |
|
||||||
|
| stub | 1 |
|
||||||
|
| verified | 1271 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2660 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 410 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1904/6942 items complete (27.4%)**
|
||||||
35
reports/report_c5c6fbc.md
Normal file
35
reports/report_c5c6fbc.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 10:16:59 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| unknown | 3394 |
|
||||||
|
| verified | 279 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2680 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**868/6942 items complete (12.5%)**
|
||||||
38
reports/report_db1de2a.md
Normal file
38
reports/report_db1de2a.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 11:11:12 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2493 |
|
||||||
|
| n_a | 18 |
|
||||||
|
| stub | 168 |
|
||||||
|
| verified | 994 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2662 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| stub | 18 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**1601/6942 items complete (23.1%)**
|
||||||
35
reports/report_e7f2597.md
Normal file
35
reports/report_e7f2597.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# NATS .NET Porting Status Report
|
||||||
|
|
||||||
|
Generated: 2026-02-27 10:09:38 UTC
|
||||||
|
|
||||||
|
## Modules (12 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| verified | 12 |
|
||||||
|
|
||||||
|
## Features (3673 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| unknown | 3394 |
|
||||||
|
| verified | 279 |
|
||||||
|
|
||||||
|
## Unit Tests (3257 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| deferred | 2680 |
|
||||||
|
| n_a | 187 |
|
||||||
|
| verified | 390 |
|
||||||
|
|
||||||
|
## Library Mappings (36 total)
|
||||||
|
|
||||||
|
| Status | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| mapped | 36 |
|
||||||
|
|
||||||
|
|
||||||
|
## Overall Progress
|
||||||
|
|
||||||
|
**868/6942 items complete (12.5%)**
|
||||||
136
tools/NatsNet.PortTracker/Audit/AuditVerifier.cs
Normal file
136
tools/NatsNet.PortTracker/Audit/AuditVerifier.cs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
namespace NatsNet.PortTracker.Audit;
|
||||||
|
|
||||||
|
using NatsNet.PortTracker.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies status updates against audit classification results.
|
||||||
|
/// Used by feature and test update commands to ensure status accuracy.
|
||||||
|
/// </summary>
|
||||||
|
public static class AuditVerifier
|
||||||
|
{
|
||||||
|
public record VerificationResult(
|
||||||
|
long ItemId,
|
||||||
|
string AuditStatus,
|
||||||
|
string AuditReason,
|
||||||
|
bool Matches);
|
||||||
|
|
||||||
|
private static readonly Dictionary<string, string> DefaultSourcePaths = new()
|
||||||
|
{
|
||||||
|
["features"] = Path.Combine(Directory.GetCurrentDirectory(), "dotnet", "src", "ZB.MOM.NatsNet.Server"),
|
||||||
|
["unit_tests"] = Path.Combine(Directory.GetCurrentDirectory(), "dotnet", "tests", "ZB.MOM.NatsNet.Server.Tests")
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Build a SourceIndexer for the appropriate table type.
|
||||||
|
/// </summary>
|
||||||
|
public static SourceIndexer BuildIndexer(string tableName)
|
||||||
|
{
|
||||||
|
var sourcePath = DefaultSourcePaths[tableName];
|
||||||
|
if (!Directory.Exists(sourcePath))
|
||||||
|
throw new DirectoryNotFoundException($"Source directory not found: {sourcePath}");
|
||||||
|
|
||||||
|
Console.WriteLine($"Building audit index from {sourcePath}...");
|
||||||
|
var indexer = new SourceIndexer();
|
||||||
|
indexer.IndexDirectory(sourcePath);
|
||||||
|
Console.WriteLine($"Indexed {indexer.FilesIndexed} files, {indexer.MethodsIndexed} methods/properties.");
|
||||||
|
return indexer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify items matching a WHERE clause against audit classification.
|
||||||
|
/// </summary>
|
||||||
|
public static List<VerificationResult> VerifyItems(
|
||||||
|
Database db, SourceIndexer indexer, string tableName,
|
||||||
|
string whereClause, List<(string, object?)> parameters, string requestedStatus)
|
||||||
|
{
|
||||||
|
var sql = $"SELECT id, dotnet_class, dotnet_method, go_file, go_method FROM {tableName}{whereClause} ORDER BY id";
|
||||||
|
var rows = db.Query(sql, parameters.ToArray());
|
||||||
|
|
||||||
|
var classifier = new FeatureClassifier(indexer);
|
||||||
|
var results = new List<VerificationResult>();
|
||||||
|
|
||||||
|
foreach (var row in rows)
|
||||||
|
{
|
||||||
|
var record = new FeatureClassifier.FeatureRecord(
|
||||||
|
Id: Convert.ToInt64(row["id"]),
|
||||||
|
DotnetClass: row["dotnet_class"]?.ToString() ?? "",
|
||||||
|
DotnetMethod: row["dotnet_method"]?.ToString() ?? "",
|
||||||
|
GoFile: row["go_file"]?.ToString() ?? "",
|
||||||
|
GoMethod: row["go_method"]?.ToString() ?? "");
|
||||||
|
|
||||||
|
var classification = classifier.Classify(record);
|
||||||
|
var matches = classification.Status == requestedStatus;
|
||||||
|
results.Add(new VerificationResult(record.Id, classification.Status, classification.Reason, matches));
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check verification results and print a report.
|
||||||
|
/// Returns true if the update should proceed.
|
||||||
|
/// </summary>
|
||||||
|
public static bool CheckAndReport(
|
||||||
|
List<VerificationResult> results, string requestedStatus, string? overrideComment)
|
||||||
|
{
|
||||||
|
var matches = results.Where(r => r.Matches).ToList();
|
||||||
|
var mismatches = results.Where(r => !r.Matches).ToList();
|
||||||
|
|
||||||
|
Console.WriteLine($"\nAudit verification: {matches.Count} match, {mismatches.Count} mismatch");
|
||||||
|
|
||||||
|
if (mismatches.Count == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Console.WriteLine($"\nMismatches (requested '{requestedStatus}'):");
|
||||||
|
foreach (var m in mismatches.Take(20))
|
||||||
|
Console.WriteLine($" ID {m.ItemId}: audit says '{m.AuditStatus}' ({m.AuditReason})");
|
||||||
|
if (mismatches.Count > 20)
|
||||||
|
Console.WriteLine($" ... and {mismatches.Count - 20} more");
|
||||||
|
|
||||||
|
if (overrideComment is null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"\n{mismatches.Count} items have audit mismatches. Use --override \"reason\" to force.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"\nOverride applied: \"{overrideComment}\"");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Log override records to the status_overrides table.
|
||||||
|
/// </summary>
|
||||||
|
public static void LogOverrides(
|
||||||
|
Database db, string tableName, IEnumerable<VerificationResult> mismatches,
|
||||||
|
string requestedStatus, string comment)
|
||||||
|
{
|
||||||
|
var mismatchList = mismatches.ToList();
|
||||||
|
if (mismatchList.Count == 0) return;
|
||||||
|
|
||||||
|
using var transaction = db.Connection.BeginTransaction();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var mismatch in mismatchList)
|
||||||
|
{
|
||||||
|
using var cmd = db.CreateCommand(
|
||||||
|
"INSERT INTO status_overrides (table_name, item_id, audit_status, audit_reason, requested_status, comment) " +
|
||||||
|
"VALUES (@table, @item, @auditStatus, @auditReason, @requestedStatus, @comment)");
|
||||||
|
cmd.Parameters.AddWithValue("@table", tableName);
|
||||||
|
cmd.Parameters.AddWithValue("@item", mismatch.ItemId);
|
||||||
|
cmd.Parameters.AddWithValue("@auditStatus", mismatch.AuditStatus);
|
||||||
|
cmd.Parameters.AddWithValue("@auditReason", mismatch.AuditReason);
|
||||||
|
cmd.Parameters.AddWithValue("@requestedStatus", requestedStatus);
|
||||||
|
cmd.Parameters.AddWithValue("@comment", comment);
|
||||||
|
cmd.Transaction = transaction;
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
transaction.Commit();
|
||||||
|
Console.WriteLine($"Logged {mismatchList.Count} override(s) to status_overrides table.");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
transaction.Rollback();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
91
tools/NatsNet.PortTracker/Audit/FeatureClassifier.cs
Normal file
91
tools/NatsNet.PortTracker/Audit/FeatureClassifier.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
namespace NatsNet.PortTracker.Audit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Classifies features by inspecting the SourceIndexer for their .NET implementation status.
|
||||||
|
/// Priority: n_a lookup -> method-not-found -> stub detection -> verified.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class FeatureClassifier
|
||||||
|
{
|
||||||
|
public record ClassificationResult(string Status, string Reason);
|
||||||
|
|
||||||
|
public record FeatureRecord(
|
||||||
|
long Id,
|
||||||
|
string DotnetClass,
|
||||||
|
string DotnetMethod,
|
||||||
|
string GoFile,
|
||||||
|
string GoMethod);
|
||||||
|
|
||||||
|
private readonly SourceIndexer _indexer;
|
||||||
|
|
||||||
|
// N/A lookup: (goMethod pattern) -> reason
|
||||||
|
// Checked case-insensitively against go_method
|
||||||
|
private static readonly Dictionary<string, string> NaByGoMethod = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["Noticef"] = ".NET uses Microsoft.Extensions.Logging",
|
||||||
|
["Debugf"] = ".NET uses Microsoft.Extensions.Logging",
|
||||||
|
["Tracef"] = ".NET uses Microsoft.Extensions.Logging",
|
||||||
|
["Warnf"] = ".NET uses Microsoft.Extensions.Logging",
|
||||||
|
["Errorf"] = ".NET uses Microsoft.Extensions.Logging",
|
||||||
|
["Fatalf"] = ".NET uses Microsoft.Extensions.Logging",
|
||||||
|
};
|
||||||
|
|
||||||
|
// N/A lookup: go_file + go_method patterns
|
||||||
|
private static readonly List<(Func<FeatureRecord, bool> Match, string Reason)> NaPatterns =
|
||||||
|
[
|
||||||
|
// Signal handling — .NET uses IHostApplicationLifetime
|
||||||
|
(f => f.GoMethod.Equals("handleSignals", StringComparison.OrdinalIgnoreCase), ".NET uses IHostApplicationLifetime"),
|
||||||
|
(f => f.GoMethod.Equals("processSignal", StringComparison.OrdinalIgnoreCase), ".NET uses IHostApplicationLifetime"),
|
||||||
|
];
|
||||||
|
|
||||||
|
public FeatureClassifier(SourceIndexer indexer)
|
||||||
|
{
|
||||||
|
_indexer = indexer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Classify a single feature. Returns status and reason.
|
||||||
|
/// </summary>
|
||||||
|
public ClassificationResult Classify(FeatureRecord feature)
|
||||||
|
{
|
||||||
|
// 1. N/A lookup — check go_method against known patterns
|
||||||
|
if (NaByGoMethod.TryGetValue(feature.GoMethod, out var naReason))
|
||||||
|
return new ClassificationResult("n_a", naReason);
|
||||||
|
|
||||||
|
foreach (var (match, reason) in NaPatterns)
|
||||||
|
{
|
||||||
|
if (match(feature))
|
||||||
|
return new ClassificationResult("n_a", reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Handle comma-separated dotnet_class (e.g. "ClosedRingBuffer,ClosedClient")
|
||||||
|
var classNames = feature.DotnetClass.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
var methodName = feature.DotnetMethod;
|
||||||
|
|
||||||
|
// Try each class name
|
||||||
|
foreach (var className in classNames)
|
||||||
|
{
|
||||||
|
var methods = _indexer.Lookup(className, methodName);
|
||||||
|
if (methods.Count > 0)
|
||||||
|
{
|
||||||
|
// Found the method — classify based on body analysis
|
||||||
|
// Use the "best" match: prefer non-stub over stub
|
||||||
|
var best = methods.OrderByDescending(m => m.StatementCount).First();
|
||||||
|
|
||||||
|
if (best.IsStub)
|
||||||
|
return new ClassificationResult("stub", $"Body is throw NotImplementedException at {Path.GetFileName(best.FilePath)}:{best.LineNumber}");
|
||||||
|
|
||||||
|
if (best.IsPartial)
|
||||||
|
return new ClassificationResult("stub", $"Partial implementation with NotImplementedException at {Path.GetFileName(best.FilePath)}:{best.LineNumber}");
|
||||||
|
|
||||||
|
return new ClassificationResult("verified", $"Method found with {best.StatementCount} statement(s) at {Path.GetFileName(best.FilePath)}:{best.LineNumber}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Method not found — check if any class exists
|
||||||
|
var anyClassFound = classNames.Any(c => _indexer.HasClass(c));
|
||||||
|
if (anyClassFound)
|
||||||
|
return new ClassificationResult("deferred", "Class exists but method not found");
|
||||||
|
|
||||||
|
return new ClassificationResult("deferred", "Class not found in .NET source");
|
||||||
|
}
|
||||||
|
}
|
||||||
201
tools/NatsNet.PortTracker/Audit/SourceIndexer.cs
Normal file
201
tools/NatsNet.PortTracker/Audit/SourceIndexer.cs
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
|
namespace NatsNet.PortTracker.Audit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses .cs files using Roslyn syntax trees and builds a lookup index
|
||||||
|
/// of (className, memberName) -> list of MethodInfo.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SourceIndexer
|
||||||
|
{
|
||||||
|
public record MethodInfo(
|
||||||
|
string FilePath,
|
||||||
|
int LineNumber,
|
||||||
|
int BodyLineCount,
|
||||||
|
bool IsStub,
|
||||||
|
bool IsPartial,
|
||||||
|
int StatementCount);
|
||||||
|
|
||||||
|
// Key: (className lowercase, memberName lowercase)
|
||||||
|
private readonly Dictionary<(string, string), List<MethodInfo>> _index = new();
|
||||||
|
|
||||||
|
public int FilesIndexed { get; private set; }
|
||||||
|
public int MethodsIndexed { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recursively parses all .cs files under <paramref name="sourceDir"/>
|
||||||
|
/// (skipping obj/ and bin/) and populates the index.
|
||||||
|
/// </summary>
|
||||||
|
public void IndexDirectory(string sourceDir)
|
||||||
|
{
|
||||||
|
var files = Directory.EnumerateFiles(sourceDir, "*.cs", SearchOption.AllDirectories)
|
||||||
|
.Where(f =>
|
||||||
|
{
|
||||||
|
var rel = Path.GetRelativePath(sourceDir, f);
|
||||||
|
return !rel.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}")
|
||||||
|
&& !rel.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}")
|
||||||
|
&& !rel.StartsWith($"obj{Path.DirectorySeparatorChar}")
|
||||||
|
&& !rel.StartsWith($"bin{Path.DirectorySeparatorChar}");
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
IndexFile(file);
|
||||||
|
FilesIndexed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up all method declarations for a given class and member name.
|
||||||
|
/// Case-insensitive. Returns empty list if not found.
|
||||||
|
/// </summary>
|
||||||
|
public List<MethodInfo> Lookup(string className, string memberName)
|
||||||
|
{
|
||||||
|
var key = (className.ToLowerInvariant(), memberName.ToLowerInvariant());
|
||||||
|
return _index.TryGetValue(key, out var list) ? list : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the class exists anywhere in the index (any member).
|
||||||
|
/// </summary>
|
||||||
|
public bool HasClass(string className)
|
||||||
|
{
|
||||||
|
var lower = className.ToLowerInvariant();
|
||||||
|
return _index.Keys.Any(k => k.Item1 == lower);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IndexFile(string filePath)
|
||||||
|
{
|
||||||
|
var source = File.ReadAllText(filePath);
|
||||||
|
var tree = CSharpSyntaxTree.ParseText(source, path: filePath);
|
||||||
|
var root = tree.GetCompilationUnitRoot();
|
||||||
|
|
||||||
|
foreach (var typeDecl in root.DescendantNodes().OfType<TypeDeclarationSyntax>())
|
||||||
|
{
|
||||||
|
var className = typeDecl.Identifier.Text.ToLowerInvariant();
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
foreach (var method in typeDecl.Members.OfType<MethodDeclarationSyntax>())
|
||||||
|
{
|
||||||
|
var info = AnalyzeMethod(filePath, method.Body, method.ExpressionBody, method.GetLocation());
|
||||||
|
AddToIndex(className, method.Identifier.Text.ToLowerInvariant(), info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties (get/set are like methods)
|
||||||
|
foreach (var prop in typeDecl.Members.OfType<PropertyDeclarationSyntax>())
|
||||||
|
{
|
||||||
|
var info = AnalyzeProperty(filePath, prop);
|
||||||
|
AddToIndex(className, prop.Identifier.Text.ToLowerInvariant(), info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructors — index as class name
|
||||||
|
foreach (var ctor in typeDecl.Members.OfType<ConstructorDeclarationSyntax>())
|
||||||
|
{
|
||||||
|
var info = AnalyzeMethod(filePath, ctor.Body, ctor.ExpressionBody, ctor.GetLocation());
|
||||||
|
AddToIndex(className, ctor.Identifier.Text.ToLowerInvariant(), info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodInfo AnalyzeMethod(string filePath, BlockSyntax? body, ArrowExpressionClauseSyntax? expressionBody, Location location)
|
||||||
|
{
|
||||||
|
var lineSpan = location.GetLineSpan();
|
||||||
|
var lineNumber = lineSpan.StartLinePosition.Line + 1;
|
||||||
|
|
||||||
|
if (expressionBody is not null)
|
||||||
|
{
|
||||||
|
// Expression-bodied: => expr;
|
||||||
|
var isStub = IsNotImplementedExpression(expressionBody.Expression);
|
||||||
|
return new MethodInfo(filePath, lineNumber, 1, IsStub: isStub, IsPartial: false, StatementCount: isStub ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body is null || body.Statements.Count == 0)
|
||||||
|
{
|
||||||
|
// No body or empty body
|
||||||
|
return new MethodInfo(filePath, lineNumber, 0, IsStub: true, IsPartial: false, StatementCount: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bodyLines = body.GetLocation().GetLineSpan();
|
||||||
|
var bodyLineCount = bodyLines.EndLinePosition.Line - bodyLines.StartLinePosition.Line - 1; // exclude braces
|
||||||
|
|
||||||
|
var statements = body.Statements;
|
||||||
|
var hasNotImplemented = statements.Any(s => IsNotImplementedStatement(s));
|
||||||
|
var meaningfulCount = statements.Count(s => !IsNotImplementedStatement(s));
|
||||||
|
|
||||||
|
// Pure stub: single throw NotImplementedException
|
||||||
|
if (statements.Count == 1 && hasNotImplemented)
|
||||||
|
return new MethodInfo(filePath, lineNumber, bodyLineCount, IsStub: true, IsPartial: false, StatementCount: 0);
|
||||||
|
|
||||||
|
// Partial: has some logic AND a NotImplementedException
|
||||||
|
if (hasNotImplemented && meaningfulCount > 0)
|
||||||
|
return new MethodInfo(filePath, lineNumber, bodyLineCount, IsStub: false, IsPartial: true, StatementCount: meaningfulCount);
|
||||||
|
|
||||||
|
// Real logic
|
||||||
|
return new MethodInfo(filePath, lineNumber, bodyLineCount, IsStub: false, IsPartial: false, StatementCount: meaningfulCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodInfo AnalyzeProperty(string filePath, PropertyDeclarationSyntax prop)
|
||||||
|
{
|
||||||
|
var lineSpan = prop.GetLocation().GetLineSpan();
|
||||||
|
var lineNumber = lineSpan.StartLinePosition.Line + 1;
|
||||||
|
|
||||||
|
// Expression-bodied property: int Foo => expr;
|
||||||
|
if (prop.ExpressionBody is not null)
|
||||||
|
{
|
||||||
|
var isStub = IsNotImplementedExpression(prop.ExpressionBody.Expression);
|
||||||
|
return new MethodInfo(filePath, lineNumber, 1, IsStub: isStub, IsPartial: false, StatementCount: isStub ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-property: int Foo { get; set; } — this is valid, not a stub
|
||||||
|
if (prop.AccessorList is not null && prop.AccessorList.Accessors.All(a => a.Body is null && a.ExpressionBody is null))
|
||||||
|
return new MethodInfo(filePath, lineNumber, 0, IsStub: false, IsPartial: false, StatementCount: 1);
|
||||||
|
|
||||||
|
// Property with accessor bodies — check if any are stubs
|
||||||
|
if (prop.AccessorList is not null)
|
||||||
|
{
|
||||||
|
var hasStub = prop.AccessorList.Accessors.Any(a =>
|
||||||
|
(a.ExpressionBody is not null && IsNotImplementedExpression(a.ExpressionBody.Expression)) ||
|
||||||
|
(a.Body is not null && a.Body.Statements.Count == 1 && IsNotImplementedStatement(a.Body.Statements[0])));
|
||||||
|
return new MethodInfo(filePath, lineNumber, 0, IsStub: hasStub, IsPartial: false, StatementCount: hasStub ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MethodInfo(filePath, lineNumber, 0, IsStub: false, IsPartial: false, StatementCount: 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsNotImplementedExpression(ExpressionSyntax expr)
|
||||||
|
{
|
||||||
|
// throw new NotImplementedException(...)
|
||||||
|
if (expr is ThrowExpressionSyntax throwExpr)
|
||||||
|
return throwExpr.Expression is ObjectCreationExpressionSyntax oc
|
||||||
|
&& oc.Type.ToString().Contains("NotImplementedException");
|
||||||
|
// new NotImplementedException() — shouldn't normally be standalone but handle it
|
||||||
|
return expr is ObjectCreationExpressionSyntax oc2
|
||||||
|
&& oc2.Type.ToString().Contains("NotImplementedException");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsNotImplementedStatement(StatementSyntax stmt)
|
||||||
|
{
|
||||||
|
// throw new NotImplementedException(...);
|
||||||
|
if (stmt is ThrowStatementSyntax throwStmt && throwStmt.Expression is not null)
|
||||||
|
return throwStmt.Expression is ObjectCreationExpressionSyntax oc
|
||||||
|
&& oc.Type.ToString().Contains("NotImplementedException");
|
||||||
|
// Expression statement containing throw expression
|
||||||
|
if (stmt is ExpressionStatementSyntax exprStmt)
|
||||||
|
return IsNotImplementedExpression(exprStmt.Expression);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddToIndex(string className, string memberName, MethodInfo info)
|
||||||
|
{
|
||||||
|
var key = (className, memberName);
|
||||||
|
if (!_index.TryGetValue(key, out var list))
|
||||||
|
{
|
||||||
|
list = [];
|
||||||
|
_index[key] = list;
|
||||||
|
}
|
||||||
|
list.Add(info);
|
||||||
|
MethodsIndexed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
239
tools/NatsNet.PortTracker/Commands/AuditCommand.cs
Normal file
239
tools/NatsNet.PortTracker/Commands/AuditCommand.cs
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
using System.CommandLine;
|
||||||
|
using System.Text;
|
||||||
|
using NatsNet.PortTracker.Audit;
|
||||||
|
using NatsNet.PortTracker.Data;
|
||||||
|
|
||||||
|
namespace NatsNet.PortTracker.Commands;
|
||||||
|
|
||||||
|
public static class AuditCommand
|
||||||
|
{
|
||||||
|
private record AuditTarget(string Table, string Label, string DefaultSource, string DefaultOutput);
|
||||||
|
|
||||||
|
private static readonly AuditTarget FeaturesTarget = new(
|
||||||
|
"features", "features",
|
||||||
|
Path.Combine(Directory.GetCurrentDirectory(), "dotnet", "src", "ZB.MOM.NatsNet.Server"),
|
||||||
|
Path.Combine(Directory.GetCurrentDirectory(), "reports", "audit-results.csv"));
|
||||||
|
|
||||||
|
private static readonly AuditTarget TestsTarget = new(
|
||||||
|
"unit_tests", "unit tests",
|
||||||
|
Path.Combine(Directory.GetCurrentDirectory(), "dotnet", "tests", "ZB.MOM.NatsNet.Server.Tests"),
|
||||||
|
Path.Combine(Directory.GetCurrentDirectory(), "reports", "audit-results-tests.csv"));
|
||||||
|
|
||||||
|
public static Command Create(Option<string> dbOption)
|
||||||
|
{
|
||||||
|
var sourceOpt = new Option<string?>("--source")
|
||||||
|
{
|
||||||
|
Description = "Path to the .NET source directory (defaults based on --type)"
|
||||||
|
};
|
||||||
|
|
||||||
|
var outputOpt = new Option<string?>("--output")
|
||||||
|
{
|
||||||
|
Description = "CSV report output path (defaults based on --type)"
|
||||||
|
};
|
||||||
|
|
||||||
|
var moduleOpt = new Option<int?>("--module")
|
||||||
|
{
|
||||||
|
Description = "Restrict to a specific module ID"
|
||||||
|
};
|
||||||
|
|
||||||
|
var executeOpt = new Option<bool>("--execute")
|
||||||
|
{
|
||||||
|
Description = "Apply DB updates (default: dry-run preview)",
|
||||||
|
DefaultValueFactory = _ => false
|
||||||
|
};
|
||||||
|
|
||||||
|
var typeOpt = new Option<string>("--type")
|
||||||
|
{
|
||||||
|
Description = "What to audit: features, tests, or all",
|
||||||
|
DefaultValueFactory = _ => "features"
|
||||||
|
};
|
||||||
|
|
||||||
|
var cmd = new Command("audit", "Classify unknown features/tests by inspecting .NET source code");
|
||||||
|
cmd.Add(sourceOpt);
|
||||||
|
cmd.Add(outputOpt);
|
||||||
|
cmd.Add(moduleOpt);
|
||||||
|
cmd.Add(executeOpt);
|
||||||
|
cmd.Add(typeOpt);
|
||||||
|
|
||||||
|
cmd.SetAction(parseResult =>
|
||||||
|
{
|
||||||
|
var dbPath = parseResult.GetValue(dbOption)!;
|
||||||
|
var sourceOverride = parseResult.GetValue(sourceOpt);
|
||||||
|
var outputOverride = parseResult.GetValue(outputOpt);
|
||||||
|
var moduleId = parseResult.GetValue(moduleOpt);
|
||||||
|
var execute = parseResult.GetValue(executeOpt);
|
||||||
|
var type = parseResult.GetValue(typeOpt)!;
|
||||||
|
|
||||||
|
AuditTarget[] targets = type switch
|
||||||
|
{
|
||||||
|
"features" => [FeaturesTarget],
|
||||||
|
"tests" => [TestsTarget],
|
||||||
|
"all" => [FeaturesTarget, TestsTarget],
|
||||||
|
_ => throw new ArgumentException($"Unknown audit type: {type}. Use features, tests, or all.")
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var target in targets)
|
||||||
|
{
|
||||||
|
var sourcePath = sourceOverride ?? target.DefaultSource;
|
||||||
|
var outputPath = outputOverride ?? target.DefaultOutput;
|
||||||
|
RunAudit(dbPath, sourcePath, outputPath, moduleId, execute, target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunAudit(string dbPath, string sourcePath, string outputPath, int? moduleId, bool execute, AuditTarget target)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(sourcePath))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error: source directory not found: {sourcePath}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Parsing .NET source files in {sourcePath}...");
|
||||||
|
var indexer = new SourceIndexer();
|
||||||
|
indexer.IndexDirectory(sourcePath);
|
||||||
|
Console.WriteLine($"Indexed {indexer.FilesIndexed} files, {indexer.MethodsIndexed} methods/properties.");
|
||||||
|
|
||||||
|
using var db = new Database(dbPath);
|
||||||
|
var sql = $"SELECT id, dotnet_class, dotnet_method, go_file, go_method FROM {target.Table} WHERE status = 'unknown'";
|
||||||
|
var parameters = new List<(string, object?)>();
|
||||||
|
if (moduleId is not null)
|
||||||
|
{
|
||||||
|
sql += " AND module_id = @module";
|
||||||
|
parameters.Add(("@module", moduleId));
|
||||||
|
}
|
||||||
|
sql += " ORDER BY id";
|
||||||
|
|
||||||
|
var rows = db.Query(sql, parameters.ToArray());
|
||||||
|
if (rows.Count == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"No unknown {target.Label} found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Console.WriteLine($"Found {rows.Count} unknown {target.Label} to classify.\n");
|
||||||
|
|
||||||
|
var classifier = new FeatureClassifier(indexer);
|
||||||
|
var results = new List<(FeatureClassifier.FeatureRecord Feature, FeatureClassifier.ClassificationResult Result)>();
|
||||||
|
|
||||||
|
foreach (var row in rows)
|
||||||
|
{
|
||||||
|
var feature = new FeatureClassifier.FeatureRecord(
|
||||||
|
Id: Convert.ToInt64(row["id"]),
|
||||||
|
DotnetClass: row["dotnet_class"]?.ToString() ?? "",
|
||||||
|
DotnetMethod: row["dotnet_method"]?.ToString() ?? "",
|
||||||
|
GoFile: row["go_file"]?.ToString() ?? "",
|
||||||
|
GoMethod: row["go_method"]?.ToString() ?? "");
|
||||||
|
|
||||||
|
var result = classifier.Classify(feature);
|
||||||
|
results.Add((feature, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteCsvReport(outputPath, results);
|
||||||
|
|
||||||
|
var grouped = results.GroupBy(r => r.Result.Status)
|
||||||
|
.ToDictionary(g => g.Key, g => g.Count());
|
||||||
|
|
||||||
|
var label = char.ToUpper(target.Label[0]) + target.Label[1..];
|
||||||
|
Console.WriteLine($"{label} Status Audit Results");
|
||||||
|
Console.WriteLine(new string('=', $"{label} Status Audit Results".Length));
|
||||||
|
Console.WriteLine($"Source: {sourcePath} ({indexer.FilesIndexed} files, {indexer.MethodsIndexed} methods indexed)");
|
||||||
|
Console.WriteLine($"{label} audited: {results.Count}");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine($" verified: {grouped.GetValueOrDefault("verified", 0)}");
|
||||||
|
Console.WriteLine($" stub: {grouped.GetValueOrDefault("stub", 0)}");
|
||||||
|
Console.WriteLine($" n_a: {grouped.GetValueOrDefault("n_a", 0)}");
|
||||||
|
Console.WriteLine($" deferred: {grouped.GetValueOrDefault("deferred", 0)}");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
if (!execute)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Dry-run mode. Add --execute to apply changes.");
|
||||||
|
Console.WriteLine($"Report: {outputPath}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyUpdates(db, results, target);
|
||||||
|
Console.WriteLine($"Report: {outputPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteCsvReport(
|
||||||
|
string outputPath,
|
||||||
|
List<(FeatureClassifier.FeatureRecord Feature, FeatureClassifier.ClassificationResult Result)> results)
|
||||||
|
{
|
||||||
|
var dir = Path.GetDirectoryName(outputPath);
|
||||||
|
if (!string.IsNullOrEmpty(dir))
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("id,dotnet_class,dotnet_method,go_file,go_method,old_status,new_status,reason");
|
||||||
|
foreach (var (feature, result) in results)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"{feature.Id},{CsvEscape(feature.DotnetClass)},{CsvEscape(feature.DotnetMethod)},{CsvEscape(feature.GoFile)},{CsvEscape(feature.GoMethod)},unknown,{result.Status},{CsvEscape(result.Reason)}");
|
||||||
|
}
|
||||||
|
File.WriteAllText(outputPath, sb.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyUpdates(
|
||||||
|
Database db,
|
||||||
|
List<(FeatureClassifier.FeatureRecord Feature, FeatureClassifier.ClassificationResult Result)> results,
|
||||||
|
AuditTarget target)
|
||||||
|
{
|
||||||
|
var groups = results
|
||||||
|
.GroupBy(r => (r.Result.Status, Notes: r.Result.Status == "n_a" ? r.Result.Reason : (string?)null))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var totalUpdated = 0;
|
||||||
|
using var transaction = db.Connection.BeginTransaction();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var group in groups)
|
||||||
|
{
|
||||||
|
var ids = group.Select(r => r.Feature.Id).ToList();
|
||||||
|
var status = group.Key.Status;
|
||||||
|
var notes = group.Key.Notes;
|
||||||
|
|
||||||
|
var placeholders = new List<string>();
|
||||||
|
using var cmd = db.CreateCommand("");
|
||||||
|
for (var i = 0; i < ids.Count; i++)
|
||||||
|
{
|
||||||
|
placeholders.Add($"@id{i}");
|
||||||
|
cmd.Parameters.AddWithValue($"@id{i}", ids[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Parameters.AddWithValue("@status", status);
|
||||||
|
|
||||||
|
if (notes is not null)
|
||||||
|
{
|
||||||
|
cmd.CommandText = $"UPDATE {target.Table} SET status = @status, notes = @notes WHERE id IN ({string.Join(", ", placeholders)})";
|
||||||
|
cmd.Parameters.AddWithValue("@notes", notes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cmd.CommandText = $"UPDATE {target.Table} SET status = @status WHERE id IN ({string.Join(", ", placeholders)})";
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Transaction = transaction;
|
||||||
|
var affected = cmd.ExecuteNonQuery();
|
||||||
|
totalUpdated += affected;
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
Console.WriteLine($"Updated {totalUpdated} {target.Label}.");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
transaction.Rollback();
|
||||||
|
Console.WriteLine("Error: transaction rolled back.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CsvEscape(string value)
|
||||||
|
{
|
||||||
|
if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
|
||||||
|
return $"\"{value.Replace("\"", "\"\"")}\"";
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.CommandLine;
|
using System.CommandLine;
|
||||||
|
using NatsNet.PortTracker.Audit;
|
||||||
using NatsNet.PortTracker.Data;
|
using NatsNet.PortTracker.Data;
|
||||||
|
|
||||||
namespace NatsNet.PortTracker.Commands;
|
namespace NatsNet.PortTracker.Commands;
|
||||||
@@ -96,31 +97,59 @@ public static class FeatureCommands
|
|||||||
var updateId = new Argument<int>("id") { Description = "Feature ID (use 0 with --all-in-module)" };
|
var updateId = new Argument<int>("id") { Description = "Feature ID (use 0 with --all-in-module)" };
|
||||||
var updateStatus = new Option<string>("--status") { Description = "New status", Required = true };
|
var updateStatus = new Option<string>("--status") { Description = "New status", Required = true };
|
||||||
var updateAllInModule = new Option<int?>("--all-in-module") { Description = "Update all features in this module ID" };
|
var updateAllInModule = new Option<int?>("--all-in-module") { Description = "Update all features in this module ID" };
|
||||||
var updateCmd = new Command("update", "Update feature status");
|
var updateOverride = new Option<string?>("--override") { Description = "Override audit mismatch with this comment" };
|
||||||
|
var updateCmd = new Command("update", "Update feature status (audit-verified)");
|
||||||
updateCmd.Add(updateId);
|
updateCmd.Add(updateId);
|
||||||
updateCmd.Add(updateStatus);
|
updateCmd.Add(updateStatus);
|
||||||
updateCmd.Add(updateAllInModule);
|
updateCmd.Add(updateAllInModule);
|
||||||
|
updateCmd.Add(updateOverride);
|
||||||
updateCmd.SetAction(parseResult =>
|
updateCmd.SetAction(parseResult =>
|
||||||
{
|
{
|
||||||
var dbPath = parseResult.GetValue(dbOption)!;
|
var dbPath = parseResult.GetValue(dbOption)!;
|
||||||
var id = parseResult.GetValue(updateId);
|
var id = parseResult.GetValue(updateId);
|
||||||
var status = parseResult.GetValue(updateStatus)!;
|
var status = parseResult.GetValue(updateStatus)!;
|
||||||
var allInModule = parseResult.GetValue(updateAllInModule);
|
var allInModule = parseResult.GetValue(updateAllInModule);
|
||||||
|
var overrideComment = parseResult.GetValue(updateOverride);
|
||||||
using var db = new Database(dbPath);
|
using var db = new Database(dbPath);
|
||||||
|
|
||||||
|
var indexer = AuditVerifier.BuildIndexer("features");
|
||||||
|
|
||||||
if (allInModule is not null)
|
if (allInModule is not null)
|
||||||
{
|
{
|
||||||
|
var verifications = AuditVerifier.VerifyItems(db, indexer, "features",
|
||||||
|
" WHERE module_id = @module", [("@module", (object?)allInModule)], status);
|
||||||
|
if (!AuditVerifier.CheckAndReport(verifications, status, overrideComment))
|
||||||
|
return;
|
||||||
|
|
||||||
var affected = db.Execute(
|
var affected = db.Execute(
|
||||||
"UPDATE features SET status = @status WHERE module_id = @module",
|
"UPDATE features SET status = @status WHERE module_id = @module",
|
||||||
("@status", status), ("@module", allInModule));
|
("@status", status), ("@module", allInModule));
|
||||||
Console.WriteLine($"Updated {affected} features in module {allInModule} to '{status}'.");
|
Console.WriteLine($"Updated {affected} features in module {allInModule} to '{status}'.");
|
||||||
|
|
||||||
|
var mismatches = verifications.Where(r => !r.Matches).ToList();
|
||||||
|
if (mismatches.Count > 0 && overrideComment is not null)
|
||||||
|
AuditVerifier.LogOverrides(db, "features", mismatches, status, overrideComment);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var verifications = AuditVerifier.VerifyItems(db, indexer, "features",
|
||||||
|
" WHERE id = @id", [("@id", (object?)id)], status);
|
||||||
|
if (verifications.Count == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Feature {id} not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!AuditVerifier.CheckAndReport(verifications, status, overrideComment))
|
||||||
|
return;
|
||||||
|
|
||||||
var affected = db.Execute(
|
var affected = db.Execute(
|
||||||
"UPDATE features SET status = @status WHERE id = @id",
|
"UPDATE features SET status = @status WHERE id = @id",
|
||||||
("@status", status), ("@id", id));
|
("@status", status), ("@id", id));
|
||||||
Console.WriteLine(affected > 0 ? $"Feature {id} updated to '{status}'." : $"Feature {id} not found.");
|
Console.WriteLine(affected > 0 ? $"Feature {id} updated to '{status}'." : $"Feature {id} not found.");
|
||||||
|
|
||||||
|
var mismatches = verifications.Where(r => !r.Matches).ToList();
|
||||||
|
if (mismatches.Count > 0 && overrideComment is not null)
|
||||||
|
AuditVerifier.LogOverrides(db, "features", mismatches, status, overrideComment);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -179,13 +208,14 @@ public static class FeatureCommands
|
|||||||
|
|
||||||
private static Command CreateBatchUpdate(Option<string> dbOption)
|
private static Command CreateBatchUpdate(Option<string> dbOption)
|
||||||
{
|
{
|
||||||
var cmd = new Command("batch-update", "Bulk update feature status");
|
var cmd = new Command("batch-update", "Bulk update feature status (audit-verified)");
|
||||||
var idsOpt = BatchFilters.IdsOption();
|
var idsOpt = BatchFilters.IdsOption();
|
||||||
var moduleOpt = BatchFilters.ModuleOption();
|
var moduleOpt = BatchFilters.ModuleOption();
|
||||||
var statusOpt = BatchFilters.StatusOption();
|
var statusOpt = BatchFilters.StatusOption();
|
||||||
var executeOpt = BatchFilters.ExecuteOption();
|
var executeOpt = BatchFilters.ExecuteOption();
|
||||||
var setStatus = new Option<string>("--set-status") { Description = "New status to set", Required = true };
|
var setStatus = new Option<string>("--set-status") { Description = "New status to set", Required = true };
|
||||||
var setNotes = new Option<string?>("--set-notes") { Description = "Notes to set" };
|
var setNotes = new Option<string?>("--set-notes") { Description = "Notes to set" };
|
||||||
|
var overrideOpt = new Option<string?>("--override") { Description = "Override audit mismatches with this comment" };
|
||||||
|
|
||||||
cmd.Add(idsOpt);
|
cmd.Add(idsOpt);
|
||||||
cmd.Add(moduleOpt);
|
cmd.Add(moduleOpt);
|
||||||
@@ -193,6 +223,7 @@ public static class FeatureCommands
|
|||||||
cmd.Add(executeOpt);
|
cmd.Add(executeOpt);
|
||||||
cmd.Add(setStatus);
|
cmd.Add(setStatus);
|
||||||
cmd.Add(setNotes);
|
cmd.Add(setNotes);
|
||||||
|
cmd.Add(overrideOpt);
|
||||||
|
|
||||||
cmd.SetAction(parseResult =>
|
cmd.SetAction(parseResult =>
|
||||||
{
|
{
|
||||||
@@ -203,6 +234,7 @@ public static class FeatureCommands
|
|||||||
var execute = parseResult.GetValue(executeOpt);
|
var execute = parseResult.GetValue(executeOpt);
|
||||||
var newStatus = parseResult.GetValue(setStatus)!;
|
var newStatus = parseResult.GetValue(setStatus)!;
|
||||||
var notes = parseResult.GetValue(setNotes);
|
var notes = parseResult.GetValue(setNotes);
|
||||||
|
var overrideComment = parseResult.GetValue(overrideOpt);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(ids) && module is null && string.IsNullOrWhiteSpace(status))
|
if (string.IsNullOrWhiteSpace(ids) && module is null && string.IsNullOrWhiteSpace(status))
|
||||||
{
|
{
|
||||||
@@ -213,6 +245,12 @@ public static class FeatureCommands
|
|||||||
using var db = new Database(dbPath);
|
using var db = new Database(dbPath);
|
||||||
var (whereClause, filterParams) = BatchFilters.BuildWhereClause(ids, module, status);
|
var (whereClause, filterParams) = BatchFilters.BuildWhereClause(ids, module, status);
|
||||||
|
|
||||||
|
// Audit verification
|
||||||
|
var indexer = AuditVerifier.BuildIndexer("features");
|
||||||
|
var verifications = AuditVerifier.VerifyItems(db, indexer, "features", whereClause, filterParams, newStatus);
|
||||||
|
if (!AuditVerifier.CheckAndReport(verifications, newStatus, overrideComment))
|
||||||
|
return;
|
||||||
|
|
||||||
var setClauses = new List<string> { "status = @newStatus" };
|
var setClauses = new List<string> { "status = @newStatus" };
|
||||||
var updateParams = new List<(string, object?)> { ("@newStatus", newStatus) };
|
var updateParams = new List<(string, object?)> { ("@newStatus", newStatus) };
|
||||||
if (notes is not null)
|
if (notes is not null)
|
||||||
@@ -225,6 +263,14 @@ public static class FeatureCommands
|
|||||||
"id, name, status, module_id, notes",
|
"id, name, status, module_id, notes",
|
||||||
string.Join(", ", setClauses), updateParams,
|
string.Join(", ", setClauses), updateParams,
|
||||||
whereClause, filterParams, execute);
|
whereClause, filterParams, execute);
|
||||||
|
|
||||||
|
// Log overrides after successful execute
|
||||||
|
if (execute)
|
||||||
|
{
|
||||||
|
var mismatches = verifications.Where(r => !r.Matches).ToList();
|
||||||
|
if (mismatches.Count > 0 && overrideComment is not null)
|
||||||
|
AuditVerifier.LogOverrides(db, "features", mismatches, newStatus, overrideComment);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return cmd;
|
return cmd;
|
||||||
|
|||||||
66
tools/NatsNet.PortTracker/Commands/OverrideCommands.cs
Normal file
66
tools/NatsNet.PortTracker/Commands/OverrideCommands.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System.CommandLine;
|
||||||
|
using NatsNet.PortTracker.Data;
|
||||||
|
|
||||||
|
namespace NatsNet.PortTracker.Commands;
|
||||||
|
|
||||||
|
public static class OverrideCommands
|
||||||
|
{
|
||||||
|
public static Command Create(Option<string> dbOption)
|
||||||
|
{
|
||||||
|
var overrideCommand = new Command("override", "Review status override records");
|
||||||
|
|
||||||
|
var typeOpt = new Option<string?>("--type")
|
||||||
|
{
|
||||||
|
Description = "Filter by table: features or tests"
|
||||||
|
};
|
||||||
|
|
||||||
|
var listCmd = new Command("list", "List all status overrides");
|
||||||
|
listCmd.Add(typeOpt);
|
||||||
|
listCmd.SetAction(parseResult =>
|
||||||
|
{
|
||||||
|
var dbPath = parseResult.GetValue(dbOption)!;
|
||||||
|
var type = parseResult.GetValue(typeOpt);
|
||||||
|
using var db = new Database(dbPath);
|
||||||
|
|
||||||
|
var sql = "SELECT id, table_name, item_id, audit_status, requested_status, comment, created_at FROM status_overrides";
|
||||||
|
var parameters = new List<(string, object?)>();
|
||||||
|
|
||||||
|
if (type is not null)
|
||||||
|
{
|
||||||
|
var tableName = type switch
|
||||||
|
{
|
||||||
|
"features" => "features",
|
||||||
|
"tests" => "unit_tests",
|
||||||
|
_ => type
|
||||||
|
};
|
||||||
|
sql += " WHERE table_name = @table";
|
||||||
|
parameters.Add(("@table", tableName));
|
||||||
|
}
|
||||||
|
sql += " ORDER BY created_at DESC";
|
||||||
|
|
||||||
|
var rows = db.Query(sql, parameters.ToArray());
|
||||||
|
if (rows.Count == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine("No overrides found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"{"ID",-5} {"Table",-12} {"Item",-6} {"Audit",-10} {"Requested",-10} {"Comment",-35} {"Date",-20}");
|
||||||
|
Console.WriteLine(new string('-', 98));
|
||||||
|
foreach (var row in rows)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{row["id"],-5} {row["table_name"],-12} {row["item_id"],-6} {row["audit_status"],-10} {row["requested_status"],-10} {Truncate(row["comment"]?.ToString(), 34),-35} {row["created_at"],-20}");
|
||||||
|
}
|
||||||
|
Console.WriteLine($"\nTotal: {rows.Count} overrides");
|
||||||
|
});
|
||||||
|
|
||||||
|
overrideCommand.Add(listCmd);
|
||||||
|
return overrideCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Truncate(string? s, int maxLen)
|
||||||
|
{
|
||||||
|
if (s is null) return "";
|
||||||
|
return s.Length <= maxLen ? s : s[..(maxLen - 2)] + "..";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.CommandLine;
|
using System.CommandLine;
|
||||||
|
using NatsNet.PortTracker.Audit;
|
||||||
using NatsNet.PortTracker.Data;
|
using NatsNet.PortTracker.Data;
|
||||||
|
|
||||||
namespace NatsNet.PortTracker.Commands;
|
namespace NatsNet.PortTracker.Commands;
|
||||||
@@ -89,18 +90,37 @@ public static class TestCommands
|
|||||||
// update
|
// update
|
||||||
var updateId = new Argument<int>("id") { Description = "Test ID" };
|
var updateId = new Argument<int>("id") { Description = "Test ID" };
|
||||||
var updateStatus = new Option<string>("--status") { Description = "New status", Required = true };
|
var updateStatus = new Option<string>("--status") { Description = "New status", Required = true };
|
||||||
var updateCmd = new Command("update", "Update test status");
|
var updateOverride = new Option<string?>("--override") { Description = "Override audit mismatch with this comment" };
|
||||||
|
var updateCmd = new Command("update", "Update test status (audit-verified)");
|
||||||
updateCmd.Add(updateId);
|
updateCmd.Add(updateId);
|
||||||
updateCmd.Add(updateStatus);
|
updateCmd.Add(updateStatus);
|
||||||
|
updateCmd.Add(updateOverride);
|
||||||
updateCmd.SetAction(parseResult =>
|
updateCmd.SetAction(parseResult =>
|
||||||
{
|
{
|
||||||
var dbPath = parseResult.GetValue(dbOption)!;
|
var dbPath = parseResult.GetValue(dbOption)!;
|
||||||
var id = parseResult.GetValue(updateId);
|
var id = parseResult.GetValue(updateId);
|
||||||
var status = parseResult.GetValue(updateStatus)!;
|
var status = parseResult.GetValue(updateStatus)!;
|
||||||
|
var overrideComment = parseResult.GetValue(updateOverride);
|
||||||
using var db = new Database(dbPath);
|
using var db = new Database(dbPath);
|
||||||
|
|
||||||
|
var indexer = AuditVerifier.BuildIndexer("unit_tests");
|
||||||
|
var verifications = AuditVerifier.VerifyItems(db, indexer, "unit_tests",
|
||||||
|
" WHERE id = @id", [("@id", (object?)id)], status);
|
||||||
|
if (verifications.Count == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Test {id} not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!AuditVerifier.CheckAndReport(verifications, status, overrideComment))
|
||||||
|
return;
|
||||||
|
|
||||||
var affected = db.Execute("UPDATE unit_tests SET status = @status WHERE id = @id",
|
var affected = db.Execute("UPDATE unit_tests SET status = @status WHERE id = @id",
|
||||||
("@status", status), ("@id", id));
|
("@status", status), ("@id", id));
|
||||||
Console.WriteLine(affected > 0 ? $"Test {id} updated to '{status}'." : $"Test {id} not found.");
|
Console.WriteLine(affected > 0 ? $"Test {id} updated to '{status}'." : $"Test {id} not found.");
|
||||||
|
|
||||||
|
var mismatches = verifications.Where(r => !r.Matches).ToList();
|
||||||
|
if (mismatches.Count > 0 && overrideComment is not null)
|
||||||
|
AuditVerifier.LogOverrides(db, "unit_tests", mismatches, status, overrideComment);
|
||||||
});
|
});
|
||||||
|
|
||||||
// map
|
// map
|
||||||
@@ -139,13 +159,14 @@ public static class TestCommands
|
|||||||
|
|
||||||
private static Command CreateBatchUpdate(Option<string> dbOption)
|
private static Command CreateBatchUpdate(Option<string> dbOption)
|
||||||
{
|
{
|
||||||
var cmd = new Command("batch-update", "Bulk update test status");
|
var cmd = new Command("batch-update", "Bulk update test status (audit-verified)");
|
||||||
var idsOpt = BatchFilters.IdsOption();
|
var idsOpt = BatchFilters.IdsOption();
|
||||||
var moduleOpt = BatchFilters.ModuleOption();
|
var moduleOpt = BatchFilters.ModuleOption();
|
||||||
var statusOpt = BatchFilters.StatusOption();
|
var statusOpt = BatchFilters.StatusOption();
|
||||||
var executeOpt = BatchFilters.ExecuteOption();
|
var executeOpt = BatchFilters.ExecuteOption();
|
||||||
var setStatus = new Option<string>("--set-status") { Description = "New status to set", Required = true };
|
var setStatus = new Option<string>("--set-status") { Description = "New status to set", Required = true };
|
||||||
var setNotes = new Option<string?>("--set-notes") { Description = "Notes to set" };
|
var setNotes = new Option<string?>("--set-notes") { Description = "Notes to set" };
|
||||||
|
var overrideOpt = new Option<string?>("--override") { Description = "Override audit mismatches with this comment" };
|
||||||
|
|
||||||
cmd.Add(idsOpt);
|
cmd.Add(idsOpt);
|
||||||
cmd.Add(moduleOpt);
|
cmd.Add(moduleOpt);
|
||||||
@@ -153,6 +174,7 @@ public static class TestCommands
|
|||||||
cmd.Add(executeOpt);
|
cmd.Add(executeOpt);
|
||||||
cmd.Add(setStatus);
|
cmd.Add(setStatus);
|
||||||
cmd.Add(setNotes);
|
cmd.Add(setNotes);
|
||||||
|
cmd.Add(overrideOpt);
|
||||||
|
|
||||||
cmd.SetAction(parseResult =>
|
cmd.SetAction(parseResult =>
|
||||||
{
|
{
|
||||||
@@ -163,6 +185,7 @@ public static class TestCommands
|
|||||||
var execute = parseResult.GetValue(executeOpt);
|
var execute = parseResult.GetValue(executeOpt);
|
||||||
var newStatus = parseResult.GetValue(setStatus)!;
|
var newStatus = parseResult.GetValue(setStatus)!;
|
||||||
var notes = parseResult.GetValue(setNotes);
|
var notes = parseResult.GetValue(setNotes);
|
||||||
|
var overrideComment = parseResult.GetValue(overrideOpt);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(ids) && module is null && string.IsNullOrWhiteSpace(status))
|
if (string.IsNullOrWhiteSpace(ids) && module is null && string.IsNullOrWhiteSpace(status))
|
||||||
{
|
{
|
||||||
@@ -173,6 +196,12 @@ public static class TestCommands
|
|||||||
using var db = new Database(dbPath);
|
using var db = new Database(dbPath);
|
||||||
var (whereClause, filterParams) = BatchFilters.BuildWhereClause(ids, module, status);
|
var (whereClause, filterParams) = BatchFilters.BuildWhereClause(ids, module, status);
|
||||||
|
|
||||||
|
// Audit verification
|
||||||
|
var indexer = AuditVerifier.BuildIndexer("unit_tests");
|
||||||
|
var verifications = AuditVerifier.VerifyItems(db, indexer, "unit_tests", whereClause, filterParams, newStatus);
|
||||||
|
if (!AuditVerifier.CheckAndReport(verifications, newStatus, overrideComment))
|
||||||
|
return;
|
||||||
|
|
||||||
var setClauses = new List<string> { "status = @newStatus" };
|
var setClauses = new List<string> { "status = @newStatus" };
|
||||||
var updateParams = new List<(string, object?)> { ("@newStatus", newStatus) };
|
var updateParams = new List<(string, object?)> { ("@newStatus", newStatus) };
|
||||||
if (notes is not null)
|
if (notes is not null)
|
||||||
@@ -185,6 +214,14 @@ public static class TestCommands
|
|||||||
"id, name, status, module_id, notes",
|
"id, name, status, module_id, notes",
|
||||||
string.Join(", ", setClauses), updateParams,
|
string.Join(", ", setClauses), updateParams,
|
||||||
whereClause, filterParams, execute);
|
whereClause, filterParams, execute);
|
||||||
|
|
||||||
|
// Log overrides after successful execute
|
||||||
|
if (execute)
|
||||||
|
{
|
||||||
|
var mismatches = verifications.Where(r => !r.Matches).ToList();
|
||||||
|
if (mismatches.Count > 0 && overrideComment is not null)
|
||||||
|
AuditVerifier.LogOverrides(db, "unit_tests", mismatches, newStatus, overrideComment);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return cmd;
|
return cmd;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.3" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.3" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
|
||||||
<PackageReference Include="System.CommandLine" Version="3.0.0-preview.1.26104.118" />
|
<PackageReference Include="System.CommandLine" Version="3.0.0-preview.1.26104.118" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ rootCommand.Add(LibraryCommands.Create(dbOption, schemaOption));
|
|||||||
rootCommand.Add(DependencyCommands.Create(dbOption, schemaOption));
|
rootCommand.Add(DependencyCommands.Create(dbOption, schemaOption));
|
||||||
rootCommand.Add(ReportCommands.Create(dbOption, schemaOption));
|
rootCommand.Add(ReportCommands.Create(dbOption, schemaOption));
|
||||||
rootCommand.Add(PhaseCommands.Create(dbOption, schemaOption));
|
rootCommand.Add(PhaseCommands.Create(dbOption, schemaOption));
|
||||||
|
rootCommand.Add(AuditCommand.Create(dbOption));
|
||||||
|
rootCommand.Add(OverrideCommands.Create(dbOption));
|
||||||
|
|
||||||
var parseResult = rootCommand.Parse(args);
|
var parseResult = rootCommand.Parse(args);
|
||||||
return await parseResult.InvokeAsync();
|
return await parseResult.InvokeAsync();
|
||||||
|
|||||||
@@ -256,6 +256,10 @@ func (a *Analyzer) parseTestFile(filePath string) ([]TestFunc, []ImportInfo, int
|
|||||||
}
|
}
|
||||||
|
|
||||||
test.FeatureName = a.inferFeatureName(name)
|
test.FeatureName = a.inferFeatureName(name)
|
||||||
|
test.BestFeatureIdx = -1
|
||||||
|
if fn.Body != nil {
|
||||||
|
test.Calls = a.extractCalls(fn.Body)
|
||||||
|
}
|
||||||
tests = append(tests, test)
|
tests = append(tests, test)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,6 +335,210 @@ func (a *Analyzer) inferFeatureName(testName string) string {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extractCalls walks an AST block statement and extracts all function/method calls.
|
||||||
|
func (a *Analyzer) extractCalls(body *ast.BlockStmt) []CallInfo {
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
var calls []CallInfo
|
||||||
|
|
||||||
|
ast.Inspect(body, func(n ast.Node) bool {
|
||||||
|
callExpr, ok := n.(*ast.CallExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var ci CallInfo
|
||||||
|
switch fun := callExpr.Fun.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
ci = CallInfo{FuncName: fun.Name}
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
ci = CallInfo{
|
||||||
|
RecvOrPkg: extractIdent(fun.X),
|
||||||
|
MethodName: fun.Sel.Name,
|
||||||
|
IsSelector: true,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
key := ci.callKey()
|
||||||
|
if !seen[key] && !isFilteredCall(ci) {
|
||||||
|
seen[key] = true
|
||||||
|
calls = append(calls, ci)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractIdent extracts an identifier name from an expression (handles X in X.Y).
|
||||||
|
func extractIdent(expr ast.Expr) string {
|
||||||
|
switch e := expr.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
return e.Name
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
return extractIdent(e.X) + "." + e.Sel.Name
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isFilteredCall returns true if a call should be excluded from feature matching.
|
||||||
|
func isFilteredCall(c CallInfo) bool {
|
||||||
|
if c.IsSelector {
|
||||||
|
recv := c.RecvOrPkg
|
||||||
|
// testing.T/B methods
|
||||||
|
if recv == "t" || recv == "b" || recv == "tb" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// stdlib packages
|
||||||
|
if stdlibPkgs[recv] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// NATS client libs
|
||||||
|
if recv == "nats" || recv == "nuid" || recv == "nkeys" || recv == "jwt" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go builtins
|
||||||
|
name := c.FuncName
|
||||||
|
if builtinFuncs[name] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test assertion helpers
|
||||||
|
lower := strings.ToLower(name)
|
||||||
|
if strings.HasPrefix(name, "require_") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, prefix := range []string{"check", "verify", "assert", "expect"} {
|
||||||
|
if strings.HasPrefix(lower, prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// featureRef identifies a feature within the analysis result.
|
||||||
|
type featureRef struct {
|
||||||
|
moduleIdx int
|
||||||
|
featureIdx int
|
||||||
|
goFile string
|
||||||
|
goClass string
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveCallGraph matches test calls against known features across all modules.
|
||||||
|
func resolveCallGraph(result *AnalysisResult) {
|
||||||
|
// Build method index: go_method name → list of feature refs
|
||||||
|
methodIndex := make(map[string][]featureRef)
|
||||||
|
for mi, mod := range result.Modules {
|
||||||
|
for fi, feat := range mod.Features {
|
||||||
|
ref := featureRef{
|
||||||
|
moduleIdx: mi,
|
||||||
|
featureIdx: fi,
|
||||||
|
goFile: feat.GoFile,
|
||||||
|
goClass: feat.GoClass,
|
||||||
|
}
|
||||||
|
methodIndex[feat.GoMethod] = append(methodIndex[feat.GoMethod], ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each test, resolve calls to features
|
||||||
|
for mi := range result.Modules {
|
||||||
|
mod := &result.Modules[mi]
|
||||||
|
for ti := range mod.Tests {
|
||||||
|
test := &mod.Tests[ti]
|
||||||
|
seen := make(map[int]bool) // feature indices already linked
|
||||||
|
var linked []int
|
||||||
|
|
||||||
|
testFileBase := sourceFileBase(test.GoFile)
|
||||||
|
|
||||||
|
for _, call := range test.Calls {
|
||||||
|
// Look up the method name
|
||||||
|
name := call.MethodName
|
||||||
|
if !call.IsSelector {
|
||||||
|
name = call.FuncName
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates := methodIndex[name]
|
||||||
|
if len(candidates) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Ambiguity threshold: skip very common method names
|
||||||
|
if len(candidates) > 10 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter to same module
|
||||||
|
var sameModule []featureRef
|
||||||
|
for _, ref := range candidates {
|
||||||
|
if ref.moduleIdx == mi {
|
||||||
|
sameModule = append(sameModule, ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(sameModule) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ref := range sameModule {
|
||||||
|
if !seen[ref.featureIdx] {
|
||||||
|
seen[ref.featureIdx] = true
|
||||||
|
linked = append(linked, ref.featureIdx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test.LinkedFeatures = linked
|
||||||
|
|
||||||
|
// Set BestFeatureIdx using priority:
|
||||||
|
// (a) existing inferFeatureName match
|
||||||
|
// (b) same-file-base match
|
||||||
|
// (c) first remaining candidate
|
||||||
|
if test.BestFeatureIdx < 0 && len(linked) > 0 {
|
||||||
|
// Try same-file-base match first
|
||||||
|
for _, fi := range linked {
|
||||||
|
featFileBase := sourceFileBase(mod.Features[fi].GoFile)
|
||||||
|
if featFileBase == testFileBase {
|
||||||
|
test.BestFeatureIdx = fi
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fall back to first candidate
|
||||||
|
if test.BestFeatureIdx < 0 {
|
||||||
|
test.BestFeatureIdx = linked[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sourceFileBase strips _test.go suffix and path to get the base file name.
|
||||||
|
func sourceFileBase(goFile string) string {
|
||||||
|
base := filepath.Base(goFile)
|
||||||
|
base = strings.TrimSuffix(base, "_test.go")
|
||||||
|
base = strings.TrimSuffix(base, ".go")
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
var stdlibPkgs = map[string]bool{
|
||||||
|
"fmt": true, "time": true, "strings": true, "bytes": true, "errors": true,
|
||||||
|
"os": true, "math": true, "sort": true, "reflect": true, "sync": true,
|
||||||
|
"context": true, "io": true, "filepath": true, "strconv": true,
|
||||||
|
"encoding": true, "json": true, "binary": true, "hex": true, "rand": true,
|
||||||
|
"runtime": true, "atomic": true, "slices": true, "testing": true,
|
||||||
|
"net": true, "bufio": true, "crypto": true, "log": true, "regexp": true,
|
||||||
|
"unicode": true, "http": true, "url": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var builtinFuncs = map[string]bool{
|
||||||
|
"make": true, "append": true, "len": true, "cap": true, "close": true,
|
||||||
|
"delete": true, "panic": true, "recover": true, "print": true,
|
||||||
|
"println": true, "copy": true, "new": true,
|
||||||
|
}
|
||||||
|
|
||||||
// isStdlib checks if an import path is a Go standard library package.
|
// isStdlib checks if an import path is a Go standard library package.
|
||||||
func isStdlib(importPath string) bool {
|
func isStdlib(importPath string) bool {
|
||||||
firstSlash := strings.Index(importPath, "/")
|
firstSlash := strings.Index(importPath, "/")
|
||||||
|
|||||||
@@ -11,28 +11,47 @@ func main() {
|
|||||||
sourceDir := flag.String("source", "", "Path to Go source root (e.g., ../../golang/nats-server)")
|
sourceDir := flag.String("source", "", "Path to Go source root (e.g., ../../golang/nats-server)")
|
||||||
dbPath := flag.String("db", "", "Path to SQLite database file (e.g., ../../porting.db)")
|
dbPath := flag.String("db", "", "Path to SQLite database file (e.g., ../../porting.db)")
|
||||||
schemaPath := flag.String("schema", "", "Path to SQL schema file (e.g., ../../porting-schema.sql)")
|
schemaPath := flag.String("schema", "", "Path to SQL schema file (e.g., ../../porting-schema.sql)")
|
||||||
|
mode := flag.String("mode", "full", "Analysis mode: 'full' (default) or 'call-graph' (incremental)")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if *sourceDir == "" || *dbPath == "" || *schemaPath == "" {
|
if *sourceDir == "" || *dbPath == "" {
|
||||||
fmt.Fprintf(os.Stderr, "Usage: go-analyzer --source <path> --db <path> --schema <path>\n")
|
fmt.Fprintf(os.Stderr, "Usage: go-analyzer --source <path> --db <path> [--schema <path>] [--mode full|call-graph]\n")
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch *mode {
|
||||||
|
case "full":
|
||||||
|
runFull(*sourceDir, *dbPath, *schemaPath)
|
||||||
|
case "call-graph":
|
||||||
|
runCallGraph(*sourceDir, *dbPath)
|
||||||
|
default:
|
||||||
|
log.Fatalf("Unknown mode %q: must be 'full' or 'call-graph'", *mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runFull(sourceDir, dbPath, schemaPath string) {
|
||||||
|
if schemaPath == "" {
|
||||||
|
log.Fatal("--schema is required for full mode")
|
||||||
|
}
|
||||||
|
|
||||||
// Open DB and apply schema
|
// Open DB and apply schema
|
||||||
db, err := OpenDB(*dbPath, *schemaPath)
|
db, err := OpenDB(dbPath, schemaPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to open database: %v", err)
|
log.Fatalf("Failed to open database: %v", err)
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
// Run analysis
|
// Run analysis
|
||||||
analyzer := NewAnalyzer(*sourceDir)
|
analyzer := NewAnalyzer(sourceDir)
|
||||||
result, err := analyzer.Analyze()
|
result, err := analyzer.Analyze()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Analysis failed: %v", err)
|
log.Fatalf("Analysis failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve call graph before writing
|
||||||
|
resolveCallGraph(result)
|
||||||
|
|
||||||
// Write to DB
|
// Write to DB
|
||||||
writer := NewDBWriter(db)
|
writer := NewDBWriter(db)
|
||||||
if err := writer.WriteAll(result); err != nil {
|
if err := writer.WriteAll(result); err != nil {
|
||||||
@@ -46,3 +65,35 @@ func main() {
|
|||||||
fmt.Printf(" Dependencies: %d\n", len(result.Dependencies))
|
fmt.Printf(" Dependencies: %d\n", len(result.Dependencies))
|
||||||
fmt.Printf(" Imports: %d\n", len(result.Imports))
|
fmt.Printf(" Imports: %d\n", len(result.Imports))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runCallGraph(sourceDir, dbPath string) {
|
||||||
|
// Open existing DB without schema
|
||||||
|
db, err := OpenDBNoSchema(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to open database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Run analysis (parse Go source)
|
||||||
|
analyzer := NewAnalyzer(sourceDir)
|
||||||
|
result, err := analyzer.Analyze()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Analysis failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve call graph
|
||||||
|
resolveCallGraph(result)
|
||||||
|
|
||||||
|
// Update DB incrementally
|
||||||
|
writer := NewDBWriter(db)
|
||||||
|
stats, err := writer.UpdateCallGraph(result)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to update call graph: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Call graph analysis complete:\n")
|
||||||
|
fmt.Printf(" Tests analyzed: %d\n", stats.TestsAnalyzed)
|
||||||
|
fmt.Printf(" Tests linked: %d\n", stats.TestsLinked)
|
||||||
|
fmt.Printf(" Dependency rows: %d\n", stats.DependencyRows)
|
||||||
|
fmt.Printf(" Feature IDs set: %d\n", stats.FeatureIDsSet)
|
||||||
|
}
|
||||||
|
|||||||
@@ -152,3 +152,176 @@ func (w *DBWriter) insertLibrary(tx *sql.Tx, imp *ImportInfo) error {
|
|||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenDBNoSchema opens an existing SQLite database without applying schema.
|
||||||
|
// It verifies that the required tables exist.
|
||||||
|
func OpenDBNoSchema(dbPath string) (*sql.DB, error) {
|
||||||
|
db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&_foreign_keys=ON")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("opening database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify required tables exist
|
||||||
|
for _, table := range []string{"modules", "features", "unit_tests", "dependencies"} {
|
||||||
|
var name string
|
||||||
|
err := db.QueryRow("SELECT name FROM sqlite_master WHERE type='table' AND name=?", table).Scan(&name)
|
||||||
|
if err != nil {
|
||||||
|
db.Close()
|
||||||
|
return nil, fmt.Errorf("required table %q not found: %w", table, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallGraphStats holds summary statistics from a call-graph update.
|
||||||
|
type CallGraphStats struct {
|
||||||
|
TestsAnalyzed int
|
||||||
|
TestsLinked int
|
||||||
|
DependencyRows int
|
||||||
|
FeatureIDsSet int
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCallGraph writes call-graph analysis results to the database incrementally.
|
||||||
|
func (w *DBWriter) UpdateCallGraph(result *AnalysisResult) (*CallGraphStats, error) {
|
||||||
|
stats := &CallGraphStats{}
|
||||||
|
|
||||||
|
// Load module name→ID mapping
|
||||||
|
moduleIDs := make(map[string]int64)
|
||||||
|
rows, err := w.db.Query("SELECT id, name FROM modules")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("querying modules: %w", err)
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
var id int64
|
||||||
|
var name string
|
||||||
|
if err := rows.Scan(&id, &name); err != nil {
|
||||||
|
rows.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
moduleIDs[name] = id
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
|
||||||
|
// Load feature DB IDs: "module_name:go_method:go_class" → id
|
||||||
|
type featureKey struct {
|
||||||
|
moduleName string
|
||||||
|
goMethod string
|
||||||
|
goClass string
|
||||||
|
}
|
||||||
|
featureDBIDs := make(map[featureKey]int64)
|
||||||
|
rows, err = w.db.Query(`
|
||||||
|
SELECT f.id, m.name, f.go_method, COALESCE(f.go_class, '')
|
||||||
|
FROM features f
|
||||||
|
JOIN modules m ON f.module_id = m.id
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("querying features: %w", err)
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
var id int64
|
||||||
|
var modName, goMethod, goClass string
|
||||||
|
if err := rows.Scan(&id, &modName, &goMethod, &goClass); err != nil {
|
||||||
|
rows.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
featureDBIDs[featureKey{modName, goMethod, goClass}] = id
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
|
||||||
|
// Load test DB IDs: "module_name:go_method" → id
|
||||||
|
testDBIDs := make(map[string]int64)
|
||||||
|
rows, err = w.db.Query(`
|
||||||
|
SELECT ut.id, m.name, ut.go_method
|
||||||
|
FROM unit_tests ut
|
||||||
|
JOIN modules m ON ut.module_id = m.id
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("querying unit_tests: %w", err)
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
var id int64
|
||||||
|
var modName, goMethod string
|
||||||
|
if err := rows.Scan(&id, &modName, &goMethod); err != nil {
|
||||||
|
rows.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
testDBIDs[modName+":"+goMethod] = id
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
|
||||||
|
// Begin transaction
|
||||||
|
tx, err := w.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("beginning transaction: %w", err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// Clear old call-graph data
|
||||||
|
if _, err := tx.Exec("DELETE FROM dependencies WHERE source_type='unit_test' AND dependency_kind='calls'"); err != nil {
|
||||||
|
return nil, fmt.Errorf("clearing old dependencies: %w", err)
|
||||||
|
}
|
||||||
|
if _, err := tx.Exec("UPDATE unit_tests SET feature_id = NULL"); err != nil {
|
||||||
|
return nil, fmt.Errorf("clearing old feature_ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare statements
|
||||||
|
insertDep, err := tx.Prepare("INSERT OR IGNORE INTO dependencies (source_type, source_id, target_type, target_id, dependency_kind) VALUES ('unit_test', ?, 'feature', ?, 'calls')")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("preparing insert dependency: %w", err)
|
||||||
|
}
|
||||||
|
defer insertDep.Close()
|
||||||
|
|
||||||
|
updateFeatureID, err := tx.Prepare("UPDATE unit_tests SET feature_id = ? WHERE id = ?")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("preparing update feature_id: %w", err)
|
||||||
|
}
|
||||||
|
defer updateFeatureID.Close()
|
||||||
|
|
||||||
|
// Process each module's tests
|
||||||
|
for _, mod := range result.Modules {
|
||||||
|
for _, test := range mod.Tests {
|
||||||
|
stats.TestsAnalyzed++
|
||||||
|
|
||||||
|
testDBID, ok := testDBIDs[mod.Name+":"+test.GoMethod]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert dependency rows for linked features
|
||||||
|
if len(test.LinkedFeatures) > 0 {
|
||||||
|
stats.TestsLinked++
|
||||||
|
}
|
||||||
|
for _, fi := range test.LinkedFeatures {
|
||||||
|
feat := mod.Features[fi]
|
||||||
|
featDBID, ok := featureDBIDs[featureKey{mod.Name, feat.GoMethod, feat.GoClass}]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := insertDep.Exec(testDBID, featDBID); err != nil {
|
||||||
|
return nil, fmt.Errorf("inserting dependency for test %s: %w", test.GoMethod, err)
|
||||||
|
}
|
||||||
|
stats.DependencyRows++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set feature_id for best match
|
||||||
|
if test.BestFeatureIdx >= 0 {
|
||||||
|
feat := mod.Features[test.BestFeatureIdx]
|
||||||
|
featDBID, ok := featureDBIDs[featureKey{mod.Name, feat.GoMethod, feat.GoClass}]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := updateFeatureID.Exec(featDBID, testDBID); err != nil {
|
||||||
|
return nil, fmt.Errorf("updating feature_id for test %s: %w", test.GoMethod, err)
|
||||||
|
}
|
||||||
|
stats.FeatureIDsSet++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return nil, fmt.Errorf("committing transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,6 +58,28 @@ type TestFunc struct {
|
|||||||
GoLineCount int
|
GoLineCount int
|
||||||
// FeatureName links this test to a feature by naming convention
|
// FeatureName links this test to a feature by naming convention
|
||||||
FeatureName string
|
FeatureName string
|
||||||
|
// Calls holds raw function/method calls extracted from the test body AST
|
||||||
|
Calls []CallInfo
|
||||||
|
// LinkedFeatures holds indices into the parent module's Features slice
|
||||||
|
LinkedFeatures []int
|
||||||
|
// BestFeatureIdx is the primary feature match index (-1 = none)
|
||||||
|
BestFeatureIdx int
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallInfo represents a function or method call extracted from a test body.
|
||||||
|
type CallInfo struct {
|
||||||
|
FuncName string // direct call name: "newMemStore"
|
||||||
|
RecvOrPkg string // selector receiver/pkg: "ms", "fmt", "t"
|
||||||
|
MethodName string // selector method: "StoreMsg", "Fatalf"
|
||||||
|
IsSelector bool // true for X.Y() form
|
||||||
|
}
|
||||||
|
|
||||||
|
// callKey returns a deduplication key for this call.
|
||||||
|
func (c CallInfo) callKey() string {
|
||||||
|
if c.IsSelector {
|
||||||
|
return c.RecvOrPkg + "." + c.MethodName
|
||||||
|
}
|
||||||
|
return c.FuncName
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dependency represents a call relationship between two items.
|
// Dependency represents a call relationship between two items.
|
||||||
|
|||||||
Reference in New Issue
Block a user