Skip to content

Multiple concurrent queries InvalidOperationException #34882

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

Merged
merged 1 commit into from
Mar 5, 2025

Conversation

guardrex
Copy link
Collaborator

@guardrex guardrex commented Mar 4, 2025

Addresses #34881

Thanks @MagistratasHK! 🚀 ...

Cross-references:

I'm going to add a section to the QuickGrid article, and I'm even going to provide the full-blown replacement component for the Index component in the tutorial as the example here. That makes it easy to understand the before-and-after changes required. As Javier remarked, "[this] is not trivial." 😆

I won't take the extreme measure of changing the entire component in the tutorial because the simpler component, prone to the error, is scaffolded into to the app. There's also a good chance that the original code won't change. I suspect that they'll fix the problem inside the items provider. That will make my work easy here because I'm setting up this new section for <10.0, so the entire section will drop out of coverage automatically in November when 10.0 releases and only appear to readers when they're looking at an earlier version of this article.

I'll leave this up until EOD to give you a chance to look it over and provide feedback. The original issue for this work won't close because I'll need to keep an 👁️ on the product unit issue and see if I need to make further updates later.


Internal previews

📄 File 🔗 Preview link
aspnetcore/blazor/components/quickgrid.md aspnetcore/blazor/components/quickgrid

@guardrex guardrex self-assigned this Mar 4, 2025
@guardrex guardrex merged commit 4497955 into main Mar 5, 2025
3 checks passed
@guardrex guardrex deleted the guardrex-patch-2 branch March 5, 2025 17:44
@MagistratasHK
Copy link

It will be interesting to see how they implement the fix seeing how this threading issue is not resolved by simple CancellationToken. It only pushes it deeper.

The proposed implementation is better, but it still throws an exception, two in fact. If you type fast enough, following exceptions might be thrown: System.InvalidOperationException and System.Threading.Tasks.TaskCanceledException.

The more frequent exception is thrown by the EF Core itself. From the EF ExecuteAsync documentation:

A cancellation token used to cancel the retry operation, but not operations that are already in flight or that already completed successfully.

A task that will run to completion if the original task completes successfully (either the first time or after retrying transient failures). If the task fails with a non-transient error or the retry limit is reached, the returned task will become faulted and the exception must be observed.

The less frequent exception is thrown by the SQL provider. It is Micrsofot.Data.SqlClient.SqlException wrapped in System.InvalidOperationException (0x80131904): The request failed to run because the batch is aborted, this can be caused by abort signal sent from client, or another request is running in the same session, which makes the session busy.
Operation cancelled by user..

I suggest following changes to the sample:

  • Add missing filtering.
  • Add exception handling/suppression when CancellationToken.IsCancellationRequested == true
public async ValueTask<GridItemsProviderResult<Movie>> GetMovies(GridItemsProviderRequest<Movie> request)
{
	using var context = DbFactory.CreateDbContext();
	Movie[] items = new Movie[0];
	int totalCount = 0;

	IQueryable<Movie> query = context.Movie.Where(m => m.Title!.Contains(titleFilter));

	try
	{
		totalCount = await query.CountAsync(request.CancellationToken);

		query = query.OrderBy(x => x.Id);
		query = request.ApplySorting(query).Skip(request.StartIndex);

		if (request.Count.HasValue)
		{
			query = query.Take(request.Count.Value);
		}

		items = await query.ToArrayAsync(request.CancellationToken);
	}
	catch (Exception ex) when (ex is System.InvalidOperationException || ex is System.Threading.Tasks.TaskCanceledException)
	{
		if (request.CancellationToken.IsCancellationRequested)
			Logger.LogError(ex, "Thread cancelled, CancellationToken.IsCancellationRequested = " + request.CancellationToken.IsCancellationRequested);
		else
			throw;
	}

	var result = new GridItemsProviderResult<Movie>
		{
			Items = items,
			TotalItemCount = totalCount
		};

	return result;
}

Exceptions that need handling:

fail: BlazorWebAppMovies.Components.Pages.MoviePages.Index[0]
      Thread cancelled, CancellationToken.IsCancellationRequested = True
      System.Threading.Tasks.TaskCanceledException: A task was canceled.
         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.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToArrayAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
         at BlazorWebAppMovies.Components.Pages.MoviePages.Index.GetMovies(GridItemsProviderRequest`1 request) in D:\Projects\blazor-samples\8.0\BlazorWebAppMovies\Components\Pages\MoviePages\Index.razor:line 76
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (55ms) [Parameters=[@__titleFilter_0_contains='?' (Size = 60)], CommandType='Text', CommandTimeout='30']
      SELECT COUNT(*)
      FROM [Movie] AS [m]
      WHERE [m].[Title] LIKE @__titleFilter_0_contains ESCAPE N'\'
info: BlazorWebAppMovies.Components.Pages.MoviePages.Index[0]
      CancellationToken.IsCancellationRequested = False
fail: BlazorWebAppMovies.Components.Pages.MoviePages.Index[0]
      Thread cancelled, CancellationToken.IsCancellationRequested = True
      System.InvalidOperationException: An exception has been raised that is likely due to a transient failure. Consider enabling transient error resiliency by adding 'EnableRetryOnFailure' to the 'UseSqlServer' call.
       ---> Microsoft.Data.SqlClient.SqlException (0x80131904): The request failed to run because the batch is aborted, this can be caused by abort signal sent from client, or another request is running in the same session, which makes the session busy.
      Operation cancelled by user.
         at Microsoft.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__211_0(Task`1 result)
         at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
         at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
      --- End of stack trace from previous location ---
         at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
         at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
      --- End of stack trace from previous location ---
         at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
      ClientConnectionId:8f760eb5-ebe0-422d-99cd-e04439f6269d
      Error Number:3980,State:1,Class:16
         --- End of inner exception stack trace ---
         at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
         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.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToArrayAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
         at BlazorWebAppMovies.Components.Pages.MoviePages.Index.GetMovies(GridItemsProviderRequest`1 request) in D:\Projects\blazor-samples\8.0\BlazorWebAppMovies\Components\Pages\MoviePages\Index.razor:line 76```

@guardrex
Copy link
Collaborator Author

guardrex commented Mar 12, 2025

Best to place any discussion on it on their issue. This is closed, and the docs tracking issue will react to whatever they do. I'm watching them 👁️.

If Javier likes your approach better than the workaround that he provided (the one in the article now), then he should let me know that he would like it swapped in. Then, I'll open a new issue to take care of it. Ask him on their issue if he likes your proposal better.

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

Successfully merging this pull request may close these issues.

2 participants