Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sdk/data/azcosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

### Other Changes

* Set the default HTTP client connect (dial) timeout to 5 seconds (down from the azcore default of 30 seconds) and the default request timeout to 65 seconds (the azcore default was unbounded). The 65-second timeout applies to the entire HTTP round-trip, including response-body streaming. Increased the default idle connection pool size to 1000 total / 1000 per host (up from the azcore defaults of 100 / 10) to better support high-concurrency workloads against the Cosmos gateway. Callers that supply a custom `Transport` via `azcore.ClientOptions` are unaffected. See [PR 26856](https://github.com/Azure/azure-sdk-for-go/pull/26856).

## 1.5.0-beta.6 (2026-05-15)

### Features Added
Expand Down
26 changes: 20 additions & 6 deletions sdk/data/azcosmos/cosmos_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,7 @@ func NewClientFromConnectionString(connectionString string, o *ClientOptions) (*
}

func newClient(authPolicy policy.Policy, gem *globalEndpointManager, options *ClientOptions) (*azcore.Client, error) {
if options == nil {
options = &ClientOptions{}
}
options = withDefaultTransport(options)
return azcore.NewClient(moduleName, serviceLibVersion,
azruntime.PipelineOptions{
AllowedHeaders: getAllowedHeaders(),
Expand All @@ -207,9 +205,7 @@ func newClient(authPolicy policy.Policy, gem *globalEndpointManager, options *Cl
}

func newInternalPipeline(authPolicy policy.Policy, options *ClientOptions) azruntime.Pipeline {
if options == nil {
options = &ClientOptions{}
}
options = withDefaultTransport(options)
return azruntime.NewPipeline(moduleName, serviceLibVersion,
azruntime.PipelineOptions{
AllowedHeaders: getAllowedHeaders(),
Expand All @@ -220,6 +216,24 @@ func newInternalPipeline(authPolicy policy.Policy, options *ClientOptions) azrun
&options.ClientOptions)
}

// withDefaultTransport returns a *ClientOptions whose Transport is set to the
// Cosmos default HTTP client when the caller has not supplied one. The
// returned value is always non-nil and is safe to mutate without affecting
// the caller's struct.
func withDefaultTransport(options *ClientOptions) *ClientOptions {
if options == nil {
return &ClientOptions{
ClientOptions: azcore.ClientOptions{Transport: defaultCosmosHTTPClient},
}
}
if options.Transport != nil {
return options
}
clone := *options
clone.ClientOptions.Transport = defaultCosmosHTTPClient
return &clone
Comment thread
tvaron3 marked this conversation as resolved.
}

func createScopeFromEndpoint(endpoint *url.URL) ([]string, error) {
return []string{fmt.Sprintf("%s://%s/.default", endpoint.Scheme, endpoint.Hostname())}, nil
}
Expand Down
63 changes: 63 additions & 0 deletions sdk/data/azcosmos/cosmos_default_http_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package azcosmos

import (
"crypto/tls"
"net"
"net/http"
"time"

"golang.org/x/net/http2"
)

const (
// defaultConnectTimeout is the default timeout for establishing a new TCP
// connection to the Cosmos service.
defaultConnectTimeout = 5 * time.Second

// defaultRequestTimeout is the default end-to-end timeout applied by the
// HTTP client to a single request, including connection setup, sending the
// request, and reading the entire response body. Callers that need a
// different bound can supply their own Transport via ClientOptions.
defaultRequestTimeout = 65 * time.Second
)

// defaultCosmosHTTPClient is the http.Client used by Cosmos clients when the
// caller does not provide a custom Transport via ClientOptions. It mirrors the
// azcore default transport but uses Cosmos-specific connect and request
// timeouts.
var defaultCosmosHTTPClient *http.Client

func init() {
defaultCosmosHTTPClient = newDefaultCosmosHTTPClient()
}

func newDefaultCosmosHTTPClient() *http.Client {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: defaultConnectTimeout,
KeepAlive: 30 * time.Second,
}).DialContext,
ForceAttemptHTTP2: true,
Comment thread
tvaron3 marked this conversation as resolved.
MaxIdleConns: 1000,
MaxIdleConnsPerHost: 1000,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
Renegotiation: tls.RenegotiateFreelyAsClient,
},
}
if http2Transport, err := http2.ConfigureTransports(transport); err == nil {
http2Transport.ReadIdleTimeout = 2 * time.Second
http2Transport.PingTimeout = 1 * time.Second
}
return &http.Client{
Transport: transport,
Timeout: defaultRequestTimeout,
}
}
56 changes: 56 additions & 0 deletions sdk/data/azcosmos/cosmos_default_http_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package azcosmos

import (
"net/http"
"testing"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/internal/mock"
"github.com/stretchr/testify/require"
)

func TestDefaultCosmosHTTPClient_Timeouts(t *testing.T) {
require.Equal(t, 5*time.Second, defaultConnectTimeout)
require.Equal(t, 65*time.Second, defaultRequestTimeout)

c := newDefaultCosmosHTTPClient()
require.NotNil(t, c)
require.Equal(t, defaultRequestTimeout, c.Timeout)

transport, ok := c.Transport.(*http.Transport)
require.True(t, ok, "expected *http.Transport, got %T", c.Transport)
require.NotNil(t, transport.DialContext)
}

func TestWithDefaultTransport_NilOptions(t *testing.T) {
got := withDefaultTransport(nil)
require.NotNil(t, got)
require.Same(t, defaultCosmosHTTPClient, got.Transport)
}

func TestWithDefaultTransport_NoTransportSet(t *testing.T) {
in := &ClientOptions{
EnableContentResponseOnWrite: true,
}
got := withDefaultTransport(in)
require.NotSame(t, in, got, "expected withDefaultTransport to clone options")
require.Nil(t, in.Transport, "caller-supplied options must not be mutated")
require.Same(t, defaultCosmosHTTPClient, got.Transport)
require.True(t, got.EnableContentResponseOnWrite)
}

func TestWithDefaultTransport_PreservesCallerTransport(t *testing.T) {
srv, close := mock.NewServer()
defer close()

in := &ClientOptions{
ClientOptions: azcore.ClientOptions{Transport: srv},
}
got := withDefaultTransport(in)
require.Same(t, in, got, "expected withDefaultTransport to return same options when Transport is provided")
require.Same(t, srv, got.Transport)
}
Loading