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

How can I abort a running EF Core Query using GetAsyncEnumerator? #23652

Closed
janschreier opened this issue Dec 11, 2020 · 7 comments
Closed

How can I abort a running EF Core Query using GetAsyncEnumerator? #23652

janschreier opened this issue Dec 11, 2020 · 7 comments

Comments

@janschreier
Copy link

I am using EF Core 5.0 and have the following code:

public async IAsyncEnumerable<MyDataItem> GetRueckrufe([EnumeratorCancellation] CancellationToken cancellationToken)
{
    await using var dbContext = CreateDbContext();
    //Isolationlevel is required to not cause any issues with parallel working on already read items
    await dbContext.Database.BeginTransactionAsync(IsolationLevel.ReadUncommitted, cancellationToken).ConfigureAwait(false);
    var enumerator = dbContext.MyDataItem
        .OrderByDescending(mdi => mdi.Id)
        .AsNoTracking()
        .AsAsyncEnumerable()
        .GetAsyncEnumerator(cancellationToken);

    try
    {
        while (await enumerator.MoveNextAsync().ConfigureAwait(false))
        {
            yield return enumerator.Current;
        }
    }
    finally
    {
        await enumerator.DisposeAsync()).ConfigureAwait(false);
    }
}

It works as expected allowing me to populate a datagrid while more data is still loaded. If I cancel the provided CancelationToken, first I get a TaskCanceledException on the line MoveNextAsync() which is expected.

BUT: I can see in SQL Profiler that the SQL query itself is not aborted but always runs until all data is loaded and only then I get a second TaskCanceledException on that same line.

How do I abort the query itself?

provider and version information

EF Core version: 5.0.1
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: NET 5.0.1
Operating system: Win 10 1809
IDE: Visual Studio 2019 16.8.3

@roji
Copy link
Member

roji commented Dec 11, 2020

Just to be sure... You say you get a TaskCanceledException from MoveNextAsync, but then you get a second TaskCanceledException on that same line? In your code above, the first exception should make you quit the loop and dispose the enumerator immediately, so it's not clear where the second TaskCanceledException would come from... Can you please clarify and possibly provide the specific code sample you're using?

Aside from that, EF Core passes the given cancellation token to underlying operations performed on the ADO.NET provider, and it's their responsibility to cancel the query at the server - it's possible that something isn't working properly there. Will wait for an answer on the above before investigating.

@janschreier
Copy link
Author

Hi Shay,
thanks for looking into this.

Here's the exception messages. Line 99 is while (await enumerator.MoveNextAsync().ConfigureAwait(false))

first exception (stopping enumerator, works as expected)

System.Threading.Tasks.TaskCanceledException
HResult=0x8013153B
Message=A task was canceled.
Source=System.Private.CoreLib
StackTrace:
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.d__632.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.<TaskAwaiter>d__69.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.Query.Internal.SplitQueryingEnumerable1.AsyncEnumerator.d__16.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable`1.ConfiguredValueTaskAwaiter.GetResult()
at Repository.d__8.MoveNext() in Repository.cs:line 99

This exception was originally thrown at this call stack:
[External Code]
Repository.d__8.MoveNext() in Repository.cs

Second exception (is thrown once I see the query completed in SQL Profiler)

System.Threading.Tasks.TaskCanceledException
HResult=0x8013153B
Message=A task was canceled.
Source=System.Private.CoreLib
StackTrace:
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.d__632.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.<TaskAwaiter>d__69.MoveNext() at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.EntityFrameworkCore.Query.Internal.SplitQueryingEnumerable1.AsyncEnumerator.d__16.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable`1.ConfiguredValueTaskAwaiter.GetResult()
at Repository.d__8.MoveNext() in Repository.cs:line 99
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Repository.d__8.MoveNext() in Repository.cs:line 99

This exception was originally thrown at this call stack:
[External Code]
Repository.d__8.MoveNext() in Repository.cs
[External Code]
Repository.d__8.MoveNext() in Repository.cs

In case it helps, here's my connection string for ef core:

var options = new DbContextOptionsBuilder<RueckrufDbContext>()
    .UseSqlServer( 
        $"data source={server};initial catalog=databasename;integrated security=True;MultipleActiveResultSets=True;App=appname;")
    .Options;

(ATM I would also guess that the issue lies within the ADO.net provider)

@roji
Copy link
Member

roji commented Dec 11, 2020

@janschreier I'm still unclear on how the code you posted above could produce the two exceptions below... Can you please post a full, runnable code sample so that I can investigate?

FYI your original method can be simplified by using WithCancellation and not interacting with the enumerator directly:

public async IAsyncEnumerable<Blog> GetRueckrufe([EnumeratorCancellation] CancellationToken cancellationToken)
{
    await using var ctx = new BlogContext();
    await foreach (var blog in ctx.Blogs.AsAsyncEnumerable().WithCancellation(cancellationToken))
    {
        yield return blog;
    }
}

@smitpatel
Copy link
Contributor

Seems like first chance exceptions case. When underlying will throw an exception (like above), the enclosing querying enumerable catch it, log a message and re-throw it.

@janschreier
Copy link
Author

Thanks to both of you, I'll get back to you, once I have the example ready.

@janschreier
Copy link
Author

Ehm, how should I start.
My original EF query looked like so:

ctx.Rueckruf
    .AsSplitQuery()
    .Include(r => r.RueckrufProduktzuordnung)
    .ThenInclude(rp => rp.ModellVariante.BaureiheGeneration.Baureihe)
    .Include(r => r.RueckrufProduktzuordnung)
    .ThenInclude(rp => rp.BaureiheGeneration.Baureihe)
    .Include(r => r.Produktart)
    .Include(r => r.Bearbeitungsstatus)
    .Include(r => r.MarkeHerstellerkontakt)
    .Include(r => r.Marke)
    .OrderByDescending(rm => rm.RueckrufId)
    .AsNoTracking()
    .AsAsyncEnumerable()
    .WithCancellation(cancellationToken);

and I removed the important bit when trying to make a short example. Ivan Stoev guessed correctly on SO that the behavior I see is only shown, when using AsSplitQuery(). I removed the AsSplitQuery (fortunately uncritical for my current use-case) and cancellation works like a charm. So for me the case can be closed but canceling behavior of split queries seems to be a thing where cancellation might need improvements (that's why I leave this issue open but if a team member decides to close it, I am fine with it, too).

@smitpatel
Copy link
Contributor

I am not able to get repro for this. Seeing only 1 exception for non-buffering, mars enabled split query scenario. I found few bugs in how cancellation token was being used in query. (probably few more were fixed also from the time issue was filed). Created PR #25918

Closing this issue as no repro. We can re-open if a stand-alone runnable repro is provided which shows the indicated behavior on latest packages.

@smitpatel smitpatel removed this from the 6.0.0 milestone Sep 8, 2021
@smitpatel smitpatel removed their assignment Sep 8, 2021
@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants