Skip to content

RetryLogicProvider is called twice recursively when calling SqlCommand.ExecuteScalarAsync #1219

Closed
@frederic-luce

Description

Describe the bug

I use SqlClient 3.0 RetryLogic to transparently log extensive information about failed request.

When a retry logic provider is used with SqlCommand.ExecuteScalarAsync, RetryLogicProvider.ExecuteAsync is called twice recursively : first time from SqlCommand.ExecuteScalarAsync, and second time from the inner SqlCommand.ExecuteReaderAsync.

Here is a stack trace from the second call :

  • MultipleRetryCall.dll!MultipleRetryCall.ErrorInfoRetryLogicProvider.ExecuteAsync<Microsoft.Data.SqlClient.SqlDataReader>(object sender, System.Func<System.Threading.Tasks.Task<Microsoft.Data.SqlClient.SqlDataReader>> function, System.Threading.CancellationToken cancellationToken) Ligne 77
  • Microsoft.Data.SqlClient.dll!Microsoft.Data.SqlClient.SqlCommand.InternalExecuteReaderWithRetryAsync(System.Data.CommandBehavior behavior, System.Threading.CancellationToken cancellationToken) Ligne 2548
  • Microsoft.Data.SqlClient.dll!Microsoft.Data.SqlClient.SqlCommand.ExecuteReaderAsync(System.Data.CommandBehavior behavior, System.Threading.CancellationToken cancellationToken) Ligne 2543
  • Microsoft.Data.SqlClient.dll!Microsoft.Data.SqlClient.SqlCommand.ExecuteReaderAsync(System.Threading.CancellationToken cancellationToken) Ligne 2538
  • Microsoft.Data.SqlClient.dll!Microsoft.Data.SqlClient.SqlCommand.InternalExecuteScalarAsync(System.Threading.CancellationToken cancellationToken) Ligne 2635
  • Microsoft.Data.SqlClient.dll!Microsoft.Data.SqlClient.SqlCommand.InternalExecuteScalarWithRetryAsync.AnonymousMethod__0() Ligne 2626
  • MultipleRetryCall.dll!MultipleRetryCall.ErrorInfoRetryLogicProvider.LogCallsAsync(object sender, System.Func<System.Threading.Tasks.Task> function) Ligne 65
  • MultipleRetryCall.dll!MultipleRetryCall.ErrorInfoRetryLogicProvider.ExecuteAsync.AnonymousMethod__0() Ligne 81
  • Microsoft.Data.SqlClient.dll!Microsoft.Data.SqlClient.SqlRetryLogicProvider.ExecuteAsync(object sender, System.Func<System.Threading.Tasks.Task> function, System.Threading.CancellationToken cancellationToken) Ligne 106
  • MultipleRetryCall.dll!MultipleRetryCall.ErrorInfoRetryLogicProvider.ExecuteAsync(object sender, System.Func<System.Threading.Tasks.Task> function, System.Threading.CancellationToken cancellationToken) Ligne 81
  • Microsoft.Data.SqlClient.dll!Microsoft.Data.SqlClient.SqlCommand.InternalExecuteScalarWithRetryAsync(System.Threading.CancellationToken cancellationToken) Ligne 2626
  • Microsoft.Data.SqlClient.dll!Microsoft.Data.SqlClient.SqlCommand.ExecuteScalarAsync(System.Threading.CancellationToken cancellationToken) Ligne 2621
  • System.Data.Common.dll!System.Data.Common.DbCommand.ExecuteScalarAsync() Ligne 191
  • MultipleRetryCall.dll!MultipleRetryCall.Program.Main(string[] args) Ligne 147
  • (bold : custom code)

    To reproduce

    Define a SqlRetryLogicBaseProvider that encapsulate a FixedRetryProvider.
    Execute* methods add logs before and after each inner call.

    First part call ExecuteScalarAsync that will fail :

     ExecuteAsync<T> Microsoft.Data.SqlClient.SqlCommand Starting counter=1 thread=4
     LogCallsAsync<T> Microsoft.Data.SqlClient.SqlCommand    Starting inner 1
      ExecuteAsync<T> Microsoft.Data.SqlClient.SqlCommand Starting counter=2 thread=4
      LogCallsAsync<T> Microsoft.Data.SqlClient.SqlCommand    Starting inner 2
      LogCallsAsync<T> Microsoft.Data.SqlClient.SqlCommand    Stopping inner 2
      LogCallsAsync<T> Microsoft.Data.SqlClient.SqlCommand    Starting inner 3
      LogCallsAsync<T> Microsoft.Data.SqlClient.SqlCommand    Stopping inner 3
      LogCallsAsync<T> Microsoft.Data.SqlClient.SqlCommand    Starting inner 4
      LogCallsAsync<T> Microsoft.Data.SqlClient.SqlCommand    Stopping inner 4
      ExecuteAsync<T> Microsoft.Data.SqlClient.SqlCommand Stopping counter=2 thread=5
     LogCallsAsync<T> Microsoft.Data.SqlClient.SqlCommand    Stopping inner 4
     ExecuteAsync<T> Microsoft.Data.SqlClient.SqlCommand Stopping counter=1 thread=7
      Error The number of retries has exceeded the maximum of 3 attempt(s). (Error) (Error) (Error) total tries = 4
    

    Same call with ExecuteReaderAsync :

    ExecuteReaderAsync failure with retry
     ExecuteAsync<T> Microsoft.Data.SqlClient.SqlCommand Starting counter=1 thread=7
     LogCallsAsync<T> Microsoft.Data.SqlClient.SqlCommand    Starting inner 1
     LogCallsAsync<T> Microsoft.Data.SqlClient.SqlCommand    Stopping inner 1
     LogCallsAsync<T> Microsoft.Data.SqlClient.SqlCommand    Starting inner 2
     LogCallsAsync<T> Microsoft.Data.SqlClient.SqlCommand    Stopping inner 2
     LogCallsAsync<T> Microsoft.Data.SqlClient.SqlCommand    Starting inner 3
     LogCallsAsync<T> Microsoft.Data.SqlClient.SqlCommand    Stopping inner 3
     ExecuteAsync<T> Microsoft.Data.SqlClient.SqlCommand Stopping counter=1 thread=5
    Error The number of retries has exceeded the maximum of 3 attempt(s). (Error) (Error) (Error) total tries = 3
    

    Same call with ExecuteScalar (Sync) :

    ExecuteScalar failure with retry
     Execute<T> Microsoft.Data.SqlClient.SqlCommand Starting counter=1 thread=5
     LogCalls<T> Microsoft.Data.SqlClient.SqlCommand    Starting inner 1
     LogCalls<T> Microsoft.Data.SqlClient.SqlCommand    Stopping inner 1
     LogCalls<T> Microsoft.Data.SqlClient.SqlCommand    Starting inner 2
     LogCalls<T> Microsoft.Data.SqlClient.SqlCommand    Stopping inner 2
     LogCalls<T> Microsoft.Data.SqlClient.SqlCommand    Starting inner 3
     LogCalls<T> Microsoft.Data.SqlClient.SqlCommand    Stopping inner 3
     Execute<T> Microsoft.Data.SqlClient.SqlCommand Stopping counter=1 thread=5
    Error The number of retries has exceeded the maximum of 3 attempt(s). (Error) (Error) (Error) total tries = 3
    

    GIST
    MultipleRetryCall.zip

    Expected behavior

    RetryLogicProvider should be called only once when used with ExecuteScalarAsync to be consistent with ExecuteScalar and ExecuteReaderAsync.

    Further technical details

    Microsoft.Data.SqlClient version: 3.0
    .NET target: netcore3.1
    SQL Server version: 14
    Operating system: Windows 10

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions