ASP.NET에서 인증 필터를 만들어 쓰는데 DbContext 에러가 났다.

ASP.NET에서 인증 필터를 만들어 쓰는데 DbContext 에러가 났다.

어처구니 없는 실패

ASP.NET에서 권한 레벨 기능을 도입하는 중에 IAuthorizationFilter를 구현한 커스텀 필터를 제작해 사용하려 했는데, 다음과 같은 에러가 지속적으로 발생했다.

System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
at Microsoft.EntityFrameworkCore.Infrastructure.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`9.GetRolesAsync(TUser user, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Identity.UserManager`1.GetRolesAsync(TUser user)
at [FILTERED].RequiresPermissionAttribute.OnAuthorization(AuthorizationFilterContext context) in Q:\Works\naits__\[FILTERED]\[FILTERED]\Filters\RequiresPermissionAttribute.cs:line 43
at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_1(Object state)
at System.Threading.QueueUserWorkItemCallback.Execute()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()

이 예외는 이전 DbContext의 작업이 완료되기 전에 새 DbContext 작업이 시작될 경우 발생하는 문제인데, 문제는 async/await도 제대로 작성한 마당에 왜 이런 비동기 문제가 발생하는지 한동안 알 수 없었다는 것이다.

IAuthorizationFilter, IAsyncAuthorizationFilter

거의 한 시간을 삽질하다가, 문득 이상한 걸 발견했다. 그건 바로 내 필터의 OnAuthorization 메서드가 async void로 구현되어 있다는 것.

요새 하도 WPF를 만지작거려서 그런가, 무턱대고 OnAuthorization 메서드의 타입이 void이니 async void로 구현해서 UserManager, RoleManager 등의 비동기 메서드를 await하고자 했던 것이다.

당연히, 이는 올바른 사용 방법이 아니었다.

비동기 필터는 비동기 필터 전용 인터페이스를 구현하도록 하자

닷넷에는 비동기 필터를 위한 IAsyncAuthorizationFilter라는 인터페이스가 따로 존재한다. 이 인터페이스의 실제 인증 처리 부분은 Task OnAuthorizationAsync로, 확실하게 비동기 처리를 위해 디자인되어 있다.

따라서 이 인터페이스를 구현한 필터를 사용하면, 비동기 액션을 처리할 때 정상적으로 필터 처리가 우선 완료된 뒤 액션이 처리되게 된다.

결론

시간이 촉박하더라도 작업 내용을 항상 꼼꼼히 체크하도록 하자. 인텔리센스 자동완성으로 IAuthorizationFilter를 칠 때 바로 아래에 IAsyncAuthorizationFilter가 표시됐음에도 불구하고 이런 어처구니없는 실패를 겪었다.

이런 일이 발생할 때마다 자괴감이 든다.

댓글

이 블로그의 인기 게시물

C# 남아도는 메모리에도 불구하고 OutOfMemoryException이 발생한다면?

USB를 뒤는 괜찮은데 앞에 꽂으면 인식이 힘들다?

MySQL 데이터 타입과 Java 데이터 타입 비교/매칭