1

I have what seems like a relatively simple HTTP GET request to acquire the number of user notifications a specific user has. The problem occurs about 50% of the time. The request is done through an AJAX call on the client every 10 seconds or so. Similar problems (though not as frequent) have occurred in other areas of the application when UserManager is performing awaitable calls.

I have tried to be sure that other async operations are not occurring by gutting the code in this path to the bare essentials.

[Authorize]
public class NotificationsApiController : ApiController
{
    [Route("api/NotificationsApi/GetNotificationsCount/")]
    public async System.Threading.Tasks.Task<string> GetUserIdAsync()
    {
        string userId = null;

        string username = HttpContext.Current.User.Identity.Name;
        ApplicationUserManager um = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();

        var currentUser = await um.FindByNameAsync(username);
        userId = currentUser.Id;

        return userId;
    }
}

I often get this exception when running, but only sometimes (I have cut the output down to the offending line):

System.NotSupportedException

A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.

at System.Data.Entity.Internal.ThrowingMonitor.EnsureNotEntered()
at System.Data.Entity.Core.Objects.ObjectQuery`1.System.Data.Entity.Infrastructure.IDbAsyncEnumerable<T>.GetAsyncEnumerator()
at System.Data.Entity.Infrastructure.IDbAsyncEnumerableExtensions.<FirstOrDefaultAsync>d__25`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.AspNet.Identity.TaskExtensions.CultureAwaiter`1.GetResult()
at Microsoft.AspNet.Identity.EntityFramework.UserStore`6.<GetUserAggregateAsync>d__67.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at CORT.Controllers.NotificationsApiController.<GetUserIdAsync>d__0.MoveNext() in Controllers\WebAPIs\NotificationsApiController.cs:line 24
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Threading.Tasks.TaskHelpersExtensions.<CastToObject>d__1`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__5.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.Filters.AuthorizationFilterAttribute.<ExecuteAuthorizationFilterAsyncCore>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__15.MoveNext()"}

This line is shown above: var currentUser = await um.FindByNameAsync(username);

EDIT: I changed the original notification code presented in the question because it was confusing users. The code above is now the entire class involved in the problem. It focuses on the simple task of returning the user id. It is not a real part of my application (though this is what I am running), but the problem occurring simplifies to this example. Let me know if there are other sections of the code that may be helpful to post.

Macadameane
  • 148
  • 2
  • 9
  • 1
    It looks like there's some concurrency with `UserManager` and/or `RoleManager` that isn't expected or accounted for. Is it meant to be used across requests? Where are `UserManager` and `RoleManager` set? Are they ever set, or do `_userManager` and `_roleManager` remain null? – Scott Hannen Aug 02 '19 at 19:17
  • No. `_roleManager ?? something` returns `_roleManager` if it is not null, or `something` if `_roleManager` is null. But nothing in there sets `_roleManager`. – Scott Hannen Aug 02 '19 at 19:28
  • It would appear they remain null, but I have never seen a null error. – Macadameane Aug 02 '19 at 19:28
  • You wouldn't get a null error. Every time `get` is called it will return `HttpContext.Current.GetOwinContext().Get()` because `_roleManager` is null. But since `_roleManager` is never set, the actual behavior of the property would be exactly the same if you deleted the `_roleManager` field and just returned `HttpContext.Current.GetOwinContext().Get()`. – Scott Hannen Aug 02 '19 at 19:30
  • Yes, you are correct, I agree. I can try to set it an initial time to rule that out. I can't remember the reason other suggested doing this pattern. Even so, these are one off calls, and I am not sure how this would affect the issue. I appreciate your quick responses. – Macadameane Aug 02 '19 at 19:33
  • 1
    [This answer](https://stackoverflow.com/questions/48767910/entity-framework-core-a-second-operation-started-on-this-context-before-a-previ) directly addresses the exception, although it will still take some digging to determine why the condition exists. It looks like you need to make sure that database contexts aren't getting used concurrently. – Scott Hannen Aug 02 '19 at 19:36
  • Thanks Scott. The code you see here is the only thing happening (every 10 seconds). There is a database context being used in other methods in the class, but none of them are being called or used here. Just the `UserManager`. I have looked at many related answers, but there is always some other threading going on or lazy loading going on. Unless there is something about my `ApplicationUser` itself that could be causing an issue when it happens. – Macadameane Aug 02 '19 at 19:46
  • It would probably also help to look at the inner exception to see exactly where this is coming from. But `UserManager.FindByNameAsync(username)` looks like something that would access a database context. – Scott Hannen Aug 02 '19 at 19:49
  • Admittedly, I didn't write the portions of the app that deal with the user, but I believe that it is managed by Identity. – Macadameane Aug 02 '19 at 20:01
  • Seems multiple contexts of the same type are being created. Use DI, create them in your startup and then inject them via the constructor. That should fix this problem. – Nico Aug 02 '19 at 20:55
  • @nico_c Thanks. Could you expound a bit? Where are these other contexts being created? I have removed any references to database contexts. The only context being used is shown. – Macadameane Aug 02 '19 at 21:39
  • From your code it seems you are not using currentUser and userRoles for anything. Try and comment those 2 lines out. Then you can also comment out the UserManager property. Actually all what that method does is returning a new NotificationSummary no matter what. The RoleManager property is not being used so you can remove that all together. – Lasse Holm Aug 03 '19 at 05:02
  • I have cut a lot of code out that had nothing to do with the bug to narrow things down. This is why it seems useless. I'm running it as shown and the errors happen. – Macadameane Aug 04 '19 at 02:45
  • I have simplified the code to make it easier to understand – Macadameane Aug 05 '19 at 17:29

2 Answers2

0

You're making simultaneous calls to the same DbContext. The stack trace says so. So would the type of the exception if you had posted it.

Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59
  • Where is this happening? In the user manager or application user? What do you mean by "so would the type of the exception of you had posted it"? What else can I post that would help find the issue? – Macadameane Aug 04 '19 at 22:51
  • Besides the message and stack trace, that points to `UserStore.GetUserAggregateAsync`, the .NET type of the exception and properties other than the message are very useful into diagnosing issues. – Paulo Morgado Aug 05 '19 at 06:57
  • Thank you. I have never had to understand this type of stack trace before and didn't realize the sections were from the different threads. I will take a look at how our users are being created when the request happens and see what is not being awaited. Thanks also for editing the question and making it more readable. – Macadameane Aug 05 '19 at 14:10
  • They are not necessarily form different threads and they are definitely from all threads. This is a logical stack trace that follows the logical execution of async methods as if they were sync methods. – Paulo Morgado Aug 05 '19 at 14:54
  • The type of the exception is `System.NotSupportedException`. I've been looking for other places that the DbContext could be called when the user is created, but haven't really come up with much. It is obvious that it is happening, but I can't seem to find where. I also assume that the user has already been constructed by the time the controller action is called, but I looked into that as well. I don't have GetUserAggregate overridden, but I don't know where an await might have been missed in my code leading up to this. Getting the user seems to be (usually) a routine and simple process. – Macadameane Aug 05 '19 at 16:12
  • I have simplified the code to make it easier to understand – Macadameane Aug 05 '19 at 17:29
  • Are you sharing the DBContext instance? – Paulo Morgado Aug 05 '19 at 18:30
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/197520/discussion-between-macadameane-and-paulo-morgado). – Macadameane Aug 05 '19 at 20:27
0

My problem was in my CookieAuthenticationOptions Provider. In a custom OnValidateIdentity, I had used an example that I found to store a claim for the users expiration time. This example was invoking the SecurityStampValidator without awaiting. This is what was causing intermittent problems.

Macadameane
  • 148
  • 2
  • 9