Connection Resiliency Review
The current implementation of connection resiliency is built around 3 components, an
, and IRetryDelayStrategy
. While this achieves the goal of providing customizable retry policies, we thought that it would be possible to provide a simplified experience.
The proposal we considered was to remove IRetryDelayStrategy
from the public surface of EF, and instead provide a base class that could be inherited from to customize retry behavior.
This gave us the following to implement:
- Remove IRetryDelayStrategy from public surface
- Remove IRetriableExceptionDetector from public surface
- Provide a base class that can be inherited from to customize the retry behavior
- The base class will have a virtual method for ShouldRetryOn and GetNextDelay. Overriding one or both of those methods allows the changing of behavior similarly to how you could create and use an
IRetryDelayStrategy or IRetriableExceptionDetector.
- Base class for retry will unwrap EF exceptions. EF wraps exceptions that come from the provider in an EF exception. The base class will walk the inner exceptions until it finds the first non-ef exception, and then pass that exception to the ShouldRetryOn
method. This will allow developers to implement ShouldRetryOn without needing to write code to unwrap EF exceptions to get at the provider specific errors.
- The base class will allow customization of MaxRetries and MaxDelay so that developers can alter that behavior without writing all of their own retry logic.
RetriesOnFailure is important in that we use it to know whether or not an exception should be thrown when trying to use connection resiliency with an ambient transaction, as well as if you are trying to use connection resiliency in streaming mode. In both of
these cases retrying on a transient error could cause difficult to debug errors. EF provides two implementations of execution strategy that do not retry on a failed exception, so the field is required to allow those strategies to work. But the impact of setting
it to true or false is not always obvious to someone implementing their own strategy.
To help resolve this we will not make the RetriesOnFailure property virtual on our execution strategy base class, so that anyone customizing the default experience doesn't need to worry about the meaning of it. But we will leave the property on the IExecutionStrategy
interface and the execution strategies that do not actually retry can implement the interface themselves instead of inheriting.
User provided connections:
Normally, if a user provides EF with an open connection EF will not alter the connection state. Leaving it for user code to close and dispose of as they wish. However, if transient errors cause the connection to be closed and a retrying execution strategy is
enabled then EF will re-open the connection before retrying the failed operation. We think this behavior is what users would expect and so will leave it as is for now.
Our implementation of the IExecutionStrategy
interface is statefull, requiring knowledge of the number of retries made, for example. Because of this, EF requests a new IExecutionStrategy each time it needs to execute an action. The problem with this
is that asking the dependency resolver for a new IExecutionStreatgy is relatively slow and decreases the performance of EF. We could make the execution strategy stateless, and cache it, but that would make the execution strategies more difficult to implement
for providers as they would need to know that it had to be stateless. Instead we are going to investigate retrieving some type of factory from the dependency resolver and caching that. Hopefully we will be able to do this without introducing too many more
concepts specific to connection resiliency.