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

RC2 Deadlock problem with CountAsync() that does not happen with Count() #5481

Closed
iberodev opened this issue May 24, 2016 · 12 comments
Closed
Assignees
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Milestone

Comments

@iberodev
Copy link

I have found an inconsistency when querying a query.CountAsync();. It does not throw any error nor displays any error on console, it simply gets into a deadlock state (the application is still up but the next line of code does not get to execute)

The problem does not happen when using the same query to count the items in a synchronous way: query.Count();

Scenario:

I have an entity that has 2 optional references to another entity

public class Shipment 
{
  public int Id { get; set; }
  public string CarrierName { get; set; }
  public int? CargoReportId { get; set; }
  public CargoReport CargoReport { get; set; }
  public int? AltCargoReportId { get; set; }
  public CargoReport AltCargoReport { get; set; }
}

public class Report
{
  public int Id { get; set; }
  public string Reference { get; set; }
}

Now I try to count the items in database with a simple query and the synchronous call works fine but the asynchronous call gets the code onto a deadlock state:

private IQueryable<Shipment> GetTestQuery(ShipmentDirection direction) {
    var shipmentsQuery = _context.Shipments
            .Include(s => s.CargoReport)
            .Include(s => s.AltCargoReport)
            .Where(s => s.CarrierName == "carrier"
                    || s.CargoReport.Reference.Contains("whatever") 
                    || s.AltCargoReport.Reference.Contains("whatever"));
    return shipmentsQuery;
}

public async Task<int> MyTestRepositoryCount()
{
  var count1 = await _context.Shipments.CountAsync(); //this works well
  var shipmentsQuery = GetTestQuery(direction);
  var count2 = shipmentsQuery.Count(); //this works well
  var count3= await shipmentsQuery.CountAsync(); //this DOES NOT work. No exception, just deadlock
  return count3; //this never executes
}

I know the problem has to do with CargoReport and AltCargoReport being a null reference because If I had something like this:

private IQueryable<Shipment> GetTestQuery(ShipmentDirection direction) {
   var shipmentsQuery = _context.Shipments
            .Include(s => s.CargoReport)
            .Include(s => s.AltCargoReport)
            .Where(s => //s.CarrierName == "carrier" || //Noticed this condition commented out
                       s.CargoReport.Reference.Contains("whatever") 
                    || s.AltCargoReport.Reference.Contains("whatever"));
    return shipmentsQuery;
}

I would get the following exception when executing the count2 line:

Microsoft.EntityFrameworkCore.Storage.Internal.SqlServerConnection:Debug: Closing connection to database 'SpeediCargo' on server '(localdb)\mssqllocaldb'.
Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler:Error: An exception occurred in the database while iterating the results of a query.
System.NullReferenceException: Object reference not set to an instance of an object.
at lambda_method(Closure , TransparentIdentifier2 ) at System.Linq.Enumerable.WhereSelectEnumerableIterator2.MoveNext()
at System.Linq.Enumerable.Count[TSource](IEnumerable1 source) at lambda_method(Closure , QueryContext ) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass19_11.b__1(QueryContext qc)

System.NullReferenceException: Object reference not set to an instance of an object.
at lambda_method(Closure , TransparentIdentifier2 ) at System.Linq.Enumerable.WhereSelectEnumerableIterator2.MoveNext()
at System.Linq.Enumerable.Count[TSource](IEnumerable1 source) at lambda_method(Closure , QueryContext ) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass19_11.b__1(QueryContext qc)
Exception thrown: 'System.NullReferenceException' in Microsoft.EntityFrameworkCore.dll
Exception thrown: 'System.NullReferenceException' in mscorlib.ni.dll

So a few things here:

  1. How should I have the conditions on CargoReport and AltCargoReport to handle null? Shouldn't Entity Framework take care of that for me?
  2. Why the .Count() works well but the .CountAsync() doesn't? shouldn't be both consistent?
  3. In case it's fine that the behaviour is not consistent. Shouldn't the .CountAsync() call at least throw a null reference exception if that's the case for the deadlock?

Thanks guys.

@anpete
Copy link
Contributor

anpete commented May 27, 2016

@maumar for the nav prop null expansion, should this be null ref'ing?

@anpete
Copy link
Contributor

anpete commented May 27, 2016

@iberodev Does the deadlock occur if you remove the Includes. BTW, the Includes are not needed if you are just doing a Count.

@iberodev
Copy link
Author

iberodev commented May 29, 2016

@anpete Yes, if I remove the Includes the deadlock still happens when using .CountAsync(). No difference.
PS: Thanks for the tip about the Includes not being necessary for the count. Didn't know that.

@anpete
Copy link
Contributor

anpete commented Jun 1, 2016

Ping @maumar

@anpete
Copy link
Contributor

anpete commented Jun 1, 2016

@iberodev No luck repro'ing at my end yet. Are you able to create a self-contained repro for this?

@maumar
Copy link
Contributor

maumar commented Jun 1, 2016

@anpete it looks like a bug in the way we protect from null refs during optional navigation rewrites. Both CargoReport and AltCargoReport are optional, so only the first one will be translated to sql. The second one is done on the client. We do apply our null compensation logic, but we produce something like this:

(t3.Outer.Outer.Inner != null ? t3.Outer.Outer.Inner.Reference : null).Contains(
            value: whatever
        )

@maumar
Copy link
Contributor

maumar commented Jun 1, 2016

filed #5613 to track this

@iberodev
Copy link
Author

iberodev commented Jun 2, 2016

@anpete I have created a sample repository with a project showing the deadlock issue.
Instructions in the README. I hope it helps.

https://github.com/iberodev/EntityFramework5481

PS: The mapping for the navigation property does not seem quite right but this is how it was in our project. Tomorrow I will have a look at it but I don't think it has anything to do with the deadlock issue.

@anpete
Copy link
Contributor

anpete commented Jun 2, 2016

@iberodev Thanks!

@anpete anpete removed this from the 1.0.0 milestone Jun 3, 2016
@anpete
Copy link
Contributor

anpete commented Jun 3, 2016

Looks like the deadlock is in IX-Async|GroupJoin. Fix is to write our own version. Note for triage, to hit this the query needs to hit two optional nav props.

@divega divega modified the milestone: 1.0.1 Jun 6, 2016
@divega
Copy link
Contributor

divega commented Jun 7, 2016

Clearing up milestone again, so we can discuss with @anpete in triage.

@divega divega added this to the 1.0.0 milestone Jun 7, 2016
@divega
Copy link
Contributor

divega commented Jun 7, 2016

Triage: we will attempt to fix this for RTM.

@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Oct 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Projects
None yet
Development

No branches or pull requests

6 participants