Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
roji committed Feb 12, 2021
1 parent 40de47b commit 7bede79
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 70 deletions.
159 changes: 92 additions & 67 deletions src/EFCore.Relational/Query/Internal/SingleQueryingEnumerable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class SingleQueryingEnumerable<T> : IEnumerable<T>, IAsyncEnumerable<T>,
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _queryLogger;
private readonly bool _standAloneStateManager;
private readonly bool _detailedErrorsEnabled;
private readonly bool _concurrencyDetectionEnabled;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -44,7 +45,8 @@ public SingleQueryingEnumerable(
[NotNull] Func<QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator, T> shaper,
[NotNull] Type contextType,
bool standAloneStateManager,
bool detailedErrorsEnabled)
bool detailedErrorsEnabled,
bool concurrencyDetectionEnabled)
{
_relationalQueryContext = relationalQueryContext;
_relationalCommandCache = relationalCommandCache;
Expand All @@ -53,6 +55,7 @@ public SingleQueryingEnumerable(
_queryLogger = relationalQueryContext.QueryLogger;
_standAloneStateManager = standAloneStateManager;
_detailedErrorsEnabled = detailedErrorsEnabled;
_concurrencyDetectionEnabled = concurrencyDetectionEnabled;
}

/// <summary>
Expand Down Expand Up @@ -123,6 +126,7 @@ private sealed class Enumerator : IEnumerator<T>
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _queryLogger;
private readonly bool _standAloneStateManager;
private readonly bool _detailedErrorsEnabled;
private readonly bool _concurrencyDetectionEnabled;

private RelationalDataReader? _dataReader;
private SingleQueryResultCoordinator? _resultCoordinator;
Expand All @@ -136,6 +140,7 @@ public Enumerator(SingleQueryingEnumerable<T> queryingEnumerable)
_queryLogger = queryingEnumerable._queryLogger;
_standAloneStateManager = queryingEnumerable._standAloneStateManager;
_detailedErrorsEnabled = queryingEnumerable._detailedErrorsEnabled;
_concurrencyDetectionEnabled = queryingEnumerable._concurrencyDetectionEnabled;
Current = default!;
}

Expand All @@ -146,58 +151,67 @@ object IEnumerator.Current

public bool MoveNext()
{
if (_concurrencyDetectionEnabled)
{
_relationalQueryContext.ConcurrencyDetector.EnterCriticalSection();
}

try
{
using (_relationalQueryContext.ConcurrencyDetector.EnterCriticalSection())
if (_dataReader == null)
{
if (_dataReader == null)
{
_relationalQueryContext.ExecutionStrategyFactory.Create()
.Execute(true, InitializeReader, null);
}
_relationalQueryContext.ExecutionStrategyFactory.Create()
.Execute(true, InitializeReader, null);
}

var hasNext = _resultCoordinator!.HasNext ?? _dataReader!.Read();
Current = default!;
var hasNext = _resultCoordinator!.HasNext ?? _dataReader!.Read();
Current = default!;

if (hasNext)
if (hasNext)
{
while (true)
{
while (true)
_resultCoordinator.ResultReady = true;
_resultCoordinator.HasNext = null;
Current = _shaper(
_relationalQueryContext, _dataReader!.DbDataReader, _resultCoordinator.ResultContext,
_resultCoordinator);
if (_resultCoordinator.ResultReady)
{
// We generated a result so null out previously stored values
_resultCoordinator.ResultContext.Values = null;
break;
}

if (!_dataReader.Read())
{
_resultCoordinator.HasNext = false;
// Enumeration has ended, materialize last element
_resultCoordinator.ResultReady = true;
_resultCoordinator.HasNext = null;
Current = _shaper(
_relationalQueryContext, _dataReader!.DbDataReader, _resultCoordinator.ResultContext,
_relationalQueryContext, _dataReader.DbDataReader, _resultCoordinator.ResultContext,
_resultCoordinator);
if (_resultCoordinator.ResultReady)
{
// We generated a result so null out previously stored values
_resultCoordinator.ResultContext.Values = null;
break;
}

if (!_dataReader.Read())
{
_resultCoordinator.HasNext = false;
// Enumeration has ended, materialize last element
_resultCoordinator.ResultReady = true;
Current = _shaper(
_relationalQueryContext, _dataReader.DbDataReader, _resultCoordinator.ResultContext,
_resultCoordinator);

break;
}

break;
}
}

return hasNext;
}

return hasNext;
}
catch (Exception exception)
{
_queryLogger.QueryIterationFailed(_contextType, exception);

throw;
}
finally
{
if (_concurrencyDetectionEnabled)
{
_relationalQueryContext.ConcurrencyDetector.ExitCriticalSection();
}
}
}

private bool InitializeReader(DbContext _, bool result)
Expand Down Expand Up @@ -241,6 +255,7 @@ private sealed class AsyncEnumerator : IAsyncEnumerator<T>
private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _queryLogger;
private readonly bool _standAloneStateManager;
private readonly bool _detailedErrorsEnabled;
private readonly bool _concurrencyDetectionEnabled;

private RelationalDataReader? _dataReader;
private SingleQueryResultCoordinator? _resultCoordinator;
Expand All @@ -254,67 +269,77 @@ public AsyncEnumerator(SingleQueryingEnumerable<T> queryingEnumerable)
_queryLogger = queryingEnumerable._queryLogger;
_standAloneStateManager = queryingEnumerable._standAloneStateManager;
_detailedErrorsEnabled = queryingEnumerable._detailedErrorsEnabled;
_concurrencyDetectionEnabled = queryingEnumerable._concurrencyDetectionEnabled;
Current = default!;
}

public T Current { get; private set; }

public async ValueTask<bool> MoveNextAsync()
{
if (_concurrencyDetectionEnabled)
{
_relationalQueryContext.ConcurrencyDetector.EnterCriticalSection();
}

try
{
using (_relationalQueryContext.ConcurrencyDetector.EnterCriticalSection())
if (_dataReader == null)
{
if (_dataReader == null)
{
await _relationalQueryContext.ExecutionStrategyFactory.Create()
.ExecuteAsync(true, InitializeReaderAsync, null, _relationalQueryContext.CancellationToken)
.ConfigureAwait(false);
}
await _relationalQueryContext.ExecutionStrategyFactory.Create()
.ExecuteAsync(true, InitializeReaderAsync, null, _relationalQueryContext.CancellationToken)
.ConfigureAwait(false);
}

var hasNext = _resultCoordinator!.HasNext
?? await _dataReader!.ReadAsync(_relationalQueryContext.CancellationToken).ConfigureAwait(false);
Current = default!;
var hasNext = _resultCoordinator!.HasNext
?? await _dataReader!.ReadAsync(_relationalQueryContext.CancellationToken).ConfigureAwait(false);
Current = default!;

if (hasNext)
if (hasNext)
{
while (true)
{
while (true)
_resultCoordinator.ResultReady = true;
_resultCoordinator.HasNext = null;
Current = _shaper(
_relationalQueryContext, _dataReader!.DbDataReader, _resultCoordinator.ResultContext,
_resultCoordinator);
if (_resultCoordinator.ResultReady)
{
// We generated a result so null out previously stored values
_resultCoordinator.ResultContext.Values = null;
break;
}

if (!await _dataReader.ReadAsync(_relationalQueryContext.CancellationToken).ConfigureAwait(false))
{
_resultCoordinator.HasNext = false;
// Enumeration has ended, materialize last element
_resultCoordinator.ResultReady = true;
_resultCoordinator.HasNext = null;
Current = _shaper(
_relationalQueryContext, _dataReader!.DbDataReader, _resultCoordinator.ResultContext,
_relationalQueryContext, _dataReader.DbDataReader, _resultCoordinator.ResultContext,
_resultCoordinator);
if (_resultCoordinator.ResultReady)
{
// We generated a result so null out previously stored values
_resultCoordinator.ResultContext.Values = null;
break;
}

if (!await _dataReader.ReadAsync(_relationalQueryContext.CancellationToken).ConfigureAwait(false))
{
_resultCoordinator.HasNext = false;
// Enumeration has ended, materialize last element
_resultCoordinator.ResultReady = true;
Current = _shaper(
_relationalQueryContext, _dataReader.DbDataReader, _resultCoordinator.ResultContext,
_resultCoordinator);

break;
}

break;
}
}

return hasNext;
}

return hasNext;
}
catch (Exception exception)
{
_queryLogger.QueryIterationFailed(_contextType, exception);

throw;
}
finally
{
if (_concurrencyDetectionEnabled)
{
_relationalQueryContext.ConcurrencyDetector.ExitCriticalSection();
}
}
}

private async Task<bool> InitializeReaderAsync(DbContext _, bool result, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public partial class RelationalShapedQueryCompilingExpressionVisitor : ShapedQue
{
private readonly Type _contextType;
private readonly ISet<string> _tags;
private readonly bool _concurrencyDetectionEnabled;
private readonly bool _detailedErrorsEnabled;
private readonly bool _useRelationalNulls;

Expand All @@ -43,6 +44,7 @@ public RelationalShapedQueryCompilingExpressionVisitor(

_contextType = queryCompilationContext.ContextType;
_tags = queryCompilationContext.Tags;
_concurrencyDetectionEnabled = queryCompilationContext.IsConcurrencyDetectionEnabled;
_detailedErrorsEnabled = relationalDependencies.CoreSingletonOptions.AreDetailedErrorsEnabled;
_useRelationalNulls = RelationalOptionsExtension.Extract(queryCompilationContext.ContextOptions).UseRelationalNulls;
}
Expand Down Expand Up @@ -79,7 +81,8 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery
Expression.Constant(_contextType),
Expression.Constant(
QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution),
Expression.Constant(_detailedErrorsEnabled));
Expression.Constant(_detailedErrorsEnabled),
Expression.Constant(_concurrencyDetectionEnabled));
}

if (splitQuery)
Expand All @@ -102,7 +105,8 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery
Expression.Constant(_contextType),
Expression.Constant(
QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution),
Expression.Constant(_detailedErrorsEnabled));
Expression.Constant(_detailedErrorsEnabled),
Expression.Constant(_concurrencyDetectionEnabled));
}

return Expression.New(
Expand All @@ -113,7 +117,8 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery
Expression.Constant(_contextType),
Expression.Constant(
QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution),
Expression.Constant(_detailedErrorsEnabled));
Expression.Constant(_detailedErrorsEnabled),
Expression.Constant(_concurrencyDetectionEnabled));
}
}
}
22 changes: 22 additions & 0 deletions src/EFCore/DbContextOptionsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,28 @@ public virtual DbContextOptionsBuilder LogTo(
private DbContextOptionsBuilder LogTo([NotNull] IDbContextLogger logger)
=> WithOption(e => e.WithDbContextLogger(logger));

/// <summary>
/// <para>
/// Disables concurrency detection, which detects many cases of erroneous concurrent usage of a <see cref="DbContext" />
/// instance and causes an informative exception to be thrown. This provides a minor performance improvement, but if a
/// <see cref="DbContext" /> instance is used concurrently, the behavior will be undefined and the program may fail in
/// unpredictable ways.
/// </para>
/// <para>
/// Only disable concurrency detection after seeing that the performance gains are considerable, and the application has
/// been thoroughly tested against concurrency bugs.
/// </para>
/// <para>
/// Note that if the application is setting the internal service provider through a call to
/// <see cref="UseInternalServiceProvider" />, then this option must configured the same way
/// for all uses of that service provider. Consider instead not calling <see cref="UseInternalServiceProvider" />
/// so that EF will manage the service providers and can create new instances as required.
/// </para>
/// </summary>
/// <returns> The same builder instance so that multiple calls can be chained. </returns>
public virtual DbContextOptionsBuilder DisableConcurrencyDetection(bool concurrencyDetectionDisabled = true)
=> WithOption(e => e.WithConcurrencyDetectionDisabled(concurrencyDetectionDisabled));

/// <summary>
/// <para>
/// Enables detailed errors when handling of data value exceptions that occur during processing of store query results. Such errors
Expand Down
Loading

0 comments on commit 7bede79

Please sign in to comment.