From 73f1b4823c28a57dbd7645abef819b4f775c5aa2 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 18 Sep 2026 11:09:49 +0100 Subject: [PATCH] Reinstate auto-enlistment for the hosted providers 3.3.0 shipped maintenance notifications purely opt-in, with the provider overrides commented out rather than deleted so that what came back would be exactly what was reviewed. This is that, pending the thumbs up. The three overrides return in their original form: Redis Cloud, Azure Managed Redis and Redis Enterprise select Auto, so a connection string naming nothing but a recognized hostname gets the feature. Auto rather than Enabled throughout, because a deployment that cannot deliver has to keep working - the opt-in is refused and the feature stays off, rather than the connection being rejected. The library default stays Disabled: OSS Redis, Valkey and Garnet do not know the subcommand, and an unsolicited error on every connection would be a poor first impression. Docs follow the behaviour back: - ServerMaintenanceEvent's "do I need to configure anything?" returns to "usually not", with the two-column now/will-be table collapsed and the temporary notice dropped. Kept the `defaults=` table row that was added while the feature was opt-in, since it is useful either way. - Configuration.md and exp/SER010.md lose their "purely opt-in" paragraphs. - DefaultOptionsTests.MaintenanceNotificationDefaultPerProvider flips its first two rows to Auto, which is what it was written to do. The last two rows stay Disabled, and they are what stops the theory passing merely because every host returns the same answer. MaintenanceOptInClientTests.DefaultIsOff needed no change: it connects to the in-process server on loopback, which no provider recognizes, so Disabled is still the right answer there. The three API entries go back into Unshipped; eng/public-api.py --check agrees the file is sorted. Also preserved each source file's existing BOM state - RedisEnterpriseOptionsProvider.cs has none, and writing one in would have put an unrelated first-line change in the diff. --- docs/Configuration.md | 9 ++---- docs/ServerMaintenanceEvent.md | 31 ++++++++----------- docs/exp/SER010.md | 6 ---- .../AzureManagedRedisOptionsProvider.cs | 23 +++++++------- .../RedisCloudOptionsProvider.cs | 21 +++++++------ .../RedisEnterpriseOptionsProvider.cs | 25 +++++++-------- .../PublicAPI/PublicAPI.Unshipped.txt | 3 ++ .../DefaultOptionsTests.cs | 14 ++++----- 8 files changed, 59 insertions(+), 73 deletions(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index 9297386a86..b86f2148de 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -368,13 +368,8 @@ broken connection. This requires RESP3, and the client asks for it per connectio | `enabled` | **Required**: ask, and reject the connection unless notifications are live. Only point this at a deployment you know supports them. | Note that `enabled` means *required*, not merely *on* - it is the cross-client name for that mode, and it will -refuse a connection that cannot deliver notifications, including any RESP2 connection. - -**For now this is purely opt-in: you must set `maintNotifications` yourself.** The `amr`, `rediscloud` and -`enterprise` providers are intended to select `auto` for you, so that on those deployments nothing needs -setting - but that auto-enlistment is held back until the feature has been through formal acceptance testing, -and is expected in a follow-up release. Naming a provider today sets its other defaults without turning -notifications on. +refuse a connection that cannot deliver notifications, including any RESP2 connection. The `amr`, `rediscloud` +and `enterprise` providers select `auto` for you, so on those deployments you need not set anything. While a disruption has been announced, command timeouts are relaxed - raised to `maintRelaxedTimeout`, never lowered, so a caller with a more generous timeout keeps it. The window ends when the server says the disruption diff --git a/docs/ServerMaintenanceEvent.md b/docs/ServerMaintenanceEvent.md index 91790bf46e..3a6ebf3807 100644 --- a/docs/ServerMaintenanceEvent.md +++ b/docs/ServerMaintenanceEvent.md @@ -29,34 +29,29 @@ Server-side you may also see it discussed as *maintenance mode*, *shard migratio ## Do I need to configure anything? -**Yes — for now.** In this release maintenance notifications are **purely opt-in**: nothing turns them on for you, whatever you connect to. You get them only by asking, with `maintNotifications=Auto` (or `Enabled`). +Usually not. If you connect using the hostname your provider gave you, the matching options provider recognizes it and turns the feature on for you. -> **This is temporary.** The intent is that the options providers enlist you automatically, so that connecting to a recognized Redis Cloud or Azure Managed Redis hostname turns the feature on without any configuration — exactly as the table below describes. That is held back only until the feature has been through formal acceptance testing, and is expected to land in a follow-up release. Until then, treat the "will be" column as a statement of direction, not of current behaviour. - -| You connect to | Recognized as | Notifications now | Will be | -|---|---|---|---| -| `something.cloud.redislabs.com`, `.cloud.redis.io`, `.redislabs.com` | Redis Cloud | **off** unless asked | on (`Auto`) | -| `something.redis.azure.net`, `.redisenterprise.cache.azure.net` | Azure Managed Redis | **off** unless asked | on (`Auto`) | -| `defaults=enterprise`, `defaults=rediscloud`, `defaults=amr` | named explicitly | **off** unless asked | on (`Auto`) | -| your own hostname, a CNAME, private DNS, or through a proxy | nothing | **off** | **off** | -| a self-managed Redis Enterprise cluster | nothing (there is no DNS pattern to recognize) | **off** | **off** | +| You connect to | Recognized as | Notifications | +|---|---|---| +| `something.cloud.redislabs.com`, `.cloud.redis.io`, `.redislabs.com` | Redis Cloud | on (`Auto`) | +| `something.redis.azure.net`, `.redisenterprise.cache.azure.net` | Azure Managed Redis | on (`Auto`) | +| `defaults=enterprise`, `defaults=rediscloud`, `defaults=amr` | named explicitly | on (`Auto`) | +| your own hostname, a CNAME, private DNS, or through a proxy | nothing | **off** | +| a self-managed Redis Enterprise cluster | nothing (there is no DNS pattern to recognize) | **off** | -Nothing fails when the feature is off: the connection works normally and you simply never receive a notification. So to use it today, ask for it explicitly. Either: +The last two rows are the ones to know about, because nothing fails: the connection works normally and you simply never receive a notification. If your endpoint does not look like your provider's, say so explicitly. Either: ```csharp -// change nothing except this feature -var options = ConfigurationOptions.Parse("my-redis.internal.example.com:6379,maintNotifications=Auto"); +// the whole deployment posture: prefer RESP3, skip the OSS config-broadcast channel, and ask for notifications +var options = ConfigurationOptions.Parse("my-redis.internal.example.com:6379,defaults=enterprise"); ``` -or, to take the whole deployment posture as well - prefer RESP3 and skip the OSS config-broadcast channel: +or, to change nothing except this feature: ```csharp -var options = ConfigurationOptions.Parse( - "my-redis.internal.example.com:6379,defaults=enterprise,maintNotifications=Auto"); +var options = ConfigurationOptions.Parse("my-redis.internal.example.com:6379,maintNotifications=Auto"); ``` -Note that `maintNotifications` is needed in *both* forms for now: while the feature is opt-in, naming a provider sets that provider's other defaults but does not turn notifications on. Once auto-enlistment lands, `defaults=enterprise` alone will be enough. - `defaults=` accepts `rediscloud`, `enterprise`, `amr` and `azure`; see [Configuration](Configuration.md) for what each provider sets. It is also the right answer for a *hosted* deployment reached somewhere its own provider cannot see it, such as behind a CNAME or a private endpoint. ### RESP3 is required, and is already the default diff --git a/docs/exp/SER010.md b/docs/exp/SER010.md index 89dcbff70f..a09ccd2796 100644 --- a/docs/exp/SER010.md +++ b/docs/exp/SER010.md @@ -17,12 +17,6 @@ The feature is experimental here for three separate reasons: timeout relaxation, then endpoint handoff. Behaviour may therefore change materially between versions while the diagnostic is in place, even where the API does not. -**Currently purely opt-in.** Nothing enables this for you: the options providers for Redis Cloud, Azure -Managed Redis and Redis Enterprise are intended to select `Auto` so that a recognized endpoint needs no -configuration, but that auto-enlistment is held back until the feature has been through formal acceptance -testing, and is expected in a follow-up release. Until then the only thing that turns notifications on is -setting `maintNotifications` yourself. - `MaintenanceNotificationMode`: - `Disabled` (the default) - never ask; the server sends nothing. diff --git a/src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs b/src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs index 1f3198eda8..51e2caacfc 100644 --- a/src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs +++ b/src/StackExchange.Redis/Configuration/AzureManagedRedisOptionsProvider.cs @@ -71,16 +71,17 @@ public override Task AfterConnectAsync(ConnectionMultiplexer muxer, Action public override string ConfigurationChannel => ""; // disable on AMR - // Auto-enlistment, deliberately withheld for now; maintenance notifications ship purely opt-in, and - // this goes back in a follow-up once the feature has been through formal acceptance testing. Kept here - // commented rather than deleted so that what returns is exactly what was reviewed. - // - // Pre-emptive: AMR does not emit these yet, and support is being added concurrently with this - // client-side work. Auto is what makes that safe - until the server side ships, the opt-in is refused - // and the feature stays off, and it then starts working without anybody needing to change a connection - // string. AMR also already prefers RESP3 here, which the feature requires. - // - // [Experimental(Experiments.MaintenanceNotifications, UrlFormat = Experiments.UrlFormat)] - // public override MaintenanceNotificationMode MaintenanceNotifications => MaintenanceNotificationMode.Auto; + /// + /// Ask for maintenance notifications, tolerating a server that doesn't offer them. + /// + /// + /// Pre-emptive: AMR does not emit these yet, and support is being added concurrently with this + /// client-side work. is what makes that safe - until + /// the server side ships, the opt-in is refused and the feature stays off, and it then starts working + /// without anybody needing to change a connection string. AMR also already prefers RESP3 here, which + /// the feature requires. + /// + [Experimental(Experiments.MaintenanceNotifications, UrlFormat = Experiments.UrlFormat)] + public override MaintenanceNotificationMode MaintenanceNotifications => MaintenanceNotificationMode.Auto; } } diff --git a/src/StackExchange.Redis/Configuration/RedisCloudOptionsProvider.cs b/src/StackExchange.Redis/Configuration/RedisCloudOptionsProvider.cs index 8787a578aa..fc00c4f9bd 100644 --- a/src/StackExchange.Redis/Configuration/RedisCloudOptionsProvider.cs +++ b/src/StackExchange.Redis/Configuration/RedisCloudOptionsProvider.cs @@ -59,16 +59,17 @@ private static bool IsHostInDomains(string hostName, string[] domains) /// public override string ConfigurationChannel => ""; - // Auto-enlistment, deliberately withheld for now; maintenance notifications ship purely opt-in, and - // this goes back in a follow-up once the feature has been through formal acceptance testing. Kept here - // commented rather than deleted so that what returns is exactly what was reviewed. - // - // This is the deployment family the feature exists for, and Auto rather than Enabled because a - // database that has not been updated yet must keep working: the opt-in is then refused and the feature - // stays off, rather than the connection being rejected. - // - // [Experimental(Experiments.MaintenanceNotifications, UrlFormat = Experiments.UrlFormat)] - // public override MaintenanceNotificationMode MaintenanceNotifications => MaintenanceNotificationMode.Auto; + /// + /// Ask for maintenance notifications, tolerating a server that doesn't offer them. + /// + /// + /// This is the deployment family the feature exists for. + /// rather than because a database that has not been + /// updated yet must keep working: the opt-in is then refused and the feature stays off, rather than the + /// connection being rejected. + /// + [Experimental(Experiments.MaintenanceNotifications, UrlFormat = Experiments.UrlFormat)] + public override MaintenanceNotificationMode MaintenanceNotifications => MaintenanceNotificationMode.Auto; // Two things AzureManagedRedisOptionsProvider does that are deliberately *not* repeated here: // diff --git a/src/StackExchange.Redis/Configuration/RedisEnterpriseOptionsProvider.cs b/src/StackExchange.Redis/Configuration/RedisEnterpriseOptionsProvider.cs index aafe4c54d1..87634fbf58 100644 --- a/src/StackExchange.Redis/Configuration/RedisEnterpriseOptionsProvider.cs +++ b/src/StackExchange.Redis/Configuration/RedisEnterpriseOptionsProvider.cs @@ -31,20 +31,17 @@ public class RedisEnterpriseOptionsProvider : DefaultOptionsProvider /// public override string ConfigurationChannel => ""; - // Auto-enlistment, deliberately withheld for now; maintenance notifications ship purely opt-in, and - // this goes back in a follow-up once the feature has been through formal acceptance testing. Kept here - // commented rather than deleted so that what returns is exactly what was reviewed. - // - // Note this provider is reached only by naming it (defaults=enterprise), never by endpoint matching, - // so restoring it enlists nobody who did not already ask for this deployment posture. It is withheld - // anyway, so that in this release the *only* thing that turns the feature on is maintNotifications. - // - // Auto rather than Enabled: a cluster that has not been updated yet, or has the feature switched off, - // must keep working. Choose Enabled explicitly if you would rather a connection be refused than run - // without advance warning. - // - // [Experimental(Experiments.MaintenanceNotifications, UrlFormat = Experiments.UrlFormat)] - // public override MaintenanceNotificationMode MaintenanceNotifications => MaintenanceNotificationMode.Auto; + /// + /// Ask for maintenance notifications, tolerating a server that doesn't offer them. + /// + /// + /// rather than + /// : a cluster that has not been updated yet, or has + /// the feature switched off, must keep working. Choose Enabled explicitly if you would rather a + /// connection be refused than run without advance warning. + /// + [Experimental(Experiments.MaintenanceNotifications, UrlFormat = Experiments.UrlFormat)] + public override MaintenanceNotificationMode MaintenanceNotifications => MaintenanceNotificationMode.Auto; // Note: no GetDefaultSsl and no DefaultVersion override. Both are deployment choices here rather than // properties of the product - TLS is configured per database, and the version is whatever was diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt index ab058de62d..43eab028e8 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,4 @@ #nullable enable +[SER010]override StackExchange.Redis.Configuration.AzureManagedRedisOptionsProvider.MaintenanceNotifications.get -> StackExchange.Redis.MaintenanceNotificationMode +[SER010]override StackExchange.Redis.Configuration.RedisCloudOptionsProvider.MaintenanceNotifications.get -> StackExchange.Redis.MaintenanceNotificationMode +[SER010]override StackExchange.Redis.Configuration.RedisEnterpriseOptionsProvider.MaintenanceNotifications.get -> StackExchange.Redis.MaintenanceNotificationMode diff --git a/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs b/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs index 445027bd48..1f508f2625 100644 --- a/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs +++ b/tests/StackExchange.Redis.Tests/DefaultOptionsTests.cs @@ -197,17 +197,17 @@ public void RedisCloudDoesNotInheritTheAzureManagedAssumptions() } [Theory] - [InlineData("contoso.redis.azure.net", MaintenanceNotificationMode.Disabled)] // AMR: will be Auto - [InlineData("contoso.cloud.redislabs.com", MaintenanceNotificationMode.Disabled)] // Redis Cloud: will be Auto + [InlineData("contoso.redis.azure.net", MaintenanceNotificationMode.Auto)] // AMR + [InlineData("contoso.cloud.redislabs.com", MaintenanceNotificationMode.Auto)] // Redis Cloud [InlineData("contoso.redis.cache.windows.net", MaintenanceNotificationMode.Disabled)] // classic Azure: stays off [InlineData("contoso.example.com", MaintenanceNotificationMode.Disabled)] // and so does anything unrecognized public void MaintenanceNotificationDefaultPerProvider(string hostName, MaintenanceNotificationMode expected) { - // Every endpoint is Disabled for now: maintenance notifications ship purely opt-in, so *nothing* - // enlists you and the only thing that turns them on is setting maintNotifications yourself. The - // providers for AMR and Redis Cloud are intended to select Auto - see the commented-out overrides on - // each - and this theory is what flips back when auto-enlistment lands: the first two rows become - // Auto, and the last two stay Disabled because nothing recognizes them either way. + // The recognized hosted families enlist automatically, which is the whole point of the providers: a + // connection string that names nothing but the hostname gets the feature. Auto rather than Enabled so + // that a deployment which cannot deliver keeps working - the opt-in is refused and the feature stays + // off. The last two rows stay Disabled because nothing recognizes them either way, and they are what + // stops this test passing merely because everything returns the same answer. var epc = new EndPointCollection(new List() { new DnsEndPoint(hostName, 0) }); var provider = DefaultOptionsProvider.GetProvider(epc); Output.WriteLine($"{hostName} -> {provider.GetType().Name}");