Elfocrash / Cosmonaut

🌐 A supercharged Azure CosmosDB .NET SDK with ORM support
https://cosmonaut.readthedocs.io
MIT License
342 stars 44 forks source link

On .net webapi 2 with framework 4.6.1, when creating a ICosmosStore, the code hangs there and never continues #64

Closed levalencia closed 5 years ago

levalencia commented 5 years ago

I followed the documentation as stated, and when I try to create a ICosmosStore to get information from CosmosDB, I got webapi timeouts, when I attached the debugger, I noticed the problem is not in the webapi, but apparently something in the cosmonaut library

My code is very simple>

[HttpGet]
        public async Task<List<SharepointTenant>> GetTenants()
        {
            //var tenantStore = CosmosStoreFactory.CreateForEntity<SharepointTenant>();

            var  _settings = new CosmosStoreSettings(ConfigurationManager.AppSettings["database"].ToString(),
                ConfigurationManager.AppSettings["endpoint"].ToString(),
                ConfigurationManager.AppSettings["authKey"].ToString());

            ICosmosStore<SharepointTenant> tenantStore = new CosmosStore<SharepointTenant>(_settings);

            return await tenantStore.Query().Where(x => x.TenantName != null).ToListAsync();

        }
Elfocrash commented 5 years ago

Ok I figured it out.

Cosmonaut in the CosmosStore constructor will open the connection to the database and also create the database and the collection if they don't exist. Those calls are in the constructor and they are synchronised with .GetAwaiter().GetResult().

This should not be a problem in most cases, but in your case it is because you are initialising the CosmosStore in the Controller. The controller is part of a synchronisation context, almost like the UI thread in WinForm apps. They are known to deadlock if you synchronise an async call in them.

I tested it with a DI framework like Unity and Ninject and it is working fine if you inject it. Here is a tutorial by Microsoft on how to use dependency injection in Web API 2 with Unity. https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection

To test that this was the problem I simply created a singleton and I initialised it on the Global.asax.cs.

image image

This is NOT recommended at all, because it's untestable. You should use dependency injection, I just wanted to make sure that the deadlock was the problem.

Let me know if that helped.

levalencia commented 5 years ago

can you please post the singleton class in text; so I dont have to type it all

Elfocrash commented 5 years ago

Sure. Here you go:

public sealed class CosmosStoreHolder
{
    private static CosmosStoreHolder instance = null;
    private static readonly object padlock = new object();
    public ICosmosStore<TestUser> CosmosStore { get; }

    CosmosStoreHolder()
    {
        CosmosStore = new CosmosStore<TestUser>(new CosmosStoreSettings("localdb", "https://localhost:8081", "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
            settings =>
            {
                settings.ConnectionPolicy = new ConnectionPolicy
                    { ConnectionMode = ConnectionMode.Direct, ConnectionProtocol = Protocol.Tcp };
            }));
    }

    public static CosmosStoreHolder Instance
    {
        get
        {
            lock (padlock)
            {
                if (instance == null)
                {
                    instance = new CosmosStoreHolder();
                }
                return instance;
            }
        }
    }
}
levalencia commented 5 years ago

btw; it would be nice if you can put in the documentation how to use it with Unity as well; as I dont have experience with DI, then that will make implementation straigh tforward

levalencia commented 5 years ago

Hi Nick

Still the same problem, when remote debugging, the code hangs on the lock statement on the singleton forever and never steps inside.

Elfocrash commented 5 years ago

Are you initialising the CosmosStores in the Application_Start() method as shown at the picture above?

levalencia commented 5 years ago

lol, no, I didnt think that was needed because the picture shows that variable as not used anywhere else; but I will add it and try it again this afternoon

Elfocrash commented 5 years ago

Haha that’s the whole point. Initialising the CosmosStore outside the controller which is part of the “bad” synchronisation context.

levalencia commented 5 years ago

thank you for the help!