Skip to content

Commit

Permalink
Merge branch 'users/askagarw/LinqSeializerFix' of https://github.com/…
Browse files Browse the repository at this point in the history
…Azure/azure-cosmos-dotnet-v3 into users/askagarw/LinqSeializerFix
  • Loading branch information
asketagarwal committed Feb 23, 2021
2 parents 4b50f90 + a1615a1 commit 5799acc
Show file tree
Hide file tree
Showing 26 changed files with 1,707 additions and 606 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@ public async ValueTask<bool> MoveNextAsync()
this.jsonSerializationFormatOptions),
successPage.RequestCharge,
successPage.ActivityId,
state),
state,
successPage.AdditionalHeaders),
Pagination.ChangeFeedNotModifiedPage notModifiedPage => ChangeFeedPage.CreateNotModifiedPage(
notModifiedPage.RequestCharge,
notModifiedPage.ActivityId,
state),
state,
notModifiedPage.AdditionalHeaders),
_ => throw new InvalidOperationException($"Unknown type: {innerChangeFeedPage.Page.GetType()}"),
};

Expand Down
24 changes: 19 additions & 5 deletions Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Microsoft.Azure.Cosmos.ChangeFeed
{
using System;
using System.Collections.Immutable;
using Microsoft.Azure.Cosmos.CosmosElements;

#if INTERNAL
Expand All @@ -22,13 +23,15 @@ private ChangeFeedPage(
bool notModified,
double requestCharge,
string activityId,
ChangeFeedCrossFeedRangeState state)
ChangeFeedCrossFeedRangeState state,
ImmutableDictionary<string, string> additionalHeaders)
{
this.Documents = documents ?? throw new ArgumentOutOfRangeException(nameof(documents));
this.NotModified = notModified;
this.RequestCharge = requestCharge < 0 ? throw new ArgumentOutOfRangeException(nameof(requestCharge)) : requestCharge;
this.ActivityId = activityId ?? throw new ArgumentNullException(nameof(activityId));
this.State = state;
this.AdditionalHeaders = additionalHeaders;
}

public CosmosArray Documents { get; }
Expand All @@ -41,14 +44,25 @@ private ChangeFeedPage(

public ChangeFeedCrossFeedRangeState State { get; }

public static ChangeFeedPage CreateNotModifiedPage(double requestCharge, string activityId, ChangeFeedCrossFeedRangeState state)
public ImmutableDictionary<string, string> AdditionalHeaders { get; }

public static ChangeFeedPage CreateNotModifiedPage(
double requestCharge,
string activityId,
ChangeFeedCrossFeedRangeState state,
ImmutableDictionary<string, string> additionalHeaders)
{
return new ChangeFeedPage(CosmosArray.Empty, notModified: true, requestCharge, activityId, state);
return new ChangeFeedPage(CosmosArray.Empty, notModified: true, requestCharge, activityId, state, additionalHeaders);
}

public static ChangeFeedPage CreatePageWithChanges(CosmosArray documents, double requestCharge, string activityId, ChangeFeedCrossFeedRangeState state)
public static ChangeFeedPage CreatePageWithChanges(
CosmosArray documents,
double requestCharge,
string activityId,
ChangeFeedCrossFeedRangeState state,
ImmutableDictionary<string, string> additionalHeaders)
{
return new ChangeFeedPage(documents, notModified: false, requestCharge, activityId, state);
return new ChangeFeedPage(documents, notModified: false, requestCharge, activityId, state, additionalHeaders);
}
}
}
190 changes: 190 additions & 0 deletions Microsoft.Azure.Cosmos/src/CosmosClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Microsoft.Azure.Cosmos
{
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading;
Expand Down Expand Up @@ -261,6 +262,144 @@ public CosmosClient(
clientOptions);
}

/// <summary>
/// Creates a new CosmosClient with the account endpoint URI string and TokenCredential.
/// In addition to that it initializes the client with containers provided i.e The SDK warms up the caches and
/// connections before the first call to the service is made. Use this to obtain lower latency while startup of your application.
/// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime
/// of the application which enables efficient connection management and performance. Please refer to the
/// <see href="https://docs.microsoft.com/azure/cosmos-db/performance-tips">performance guide</see>.
/// </summary>
/// <param name="accountEndpoint">The cosmos service endpoint to use</param>
/// <param name="authKeyOrResourceToken">The cosmos account key or resource token to use to create the client.</param>
/// <param name="containers">Containers to be initialized identified by it's database name and container name.</param>
/// <param name="cosmosClientOptions">(Optional) client options</param>
/// <param name="cancellationToken">(Optional) Cancellation Token</param>
/// <returns>
/// A CosmosClient object.
/// </returns>
/// <example>
/// The CosmosClient is created with the AccountEndpoint, AccountKey or ResourceToken and 2 containers in the account are initialized
/// <code language="c#">
/// <![CDATA[
/// using Microsoft.Azure.Cosmos;
/// List<(string, string)> containersToInitialize = new List<(string, string)>
/// { ("DatabaseName1", "ContainerName1"), ("DatabaseName2", "ContainerName2") };
///
/// CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync("account-endpoint-from-portal",
/// "account-key-from-portal",
/// containersToInitialize)
///
/// // Dispose cosmosClient at application exit
/// ]]>
/// </code>
/// </example>
public static async Task<CosmosClient> CreateAndInitializeAsync(string accountEndpoint,
string authKeyOrResourceToken,
IReadOnlyList<(string databaseId, string containerId)> containers,
CosmosClientOptions cosmosClientOptions = null,
CancellationToken cancellationToken = default)
{
if (containers == null)
{
throw new ArgumentNullException(nameof(containers));
}

CosmosClient cosmosClient = new CosmosClient(accountEndpoint,
authKeyOrResourceToken,
cosmosClientOptions);

await cosmosClient.InitializeContainersAsync(containers, cancellationToken);
return cosmosClient;
}

/// <summary>
/// Creates a new CosmosClient with the account endpoint URI string and TokenCredential.
/// In addition to that it initializes the client with containers provided i.e The SDK warms up the caches and
/// connections before the first call to the service is made. Use this to obtain lower latency while startup of your application.
/// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime
/// of the application which enables efficient connection management and performance. Please refer to the
/// <see href="https://docs.microsoft.com/azure/cosmos-db/performance-tips">performance guide</see>.
/// </summary>
/// <param name="connectionString">The connection string to the cosmos account. ex: https://mycosmosaccount.documents.azure.com:443/;AccountKey=SuperSecretKey; </param>
/// <param name="containers">Containers to be initialized identified by it's database name and container name.</param>
/// <param name="cosmosClientOptions">(Optional) client options</param>
/// <param name="cancellationToken">(Optional) Cancellation Token</param>
/// <returns>
/// A CosmosClient object.
/// </returns>
/// <example>
/// The CosmosClient is created with the ConnectionString and 2 containers in the account are initialized
/// <code language="c#">
/// <![CDATA[
/// using Microsoft.Azure.Cosmos;
/// List<(string, string)> containersToInitialize = new List<(string, string)>
/// { ("DatabaseName1", "ContainerName1"), ("DatabaseName2", "ContainerName2") };
///
/// CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync("connection-string-from-portal",
/// containersToInitialize)
///
/// // Dispose cosmosClient at application exit
/// ]]>
/// </code>
/// </example>
public static async Task<CosmosClient> CreateAndInitializeAsync(string connectionString,
IReadOnlyList<(string databaseId, string containerId)> containers,
CosmosClientOptions cosmosClientOptions = null,
CancellationToken cancellationToken = default)
{
if (containers == null)
{
throw new ArgumentNullException(nameof(containers));
}

CosmosClient cosmosClient = new CosmosClient(connectionString,
cosmosClientOptions);

await cosmosClient.InitializeContainersAsync(containers, cancellationToken);
return cosmosClient;
}

/// <summary>
/// Creates a new CosmosClient with the account endpoint URI string and TokenCredential.
/// In addition to that it initializes the client with containers provided i.e The SDK warms up the caches and
/// connections before the first call to the service is made. Use this to obtain lower latency while startup of your application.
/// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime
/// of the application which enables efficient connection management and performance. Please refer to the
/// <see href="https://docs.microsoft.com/azure/cosmos-db/performance-tips">performance guide</see>.
/// </summary>
/// <param name="accountEndpoint">The cosmos service endpoint to use.</param>
/// <param name="tokenCredential"><see cref="TokenCredential"/>The token to provide AAD token for authorization.</param>
/// <param name="containers">Containers to be initialized identified by it's database name and container name.</param>
/// <param name="cosmosClientOptions">(Optional) client options</param>
/// <param name="cancellationToken">(Optional) Cancellation Token</param>
/// <returns>
/// A CosmosClient object.
/// </returns>
#if PREVIEW
public
#else
internal
#endif
static async Task<CosmosClient> CreateAndInitializeAsync(string accountEndpoint,
TokenCredential tokenCredential,
IReadOnlyList<(string databaseId, string containerId)> containers,
CosmosClientOptions cosmosClientOptions = null,
CancellationToken cancellationToken = default)
{
if (containers == null)
{
throw new ArgumentNullException(nameof(containers));
}

CosmosClient cosmosClient = new CosmosClient(accountEndpoint,
tokenCredential,
cosmosClientOptions);

await cosmosClient.InitializeContainersAsync(containers, cancellationToken);
return cosmosClient;
}

/// <summary>
/// Used for unit testing only.
/// </summary>
Expand Down Expand Up @@ -1035,6 +1174,57 @@ private FeedIteratorInternal GetDatabaseQueryStreamIteratorHelper(
options: requestOptions);
}

private Task InitializeContainersAsync(IReadOnlyList<(string databaseId, string containerId)> containers,
CancellationToken cancellationToken)
{
try
{
List<Task> tasks = new List<Task>();
foreach ((string databaseId, string containerId) in containers)
{
tasks.Add(this.InitializeContainerAsync(databaseId, containerId, cancellationToken));
}

return Task.WhenAll(tasks);
}
catch
{
this.Dispose();
throw;
}
}

private async Task InitializeContainerAsync(string databaseId, string containerId, CancellationToken cancellationToken = default)
{
ContainerInternal container = (ContainerInternal)this.GetContainer(databaseId, containerId);
IReadOnlyList<FeedRange> feedRanges = await container.GetFeedRangesAsync(cancellationToken);
List<Task> tasks = new List<Task>();
foreach (FeedRange feedRange in feedRanges)
{
tasks.Add(CosmosClient.InitializeFeedRangeAsync(container, feedRange, cancellationToken));
}

await Task.WhenAll(tasks);
}

private static async Task InitializeFeedRangeAsync(ContainerInternal container, FeedRange feedRange, CancellationToken cancellationToken = default)
{
// Do a dummy querry for each Partition Key Range to warm up the caches and connections
string guidToCheck = Guid.NewGuid().ToString();
QueryDefinition queryDefinition = new QueryDefinition($"select * from c where c.id = '{guidToCheck}'");
using (FeedIterator feedIterator = container.GetItemQueryStreamIterator(feedRange,
queryDefinition,
continuationToken: null,
requestOptions: new QueryRequestOptions() { }))
{
while (feedIterator.HasMoreResults)
{
using ResponseMessage response = await feedIterator.ReadNextAsync(cancellationToken);
response.EnsureSuccessStatusCode();
}
}
}

/// <summary>
/// Dispose of cosmos client
/// </summary>
Expand Down
44 changes: 17 additions & 27 deletions Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public int GatewayModeMaxConnectionLimit
throw new ArgumentOutOfRangeException(nameof(value));
}

if (this.HttpClientFactory != null && value != ConnectionPolicy.Default.MaxConnectionLimit )
if (this.HttpClientFactory != null && value != ConnectionPolicy.Default.MaxConnectionLimit)
{
throw new ArgumentException($"{nameof(this.httpClientFactory)} can not be set along with {nameof(this.GatewayModeMaxConnectionLimit)}. This must be set on the HttpClientHandler.MaxConnectionsPerServer property.");
}
Expand Down Expand Up @@ -637,12 +637,13 @@ internal CosmosClientOptions Clone()
return cloneConfiguration;
}

internal ConnectionPolicy GetConnectionPolicy()
internal virtual ConnectionPolicy GetConnectionPolicy()
{
this.ValidateDirectTCPSettings();
this.ValidateLimitToEndpointSettings();
UserAgentContainer userAgent = this.BuildUserAgentContainer();

UserAgentContainer userAgent = new UserAgentContainer();
this.SetUserAgentFeatures(userAgent);

ConnectionPolicy connectionPolicy = new ConnectionPolicy()
{
MaxConnectionLimit = this.GatewayModeMaxConnectionLimit,
Expand Down Expand Up @@ -799,25 +800,7 @@ private void ValidateDirectTCPSettings()
}
}

internal UserAgentContainer BuildUserAgentContainer()
{
UserAgentContainer userAgent = new UserAgentContainer();
string features = this.GetUserAgentFeatures();

if (!string.IsNullOrEmpty(features))
{
userAgent.SetFeatures(features.ToString());
}

if (!string.IsNullOrEmpty(this.ApplicationName))
{
userAgent.Suffix = this.ApplicationName;
}

return userAgent;
}

private string GetUserAgentFeatures()
internal void SetUserAgentFeatures(UserAgentContainer userAgent)
{
CosmosClientOptionsFeatures features = CosmosClientOptionsFeatures.NoFeatures;
if (this.AllowBulkExecution)
Expand All @@ -830,12 +813,19 @@ private string GetUserAgentFeatures()
features |= CosmosClientOptionsFeatures.HttpClientFactory;
}

if (features == CosmosClientOptionsFeatures.NoFeatures)
if (features != CosmosClientOptionsFeatures.NoFeatures)
{
return null;
string featureString = Convert.ToString((int)features, 2).PadLeft(8, '0');
if (!string.IsNullOrEmpty(featureString))
{
userAgent.SetFeatures(featureString);
}
}

if (!string.IsNullOrEmpty(this.ApplicationName))
{
userAgent.Suffix = this.ApplicationName;
}

return Convert.ToString((int)features, 2).PadLeft(8, '0');
}

/// <summary>
Expand Down
Loading

0 comments on commit 5799acc

Please sign in to comment.