Repository navigation
fix: read a request's configuration once and never write it - #62
Merged
Merged
Conversation
Four scenarios pin what a request does when OnRetry changes the client: Retries set to 0 ends the request one attempt early, ExponentialTimeout doubles the next attempts' timeouts, a canceled Context ends the request, and a replaced OnSent runs in the next attempt. The running request reads the client's configuration at each step.
The engine read the client's configuration live during a request: Retries and ExponentialTimeout at each retry decision, the Context at each attempt, Transport for the TCP reconnect, the hooks at each call, and the client's version and security model, the latter in three guards that compared the client with the packet. A hook or the Logger that changed the client changed the running request: with a version changed after the negotiation, the request went out with msgID 0. The engine also wrote configuration as working state: a request clamped a negative Retries to 0, Connect appended the IPv4/IPv6 suffix to Transport on every call (a second ConnectIPv4 dialed "udp44") and narrowed it to IPv4 for an IPv4 local address, and Connect reseeded the ID counters, discarding SetRequestID and SetMsgID and replaying the same IDs after a second Connect. A client given a Conn without Connect panicked for want of a Context and a receive buffer. A request now builds its configuration once, at its start: the retries (a negative value counts as 0), the timeout and exponential timeout, the Context (a background one when the client has none), the network and the hooks; it allocates the receive buffer the first time. The packet answers the version, security level and model, and the context: the guards are gone, the msgID follows the packet's version, discovery takes the packet's context name, and the reply-model check compares the reply with the request. The ID counters start at a random value on first use unless SetRequestID or SetMsgID set them, and Connect leaves them alone. Connect derives the network into an unexported field and leaves Transport as set; it still fills in its documented defaults. A hook's or the Logger's change to the client now applies from the next request; a Conn given without Connect works, given the fields Connect would have filled in.
…guration The derived network outlived the connection it described: after Close, or with another Conn set in place of the one Connect opened, a request took the old network instead of Transport, so a TCP Conn the caller set no longer reconnected when the agent closed it; a failed dial also left a network behind. The client now records the network of a connection only when it opens one, together with that connection, and a request uses it only while Conn is that connection. A random start of 2147483647 made the first ID 0, which a Response may carry for any request; the start is now below it. Tests: every request scenario records the configuration fields the run changed, so a library write shows next to the hooks' own; SNMPv3 scenarios where the discovery and the resync exchanges' hooks change the client, and one where OnRetry sets a Context with a deadline; each ID counter's random start on its own; the caller-set TCP Conn test after an earlier Connect and Close, and with the Conn replaced. Docs name what a request does not read once: the Logger, the SNMPv3 engine state the client learns, and the TCP reconnect's dial settings.
ilyam8
added this pull request to stack #61
October 14, 2026 07:10
stelfrag
approved these changes
Oct 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Third step of the bug-fix phase: a request reads its configuration once, at its start, and never writes it.
The engine used to read the client's configuration live during a request:
RetriesandExponentialTimeoutat each retry decision, the Context at each attempt, andTransportfor the TCPreconnect;
So a hook or the Logger that changed the client changed the running request. With the version changed after the
negotiation, the request went out with msgID 0.
The library also wrote configuration as working state:
Retriesto 0.Transporton every call, so a secondConnectIPv4dialed "udp44", andnarrowed it to IPv4 for an IPv4 local address.
SetRequestID/SetMsgIDand made a second Connect replay thesame IDs.
A client given a
Connwithout Connect panicked, for want of a Context and a receive buffer.Now:
sendbuilds one request configuration: the retries (a negative value counts as 0), the timeout, the exponentialtimeout, the Context (a background one when the client has none), the network and the hooks. It allocates the
receive buffer the first time. The engine reads only this configuration and the packet.
SetRequestIDorSetMsgIDset them. Connect leavesthem alone.
uses only while
Connis that connection.Transportis left as set. Connect still fills in its documenteddefaults: "udp",
MaxOids, a background Context andReportable.step rewrites it with the connection's ownership.
any request.
Behavior changes:
RetriesandTransportkeep the caller's values.SetRequestID/SetMsgID. A client that never connected draws random IDs.Conngiven without Connect works, provided the caller sets the fields Connect would have filled in(
MaxOids, for one).Tests
OnRetrychangesRetries,ExponentialTimeout, the Context or a hook,pinning the old behavior.
changes the context name);
Retrieskeeps its value.TestConnectrecords the derived network next toTransport.TestRequestAndMsgIDspins the new counter rules,each counter's random start on its own.
Connthe caller set, without a Context, with a hook that changesTransport. It covers a client that never connected, one that connected over UDP and closed, and one whoseconnection was replaced. The request still reconnects over TCP each time.
to the hooks' own;
Validation
netsnmpplayback pass.GOARCH=386 and the end-to-end suite run in CI.
Getcosts about 21 ns more per request (+2.7% on the in-memory benchmark), for building and copying theconfiguration;
Close (fixed), an unpinned per-counter random start, gaps in the coverage of the "read once, never write" rule
(closed), and docs that needed to name their exceptions.