Skip to content

Commit ad39d11

Browse files
feat: EFCore supports YdbRetryPolicy (#546)
1 parent cadf257 commit ad39d11

File tree

11 files changed

+61
-37
lines changed

11 files changed

+61
-37
lines changed

examples/EntityFrameworkCore.Ydb.Yandex.Cloud/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ await Parser.Default.ParseArguments<CmdOptions>(args).WithParsedAsync(async cmd
1212

1313
var options = new DbContextOptionsBuilder<AppDbContext>()
1414
.UseYdb(cmd.ConnectionString, builder => builder
15-
.WithCredentialsProvider(saProvider)
16-
.WithServerCertificates(YcCerts.GetYcServerCertificates())
15+
.UseCredentialsProvider(saProvider)
16+
.UseServerCertificates(YcCerts.GetYcServerCertificates())
1717
)
1818
.Options;
1919

slo/src/EF/SloTableContext.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ public class SloTableContext : SloTableContext<PooledDbContextFactory<TableDbCon
1212
protected override string Job => "EF";
1313

1414
protected override PooledDbContextFactory<TableDbContext> CreateClient(Config config) =>
15-
new(new DbContextOptionsBuilder<TableDbContext>().UseYdb(config.ConnectionString).Options);
15+
new(new DbContextOptionsBuilder<TableDbContext>().UseYdb(config.ConnectionString,
16+
builder => builder.EnableRetryIdempotence()).Options);
1617

1718
protected override async Task Create(
1819
PooledDbContextFactory<TableDbContext> client,

src/EFCore.Ydb/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
- Added support for the YDB retry policy (ADO.NET) and new configuration methods in `YdbDbContextOptionsBuilder`:
2+
- `EnableRetryIdempotence()`: enables retries for errors classified as idempotent. You must ensure the operation itself is idempotent.
3+
- `UseRetryPolicy(YdbRetryPolicyConfig retryPolicyConfig)`: configures custom backoff parameters and the maximum number of retry attempts.
4+
15
## v0.1.0
26

37
- Fixed bug: incompatible coalesce types ([#531](https://github.com/ydb-platform/ydb-dotnet-sdk/issues/531)).

src/EFCore.Ydb/src/Infrastructure/Internal/YdbOptionsExtension.cs

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ public class YdbOptionsExtension : RelationalOptionsExtension
1313

1414
public X509Certificate2Collection? ServerCertificates { get; private set; }
1515

16-
public bool DisableRetryExecutionStrategy { get; private set; }
17-
1816
private DbContextOptionsExtensionInfo? _info;
1917

2018
public YdbOptionsExtension()
@@ -25,13 +23,11 @@ private YdbOptionsExtension(YdbOptionsExtension copyFrom) : base(copyFrom)
2523
{
2624
CredentialsProvider = copyFrom.CredentialsProvider;
2725
ServerCertificates = copyFrom.ServerCertificates;
28-
DisableRetryExecutionStrategy = copyFrom.DisableRetryExecutionStrategy;
2926
}
3027

3128
protected override RelationalOptionsExtension Clone() => new YdbOptionsExtension(this);
3229

33-
public override void ApplyServices(IServiceCollection services) =>
34-
services.AddEntityFrameworkYdb(!DisableRetryExecutionStrategy);
30+
public override void ApplyServices(IServiceCollection services) => services.AddEntityFrameworkYdb();
3531

3632
public override DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this);
3733

@@ -53,15 +49,6 @@ public YdbOptionsExtension WithServerCertificates(X509Certificate2Collection? se
5349
return clone;
5450
}
5551

56-
public YdbOptionsExtension DisableRetryOnFailure()
57-
{
58-
var clone = (YdbOptionsExtension)Clone();
59-
60-
clone.DisableRetryExecutionStrategy = true;
61-
62-
return clone;
63-
}
64-
6552
private sealed class ExtensionInfo(YdbOptionsExtension extension) : RelationalExtensionInfo(extension)
6653
{
6754
public override bool IsDatabaseProvider => true;
Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
11
using System.Security.Cryptography.X509Certificates;
22
using EntityFrameworkCore.Ydb.Infrastructure.Internal;
3+
using EntityFrameworkCore.Ydb.Storage.Internal;
34
using Microsoft.EntityFrameworkCore;
45
using Microsoft.EntityFrameworkCore.Infrastructure;
6+
using Microsoft.EntityFrameworkCore.Storage;
7+
using Ydb.Sdk.Ado.RetryPolicy;
58
using Ydb.Sdk.Auth;
69

710
namespace EntityFrameworkCore.Ydb.Infrastructure;
811

9-
public class YdbDbContextOptionsBuilder(DbContextOptionsBuilder optionsBuilder)
12+
public sealed class YdbDbContextOptionsBuilder(DbContextOptionsBuilder optionsBuilder)
1013
: RelationalDbContextOptionsBuilder<YdbDbContextOptionsBuilder, YdbOptionsExtension>(optionsBuilder)
1114
{
12-
public YdbDbContextOptionsBuilder WithCredentialsProvider(ICredentialsProvider? credentialsProvider) =>
15+
public YdbDbContextOptionsBuilder UseCredentialsProvider(ICredentialsProvider? credentialsProvider) =>
1316
WithOption(optionsBuilder => optionsBuilder.WithCredentialsProvider(credentialsProvider));
1417

15-
public YdbDbContextOptionsBuilder WithServerCertificates(X509Certificate2Collection? serverCertificates) =>
18+
public YdbDbContextOptionsBuilder UseServerCertificates(X509Certificate2Collection? serverCertificates) =>
1619
WithOption(optionsBuilder => optionsBuilder.WithServerCertificates(serverCertificates));
1720

21+
public YdbDbContextOptionsBuilder EnableRetryIdempotence()
22+
=> UseRetryPolicy(new YdbRetryPolicyConfig { EnableRetryIdempotence = true });
23+
24+
public YdbDbContextOptionsBuilder UseRetryPolicy(YdbRetryPolicyConfig retryPolicyConfig)
25+
=> ExecutionStrategy(d => new YdbExecutionStrategy(d, retryPolicyConfig));
26+
1827
public YdbDbContextOptionsBuilder DisableRetryOnFailure() =>
19-
WithOption(optionsBuilder => optionsBuilder.DisableRetryOnFailure());
28+
ExecutionStrategy(d => new NonRetryingExecutionStrategy(d));
2029
}
Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,38 @@
11
using System;
22
using Microsoft.EntityFrameworkCore.Storage;
3+
using Ydb.Sdk;
34
using Ydb.Sdk.Ado;
5+
using Ydb.Sdk.Ado.RetryPolicy;
46

57
namespace EntityFrameworkCore.Ydb.Storage.Internal;
68

7-
public class YdbExecutionStrategy(ExecutionStrategyDependencies dependencies)
8-
: ExecutionStrategy(dependencies, maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(10)) // TODO User settings
9+
/// <summary>
10+
/// Retry strategy for YDB.<br/>
11+
///
12+
/// <br/>IMPORTANT:
13+
/// <br/>- The maximum number of attempts and backoff logic are encapsulated in <see cref="YdbRetryPolicy"/>.
14+
/// The base ExecutionStrategy parameters (maxRetryCount, maxRetryDelay) are not used.
15+
/// <br/>- This strategy must be invoked in the correct EF Core context/connection (YDB),
16+
/// so that exception types and ShouldRetryOn semantics match the provider.
17+
/// <br/>- The base <see cref="ExecutionStrategy"/> is a good place to emit metrics/logs (attempt number, delay, exception type, etc.).
18+
/// </summary>
19+
public class YdbExecutionStrategy(ExecutionStrategyDependencies dependencies, YdbRetryPolicyConfig retryPolicyConfig)
20+
// We pass "placeholders" to the base class:
21+
// - MaxAttempts and TimeSpan.Zero are not used in the real retry logic.
22+
// - Actual limits/delays are driven by YdbRetryPolicy.
23+
: ExecutionStrategy(dependencies, retryPolicyConfig.MaxAttempts, TimeSpan.Zero /* unused! */)
924
{
10-
protected override bool ShouldRetryOn(Exception exception)
11-
=> exception is YdbException { IsTransient: true };
25+
private readonly YdbRetryPolicy _retryPolicy = new(retryPolicyConfig);
26+
27+
public override bool RetriesOnFailure => true;
28+
29+
protected override bool ShouldRetryOn(Exception exception) =>
30+
exception is YdbException ydbException &&
31+
(ydbException.IsTransient || (retryPolicyConfig.EnableRetryIdempotence && ydbException.Code is
32+
StatusCode.ClientTransportUnknown or
33+
StatusCode.ClientTransportUnavailable or
34+
StatusCode.Undetermined));
35+
36+
protected override TimeSpan? GetNextDelay(Exception lastException) =>
37+
_retryPolicy.GetNextDelay((YdbException)lastException, ExceptionsEncountered.Count - 1);
1238
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
using Microsoft.EntityFrameworkCore.Storage;
2+
using Ydb.Sdk.Ado.RetryPolicy;
23

34
namespace EntityFrameworkCore.Ydb.Storage.Internal;
45

56
public class YdbExecutionStrategyFactory(ExecutionStrategyDependencies dependencies)
67
: RelationalExecutionStrategyFactory(dependencies)
78
{
8-
protected override IExecutionStrategy CreateDefaultStrategy(ExecutionStrategyDependencies dependencies)
9-
=> new YdbExecutionStrategy(dependencies);
9+
protected override IExecutionStrategy CreateDefaultStrategy(ExecutionStrategyDependencies dependencies) =>
10+
new YdbExecutionStrategy(dependencies, YdbRetryPolicyConfig.Default);
1011
}

src/EFCore.Ydb/src/Storage/Internal/YdbRelationalConnection.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ public class YdbRelationalConnection : RelationalConnection, IYdbRelationalConne
1313
{
1414
private readonly ICredentialsProvider? _credentialsProvider;
1515
private readonly X509Certificate2Collection? _serverCertificates;
16-
private readonly bool _disableRetryExecuteStrategy;
1716

1817
public YdbRelationalConnection(RelationalConnectionDependencies dependencies) : base(dependencies)
1918
{
@@ -22,7 +21,6 @@ public YdbRelationalConnection(RelationalConnectionDependencies dependencies) :
2221

2322
_credentialsProvider = ydbOptionsExtension.CredentialsProvider;
2423
_serverCertificates = ydbOptionsExtension.ServerCertificates;
25-
_disableRetryExecuteStrategy = ydbOptionsExtension.DisableRetryExecutionStrategy;
2624
}
2725

2826
protected override DbConnection CreateDbConnection()
@@ -40,12 +38,8 @@ public IYdbRelationalConnection Clone()
4038
{
4139
var options = new DbContextOptionsBuilder().UseYdb(GetValidatedConnectionString(), builder =>
4240
{
43-
builder.WithCredentialsProvider(_credentialsProvider);
44-
builder.WithServerCertificates(_serverCertificates);
45-
if (_disableRetryExecuteStrategy)
46-
{
47-
builder.DisableRetryOnFailure();
48-
}
41+
builder.UseCredentialsProvider(_credentialsProvider);
42+
builder.UseServerCertificates(_serverCertificates);
4943
}).Options;
5044

5145
return new YdbRelationalConnection(Dependencies with { ContextOptions = options });

src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/QueryExpressionInterceptionYdbTestBase.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.EntityFrameworkCore.TestUtilities;
88
using Microsoft.Extensions.DependencyInjection;
99
using Xunit;
10+
using Ydb.Sdk.Ado.RetryPolicy;
1011

1112
namespace EntityFrameworkCore.Ydb.FunctionalTests;
1213

@@ -28,7 +29,7 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build
2829
{
2930
new YdbDbContextOptionsBuilder(base.AddOptions(builder))
3031
#pragma warning disable EF1001
31-
.ExecutionStrategy(d => new YdbExecutionStrategy(d));
32+
.ExecutionStrategy(d => new YdbExecutionStrategy(d, YdbRetryPolicyConfig.Default));
3233
#pragma warning restore EF1001
3334
return builder;
3435
}

src/Ydb.Sdk/src/Ado/RetryPolicy/YdbRetryPolicy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ internal YdbRetryPolicy(YdbRetryPolicyConfig config, IRandom random) : this(conf
8585

8686
return ydbException.Code switch
8787
{
88-
StatusCode.BadSession or StatusCode.SessionBusy => TimeSpan.Zero,
88+
StatusCode.BadSession or StatusCode.SessionBusy or StatusCode.SessionExpired => TimeSpan.Zero,
8989
StatusCode.Aborted or StatusCode.Undetermined =>
9090
FullJitter(_fastBackoffBaseMs, _fastCapBackoffMs, _fastCeiling, attempt, _random),
9191
StatusCode.Unavailable or StatusCode.ClientTransportUnknown or StatusCode.ClientTransportUnavailable =>

0 commit comments

Comments
 (0)