Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hybrid Async Scoped MVC 5 + Async Method bug #983

Open
leonardolb opened this issue Oct 25, 2023 · 12 comments
Open

Hybrid Async Scoped MVC 5 + Async Method bug #983

leonardolb opened this issue Oct 25, 2023 · 12 comments
Labels

Comments

@leonardolb
Copy link

Hi!
i'm facing this error and I really can't go through it.

image

My code:

it start at this action:

image

goes to this block
image

and exception is thrown at the highlighted part of code.

Can anyone help me?

this is my lifestyle
image

It's an ASP.NET MVC5 app that runs some background tasks, WebAPI controllers and SignalR.

thanks!!

@dotnetjunkie
Copy link
Collaborator

Hi @leonardolb,

I've got trouble reading the images on my mobile phone. Would you mind replacing them with actual text, as was requested in the bug report template you used while filling in your question?

Thanks in advance

@dotnetjunkie
Copy link
Collaborator

And don't forget to post a full stack trace

@leonardolb
Copy link
Author

leonardolb commented Oct 25, 2023

Sorry about that!!

Im facing this exception:

PersonDataConfigBO is registered using the 'Hybrid Async Scoped / Web Request' lifestyle, but the instance is requested outside the context of an active (Hybrid Async Scoped / Web Request) scope. Please see https://simpleinjector.org/scoped for more information about how apply lifestyles and manage scopes.

My stacktrace is:

SimpleInjector.ActivationException: PersonDataConfigBO is registered using the 'Hybrid Async Scoped / Web Request' lifestyle, but the instance is requested outside the context of an active (Hybrid Async Scoped / Web Request) scope. Please see https://simpleinjector.org/scoped for more information about how apply lifestyles and manage scopes.
at SimpleInjector.Scope.GetScopelessInstance(ScopedRegistration registration)
at SimpleInjector.Scope.GetInstance[TImplementation](ScopedRegistration registration, Scope scope)
at SimpleInjector.Advanced.Internal.LazyScopedRegistration1.GetInstance(Scope scope)
at lambda_method(Closure )
at SimpleInjector.InstanceProducer.GetInstance()
at SimpleInjector.Container.GetInstance[TService]()
at AdSolutions.AdData.Context.Pessoa.PessoaContext.<ConsultarCNPJ>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter1.GetResult()
at AdSolutions.Site.AdData.Controllers.PersonController.<Detail>d__3.MoveNext() in C:\Users\leonardo\Desktop\svn\AdTraffic\master\AdSolutions.Site.Host\Areas\AdData\Controllers\PersonController.cs:line 100
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Mvc.Async.TaskAsyncActionDescriptor.EndExecute(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass8_0.<BeginInvokeAsynchronousActionMethod>b__1(IAsyncResult asyncResult)
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()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass11_2.<InvokeActionMethodFilterAsynchronouslyRecursive>b__2()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass11_2.<InvokeActionMethodFilterAsynchronouslyRecursive>b__2()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass3_6.<BeginInvokeAction>b__4()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass3_1.<BeginInvokeAction>b__1(IAsyncResult asyncResult)`

Everything start at my PersonController "Detail" Action. It async calls "ConsultarCNPJ" in my context class

public async Task<JsonResult> Detail(PessoaModel model, PessoaDadoPessoaConfigModel documento)
{
    if (model == null)
        model = new PessoaModel();

    var service = base.CreateContext<IPessoaContext>();

    if (documento != null)
    {
        var consultarCnpj = false;

        if (consultarCnpj.False())
            model = service.ObterNovo(documento);
        else
            **_model = await service.ConsultarCNPJ(documento);_**
    }
    else if (model.State.In(ModelStateType.Normal))
        model = service.ObterDetalhes(model);

    return AjaxViewResult(new PessoaViewModel(model));
}

"ConsultarCNPJ" is a simple method that triggers a mediator (ThirdPartyCommandMediator) and awaits all available commands complete (just one expected for this ConsultarCNPJThirdPartyCommand).

When finished, base.CreateBO().List is called to continue some procedures.

base.CreateBO().List is not async and it goes to directly to database (oracle).

CreateBO method is just a wrapper to simpleinjector Container.Resolve.

public async Task<PessoaModel> ConsultarCNPJ(PessoaDadoPessoaConfigModel documento)
{
    var command = new ConsultarCNPJThirdPartyCommand
    {
        CNPJ = documento.dsValor.RemoveSpecialCharacters().RemoveDiacritics()
    };

    var results = await **ThirdPartyCommandMediator**.Handle(command).ConfigureAwait(false);

    var queryData = results[0];

    if (queryData.Success)
    {
        var pessoa = queryData.Command.ReturnValue.Pessoa;

        var documentosPessoa = **base.CreateBO<IPersonDataConfigBO>().List**(
            new PaisModel { sqPais = PaisModel.Brasil },
            TipoPessoa.Juridica,
            Situacao.Ativo).Select(sel => new PessoaDadoPessoaConfigModel
            {
                DadoPessoaConfig = sel,
                State = ModelStateType.New
            }).ToList();
}

I'm using "Lifestyle = Lifestyle.CreateHybrid(new SimpleInjector.Lifestyles.AsyncScopedLifestyle(), new WebRequestLifestyle())" as my lifestyle.

Finally, this is the Handler triggered when ThirdPartyCommandMediator.Handle() is invoked

public override async Task<ThirdPartyCommandHandlerContext<ConsultarCNPJThirdPartyCommand>>
    Handle(ThirdPartyCommandHandlerContext<ConsultarCNPJThirdPartyCommand> commandContext)
{

    var targetApp = new SERPROPartyApp(commandContext.Context);
    var command = commandContext.Command;

    if (targetApp.BaseUrl.IsNullOrWhiteSpace())
        return commandContext;
    else
    {
        var client = new SERPRORestClient(targetApp.Username, targetApp.Password, targetApp.BaseUrl);

        var dados = await client.Consultar(command.CNPJ).ConfigureAwait(false);
    }
}


public async Task<CNPJ> Consultar(string cnpj)
{
    var request = new RestRequest("/consulta-cnpj-df/v2/basica/" + cnpj, Method.Get);

    var response = await this._client.ExecuteAsync<CNPJ>(request).ConfigureAwait(false);

    return response?.Data;
}

@dotnetjunkie
Copy link
Collaborator

dotnetjunkie commented Oct 25, 2023

Can you debug through your application and determine exactly after which line of code the scope is gone? e.g. is that after:

  • var response = await this._client.ExecuteAsync<CNPJ>(request).ConfigureAwait(false);
  • or after var dados = await client.Consultar(command.CNPJ).ConfigureAwait(false);
  • or after var results = await **ThirdPartyCommandMediator**.Handle(command).ConfigureAwait(false);
  • or later?

@leonardolb
Copy link
Author

leonardolb commented Oct 25, 2023

Yes!
Exception is thrown at this line when resoling IPersonDataConfigBO

var documentosPessoa = base.CreateBO().List in ConsultarCNPJ method.

@dotnetjunkie
Copy link
Collaborator

Sorry, that's not what I meant. It's clear to me where the exception is thrown, but there is a point in the application that Simple Injector's Scope is gone. This is what eventually leads to the exception. What I want to know is: what is the first ocurrance in your code where that Scope is gone. This will likely be long before the exception happens.

In order to test this, you might need to make some temporary adjustments to your code; the easiest way to test is by calling into the container to resolve a scoped dependency, for instance just after the lines of code I mentioned above.

@leonardolb
Copy link
Author

Sorry my misunderstanding!

I've debuged my code, scope is gone after

var response = await this._client.ExecuteAsync<CNPJ>(request).ConfigureAwait(false);

@leonardolb
Copy link
Author

Unfortunately I can't debug further into ExecuteAsync as its is a method from RestSharp.RestClient class.

@dotnetjunkie
Copy link
Collaborator

dotnetjunkie commented Oct 25, 2023

That likely means that there is something happening within RestSharp.RestClient that causes the asynchronous context not to flow. The most likely case is that it is using an async foreach like construct over IAsyncEnumerable. This can cause the asynchronous context to be cleared and Microsoft hasn't provided a fix for this. Although you could try posting a bug report with RestSharp.RestClient, chances are slim that the makers will act on this.

What you can do instead is make sure that all your dependencies for that scope are resolved before this point. This means stepping away from lazy loading your dependencies, as you are doing right now.

@leonardolb
Copy link
Author

Thanks!
I've removed .ConfigureAwait(false) from it just to test and scope stopped being lost. However, I don't feel I should do it as it may cause deadlock at some background tasks that consumes this same method.

I'll step away from lazyloading so!

thanks!

@dotnetjunkie
Copy link
Collaborator

I've removed .ConfigureAwait(false)

I didn't expect this.

However, I don't feel I should do it as it may cause deadlock at some background tasks that consumes this same method.

You don't have to worry about that. This only holds when building client applications such as WPF and Win Forms. This doesn't hold for ASP.NET. AFAIK, It will never deadlock on this.

@leonardolb
Copy link
Author

Maybe i need to remove other ConfigureAwait I have so, maybe they are causing some trouble too i guess

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants