How to fix 'A second operation started on this context' error on SignInManager.PasswordSignIn()

0

I have an MVC system that uses .net Identity login, however, when two separate users log into the system at roughly the same time, the user who clicks the button last gets an error saying;

One or more errors occurred. 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.

This error happens on the SignInManager.PasswordSignInAsync() line but I am stuck as to what I've done wrong.

Admittedly, I am not too familiar with OWIN, but if anyone has any clues as to what I can do to stop this happening that would be much appreciated.

To cover off a few things, I have already tried calling PasswordSignIn instead of PasswordSingInAsync and I've tried awaiting the call but its the same story - I believe this is because it's two entirely separate requests.

In brief, my code is set up as follows;

LoginController.cs

public ActionResult Login(LoginModel model)
{
    _identityManagement.SignInManager.PasswordSignInAsync(model.Username, model.Password, model.PersistentLogin, false);

    //We do some stuff here but it fails before this point so code removed.
    return null;
}

IdentityManagement.cs

public class IdentityManagement
{
    public ApplicationSignInManager SignInManager
    {
        get
        {
            return HttpContext.Current.GetOwinContext().Get<ApplicationSignInManager>();
        }
    }
}

ApplicationSignInManager.cs

public class ApplicationSignInManager : SignInManager<SystemUser, string>
{
    public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) : base(userManager, authenticationManager)
    {
    }

    public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
    {
        return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
    }
}

Startup.Auth.cs

public void ConfigureAuth(IAppBuilder app)
{
    DatabaseContext dbContext = DependencyResolver.Current.GetService<DatabaseContext>();

    app.CreatePerOwinContext<IUserStore<DatabaseContext>>(() => ApplicationUserStore.Create(dbContext));

    app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

}

Global.asax.cs

var container = new SimpleInjector.Container();
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
container.Register<DatabaseContext>(Lifestyle.Scoped);
container.Verify();

Many Thanks, Tom

As requested here is the stack trace

System.AggregateException
HResult=0x80131500
Message=One or more errors occurred.
Source=mscorlib
StackTrace:
 at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
 at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
 at System.Threading.Tasks.Task`1.get_Result()
 at Controllers.LoginController.Login(LoginModel model) in C:\...\Controllers\LoginController.cs:line 205
 at Controllers.LoginController.Index(LoginModel model) in C:\...\Controllers\LoginController.cs:line 111
 at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
 at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
 at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c.<BeginInvokeSynchronousActionMethod>b__9_0(IAsyncResult asyncResult, ActionInvocation innerInvokeState)
 at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`2.CallEndDelegate(IAsyncResult asyncResult)
 at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResultBase`1.End()
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult)
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass11_0.<InvokeActionMethodFilterAsynchronouslyRecursive>b__0()
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass11_2.<InvokeActionMethodFilterAsynchronouslyRecursive>b__2()

Inner Exception 1:
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.
c#
asp.net-mvc
asp.net-identity
owin
asked on Stack Overflow Aug 13, 2019 by Tom Kirkland • edited Aug 14, 2019 by Tom Kirkland

3 Answers

2

The error explains what's wrong and how to fix it - an await is missing from an asynchronous operation. The only such operation is in the first snippet :

public ActionResult Login(LoginModel model)
{
    _identityManagement.SignInManager.PasswordSignInAsync(model.Username, model.Password, model.PersistentLogin, false);

    //We do some stuff here but it fails before this point so code removed.
    return null;
}

By convention asynchronous methods end in Async and yet, nothing awaits for PasswordSignInAsync to complete before continuing and returning. PasswordSignInAsync is indeed an asynchronous method.

This means that the user hasn't logged in by the time this method returns. For all anyone knows login may have failed.

Any subsequent attempts to login can easily happen before the first attempt finishes. It also means that the sign-in cookie is never set and any subsequent calls to the site will try to login again.

Change the method to :

public async Task<ActionResult> Login(LoginModel model)
{
    var result=await _identityManagement.SignInManager.PasswordSignInAsync(model.Username, model.Password, model.PersistentLogin, false);
    if(result.Succeeded)
    {
        ....
    }
}
answered on Stack Overflow Aug 13, 2019 by Panagiotis Kanavos • edited Aug 13, 2019 by Panagiotis Kanavos
1

The problem is that a second user ( which is running in different thread ) is trying to access the DatabaseContext while it is used by first user. You should create a new DatabaseContext for every thread

answered on Stack Overflow Aug 13, 2019 by Ikacho
0

Thank you both, it put me onto the right track and I've now solved the issue.

In Startup.auth.cs I had the following code

DatabaseContext dbContext = DependencyResolver.Current.GetService<DatabaseContext>();
app.CreatePerOwinContext<IUserStore<SystemUser>>(() => ApplicationUserStore.Create(dbContext));

I've now changed it to the following which works for multiple simultaneous login requests

app.CreatePerOwinContext<IUserStore<SystemUser>>(() => ApplicationUserStore.Create(new DatabaseContext()));

Presumably, the was I was declaring the dbContext here was using the same one for every instance of "SystemUser"

Thank you both again for the help and ideas.

Tom

answered on Stack Overflow Aug 14, 2019 by Tom Kirkland

User contributions licensed under CC BY-SA 3.0