Repository: moby/moby
File: client/client.go (Go)

Code before:
	transport := &http.Transport{}
	err := sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host)
	if err != nil {
		return nil, err
	}
	return &http.Client{
		Transport:     transport,
		CheckRedirect: CheckRedirect,
	}, nil
}

// Close the transport used by the client
func (cli *Client) Close() error {
	if cli.baseTransport != nil {
		cli.baseTransport.CloseIdleConnections()
		return nil
	}
	return nil
}

// checkVersion manually triggers API version negotiation (if configured).
// This allows for version-dependent code to use the same version as will
// be negotiated when making the actual requests, and for which cases
// we cannot do the negotiation lazily.
func (cli *Client) checkVersion(ctx context.Context) error {
	if !cli.manualOverride && cli.negotiateVersion && !cli.negotiated {
		// Ensure exclusive write access to version and negotiated fields
		cli.versionLock.Lock()
		defer cli.versionLock.Unlock()

		// May have been set during last execution of critical zone
		if cli.negotiated {
			return nil
		}

		ping, err := cli.Ping(ctx)
		if err != nil {
			return err
		}
		cli.negotiateAPIVersionPing(ping)
	}
	return nil
}

// getAPIPath returns the versioned request path to call the API.
// It appends the query parameters to the path if they are not empty.
func (cli *Client) getAPIPath(ctx context.Context, p string, query url.Values) string {
	var apiPath string
	_ = cli.checkVersion(ctx)
	if cli.version != "" {
		v := strings.TrimPrefix(cli.version, "v")

Review comment (bug):
That's a data race. `cli.negotiated` may be concurrently written by another goroutine. The race can be fixed by changing `cli.negotiated` to an [atomic.Bool](https://pkg.go.dev/sync/atomic#Bool).

Code after:
func defaultHTTPClient(hostURL *url.URL) (*http.Client, error) {
	transport := &http.Transport{}
	err := sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host)
	if err != nil {
		return nil, err
	}
	return &http.Client{
		Transport:     transport,
		CheckRedirect: CheckRedirect,
	}, nil
}

// Close the transport used by the client
func (cli *Client) Close() error {
	if cli.baseTransport != nil {
		cli.baseTransport.CloseIdleConnections()
		return nil
	}
	return nil
}

// checkVersion manually triggers API version negotiation (if configured).
// This allows for version-dependent code to use the same version as will
// be negotiated when making the actual requests, and for which cases
// we cannot do the negotiation lazily.
func (cli *Client) checkVersion(ctx context.Context) error {
	if !cli.manualOverride && cli.negotiateVersion && !cli.negotiated.Load() {
		// Ensure exclusive write access to version and negotiated fields
		cli.negotiateLock.Lock()
		defer cli.negotiateLock.Unlock()

		// May have been set during last execution of critical zone
		if cli.negotiated.Load() {
			return nil
		}

		ping, err := cli.Ping(ctx)
		if err != nil {
			return err
		}
		cli.negotiateAPIVersionPing(ping)
	}
	return nil
}

// getAPIPath returns the versioned request path to call the API.
// It appends the query parameters to the path if they are not empty.
func (cli *Client) getAPIPath(ctx context.Context, p string, query url.Values) string {
	var apiPath string
	_ = cli.checkVersion(ctx)
	if cli.version != "" {