Repository navigation
fix(transport): reject an invalid ConnectionTimeout instead of retrying it - #692
Conversation
…ng it ConnectionRetryOptions never sanity-checked its values. A zero or negative ConnectionTimeout was handed straight to the platform, which answered with one of four different exceptions naming a property the caller never touched (SerialPort.WriteTimeout, ReadTimeout, a raw SocketException), and then ConnectRetryExecutor — unable to tell a permanent misconfiguration from a transient connect failure — sat through the entire backoff curve re-dialling a device that was present and healthy (7 s at the default policy). Validate at the boundary instead, mirroring the guards ReconnectOptions already carries: MaxAttempts >= 1, InitialDelay/MaxDelay non-negative, BackoffMultiplier >= 1.0 (which also rejects NaN), and ConnectionTimeout strictly positive and within the millisecond int both transports narrow it to. The throw now happens where the value is set, naming ConnectionTimeout, so the pointless backoff disappears for free. Closes #681 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
/agentic_review |
PR Summary by QodoReject invalid connection retry options before dialing
AI Description
Diagram
High-Level Assessment
Files changed (2)
|
Code Review by Qodo
1.
|
Both transports narrow the timeout to a millisecond int, so anything under 1 ms truncated to 0 and produced the exact platform error — retried through the full backoff — that this validation exists to prevent. Raise the lower bound to 1 ms so the smallest accepted value survives the narrowing intact.
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit efeab0e |
What was wrong
If you set
ConnectionRetryOptions.ConnectionTimeoutto zero or a negative value, connectingdidn't tell you that. It failed with a raw platform exception naming a property you never
touched —
ArgumentOutOfRangeException ... (Parameter 'WriteTimeout')on serial, a bareSocketException: Invalid argumenton TCP, four different messages for the one mistake — andthen Core retried the misconfiguration, sitting through the full exponential backoff against
a device that was plugged in and perfectly healthy. Bench-measured at 7.08 s with the default
4-attempt policy on a working Nq1; a
Resilientpolicy would burn far longer. None of the fourmessages mentioned
ConnectionTimeout.How it was fixed
ConnectionRetryOptionsnow validates each value where it is set, so the failure is anArgumentOutOfRangeExceptionnamingConnectionTimeout, thrown before any dialling happens.Because it throws before the attempt loop is ever entered, the pointless backoff disappears for
free — no change to
ConnectRetryExecutorwas needed.The other four properties are guarded in the same pass, since a negative
MaxDelaymakesCalculateDelayreturn a negative span and a NaNBackoffMultiplierpoisons the whole curve.The shape is copied verbatim from the guards
ReconnectOptionsalready carries, so the twosibling policy classes still read the same way.
Two judgement calls worth pushing back on if you disagree:
BackoffMultipliermust now be>= 1.0, matchingReconnectOptions. A value below 1 wouldshrink the delays, which isn't backoff — but it wasn't previously rejected.
ConnectionTimeoutalso has an upper bound ofint.MaxValuems (~24.8 days), because bothtransports narrow it to a millisecond
int; a longer span would wrap round to a negativetimeout and land right back in the platform error this PR exists to remove.
Verification
Full suite green on net9.0 and net10.0: 4084 Core tests (3 skipped, platform-gated) and 217 MCP
tests, zero build warnings. Eleven new tests cover each rejected value, each accepted boundary
(
InitialDelay = 0still means "retry immediately",ConnectionTimeout = int.MaxValuems isaccepted), and that the
NoRetry/Fast/Resilientpresets still satisfy their own guards.closes #681
🤖 Generated with Claude Code