diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/ComparableTask/ComparableTaskScheduler.cs b/Microsoft.Azure.Cosmos/src/Query/Core/ComparableTask/ComparableTaskScheduler.cs index 6819f231b7..e2b2098139 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/ComparableTask/ComparableTaskScheduler.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/ComparableTask/ComparableTaskScheduler.cs @@ -45,29 +45,11 @@ public ComparableTaskScheduler(IEnumerable tasks, int maximumCo public int MaximumConcurrencyLevel { get; private set; } - public int CurrentRunningTaskCount - { - get - { - return this.MaximumConcurrencyLevel - Math.Max(0, this.canRunTaskSemaphoreSlim.CurrentCount); - } - } + public int CurrentRunningTaskCount => this.MaximumConcurrencyLevel - Math.Max(0, this.canRunTaskSemaphoreSlim.CurrentCount); - public bool IsStopped - { - get - { - return this.isStopped; - } - } + public bool IsStopped => this.isStopped; - private CancellationToken CancellationToken - { - get - { - return this.tokenSource.Token; - } - } + private CancellationToken CancellationToken => this.tokenSource.Token; public void IncreaseMaximumConcurrencyLevel(int delta) { @@ -83,6 +65,9 @@ public void IncreaseMaximumConcurrencyLevel(int delta) public void Dispose() { this.Stop(); + + this.canRunTaskSemaphoreSlim.Dispose(); + this.tokenSource.Dispose(); } public void Stop() diff --git a/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryIterator.cs b/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryIterator.cs index 02e80060fc..d79008c7a2 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryIterator.cs +++ b/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryIterator.cs @@ -200,5 +200,11 @@ public override CosmosElement GetCosmosElementContinuationToken() { return this.cosmosQueryExecutionContext.GetCosmosElementContinuationToken(); } + + protected override void Dispose(bool disposing) + { + this.cosmosQueryExecutionContext.Dispose(); + base.Dispose(disposing); + } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIterator.cs b/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIterator.cs index 8063a76463..cc053804ed 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIterator.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIterator.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.Cosmos { + using System; using System.Threading; using System.Threading.Tasks; @@ -33,8 +34,10 @@ namespace Microsoft.Azure.Cosmos /// ]]> /// /// - public abstract class FeedIterator + public abstract class FeedIterator : IDisposable { + private bool disposedValue; + /// /// Tells if there is more results that need to be retrieved from the service /// @@ -92,5 +95,29 @@ public abstract class FeedIterator /// /// public abstract Task ReadNextAsync(CancellationToken cancellationToken = default); + + /// + /// Releases the unmanaged resources used by the FeedIterator and optionally + /// releases the managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + // Default implementation does not need to clean anything up + if (!this.disposedValue) + { + this.disposedValue = true; + } + } + + /// + /// Releases the unmanaged resources used by the FeedIterator and optionally + /// releases the managed resources. + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + this.Dispose(disposing: true); + } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIteratorCore.cs index 89c8cc25dc..2af94128b7 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIteratorCore.cs @@ -156,5 +156,11 @@ public override async Task> ReadNextAsync(CancellationToken canc ResponseMessage response = await this.feedIterator.ReadNextAsync(cancellationToken); return this.responseCreator(response); } + + protected override void Dispose(bool disposing) + { + this.feedIterator.Dispose(); + base.Dispose(disposing); + } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIteratorInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIteratorInlineCore.cs new file mode 100644 index 0000000000..6e8818ffbd --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIteratorInlineCore.cs @@ -0,0 +1,51 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.CosmosElements; + + internal sealed class FeedIteratorInlineCore : FeedIteratorInternal + { + private readonly FeedIteratorInternal feedIteratorInternal; + + internal FeedIteratorInlineCore( + FeedIterator feedIterator) + { + if (!(feedIterator is FeedIteratorInternal feedIteratorInternal)) + { + throw new ArgumentNullException(nameof(feedIterator)); + } + + this.feedIteratorInternal = feedIteratorInternal; + } + + internal FeedIteratorInlineCore( + FeedIteratorInternal feedIteratorInternal) + { + this.feedIteratorInternal = feedIteratorInternal ?? throw new ArgumentNullException(nameof(feedIteratorInternal)); + } + + public override bool HasMoreResults => this.feedIteratorInternal.HasMoreResults; + + public override CosmosElement GetCosmosElementContinuationToken() + { + return this.feedIteratorInternal.GetCosmosElementContinuationToken(); + } + + public override Task ReadNextAsync(CancellationToken cancellationToken = default) + { + return TaskHelper.RunInlineIfNeededAsync(() => this.feedIteratorInternal.ReadNextAsync(cancellationToken)); + } + + protected override void Dispose(bool disposing) + { + this.feedIteratorInternal.Dispose(); + base.Dispose(disposing); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Query/v3Query/FeedIteratorInlineCore.cs b/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIteratorInlineCore{T}.cs similarity index 50% rename from Microsoft.Azure.Cosmos/src/Query/v3Query/FeedIteratorInlineCore.cs rename to Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIteratorInlineCore{T}.cs index 1a6795d88d..30a5c494b9 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v3Query/FeedIteratorInlineCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIteratorInlineCore{T}.cs @@ -8,43 +8,6 @@ namespace Microsoft.Azure.Cosmos using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.CosmosElements; - using Microsoft.Azure.Cosmos.Json; - - internal sealed class FeedIteratorInlineCore : FeedIteratorInternal - { - private readonly FeedIteratorInternal feedIteratorInternal; - - internal FeedIteratorInlineCore( - FeedIterator feedIterator) - { - if (feedIterator is FeedIteratorInternal feedIteratorInternal) - { - this.feedIteratorInternal = feedIteratorInternal; - } - else - { - throw new ArgumentNullException(nameof(feedIterator)); - } - } - - internal FeedIteratorInlineCore( - FeedIteratorInternal feedIteratorInternal) - { - this.feedIteratorInternal = feedIteratorInternal ?? throw new ArgumentNullException(nameof(feedIteratorInternal)); - } - - public override bool HasMoreResults => this.feedIteratorInternal.HasMoreResults; - - public override CosmosElement GetCosmosElementContinuationToken() - { - return this.feedIteratorInternal.GetCosmosElementContinuationToken(); - } - - public override Task ReadNextAsync(CancellationToken cancellationToken = default) - { - return TaskHelper.RunInlineIfNeededAsync(() => this.feedIteratorInternal.ReadNextAsync(cancellationToken)); - } - } internal sealed class FeedIteratorInlineCore : FeedIteratorInternal { @@ -53,14 +16,12 @@ internal sealed class FeedIteratorInlineCore : FeedIteratorInternal internal FeedIteratorInlineCore( FeedIterator feedIterator) { - if (feedIterator is FeedIteratorInternal feedIteratorInternal) - { - this.feedIteratorInternal = feedIteratorInternal; - } - else + if (!(feedIterator is FeedIteratorInternal feedIteratorInternal)) { throw new ArgumentNullException(nameof(feedIterator)); } + + this.feedIteratorInternal = feedIteratorInternal; } internal FeedIteratorInlineCore( @@ -80,5 +41,11 @@ public override CosmosElement GetCosmosElementContinuationToken() { return this.feedIteratorInternal.GetCosmosElementContinuationToken(); } + + protected override void Dispose(bool disposing) + { + this.feedIteratorInternal.Dispose(); + base.Dispose(disposing); + } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIterator{T}.cs b/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIterator{T}.cs index 6813abacd1..dc3d9518d8 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIterator{T}.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FeedIterators/FeedIterator{T}.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.Cosmos { + using System; using System.Threading; using System.Threading.Tasks; @@ -29,8 +30,10 @@ namespace Microsoft.Azure.Cosmos /// ]]> /// /// - public abstract class FeedIterator + public abstract class FeedIterator : IDisposable { + private bool disposedValue; + /// /// Tells if there is more results that need to be retrieved from the service /// @@ -80,5 +83,29 @@ public abstract class FeedIterator /// /// public abstract Task> ReadNextAsync(CancellationToken cancellationToken = default); + + /// + /// Releases the unmanaged resources used by the FeedIterator and optionally + /// releases the managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + // Default implementation does not need to clean anything up + if (!this.disposedValue) + { + this.disposedValue = true; + } + } + + /// + /// Releases the unmanaged resources used by the FeedIterator and optionally + /// releases the managed resources. + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + this.Dispose(disposing: true); + } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosBasicQueryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosBasicQueryTests.cs index b307d2dc34..c14aedc56a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosBasicQueryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosBasicQueryTests.cs @@ -230,21 +230,22 @@ public async Task QueryRequestRateTest(bool directMode) List results = new List(); try { - FeedIterator feedIterator = containerWithThrottle.GetItemQueryIterator( + using (FeedIterator feedIterator = containerWithThrottle.GetItemQueryIterator( "select * from T where STARTSWITH(T.id, \"BasicQueryItem\")", requestOptions: new QueryRequestOptions() { MaxItemCount = 1, MaxConcurrency = 1 - }); - - while (feedIterator.HasMoreResults) + })) { - FeedResponse response = await feedIterator.ReadNextAsync(); - Assert.IsTrue(response.Count <= 1); - Assert.IsTrue(response.Resource.Count() <= 1); + while (feedIterator.HasMoreResults) + { + FeedResponse response = await feedIterator.ReadNextAsync(); + Assert.IsTrue(response.Count <= 1); + Assert.IsTrue(response.Resource.Count() <= 1); - results.AddRange(response); + results.AddRange(response); + } } Assert.Fail("Should throw 429 exception after the first page."); } @@ -365,22 +366,23 @@ public async Task ItemTest(bool directMode) Assert.AreEqual(1, results.Count); // LINQ to feed iterator Read All with partition key - FeedIterator iterator = container.GetItemLinqQueryable( + using (FeedIterator iterator = container.GetItemLinqQueryable( allowSynchronousQueryExecution: true, requestOptions: new QueryRequestOptions() { MaxItemCount = 1, PartitionKey = new PartitionKey("BasicQueryItem") - }).ToFeedIterator(); - - List linqResults = new List(); - while (iterator.HasMoreResults) + }).ToFeedIterator()) { - linqResults.AddRange(await iterator.ReadNextAsync()); - } + List linqResults = new List(); + while (iterator.HasMoreResults) + { + linqResults.AddRange(await iterator.ReadNextAsync()); + } - Assert.AreEqual(1, linqResults.Count); - Assert.AreEqual("BasicQueryItem", linqResults.First().pk.ToString()); + Assert.AreEqual(1, linqResults.Count); + Assert.AreEqual("BasicQueryItem", linqResults.First().pk.ToString()); + } } [TestMethod] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs index 32978452ec..96db1c6e96 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs @@ -16,7 +16,6 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Json; - using Microsoft.Azure.Cosmos.Query.Core; using Microsoft.Azure.Cosmos.Query.Core.ExecutionContext; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.Routing; @@ -610,22 +609,21 @@ public async Task ItemCustomSerialzierTest() toStreamCount = 0; fromStreamCount = 0; - FeedIterator feedIterator = containerSerializer.GetItemQueryIterator( - queryDefinition: queryDefinition); - List allItems = new List(); - int pageCount = 0; - while (feedIterator.HasMoreResults) + using (FeedIterator feedIterator = containerSerializer.GetItemQueryIterator( + queryDefinition: queryDefinition)) { - // Only need once to verify correct serialization of the query definition - FeedResponse response = await feedIterator.ReadNextAsync(this.cancellationToken); - Assert.AreEqual(response.Count, response.Count()); - allItems.AddRange(response); - pageCount++; + while (feedIterator.HasMoreResults) + { + // Only need once to verify correct serialization of the query definition + FeedResponse response = await feedIterator.ReadNextAsync(this.cancellationToken); + Assert.AreEqual(response.Count, response.Count()); + allItems.AddRange(response); + pageCount++; + } } - Assert.AreEqual(2, allItems.Count, $"missing query results. Only found: {allItems.Count} items for query:{queryDefinition.ToSqlQuerySpec().QueryText}"); foreach (dynamic item in allItems) { @@ -667,7 +665,7 @@ public async Task PerfItemIterator() { IList deleteList = await ToDoActivity.CreateRandomItems(this.Container, 2000, randomPartitionKey: true); HashSet itemIds = deleteList.Select(x => x.id).ToHashSet(); - + FeedIterator feedIterator = this.Container.GetItemQueryIterator(); while (feedIterator.HasMoreResults) @@ -1069,12 +1067,14 @@ public async Task EpkPointReadTest() Properties = properties, }; - FeedIterator resultSet = this.Container.GetItemQueryIterator( + using (FeedIterator resultSet = this.Container.GetItemQueryIterator( queryText: "SELECT * FROM root", - requestOptions: queryRequestOptions); - FeedResponse feedresponse = await resultSet.ReadNextAsync(); - Assert.IsNotNull(feedresponse.Resource); - Assert.AreEqual(1, feedresponse.Count()); + requestOptions: queryRequestOptions)) + { + FeedResponse feedresponse = await resultSet.ReadNextAsync(); + Assert.IsNotNull(feedresponse.Resource); + Assert.AreEqual(1, feedresponse.Count()); + } } @@ -1255,11 +1255,12 @@ public async Task NegativeQueryTest() try { - FeedIterator resultSet = this.Container.GetItemQueryIterator( + using (FeedIterator resultSet = this.Container.GetItemQueryIterator( queryText: "SELECT r.id FROM root r WHERE r._ts > 0", - requestOptions: new QueryRequestOptions() { ResponseContinuationTokenLimitInKb = 0, MaxItemCount = 10, MaxConcurrency = 1 }); - - await resultSet.ReadNextAsync(); + requestOptions: new QueryRequestOptions() { ResponseContinuationTokenLimitInKb = 0, MaxItemCount = 10, MaxConcurrency = 1 })) + { + await resultSet.ReadNextAsync(); + } Assert.Fail("Expected query to fail"); } catch (CosmosException exception) when (exception.StatusCode == HttpStatusCode.BadRequest) @@ -1269,11 +1270,12 @@ public async Task NegativeQueryTest() try { - FeedIterator resultSet = this.Container.GetItemQueryIterator( + using (FeedIterator resultSet = this.Container.GetItemQueryIterator( queryText: "SELECT r.id FROM root r WHERE r._ts >!= 0", - requestOptions: new QueryRequestOptions() { MaxConcurrency = 1 }); - - await resultSet.ReadNextAsync(); + requestOptions: new QueryRequestOptions() { MaxConcurrency = 1 })) + { + await resultSet.ReadNextAsync(); + } Assert.Fail("Expected query to fail"); } catch (CosmosException exception) when (exception.StatusCode == HttpStatusCode.BadRequest) @@ -1423,41 +1425,49 @@ public async Task ReadNonPartitionItemAsync() //Quering items on fixed container with cross partition enabled. QueryDefinition sql = new QueryDefinition("select * from r"); - FeedIterator feedIterator = fixedContainer.GetItemQueryIterator( + using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator( sql, - requestOptions: new QueryRequestOptions() { MaxConcurrency = 1, MaxItemCount = 10 }); - while (feedIterator.HasMoreResults) + requestOptions: new QueryRequestOptions() { MaxConcurrency = 1, MaxItemCount = 10 })) { - FeedResponse queryResponse = await feedIterator.ReadNextAsync(); - Assert.AreEqual(3, queryResponse.Count()); + while (feedIterator.HasMoreResults) + { + FeedResponse queryResponse = await feedIterator.ReadNextAsync(); + Assert.AreEqual(3, queryResponse.Count()); + } } //Reading all items on fixed container. - feedIterator = fixedContainer.GetItemQueryIterator(requestOptions: new QueryRequestOptions() { MaxItemCount = 10 }); - while (feedIterator.HasMoreResults) + using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator(requestOptions: new QueryRequestOptions() { MaxItemCount = 10 })) { - FeedResponse queryResponse = await feedIterator.ReadNextAsync(); - Assert.AreEqual(3, queryResponse.Count()); + while (feedIterator.HasMoreResults) + { + FeedResponse queryResponse = await feedIterator.ReadNextAsync(); + Assert.AreEqual(3, queryResponse.Count()); + } } //Quering items on fixed container with CosmosContainerSettings.NonePartitionKeyValue. - feedIterator = fixedContainer.GetItemQueryIterator( + using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator( new QueryDefinition("select * from r"), - requestOptions: new QueryRequestOptions() { MaxItemCount = 10, PartitionKey = Cosmos.PartitionKey.None, }); - while (feedIterator.HasMoreResults) + requestOptions: new QueryRequestOptions() { MaxItemCount = 10, PartitionKey = Cosmos.PartitionKey.None, })) { - FeedResponse queryResponse = await feedIterator.ReadNextAsync(); - Assert.AreEqual(2, queryResponse.Count()); + while (feedIterator.HasMoreResults) + { + FeedResponse queryResponse = await feedIterator.ReadNextAsync(); + Assert.AreEqual(2, queryResponse.Count()); + } } //Quering items on fixed container with non-none PK. - feedIterator = fixedContainer.GetItemQueryIterator( + using (FeedIterator feedIterator = fixedContainer.GetItemQueryIterator( sql, - requestOptions: new QueryRequestOptions() { MaxItemCount = 10, PartitionKey = new Cosmos.PartitionKey(itemWithPK.status) }); - while (feedIterator.HasMoreResults) + requestOptions: new QueryRequestOptions() { MaxItemCount = 10, PartitionKey = new Cosmos.PartitionKey(itemWithPK.status) })) { - FeedResponse queryResponse = await feedIterator.ReadNextAsync(); - Assert.AreEqual(1, queryResponse.Count()); + while (feedIterator.HasMoreResults) + { + FeedResponse queryResponse = await feedIterator.ReadNextAsync(); + Assert.AreEqual(1, queryResponse.Count()); + } } //Deleting item from fixed container with CosmosContainerSettings.NonePartitionKeyValue. @@ -1825,8 +1835,10 @@ public async Task CustomPropertiesItemRequestOptionsTest() { customHeaderName, customHeaderValue}, }; - ItemRequestOptions ro = new ItemRequestOptions(); - ro.Properties = properties; + ItemRequestOptions ro = new ItemRequestOptions + { + Properties = properties + }; ItemResponse responseAstype = await container.CreateItemAsync( partitionKey: new Cosmos.PartitionKey(temp.status), diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosPermissionTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosPermissionTests.cs index f059111b57..e8e2dfd842 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosPermissionTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosPermissionTests.cs @@ -341,15 +341,16 @@ public async Task ItemResourcePermissionTest() //delete resource with PermissionMode.All using (CosmosClient tokenCosmosClient = TestCommon.CreateCosmosClient(clientOptions: null, resourceToken: permission.Token)) { - FeedIterator feed = tokenCosmosClient + using (FeedIterator feed = tokenCosmosClient .GetDatabase(this.cosmosDatabase.Id) .GetContainer(containerId) - .GetItemQueryIterator(new QueryDefinition("select * from t")); - - while (feed.HasMoreResults) + .GetItemQueryIterator(new QueryDefinition("select * from t"))) { - FeedResponse response = await feed.ReadNextAsync(); - Assert.IsNotNull(response); + while (feed.HasMoreResults) + { + FeedResponse response = await feed.ReadNextAsync(); + Assert.IsNotNull(response); + } } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs index e55c195fb2..9bdedf3812 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/NameRoutingTests.cs @@ -164,16 +164,18 @@ private async Task SmokeTestForNameAPI(CosmosClient client) { //swallow } - + // read databaseCollection feed. - FeedIterator itemIterator = container.GetItemQueryIterator(queryDefinition: null); - int count = 0; - while (itemIterator.HasMoreResults) + using (FeedIterator itemIterator = container.GetItemQueryIterator(queryDefinition: null)) { - FeedResponse items = await itemIterator.ReadNextAsync(); - count += items.Count(); + int count = 0; + while (itemIterator.HasMoreResults) + { + FeedResponse items = await itemIterator.ReadNextAsync(); + count += items.Count(); + } + Assert.AreEqual(3, count); } - Assert.AreEqual(3, count); // query documents { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/PartitioningQueryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/PartitioningQueryTests.cs index 0e745bb618..b02e75e147 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/PartitioningQueryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/PartitioningQueryTests.cs @@ -137,19 +137,21 @@ await this.CreateIngestQueryDeleteAsync( async Task ImplementationAsync(Container container, IReadOnlyList documents) { // Query with partition key should be done in one round trip. - FeedIterator resultSetIterator = container.GetItemQueryIterator( - "SELECT * FROM c WHERE c.pk = 'doc5'"); - - FeedResponse response = await resultSetIterator.ReadNextAsync(); - Assert.AreEqual(1, response.Count()); - Assert.IsNull(response.ContinuationToken); - - resultSetIterator = container.GetItemQueryIterator( - "SELECT * FROM c WHERE c.pk = 'doc10'"); + using (FeedIterator resultSetIterator = container.GetItemQueryIterator( + "SELECT * FROM c WHERE c.pk = 'doc5'")) + { + FeedResponse response = await resultSetIterator.ReadNextAsync(); + Assert.AreEqual(1, response.Count()); + Assert.IsNull(response.ContinuationToken); + } - response = await resultSetIterator.ReadNextAsync(); - Assert.AreEqual(0, response.Count()); - Assert.IsNull(response.ContinuationToken); + using (FeedIterator resultSetIterator = container.GetItemQueryIterator( + "SELECT * FROM c WHERE c.pk = 'doc10'")) + { + FeedResponse response = await resultSetIterator.ReadNextAsync(); + Assert.AreEqual(0, response.Count()); + Assert.IsNull(response.ContinuationToken); + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/QueryTestsBase.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/QueryTestsBase.cs index 3d7c706612..aea6218df7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/QueryTestsBase.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/QueryTestsBase.cs @@ -16,10 +16,10 @@ namespace Microsoft.Azure.Cosmos.EmulatorTests.Query using System.Threading.Tasks; using Microsoft.Azure.Cosmos.CosmosElements; using Microsoft.Azure.Cosmos.Routing; + using Microsoft.Azure.Cosmos.SDK.EmulatorTests; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Routing; using Microsoft.VisualStudio.TestTools.UnitTesting; - using Microsoft.Azure.Cosmos.SDK.EmulatorTests; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -309,15 +309,16 @@ private async Task CreatePartitionedContainer( private static async Task CleanUp(CosmosClient client) { - FeedIterator allDatabases = client.GetDatabaseQueryIterator(); - - while (allDatabases.HasMoreResults) + using (FeedIterator allDatabases = client.GetDatabaseQueryIterator()) { - foreach (DatabaseProperties db in await allDatabases.ReadNextAsync()) + while (allDatabases.HasMoreResults) { - await client.GetDatabase(db.Id).DeleteAsync(); + foreach (DatabaseProperties db in await allDatabases.ReadNextAsync()) + { + await client.GetDatabase(db.Id).DeleteAsync(); + } } - } + } } internal async Task RunWithApiVersion(string apiVersion, Func function) @@ -513,13 +514,16 @@ internal async Task CreateIngestQueryDeleteAsync( } catch (Exception ex) when (ex.GetType() != typeof(AssertFailedException)) { - while (ex.InnerException != null) ex = ex.InnerException; + while (ex.InnerException != null) + { + ex = ex.InnerException; + } ExceptionDispatchInfo.Capture(ex).Throw(); } } - static ConnectionMode GetTargetConnectionMode(ConnectionModes connectionMode) + private static ConnectionMode GetTargetConnectionMode(ConnectionModes connectionMode) { ConnectionMode targetConnectionMode; switch (connectionMode) @@ -583,40 +587,39 @@ internal static async Task> QueryWithCosmosElementContinuationTokenAsync computeRequestOptions.ExecutionEnvironment = Cosmos.Query.Core.ExecutionContext.ExecutionEnvironment.Compute; computeRequestOptions.CosmosElementContinuationToken = continuationToken; - FeedIteratorInternal itemQuery = (FeedIteratorInternal)container.GetItemQueryIterator( + using (FeedIteratorInternal itemQuery = (FeedIteratorInternal)container.GetItemQueryIterator( queryText: query, - requestOptions: computeRequestOptions); - try + requestOptions: computeRequestOptions)) { - FeedResponse cosmosQueryResponse = await itemQuery.ReadNextAsync(); - if (queryRequestOptions.MaxItemCount.HasValue) + try { - Assert.IsTrue( - cosmosQueryResponse.Count <= queryRequestOptions.MaxItemCount.Value, - "Max Item Count is not being honored"); - } + FeedResponse cosmosQueryResponse = await itemQuery.ReadNextAsync(); + if (queryRequestOptions.MaxItemCount.HasValue) + { + Assert.IsTrue( + cosmosQueryResponse.Count <= queryRequestOptions.MaxItemCount.Value, + "Max Item Count is not being honored"); + } - resultsFromCosmosElementContinuationToken.AddRange(cosmosQueryResponse); + resultsFromCosmosElementContinuationToken.AddRange(cosmosQueryResponse); - // Force a rewrite of the continuation token, so that we test the case where we roundtrip it over the wire. - // There was a bug where resuming from double.NaN lead to an exception, - // since we parsed the type assuming it was always a double and not a string. - CosmosElement originalContinuationToken = itemQuery.GetCosmosElementContinuationToken(); - if(originalContinuationToken != null) - { - continuationToken = CosmosElement.Parse(originalContinuationToken.ToString()); + // Force a rewrite of the continuation token, so that we test the case where we roundtrip it over the wire. + // There was a bug where resuming from double.NaN lead to an exception, + // since we parsed the type assuming it was always a double and not a string. + CosmosElement originalContinuationToken = itemQuery.GetCosmosElementContinuationToken(); + if (originalContinuationToken != null) + { + continuationToken = CosmosElement.Parse(originalContinuationToken.ToString()); + } + else + { + continuationToken = null; + } } - else + catch (CosmosException cosmosException) when (cosmosException.StatusCode == (HttpStatusCode)429) { - continuationToken = null; } } - catch (CosmosException cosmosException) when (cosmosException.StatusCode == (HttpStatusCode)429) - { - itemQuery = (FeedIteratorInternal)container.GetItemQueryIterator( - queryText: query, - requestOptions: queryRequestOptions); - } } while (continuationToken != null); return resultsFromCosmosElementContinuationToken; @@ -641,30 +644,38 @@ internal static async Task> QueryWithContinuationTokensAsync( requestOptions: queryRequestOptions, continuationToken: continuationToken); - while (true) + try { - try + while (true) { - FeedResponse cosmosQueryResponse = await itemQuery.ReadNextAsync(); - if (queryRequestOptions.MaxItemCount.HasValue) + try { - Assert.IsTrue( - cosmosQueryResponse.Count <= queryRequestOptions.MaxItemCount.Value, - "Max Item Count is not being honored"); - } + FeedResponse cosmosQueryResponse = await itemQuery.ReadNextAsync(); + if (queryRequestOptions.MaxItemCount.HasValue) + { + Assert.IsTrue( + cosmosQueryResponse.Count <= queryRequestOptions.MaxItemCount.Value, + "Max Item Count is not being honored"); + } - resultsFromContinuationToken.AddRange(cosmosQueryResponse); - continuationToken = cosmosQueryResponse.ContinuationToken; - break; - } - catch (CosmosException cosmosException) when (cosmosException.StatusCode == (HttpStatusCode)429) - { - itemQuery = container.GetItemQueryIterator( - queryText: query, - requestOptions: queryRequestOptions, - continuationToken: continuationToken); + resultsFromContinuationToken.AddRange(cosmosQueryResponse); + continuationToken = cosmosQueryResponse.ContinuationToken; + break; + } + catch (CosmosException cosmosException) when (cosmosException.StatusCode == (HttpStatusCode)429) + { + itemQuery.Dispose(); + itemQuery = container.GetItemQueryIterator( + queryText: query, + requestOptions: queryRequestOptions, + continuationToken: continuationToken); + } } } + finally + { + itemQuery.Dispose(); + } } while (continuationToken != null); return resultsFromContinuationToken; @@ -684,45 +695,52 @@ internal static async Task> QueryWithoutContinuationTokensAsync( FeedIterator itemQuery = container.GetItemQueryIterator( queryText: query, requestOptions: queryRequestOptions); - - string continuationTokenForRetries = null; - while (itemQuery.HasMoreResults) + try { - try + string continuationTokenForRetries = null; + while (itemQuery.HasMoreResults) { - FeedResponse page = await itemQuery.ReadNextAsync(); - results.AddRange(page); - - if (queryRequestOptions.MaxItemCount.HasValue) - { - Assert.IsTrue( - page.Count <= queryRequestOptions.MaxItemCount.Value, - "Max Item Count is not being honored"); - } - try { - continuationTokenForRetries = page.ContinuationToken; + FeedResponse page = await itemQuery.ReadNextAsync(); + results.AddRange(page); + + if (queryRequestOptions.MaxItemCount.HasValue) + { + Assert.IsTrue( + page.Count <= queryRequestOptions.MaxItemCount.Value, + "Max Item Count is not being honored"); + } + + try + { + continuationTokenForRetries = page.ContinuationToken; + } + catch (Exception) + { + // Grabbing a continuation token is not supported on all queries. + } } - catch (Exception) + catch (CosmosException cosmosException) when (cosmosException.StatusCode == (HttpStatusCode)429) { - // Grabbing a continuation token is not supported on all queries. - } - } - catch (CosmosException cosmosException) when (cosmosException.StatusCode == (HttpStatusCode)429) - { - itemQuery = container.GetItemQueryIterator( - queryText: query, - requestOptions: queryRequestOptions, - continuationToken: continuationTokenForRetries); + itemQuery.Dispose(); + itemQuery = container.GetItemQueryIterator( + queryText: query, + requestOptions: queryRequestOptions, + continuationToken: continuationTokenForRetries); - if (continuationTokenForRetries == null) - { - // The query failed and we don't have a save point, so just restart the whole thing. - results = new List(); + if (continuationTokenForRetries == null) + { + // The query failed and we don't have a save point, so just restart the whole thing. + results = new List(); + } } } } + finally + { + itemQuery.Dispose(); + } return results; } @@ -821,14 +839,15 @@ internal async Task> RunSinglePartitionQuery( string query, QueryRequestOptions requestOptions = null) { - FeedIterator resultSetIterator = container.GetItemQueryIterator( - query, - requestOptions: requestOptions); - List items = new List(); - while (resultSetIterator.HasMoreResults) + using (FeedIterator resultSetIterator = container.GetItemQueryIterator( + query, + requestOptions: requestOptions)) { - items.AddRange(await resultSetIterator.ReadNextAsync()); + while (resultSetIterator.HasMoreResults) + { + items.AddRange(await resultSetIterator.ReadNextAsync()); + } } return items; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/SanityQueryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/SanityQueryTests.cs index dff041a21c..d4b9fe1f93 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/SanityQueryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/SanityQueryTests.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos.EmulatorTests.Query using System.Collections.Generic; using System.Linq; using System.Net; + using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.CosmosElements; using Microsoft.Azure.Cosmos.CosmosElements.Numbers; @@ -64,6 +65,115 @@ async Task ImplementationAsync(Container container, IReadOnlyList } } + [TestMethod] + public async Task MemoryLeak() + { + int seed = (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; + uint numberOfDocuments = 100; + QueryOracleUtil util = new QueryOracle2(seed); + IEnumerable inputDocuments = util.GetDocuments(numberOfDocuments); + + await this.CreateIngestQueryDeleteAsync( + ConnectionModes.Direct, + CollectionTypes.MultiPartition, + inputDocuments, + ImplementationAsync); + + async Task ImplementationAsync(Container container, IReadOnlyList documents) + { + List weakReferences = await CreateWeakReferenceToFeedIterator(container); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + foreach(WeakReference weakReference in weakReferences) + { + Assert.IsFalse(weakReference.IsAlive); + } + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task> CreateWeakReferenceToFeedIterator( + Container container) + { + List weakReferences = new List(); + + // Test draining typed iterator + using (FeedIterator feedIterator = container.GetItemQueryIterator( + queryDefinition: null, + continuationToken: null, + requestOptions: new QueryRequestOptions + { + MaxItemCount = 1000, + })) + { + weakReferences.Add(new WeakReference(feedIterator, true)); + while (feedIterator.HasMoreResults) + { + FeedResponse response = await feedIterator.ReadNextAsync(); + foreach (JObject jObject in response) + { + Assert.IsNotNull(jObject); + } + } + } + + // Test draining stream iterator + using (FeedIterator feedIterator = container.GetItemQueryStreamIterator( + queryDefinition: null, + continuationToken: null, + requestOptions: new QueryRequestOptions + { + MaxItemCount = 1000, + })) + { + weakReferences.Add(new WeakReference(feedIterator, true)); + while (feedIterator.HasMoreResults) + { + using (ResponseMessage response = await feedIterator.ReadNextAsync()) + { + Assert.IsNotNull(response.Content); + } + } + } + + // Test single page typed iterator + using (FeedIterator feedIterator = container.GetItemQueryIterator( + queryText: "SELECT * FROM c", + continuationToken: null, + requestOptions: new QueryRequestOptions + { + MaxItemCount = 10, + })) + { + weakReferences.Add(new WeakReference(feedIterator, true)); + FeedResponse response = await feedIterator.ReadNextAsync(); + foreach (JObject jObject in response) + { + Assert.IsNotNull(jObject); + } + } + + // Test single page stream iterator + using (FeedIterator feedIterator = container.GetItemQueryStreamIterator( + queryText: "SELECT * FROM c", + continuationToken: null, + requestOptions: new QueryRequestOptions + { + MaxItemCount = 10, + })) + { + weakReferences.Add(new WeakReference(feedIterator, true)); + using (ResponseMessage response = await feedIterator.ReadNextAsync()) + { + Assert.IsNotNull(response.Content); + } + } + + return weakReferences; + } + [TestMethod] public async Task TestNonDeterministicQueryResultsAsync() { @@ -469,11 +579,12 @@ private async Task TestMalformedPipelinedContinuationTokenRunner( // Malformed continuation token try { - FeedIterator itemQuery = container.GetItemQueryIterator( + using (FeedIterator itemQuery = container.GetItemQueryIterator( queryText: queryText, - continuationToken: continuationToken); - await itemQuery.ReadNextAsync(); - + continuationToken: continuationToken)) + { + await itemQuery.ReadNextAsync(); + } Assert.Fail("Expected bad request"); } catch (CosmosException ce) @@ -640,7 +751,7 @@ async Task ImplementationAsync(Container container, IReadOnlyList QueryDrainingMode.HoldState | QueryDrainingMode.CosmosElementContinuationToken); Assert.IsTrue(feedOptions.TestSettings.Stats.PipelineType.HasValue); - Assert.AreEqual(TestInjections.PipelineType.Specialized, feedOptions.TestSettings.Stats.PipelineType.Value); + Assert.AreEqual(TestInjections.PipelineType.Specialized, feedOptions.TestSettings.Stats.PipelineType.Value); Assert.AreEqual( 1, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/SkipTakeQueryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/SkipTakeQueryTests.cs index ccacd395e2..a61bb673f7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/SkipTakeQueryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/SkipTakeQueryTests.cs @@ -63,26 +63,28 @@ async Task ImplementationAsync(Container container, IReadOnlyList // Max DOP needs to be 0 since the query needs to run in serial => // otherwise the parallel code will prefetch from other partitions, // since the first N-1 partitions might be empty. - FeedIterator documentQuery = container.GetItemQueryIterator( + using (FeedIterator documentQuery = container.GetItemQueryIterator( query, - requestOptions: new QueryRequestOptions() { MaxConcurrency = 0, MaxItemCount = pageSize }); - - //QueryMetrics aggregatedQueryMetrics = QueryMetrics.Zero; - int numberOfDocuments = 0; - while (documentQuery.HasMoreResults) + requestOptions: new QueryRequestOptions() { MaxConcurrency = 0, MaxItemCount = pageSize })) { - FeedResponse cosmosQueryResponse = await documentQuery.ReadNextAsync(); - - numberOfDocuments += cosmosQueryResponse.Count(); - //foreach (QueryMetrics queryMetrics in cosmosQueryResponse.QueryMetrics.Values) - //{ - // aggregatedQueryMetrics += queryMetrics; - //} + //QueryMetrics aggregatedQueryMetrics = QueryMetrics.Zero; + int numberOfDocuments = 0; + while (documentQuery.HasMoreResults) + { + FeedResponse cosmosQueryResponse = await documentQuery.ReadNextAsync(); + + numberOfDocuments += cosmosQueryResponse.Count(); + //foreach (QueryMetrics queryMetrics in cosmosQueryResponse.QueryMetrics.Values) + //{ + // aggregatedQueryMetrics += queryMetrics; + //} + } + + Assert.IsTrue( + numberOfDocuments <= topCount, + $"Received {numberOfDocuments} documents with query: {query} and pageSize: {pageSize}"); } - Assert.IsTrue( - numberOfDocuments <= topCount, - $"Received {numberOfDocuments} documents with query: {query} and pageSize: {pageSize}"); //if (!useDistinct) //{ // Assert.IsTrue( diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/SmokeTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/SmokeTests.cs index 6aea95a810..6c9448c25f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/SmokeTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/SmokeTests.cs @@ -205,14 +205,16 @@ private async Task CrossPartitionQueries() await container.CreateItemAsync(new Person() { Id = id + Guid.NewGuid().ToString(), FirstName = "James", LastName = "Smith" }); } - FeedIterator query = - container.GetItemQueryIterator("SELECT TOP 10 * FROM coll"); List list = new List(); - while (query.HasMoreResults) + using (FeedIterator query = + container.GetItemQueryIterator("SELECT TOP 10 * FROM coll")) { - list.AddRange(await query.ReadNextAsync()); + while (query.HasMoreResults) + { + list.AddRange(await query.ReadNextAsync()); + } } - + await this.CleanupDocumentCollection(container); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/UserDefinedFunctionsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/UserDefinedFunctionsTests.cs index 994ae7b54c..cb60759ad5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/UserDefinedFunctionsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/UserDefinedFunctionsTests.cs @@ -129,18 +129,18 @@ public async Task ValidateUserDefinedFunctionsTest() "SELECT t.id, t.status, t.cost, udf.calculateTax(t.cost) as total FROM toDoActivity t where t.cost > @expensive and t.status = @status") .WithParameter("@expensive", 9000) .WithParameter("@status", "Done"); - - FeedIterator feedIterator = this.container.GetItemQueryIterator( - queryDefinition: sqlQuery); - HashSet iterIds = new HashSet(); - while (feedIterator.HasMoreResults) + using (FeedIterator feedIterator = this.container.GetItemQueryIterator( + queryDefinition: sqlQuery)) { - foreach (var response in await feedIterator.ReadNextAsync()) + while (feedIterator.HasMoreResults) { - Assert.IsTrue(response.cost > 9000); - Assert.AreEqual(response.cost * .05, response.total); - iterIds.Add(response.id.Value); + foreach (dynamic response in await feedIterator.ReadNextAsync()) + { + Assert.IsTrue(response.cost > 9000); + Assert.AreEqual(response.cost * .05, response.total); + iterIds.Add(response.id.Value); + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json index c656b6365a..4f72e18fcd 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json @@ -2574,6 +2574,11 @@ "Type": "Method", "Attributes": [], "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.ResponseMessage] ReadNextAsync(System.Threading.CancellationToken)" + }, + "Void Dispose()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Void Dispose()" } }, "NestedTypes": {} @@ -2595,6 +2600,11 @@ "Type": "Method", "Attributes": [], "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.FeedResponse`1[T]] ReadNextAsync(System.Threading.CancellationToken)" + }, + "Void Dispose()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Void Dispose()" } }, "NestedTypes": {}