docs: add config parsing and hot reload design document
Captures the design for resolving the two remaining high-priority gaps in differences.md: config file parsing and SIGHUP hot reload.
This commit is contained in:
184
docs/plans/2026-02-23-config-parsing-design.md
Normal file
184
docs/plans/2026-02-23-config-parsing-design.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# Config File Parsing & Hot Reload Design
|
||||
|
||||
> Resolves the two remaining high-priority gaps in differences.md.
|
||||
|
||||
## Goals
|
||||
|
||||
1. Port the Go NATS config file parser (`conf/lex.go` + `conf/parse.go`) to .NET
|
||||
2. Map parsed config to existing + new `NatsOptions` fields (single-server scope)
|
||||
3. Implement SIGHUP hot reload matching Go's reloadable option set
|
||||
4. Add unit tests for lexer, parser, config processor, and reload
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Config File
|
||||
→ NatsConfLexer (state-machine tokenizer)
|
||||
→ NatsConfParser (builds Dictionary<string, object?>)
|
||||
→ ConfigProcessor.Apply(dict, NatsOptions)
|
||||
→ NatsOptions populated
|
||||
|
||||
SIGHUP
|
||||
→ NatsServer.ReloadConfig()
|
||||
→ Re-parse config file
|
||||
→ Merge with CLI flag snapshot
|
||||
→ ConfigReloader.Diff(old, new) → IConfigChange[]
|
||||
→ Validate (reject non-reloadable)
|
||||
→ Apply each change to running server
|
||||
```
|
||||
|
||||
## Component 1: Lexer (`NatsConfLexer.cs`)
|
||||
|
||||
Direct port of Go `conf/lex.go` (~1320 lines Go → ~400 lines C#).
|
||||
|
||||
State-machine tokenizer producing typed tokens:
|
||||
- `Key`, `String`, `Bool`, `Integer`, `Float`, `DateTime`
|
||||
- `ArrayStart`/`ArrayEnd`, `MapStart`/`MapEnd`
|
||||
- `Variable`, `Include`, `Comment`, `EOF`, `Error`
|
||||
|
||||
Supported syntax:
|
||||
- Key separators: `=`, `:`, or whitespace
|
||||
- Comments: `#` and `//`
|
||||
- Strings: `"double"`, `'single'`, raw (unquoted)
|
||||
- Booleans: `true/false`, `yes/no`, `on/off`
|
||||
- Integers with size suffixes: `1k`, `2mb`, `1gb`
|
||||
- Floats, ISO8601 datetimes (`2006-01-02T15:04:05Z`)
|
||||
- Block strings: `(` multi-line raw text `)`
|
||||
- Hex escapes: `\x##`, plus `\t`, `\n`, `\r`, `\"`, `\\`
|
||||
|
||||
## Component 2: Parser (`NatsConfParser.cs`)
|
||||
|
||||
Direct port of Go `conf/parse.go` (~529 lines Go → ~300 lines C#).
|
||||
|
||||
Consumes token stream, produces `Dictionary<string, object?>`:
|
||||
- Stack-based context tracking for nested maps/arrays
|
||||
- Variable resolution: `$VAR` searches current context stack, then environment
|
||||
- Cycle detection for variable references
|
||||
- `include "path"` resolves relative to current config file
|
||||
- Pedantic mode with line/column tracking for error messages
|
||||
- SHA256 digest of parsed content for reload change detection
|
||||
|
||||
## Component 3: Config Processor (`ConfigProcessor.cs`)
|
||||
|
||||
Maps parsed dictionary keys to `NatsOptions` fields. Port of Go `processConfigFileLine` in `opts.go`.
|
||||
|
||||
Key categories handled:
|
||||
- **Network**: `listen`, `port`, `host`/`net`, `client_advertise`, `max_connections`/`max_conn`
|
||||
- **Logging**: `debug`, `trace`, `trace_verbose`, `logtime`, `logtime_utc`, `logfile`/`log_file`, `log_size_limit`, `log_max_num`, `syslog`, `remote_syslog`
|
||||
- **Auth**: `authorization { ... }` block (username, password, token, users, nkeys, timeout), `no_auth_user`
|
||||
- **Accounts**: `accounts { ... }` block, `system_account`, `no_system_account`
|
||||
- **TLS**: `tls { ... }` block (cert_file, key_file, ca_file, verify, verify_and_map, timeout, pinned_certs, handshake_first, handshake_first_fallback), `allow_non_tls`
|
||||
- **Monitoring**: `http_port`/`monitor_port`, `https_port`, `http`/`https` (combined), `http_base_path`
|
||||
- **Limits**: `max_payload`, `max_control_line`, `max_pending`, `max_subs`, `max_sub_tokens`, `max_traced_msg_len`, `write_deadline`
|
||||
- **Ping**: `ping_interval`, `ping_max`/`ping_max_out`
|
||||
- **Lifecycle**: `lame_duck_duration`, `lame_duck_grace_period`
|
||||
- **Files**: `pidfile`/`pid_file`, `ports_file_dir`
|
||||
- **Misc**: `server_name`, `server_tags`, `disable_sublist_cache`, `max_closed_clients`, `prof_port`
|
||||
|
||||
Error handling: accumulate all errors, report together (not fail-fast). Unknown keys silently ignored (allows cluster/JetStream configs to coexist).
|
||||
|
||||
## Component 4: Hot Reload (`ConfigReloader.cs`)
|
||||
|
||||
### Reloadable Options (matching Go)
|
||||
|
||||
- **Logging**: Debug, Trace, TraceVerbose, Logtime, LogtimeUTC, LogFile, LogSizeLimit, LogMaxFiles, Syslog, RemoteSyslog
|
||||
- **Auth**: Username, Password, Authorization, Users, NKeys, NoAuthUser, AuthTimeout
|
||||
- **Limits**: MaxConnections, MaxPayload, MaxPending, WriteDeadline, PingInterval, MaxPingsOut, MaxControlLine, MaxSubs, MaxSubTokens, MaxTracedMsgLen
|
||||
- **TLS**: cert/key/CA file paths (reload certs without restart)
|
||||
- **Misc**: Tags, LameDuckDuration, LameDuckGracePeriod, ClientAdvertise, MaxClosedClients
|
||||
|
||||
### Non-Reloadable (error if changed)
|
||||
|
||||
- Host, Port, ServerName
|
||||
|
||||
### IConfigChange Interface
|
||||
|
||||
```csharp
|
||||
interface IConfigChange
|
||||
{
|
||||
string Name { get; }
|
||||
void Apply(NatsServer server);
|
||||
bool IsLoggingChange { get; }
|
||||
bool IsAuthChange { get; }
|
||||
bool IsTlsChange { get; }
|
||||
}
|
||||
```
|
||||
|
||||
### Reload Flow
|
||||
|
||||
1. SIGHUP → `NatsServer.ReloadConfig()`
|
||||
2. Re-parse config file via `ConfigProcessor.ProcessConfigFile()`
|
||||
3. Merge with CLI flag snapshot (CLI always wins)
|
||||
4. `ConfigReloader.Diff(oldOpts, newOpts)` → list of `IConfigChange`
|
||||
5. Validate: reject if non-reloadable options changed
|
||||
6. Apply each change to running server (logging, auth, limits, TLS grouped)
|
||||
7. Log applied changes at Information level, errors at Warning
|
||||
|
||||
## New NatsOptions Fields
|
||||
|
||||
Added for single-server parity with Go:
|
||||
|
||||
| Field | Type | Default | Go equivalent |
|
||||
|-------|------|---------|---------------|
|
||||
| `ClientAdvertise` | string? | null | `client_advertise` |
|
||||
| `TraceVerbose` | bool | false | `trace_verbose` |
|
||||
| `MaxTracedMsgLen` | int | 0 | `max_traced_msg_len` |
|
||||
| `DisableSublistCache` | bool | false | `disable_sublist_cache` |
|
||||
| `ConnectErrorReports` | int | 3600 | `connect_error_reports` |
|
||||
| `ReconnectErrorReports` | int | 1 | `reconnect_error_reports` |
|
||||
| `NoHeaderSupport` | bool | false | `no_header_support` |
|
||||
| `MaxClosedClients` | int | 10000 | `max_closed_clients` |
|
||||
| `NoSystemAccount` | bool | false | `no_system_account` |
|
||||
| `SystemAccount` | string? | null | `system_account` |
|
||||
|
||||
## Integration Points
|
||||
|
||||
### NatsServer.cs
|
||||
|
||||
- Constructor: if `ConfigFile` set, parse before startup
|
||||
- SIGHUP handler: call `ReloadConfig()` instead of warning log
|
||||
- New `ReloadConfig()` method for reload orchestration
|
||||
- Store CLI flag snapshot (`HashSet<string> InCmdLine`)
|
||||
|
||||
### Program.cs
|
||||
|
||||
- Parse config file after defaults, before CLI args
|
||||
- Track CLI-set options in `InCmdLine`
|
||||
- Rebuild Serilog config on logging reload
|
||||
|
||||
## File Layout
|
||||
|
||||
```
|
||||
src/NATS.Server/Configuration/
|
||||
NatsConfLexer.cs (~400 lines)
|
||||
NatsConfParser.cs (~300 lines)
|
||||
NatsConfToken.cs (~30 lines)
|
||||
ConfigProcessor.cs (~350 lines)
|
||||
ConfigReloader.cs (~250 lines)
|
||||
IConfigChange.cs (~15 lines)
|
||||
```
|
||||
|
||||
## Test Plan
|
||||
|
||||
### Test Files
|
||||
|
||||
- `NatsConfLexerTests.cs` — all token types, comments, escapes, edge cases
|
||||
- `NatsConfParserTests.cs` — nested blocks, arrays, variables, includes, errors
|
||||
- `ConfigProcessorTests.cs` — all option key mappings, type coercion, error collection
|
||||
- `ConfigReloadTests.cs` — reload flow, reloadable vs non-reloadable, CLI precedence
|
||||
|
||||
### Test Data
|
||||
|
||||
```
|
||||
tests/NATS.Server.Tests/TestData/
|
||||
basic.conf — minimal server config
|
||||
auth.conf — authorization block with users/nkeys
|
||||
tls.conf — TLS configuration
|
||||
full.conf — all supported options
|
||||
includes/ — include directive tests
|
||||
invalid.conf — error case configs
|
||||
```
|
||||
|
||||
## Task Reference
|
||||
|
||||
Implementation tasks will be created via the writing-plans skill.
|
||||
Reference in New Issue
Block a user