question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Interceptor calling async method before invocation.Proceed() Problem

See original GitHub issue

Using latest .net core angular all in one solution

I create 2 interceptors to do some stuff before a call to an appservice by calling an async methods.

  • ValidationInterceptor: Call an async method to do some business logic and throw error if validation not pass and the continue execution
  • PreTreatmentInterceptor: Executed after the validationinterceptor do some treatment before the appservice is called and receive the appservice dto in parameter and then continue execution

In my step to reproduce it i call /api/services/app/Test/Delete and everything work fine only for the first call, the call stack goes like this :

  • Call to /api/services/app/Test/Delete with Id X
  • ValidationInterceptor intercept the request and call TestAppServiceValidator.DeleteValidation
  • DeleteValidation check that an entity exist with id received in parameter
  • ValidationInterceptor does invocation.Proceed()
  • PreTreatmentInteceptor intercept it and call TestAppServicePreTreatmentExecutor.PreTreatment_Delete
  • PreTreatment_Delete delete related entity of entity “Test”
  • PreTreatmentInteceptor does invocation.Proceed()
  • TestAppService.Delete is called and delete the entity with id X

The problem

  • After that i try a second call and im keep getting internalservererror.
  • If i restart the solution the first call goes well and all subsequent give internalservererror.
  • But if i remove async and task and switch method to sync inside TestAppServiceValidator and TestAppServicePreTreatmentExecutor everything works fine.

The logs are included in my sample repo bellow in the step to reproduce

I hope my explanation are clear enough. Thx in advance for any further help.

TestAppServiceValidator called by ValidationInterceptor

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Abp.Domain.Repositories;
using Abp.Runtime.Validation;
using Interceptors.Tests.Dtos;
using Microsoft.EntityFrameworkCore;

namespace Interceptors.Tests.Validations
{
  public class TestAppServiceValidator : ITestAppServiceValidator
  {
      private readonly IRepository<Test> _repository;

      public TestAppServiceValidator(IRepository<Test> pRepository)
      {
          _repository = pRepository;
      }

      public async Task DeleteValidation(DeleteIn pRequest)
      {
          if (!await _repository.GetAll().AnyAsync(a => a.Id == pRequest.Id))
          {
              throw new AbpValidationException("Unexisting entity");
          }
      }

      //public void DeleteValidation(DeleteIn pRequest)
      //{
      //    if (!_repository.GetAll().Any(a => a.Id == pRequest.Id))
      //    {
      //        throw new AbpValidationException("Unexisting entity");
      //    }
      //}
  }
}

TestAppServicePreTreatmentExecutor called by PostTreatmentInterceptor

using Interceptors.Interceptors.PreTreatment;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Abp.Domain.Repositories;
using Interceptors.Tests.Dtos;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace Interceptors.Tests.PreTreatment
{
    public class TestAppServicePreTreatmentExecutor : ITestAppServicePreTreatmentExecutor
    {
        public IRepository<TestPreTreatment> _repository;

        public TestAppServicePreTreatmentExecutor(IRepository<TestPreTreatment> pRepository)
        {
            this._repository = pRepository;
        }

        public async Task PreTreatment_Delete(DeleteIn pRequest)//async
        {
            List<TestPreTreatment> list = await this._repository.GetAll().Where(w => w.TestId == pRequest.Id).ToListAsync();

            foreach (TestPreTreatment testPreTreatment in list)
            {
                await this._repository.DeleteAsync(testPreTreatment);
            }
        }

        //public void PreTreatment_Delete(DeleteIn pRequest)//async
        //{
        //    List<TestPreTreatment> list = this._repository.GetAll().Where(w => w.TestId == pRequest.Id).ToList();

        //    foreach (TestPreTreatment testPreTreatment in list)
        //    {
        //        this._repository.Delete(testPreTreatment);
        //    }
        //}

    }
}

TestAppService

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Abp.Domain.Repositories;
using Interceptors.Interceptors.PreTreatment;
using Interceptors.Tests.Dtos;

namespace Interceptors.Tests
{
    public class TestAppService : ITestAppService
    {
        private readonly IRepository<Test> _repository;

        public TestAppService(IRepository<Test> pRepository)
        {
            _repository = pRepository;
        }

        [UxPreTreatment]
        public virtual async Task Delete(DeleteIn pRequest)
        {
            await this._repository.DeleteAsync(w => w.Id == pRequest.Id);
        }
    }
}

Validation Interceptor

using Abp.Dependency;
using Castle.Core.Logging;
using Castle.DynamicProxy;
using System;
using System.Reflection;
using System.Threading.Tasks;

namespace Interceptors.Interceptors.Validation
{
    public class ValidationInterceptor : IInterceptor
    {

        private readonly IIocResolver _iocResolver;

        public ILogger Logger { get; set; }

        public ValidationInterceptor(IIocResolver iocResolver)
        {
            _iocResolver = iocResolver;
            Logger = NullLogger.Instance;
        }
        private bool ShouldIntercept(IInvocation invocation)
        {
            return invocation.MethodInvocationTarget.GetCustomAttribute<UxValidationDisabledAttribute>() == null;
        }

        public void Intercept(IInvocation invocation)
        {

            if (ShouldIntercept(invocation))
            {
                ValidateBeforeProceeding(invocation);
            }
            else
            {
                invocation.Proceed();
            }
        }

        private void ValidateBeforeProceeding(IInvocation invocation)
        {
            IBaseValidatedAppService appService = invocation.InvocationTarget as IBaseValidatedAppService;

            string assemblyName = invocation.InvocationTarget.GetType().BaseType.Assembly.ManifestModule.Name.Replace(".dll", ".");

            string validatorName = "I" + appService.GetType().BaseType.Name + "Validator";

            TypeResolver typeResolver = _iocResolver.Resolve<TypeResolver>();

            Type validatorInterfaceType = typeResolver[assemblyName + validatorName];

            if (validatorInterfaceType is null)
                return;

            IBaseValidator baseValidator = _iocResolver.Resolve(validatorInterfaceType) as IBaseValidator;

            Type validatorType = baseValidator.GetType();

            string methodName = invocation.MethodInvocationTarget.Name + "Validation";

            MethodInfo method = validatorType.GetMethod(methodName);

            if (method != null)
            {
                try
                {
                    if (InternalAsyncHelper.IsAsyncMethod(method))
                    {
                        var returnValue = method.Invoke(baseValidator, invocation.Arguments);

                        if (method.ReturnType == typeof(Task))
                        {
                            returnValue = InternalAsyncHelper.AwaitTaskWithFinally(
                                (Task)returnValue,
                                ex =>
                                {
                                    invocation.Proceed();
                                });
                        }
                        else //Task<TResult>
                        {
                            returnValue = InternalAsyncHelper.CallAwaitTaskWithFinallyAndGetResult(
                                method.ReturnType.GenericTypeArguments[0],
                                returnValue,
                                ex =>
                                {
                                    invocation.Proceed();
                                });
                        }
                    }
                    else
                    {
                        invocation.Proceed();
                    }
                }
                catch (Exception ex)
                {
                    throw ex.InnerException;
                }
            }
            else
            {
            }

        }

    }
}

PreTreatmentInterceptor

using Abp.Dependency;
using Castle.Core.Logging;
using Castle.DynamicProxy;
using System;
using System.Reflection;
using System.Threading.Tasks;

namespace Interceptors.Interceptors.PreTreatment
{
    public class PreTreatmentInterceptor : IInterceptor
    {
        private readonly IIocResolver _iocResolver;

        public ILogger Logger { get; set; }

        public PreTreatmentInterceptor(IIocResolver iocResolver)
        {
            _iocResolver = iocResolver;
            Logger = NullLogger.Instance;
        }

        private bool ShouldIntercept(IInvocation invocation)
        {
            return invocation.MethodInvocationTarget.GetCustomAttribute<UxPreTreatment>() != null;
        }

        public void Intercept(IInvocation invocation)
        {
            if (ShouldIntercept(invocation))
            {
                PreTreatmentBeforeProceeding(invocation);
            }
            else
            {
                invocation.Proceed();
            }
        }

        private void PreTreatmentBeforeProceeding(IInvocation invocation)
        {
            IPreTreatmentAppService appService = invocation.InvocationTarget as IPreTreatmentAppService;

            string assemblyName = invocation.InvocationTarget.GetType().BaseType.Assembly.ManifestModule.Name.Replace(".dll", ".");

            string preTreatmentExecutorName = "I" + appService.GetType().BaseType.Name + "PreTreatmentExecutor";

            TypeResolver typeResolver = _iocResolver.Resolve<TypeResolver>();

            Type preTreatmentExecutorInterfaceType = typeResolver[assemblyName + preTreatmentExecutorName];

            if (preTreatmentExecutorInterfaceType is null)
                return;

            IPreTreatmentExecutor preTreatmentExecutor = _iocResolver.Resolve(preTreatmentExecutorInterfaceType) as IPreTreatmentExecutor;

            Type preTreatmentExecutorType = preTreatmentExecutor.GetType();

            string methodName = "PreTreatment_" + invocation.MethodInvocationTarget.Name;

            MethodInfo method = preTreatmentExecutorType.GetMethod(methodName);

            var request = invocation.Arguments[0];


            if (method != null)
            {
                try
                {
                    var returnValue = method.Invoke(preTreatmentExecutor, invocation.Arguments);

                    if (InternalAsyncHelper.IsAsyncMethod(method))
                    {
                        //Wait task execution and modify return value
                        if (method.ReturnType == typeof(Task))
                        {
                            invocation.ReturnValue = InternalAsyncHelper.AwaitTaskWithFinally(
                          (Task)returnValue,
                               ex =>
                               {
                                   invocation.Proceed();
                               });
                        }
                        else //Task<TResult>
                        {
                            invocation.ReturnValue = InternalAsyncHelper.CallAwaitTaskWithFinallyAndGetResult(
                            method.ReturnType.GenericTypeArguments[0],
                                returnValue,
                                ex =>
                                {
                                    invocation.Proceed();
                                });
                        }
                    }
                    else
                    {
                        invocation.Proceed();
                    }
                }
                catch (Exception ex)
                {
                    throw ex.InnerException;
                }
            }
        }
    }
}

Steps to reproduce

  1. Download the sample here https://github.com/naazz/InterceptAsync
  2. Use the following script to add some records

INSERT INTO Tests(Props1, Props2) VALUES(‘Test1’, ‘Test2’)

INSERT INTO TestPreTreatments(Props1, TestId) SELECT ‘Test1’, Id FROM Tests

  1. Using swagger interface try the following request /api/services/app/Test/Delete with the id on of the record you previously inserted with the scripts. Stacktrace

INFO 2019-03-28 15:29:42,041 [32 ] soft.AspNetCore.Hosting.Internal.WebHost - Request starting HTTP/1.1 DELETE http://localhost:23000/api/services/app/Test/Delete?Id=13
INFO 2019-03-28 15:29:42,044 [32 ] pNetCore.Cors.Infrastructure.CorsService - CORS policy execution failed. INFO 2019-03-28 15:29:42,046 [32 ] pNetCore.Cors.Infrastructure.CorsService - Request origin http://localhost:23000 does not have permission to access the resource. INFO 2019-03-28 15:29:42,055 [32 ] ore.Mvc.Internal.ControllerActionInvoker - Route matched with {area = “app”, action = “Delete”, controller = “Test”}. Executing action Interceptors.Tests.TestAppService.Delete (Interceptors.Application) INFO 2019-03-28 15:29:42,057 [32 ] pNetCore.Cors.Infrastructure.CorsService - CORS policy execution failed. INFO 2019-03-28 15:29:42,059 [32 ] pNetCore.Cors.Infrastructure.CorsService - Request origin http://localhost:23000 does not have permission to access the resource. INFO 2019-03-28 15:29:42,066 [32 ] ore.Mvc.Internal.ControllerActionInvoker - Executing action method Interceptors.Tests.TestAppService.Delete (Interceptors.Application) with arguments (Interceptors.Tests.Dtos.DeleteIn) - Validation state: Valid ERROR 2019-03-28 15:29:43,843 [36 ] Mvc.ExceptionHandling.AbpExceptionFilter - A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations. System.InvalidOperationException: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations. at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection() at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IReadOnlyList1 entriesToSave) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess) at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess) at Abp.EntityFrameworkCore.AbpDbContext.SaveChanges() in D:\Github\aspnetboilerplate\src\Abp.EntityFrameworkCore\EntityFrameworkCore\AbpDbContext.cs:line 208 at Abp.Zero.EntityFrameworkCore.AbpZeroCommonDbContext3.SaveChanges() in D:\Github\aspnetboilerplate\src\Abp.ZeroCore.EntityFrameworkCore\Zero\EntityFrameworkCore\AbpZeroCommonDbContext.cs:line 159 at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesInDbContext(DbContext dbContext) in D:\Github\aspnetboilerplate\src\Abp.EntityFrameworkCore\EntityFrameworkCore\Uow\EfCoreUnitOfWork.cs:line 163 at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChanges() in D:\Github\aspnetboilerplate\src\Abp.EntityFrameworkCore\EntityFrameworkCore\Uow\EfCoreUnitOfWork.cs:line 60 at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.CompleteUow() in D:\Github\aspnetboilerplate\src\Abp.EntityFrameworkCore\EntityFrameworkCore\Uow\EfCoreUnitOfWork.cs:line 77 at Abp.Domain.Uow.UnitOfWorkBase.Complete() in D:\Github\aspnetboilerplate\src\Abp\Domain\Uow\UnitOfWorkBase.cs:line 256 at Abp.Auditing.AuditingHelper.Save(AuditInfo auditInfo) in D:\Github\aspnetboilerplate\src\Abp\Auditing\AuditingHelper.cs:line 135 at Abp.Auditing.AuditingInterceptor.SaveAuditInfo(AuditInfo auditInfo, Stopwatch stopwatch, Exception exception, Task task) in D:\Github\aspnetboilerplate\src\Abp\Auditing\AuditingInterceptor.cs:line 111 at Abp.Auditing.AuditingInterceptor.<>c__DisplayClass6_0.<PerformAsyncAuditing>b__0(Exception exception) in D:\Github\aspnetboilerplate\src\Abp\Auditing\AuditingInterceptor.cs:line 90 at Abp.Threading.InternalAsyncHelper.AwaitTaskWithFinally(Task actualReturnValue, Action`1 finalAction) in D:\Github\aspnetboilerplate\src\Abp\Threading\InternalAsyncHelper.cs:line 24 at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync() at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync() INFO 2019-03-28 15:29:59,760 [36 ] .Mvc.Infrastructure.ObjectResultExecutor - Executing ObjectResult, writing value of type ‘Abp.Web.Models.AjaxResponse’. INFO 2019-03-28 15:29:59,769 [36 ] ore.Mvc.Internal.ControllerActionInvoker - Executed action Interceptors.Tests.TestAppService.Delete (Interceptors.Application) in 17711.8481ms INFO 2019-03-28 15:29:59,772 [36 ] soft.AspNetCore.Hosting.Internal.WebHost - Request finished in 17731.019ms 500 application/json; charset=utf-8

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:1
  • Comments:10 (7 by maintainers)

github_iconTop GitHub Comments

6reactions
stakxcommented, Apr 5, 2019

FYI Castle.Core 4.4.0 (which includes the changes mentioned above) has been published on NuGet today.

4reactions
stakxcommented, Mar 29, 2019

Hi aspnetboilerplace project! Hailing from Castle.Core for a quick visit. A quick comment regarding https://aspnetboilerplate.com/Pages/Documents/Articles/Aspect-Oriented-Programming-using-Interceptors/index.html#ArticleInterceptAsync:

First of all, I could not find a way of executing async code before invocation.Proceed().

We’ve finally merged a solution to enable Proceed-after-await in interceptors: see https://github.com/castleproject/Core/pull/439, as well as some new documentation that describes the problem and the proposed solution. This will become available in the next release of Castle.Core (shouldn’t be too long).

Read more comments on GitHub >

github_iconTop Results From Across the Web

Intercept the call to an async method using DynamicProxy
Presumably the "problem" is that it's just logging that it's returning a task - and you want the value within that task?
Read more >
Making DynamicProxy Interceptor's Intercept method an ...
So problem is awaiting anything before "invocation.Proceed();" because proxy isn't generating async code. (http://stackoverflow.com/questions/ ...
Read more >
Intercepting Asynchronous Methods Using Unity Interception
Interceptors determine the mechanism used to intercept the calls to methods in the intercepted object, while the interception behaviors determine the actions ...
Read more >
36.1 Asynchronous Method Invocation - Java Platform ...
isDone method to determine whether processing has completed before calling one of the get methods. The get() method returns the result as the...
Read more >
Aspect Oriented Programming using Interceptors within ...
Because, an async method immediately returns a Task and it's executed asynchronously. So, we can not measure when it's actually completed ( ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found