Azure / azure-cosmos-dotnet-v3

.NET SDK for Azure Cosmos DB for the core SQL API
MIT License
735 stars 491 forks source link

Managed Debugging Assistant 'PInvokeStackImbalance' : 'A call to PInvoke function 'Microsoft.Azure.Cosmos.Direct!Microsoft.Azure.Cosmos.Core.Trace.EtwNativeInterop::EventWriteString' has unbalanced the stack #635

Closed JohnSpencerTerry closed 5 years ago

JohnSpencerTerry commented 5 years ago

We are in the process of upgrading from the 2.x document client to the 3.1 Cosmo client. I have created a repository class to generalize/isolate some of the cosmo sdk code. In my upsert method, I am attempting to construct the cosmo client in a using statement - this seemed to be supported by documentation and runs properly when NOT DEBUGGING.

To Reproduce I don't want to share the full code base if I can avoid it. My test is set up this this:


        [Fact]
        public async void TestRepositoryExample()
        {
            await activityPlayRepository.UpdateActivityPlayDocument(new ActivityPlayDocument()
            {
                GameID = new Guid("896BB422-5A9F-E511-80E7-000D3A001FE3"),
                TenantUserID = new Guid("896BB422-5A9F-E511-80E7-000D3A001FE3")
            });

            // method has no return - just a bs assertion to prove the test
            Assert.NotEmpty(new List<string>(){"f"});

        }

Which calls a collection specific repo class (basically just a configured wrapper for the actual cosmo repo class we have):


        public async Task UpdateActivityPlayDocument(ActivityPlayDocument activityPlayDocument)
        {
            await _cosmoDbCollectionRepository.Upsert(new CosmoDBCollectionUpsertQuery<ActivityPlayDocument>()
            {
                AccessConfiguration = _accessConfiguration,
                CosmoIDResolver = x => x.GameSetScoreID.ToString(),
                CosmoPartitionResolver = x => new PartitionKey(x.GameID.ToString()),
                Data = activityPlayDocument,
                InsertionRuleType = InsertionRuleType.ErrorIfOldRecordDoesNotExist
            });
        }

Which then calls my actual cosmo connection class:


        public async Task<CosmoDBCollectionUpsertQueryResult> Upsert<T>(CosmoDBCollectionUpsertQuery<T> cosmoDbCollectionUpsertQuery)
        {
            var result = new CosmoDBCollectionUpsertQueryResult();

            try
            {
                using (CosmosClient cosmosClient = new CosmosClient(cosmoDbCollectionUpsertQuery.AccessConfiguration.AccountEndPoint,
                    cosmoDbCollectionUpsertQuery.AccessConfiguration.AccountKey))
                {
                    var propertyResolver =
                        cosmoDbCollectionUpsertQuery.CosmoIDResolver(cosmoDbCollectionUpsertQuery.Data);

                    var partitionResolver = cosmoDbCollectionUpsertQuery.CosmoPartitionResolver(cosmoDbCollectionUpsertQuery.Data);

                    var container = cosmosClient
                        .GetContainer(cosmoDbCollectionUpsertQuery.AccessConfiguration.DatabaseId,
                            cosmoDbCollectionUpsertQuery.AccessConfiguration.ContainerId);
                    try
                    {

                        // see if the record exists already - if it doesn't, an error will be throw
                        await container.ReadItemAsync<T>(propertyResolver, partitionResolver);

                        switch (cosmoDbCollectionUpsertQuery.InsertionRuleType)
                        {
                            case InsertionRuleType.UseImplementationRuleChoice: // impl choice is merge here
                            case InsertionRuleType.MergeOldRecordIfExists:
                                await container.UpsertItemAsync(cosmoDbCollectionUpsertQuery.Data, partitionResolver);
                                break;
                            case InsertionRuleType.DeleteOldRecordIfExists:
                                await container.DeleteItemAsync<T>(propertyResolver, partitionResolver);
                                await container.CreateItemAsync<T>(cosmoDbCollectionUpsertQuery.Data, partitionResolver);
                                break;
                            default:
                                throw new ArgumentOutOfRangeException(nameof(cosmoDbCollectionUpsertQuery
                                    .InsertionRuleType));
                        }
                    }
                    catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound
                                                     && cosmoDbCollectionUpsertQuery.InsertionRuleType != InsertionRuleType.ErrorIfOldRecordDoesNotExist)
                    {
                        await container.CreateItemAsync<T>(cosmoDbCollectionUpsertQuery.Data, partitionResolver);
                    }
                }
            }
            catch (Exception e)
            {
                result.Exception = e;
            }

            return result;
        }

The using statement is where things go bad for me. I get the following error as soon as it hits the using statement:

Managed Debugging Assistant 'PInvokeStackImbalance' : 'A call to PInvoke function 'Microsoft.Azure.Cosmos.Direct!Microsoft.Azure.Cosmos.Core.Trace.EtwNativeInterop::EventWriteString' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.'

Expected behavior I should be able to debug my methods and skip over unmanaged code or get an exception that I can act on (if my call signature is wrong, etc).

Actual behavior Exception is thrown when debugging from a unit test but exception does not occur if the test is run without debugging. Information is persisted to cosmo repo as expected when "Run", but fails when "Debug".

Environment summary SDK Version: Microsoft.Azure.Cosmos 3.1.0 xunit 2.4.0 / .NET Framework 4.71 OS Version: Windows 10 17134.885

Additional context Please let me know if I've excluded any critical details. Thanks

j82w commented 5 years ago

Not related to this bug but is there any reason you are creating the CosmosClient for each call? The best practices recommend using a singleton. It avoids getting the caches for each new instance.

JohnSpencerTerry commented 5 years ago

Thanks. I saw that but I figured I could pretty quickly refactor to inject it as a singleton instance later. The project that contains this code is a collection of class libraries that we use in a few asp net projects, so I'd rather save that for last. I'm pretty sure you should be able to create the client for each call though, if you want.

hendriksteinhorst commented 5 years ago

Same here, only happens in .net 4.7.2 project, not in netcoreapp2.2

kicybot commented 5 years ago

Happens here as well, 4.6.2, without a using statement.

j82w commented 5 years ago

@ausfeldt do you have any suggestions?

Lambda4 commented 5 years ago

Same, .NET Standard 2.0 project.

ausfeldt commented 5 years ago

Root cause found. Working on an action plan now.

j82w commented 5 years ago

This issue was fixed in 3.1.1 .

JohnSpencerTerry commented 5 years ago

Thanks for the quick turn around. Appreciate the help!