New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make *client.Client
safe for concurrent use
#43738
base: master
Are you sure you want to change the base?
Conversation
Signed-off-by: Cory Snider <csnider@mirantis.com>
The API version is the only stateful thing in the client. It has historically had no locking or synchronization, making the client de facto unsafe for concurrent use. Making the client concurrency-safe is not exactly a trivial undertaking, however: the client uses the API version when building requests and handling the responses, so care must be taken to ensure that the responses to any requests be handled with the semantics of the same API version that was used when building the request, even if the client's API version is changed while the request is in-flight. Merely converting all accesses to the version field to atomic loads and stores would not be sufficient. The client also has an optional feature to lazily auto-negotiate the API version on the first API request, so care also needs to be taken to suppress redundant auto-negotiation attempts while an auto-negotiation is in progress. Introduce the concept of a "versioned client" to the client internals which encapsulates a point-in-time snapshot of the client's configured API version. Refactor the client methods to perform all version- dependent operations through a versioned-client instance to ensure that they always operate on an immutable snapshot of the version. Move version auto-negotiation into the versioned-client constructor and synchronize accesses to the version state to prevent data races and redundant auto-negotation attempts. Exclusive access to the version state is held by the goroutine throughout the entire duration of the auto-negotiation process, blocking the construction of new versioned clients or starting any concurrent negotiation processes until the auto-negotiation process completes. With auto-negotiation now performed explicitly in the versioned-client constructor rather than implicitly in the high-level HTTP request helper methods, it is now possible to implement Ping() in terms of those high- level helpers. Simplify the Ping implementation through the use of an "unversioned" versioned client (i.e. with an empty version) and the aforementioned high-level helpers. Signed-off-by: Cory Snider <csnider@mirantis.com>
3d56b1f
to
632e667
Compare
When a client is configured for API version negotiation, it would try exactly once to ping the server, then unconditionally configure the client API version and set the negotiated flag. Any failure to ping the server would result in the client "negotiating" the fallback API version v1.24, even if the request never reached the daemon. Transient network issues or simply calling a client method with an already-canceled context would be sufficient to cause this to happen. Modify the client's negotiation process to retry negotiation on each subsequent request until it receives a ping response from the daemon. Only responses with HTTP status codes in the range [1, 500] are considered to be ping responses from the daemon as HTTP status codes >= 501 (e.g. 502 Gateway Timeout, 503 Service Unavailable) could be returned by intermediate hops. Signed-off-by: Cory Snider <csnider@mirantis.com>
0babb94
to
2d4806a
Compare
Sounds to me a damn complicated way to address this issue while API version negociation could just take place once within |
Nearly all of the complexity will still be required to make the client safe for concurrent use so long as There are more ways to implement API version negotiation than just eager and lazy pinging. If I have time someday, I would like to experiment with what I like to call optimistic version negotiation: do the user's request initially using the highest API version the client supports. If the response is a 400 Bad Request, downgrade the client's API version using the |
- What I did
NewClientWithOpts
constructor instead of a struct literal- How I did it
Way too much tedious and repetitive semi-manual editing.
- How to verify it
New tests to exercise concurrent client usage for the race detector and to verify the new negotiation retry behaviour.
- Description for the changelog
*client.Client
instances can now safely be used concurrently by multiple goroutines in all cases. Older client versions are not safe for concurrent use when theWithAPIVersionNegotation
option is used.WithAPIVersionNegotiation
option for*client.Client
has been made more robust. API version negotiation will now be retried if the first attempt timed out or could not be completed due to network conditions.- A picture of a cute animal (not mandatory but encouraged)