Files
natsdotnet/docs/plans/2026-02-23-config-parsing-design.md
Joseph Doherty 65fac32a14 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.
2026-02-23 04:06:16 -05:00

7.0 KiB

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

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.