Skip to content

Commit 8abe002

Browse files
authored
add RedisChannel UseImplicitAutoPattern and IsPatternBased (#2480)
* add RedisChannel UseImplicitAutoPattern and IsPatternBased * mark the implicit RedisChannel operators as [Obsolete] (#2481)
1 parent ae6419a commit 8abe002

23 files changed

+322
-50
lines changed

docs/ReleaseNotes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Current package versions:
88

99
## Unreleased
1010

11+
- Fix [#2479](https://github.com/StackExchange/StackExchange.Redis/issues/2479): Add `RedisChannel.UseImplicitAutoPattern` (global) and `RedisChannel.IsPatternBased` ([#2480 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2480))
12+
- Fix [#2479](https://github.com/StackExchange/StackExchange.Redis/issues/2479): Mark `RedisChannel` conversion operators as obsolete; add `RedisChannel.Literal` and `RedisChannel.Pattern` helpers ([#2481 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2481))
1113
- Fix [#2449](https://github.com/StackExchange/StackExchange.Redis/issues/2449): Update `Pipelines.Sockets.Unofficial` to `v2.2.8` to support native AOT ([#2456 by eerhardt](https://github.com/StackExchange/StackExchange.Redis/pull/2456))
1214

1315
## 2.6.111

src/StackExchange.Redis/ConfigurationOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -860,7 +860,7 @@ private ConfigurationOptions DoParse(string configuration, bool ignoreUnknown)
860860
ClientName = value;
861861
break;
862862
case OptionKeys.ChannelPrefix:
863-
ChannelPrefix = value;
863+
ChannelPrefix = RedisChannel.Literal(value);
864864
break;
865865
case OptionKeys.ConfigChannel:
866866
ConfigurationChannel = value;

src/StackExchange.Redis/ConnectionMultiplexer.Sentinel.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
using System;
1+
using Pipelines.Sockets.Unofficial;
2+
using System;
23
using System.Collections.Generic;
34
using System.IO;
45
using System.Linq;
56
using System.Net;
67
using System.Threading;
78
using System.Threading.Tasks;
8-
using Pipelines.Sockets.Unofficial;
99

1010
namespace StackExchange.Redis;
1111

@@ -30,9 +30,9 @@ internal void InitializeSentinel(LogProxy? logProxy)
3030
// Subscribe to sentinel change events
3131
ISubscriber sub = GetSubscriber();
3232

33-
if (sub.SubscribedEndpoint("+switch-master") == null)
33+
if (sub.SubscribedEndpoint(RedisChannel.Literal("+switch-master")) == null)
3434
{
35-
sub.Subscribe("+switch-master", (__, message) =>
35+
sub.Subscribe(RedisChannel.Literal("+switch-master"), (__, message) =>
3636
{
3737
string[] messageParts = ((string)message!).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
3838
// We don't care about the result of this - we're just trying
@@ -68,9 +68,9 @@ internal void InitializeSentinel(LogProxy? logProxy)
6868
ReconfigureAsync(first: false, reconfigureAll: true, logProxy, e.EndPoint, "Lost sentinel connection", false).Wait();
6969

7070
// Subscribe to new sentinels being added
71-
if (sub.SubscribedEndpoint("+sentinel") == null)
71+
if (sub.SubscribedEndpoint(RedisChannel.Literal("+sentinel")) == null)
7272
{
73-
sub.Subscribe("+sentinel", (_, message) =>
73+
sub.Subscribe(RedisChannel.Literal("+sentinel"), (_, message) =>
7474
{
7575
string[] messageParts = ((string)message!).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
7676
UpdateSentinelAddressList(messageParts[0]);

src/StackExchange.Redis/ConnectionMultiplexer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2221,7 +2221,7 @@ public long PublishReconfigure(CommandFlags flags = CommandFlags.None)
22212221

22222222
private long PublishReconfigureImpl(CommandFlags flags) =>
22232223
ConfigurationChangedChannel is byte[] channel
2224-
? GetSubscriber().Publish(channel, RedisLiterals.Wildcard, flags)
2224+
? GetSubscriber().Publish(RedisChannel.Literal(channel), RedisLiterals.Wildcard, flags)
22252225
: 0;
22262226

22272227
/// <summary>
@@ -2231,7 +2231,7 @@ ConfigurationChangedChannel is byte[] channel
22312231
/// <returns>The number of instances known to have received the message (however, the actual number can be higher).</returns>
22322232
public Task<long> PublishReconfigureAsync(CommandFlags flags = CommandFlags.None) =>
22332233
ConfigurationChangedChannel is byte[] channel
2234-
? GetSubscriber().PublishAsync(channel, RedisLiterals.Wildcard, flags)
2234+
? GetSubscriber().PublishAsync(RedisChannel.Literal(channel), RedisLiterals.Wildcard, flags)
22352235
: CompletedTask<long>.Default(null);
22362236

22372237
/// <summary>

src/StackExchange.Redis/KeyspaceIsolation/KeyPrefixed.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -841,8 +841,11 @@ protected RedisValue SortGetToInner(RedisValue outer) =>
841841
}
842842
}
843843

844-
protected RedisChannel ToInner(RedisChannel outer) =>
845-
RedisKey.ConcatenateBytes(Prefix, null, (byte[]?)outer);
844+
protected RedisChannel ToInner(RedisChannel outer)
845+
{
846+
var combined = RedisKey.ConcatenateBytes(Prefix, null, (byte[]?)outer);
847+
return new RedisChannel(combined, outer.IsPatternBased ? RedisChannel.PatternMode.Pattern : RedisChannel.PatternMode.Literal);
848+
}
846849

847850
private Func<RedisKey, RedisKey>? mapFunction;
848851
protected Func<RedisKey, RedisKey> GetMapFunction() =>

src/StackExchange.Redis/Maintenance/AzureMaintenanceEvent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ internal async static Task AddListenerAsync(ConnectionMultiplexer multiplexer, A
130130
return;
131131
}
132132

133-
await sub.SubscribeAsync(PubSubChannelName, async (_, message) =>
133+
await sub.SubscribeAsync(RedisChannel.Literal(PubSubChannelName), async (_, message) =>
134134
{
135135
var newMessage = new AzureMaintenanceEvent(message!);
136136
newMessage.NotifyMultiplexer(multiplexer);

src/StackExchange.Redis/PhysicalBridge.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ internal void KeepAlive()
373373
else if (commandMap.IsAvailable(RedisCommand.UNSUBSCRIBE))
374374
{
375375
msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.UNSUBSCRIBE,
376-
(RedisChannel)Multiplexer.UniqueId);
376+
RedisChannel.Literal(Multiplexer.UniqueId));
377377
msg.SetSource(ResultProcessor.TrackSubscriptions, null);
378378
}
379379
break;

src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,7 @@ StackExchange.Redis.Proxy.Twemproxy = 1 -> StackExchange.Redis.Proxy
12591259
StackExchange.Redis.RedisChannel
12601260
StackExchange.Redis.RedisChannel.Equals(StackExchange.Redis.RedisChannel other) -> bool
12611261
StackExchange.Redis.RedisChannel.IsNullOrEmpty.get -> bool
1262+
StackExchange.Redis.RedisChannel.IsPatternBased.get -> bool
12621263
StackExchange.Redis.RedisChannel.PatternMode
12631264
StackExchange.Redis.RedisChannel.PatternMode.Auto = 0 -> StackExchange.Redis.RedisChannel.PatternMode
12641265
StackExchange.Redis.RedisChannel.PatternMode.Literal = 1 -> StackExchange.Redis.RedisChannel.PatternMode
@@ -1657,6 +1658,8 @@ static StackExchange.Redis.RedisChannel.implicit operator byte[]?(StackExchange.
16571658
static StackExchange.Redis.RedisChannel.implicit operator StackExchange.Redis.RedisChannel(byte[]? key) -> StackExchange.Redis.RedisChannel
16581659
static StackExchange.Redis.RedisChannel.implicit operator StackExchange.Redis.RedisChannel(string! key) -> StackExchange.Redis.RedisChannel
16591660
static StackExchange.Redis.RedisChannel.implicit operator string?(StackExchange.Redis.RedisChannel key) -> string?
1661+
static StackExchange.Redis.RedisChannel.Literal(byte[]! value) -> StackExchange.Redis.RedisChannel
1662+
static StackExchange.Redis.RedisChannel.Literal(string! value) -> StackExchange.Redis.RedisChannel
16601663
static StackExchange.Redis.RedisChannel.operator !=(byte[]! x, StackExchange.Redis.RedisChannel y) -> bool
16611664
static StackExchange.Redis.RedisChannel.operator !=(StackExchange.Redis.RedisChannel x, byte[]! y) -> bool
16621665
static StackExchange.Redis.RedisChannel.operator !=(StackExchange.Redis.RedisChannel x, StackExchange.Redis.RedisChannel y) -> bool
@@ -1667,6 +1670,10 @@ static StackExchange.Redis.RedisChannel.operator ==(StackExchange.Redis.RedisCha
16671670
static StackExchange.Redis.RedisChannel.operator ==(StackExchange.Redis.RedisChannel x, StackExchange.Redis.RedisChannel y) -> bool
16681671
static StackExchange.Redis.RedisChannel.operator ==(StackExchange.Redis.RedisChannel x, string! y) -> bool
16691672
static StackExchange.Redis.RedisChannel.operator ==(string! x, StackExchange.Redis.RedisChannel y) -> bool
1673+
static StackExchange.Redis.RedisChannel.Pattern(byte[]! value) -> StackExchange.Redis.RedisChannel
1674+
static StackExchange.Redis.RedisChannel.Pattern(string! value) -> StackExchange.Redis.RedisChannel
1675+
static StackExchange.Redis.RedisChannel.UseImplicitAutoPattern.get -> bool
1676+
static StackExchange.Redis.RedisChannel.UseImplicitAutoPattern.set -> void
16701677
static StackExchange.Redis.RedisFeatures.operator !=(StackExchange.Redis.RedisFeatures left, StackExchange.Redis.RedisFeatures right) -> bool
16711678
static StackExchange.Redis.RedisFeatures.operator ==(StackExchange.Redis.RedisFeatures left, StackExchange.Redis.RedisFeatures right) -> bool
16721679
static StackExchange.Redis.RedisKey.implicit operator byte[]?(StackExchange.Redis.RedisKey key) -> byte[]?

src/StackExchange.Redis/RedisChannel.cs

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,67 @@ namespace StackExchange.Redis
99
public readonly struct RedisChannel : IEquatable<RedisChannel>
1010
{
1111
internal readonly byte[]? Value;
12-
internal readonly bool IsPatternBased;
12+
internal readonly bool _isPatternBased;
1313

1414
/// <summary>
1515
/// Indicates whether the channel-name is either null or a zero-length value.
1616
/// </summary>
1717
public bool IsNullOrEmpty => Value == null || Value.Length == 0;
1818

19+
/// <summary>
20+
/// Indicates whether this channel represents a wildcard pattern (see <c>PSUBSCRIBE</c>)
21+
/// </summary>
22+
public bool IsPatternBased => _isPatternBased;
23+
1924
internal bool IsNull => Value == null;
2025

26+
27+
/// <summary>
28+
/// Indicates whether channels should use <see cref="PatternMode.Auto"/> when no <see cref="PatternMode"/>
29+
/// is specified; this is enabled by default, but can be disabled to avoid unexpected wildcard scenarios.
30+
/// </summary>
31+
public static bool UseImplicitAutoPattern
32+
{
33+
get => s_DefaultPatternMode == PatternMode.Auto;
34+
set => s_DefaultPatternMode = value ? PatternMode.Auto : PatternMode.Literal;
35+
}
36+
private static PatternMode s_DefaultPatternMode = PatternMode.Auto;
37+
38+
/// <summary>
39+
/// Creates a new <see cref="RedisChannel"/> that does not act as a wildcard subscription
40+
/// </summary>
41+
public static RedisChannel Literal(string value) => new RedisChannel(value, PatternMode.Literal);
42+
/// <summary>
43+
/// Creates a new <see cref="RedisChannel"/> that does not act as a wildcard subscription
44+
/// </summary>
45+
public static RedisChannel Literal(byte[] value) => new RedisChannel(value, PatternMode.Literal);
46+
/// <summary>
47+
/// Creates a new <see cref="RedisChannel"/> that acts as a wildcard subscription
48+
/// </summary>
49+
public static RedisChannel Pattern(string value) => new RedisChannel(value, PatternMode.Pattern);
50+
/// <summary>
51+
/// Creates a new <see cref="RedisChannel"/> that acts as a wildcard subscription
52+
/// </summary>
53+
public static RedisChannel Pattern(byte[] value) => new RedisChannel(value, PatternMode.Pattern);
54+
2155
/// <summary>
2256
/// Create a new redis channel from a buffer, explicitly controlling the pattern mode.
2357
/// </summary>
2458
/// <param name="value">The name of the channel to create.</param>
2559
/// <param name="mode">The mode for name matching.</param>
26-
public RedisChannel(byte[]? value, PatternMode mode) : this(value, DeterminePatternBased(value, mode)) {}
60+
public RedisChannel(byte[]? value, PatternMode mode) : this(value, DeterminePatternBased(value, mode)) { }
2761

2862
/// <summary>
2963
/// Create a new redis channel from a string, explicitly controlling the pattern mode.
3064
/// </summary>
3165
/// <param name="value">The string name of the channel to create.</param>
3266
/// <param name="mode">The mode for name matching.</param>
33-
public RedisChannel(string value, PatternMode mode) : this(value == null ? null : Encoding.UTF8.GetBytes(value), mode) {}
67+
public RedisChannel(string value, PatternMode mode) : this(value == null ? null : Encoding.UTF8.GetBytes(value), mode) { }
3468

3569
private RedisChannel(byte[]? value, bool isPatternBased)
3670
{
3771
Value = value;
38-
IsPatternBased = isPatternBased;
72+
_isPatternBased = isPatternBased;
3973
}
4074

4175
private static bool DeterminePatternBased(byte[]? value, PatternMode mode) => mode switch
@@ -87,7 +121,7 @@ private RedisChannel(byte[]? value, bool isPatternBased)
87121
/// <param name="x">The first <see cref="RedisChannel"/> to compare.</param>
88122
/// <param name="y">The second <see cref="RedisChannel"/> to compare.</param>
89123
public static bool operator ==(RedisChannel x, RedisChannel y) =>
90-
x.IsPatternBased == y.IsPatternBased && RedisValue.Equals(x.Value, y.Value);
124+
x._isPatternBased == y._isPatternBased && RedisValue.Equals(x.Value, y.Value);
91125

92126
/// <summary>
93127
/// Indicate whether two channel names are equal.
@@ -135,10 +169,10 @@ private RedisChannel(byte[]? value, bool isPatternBased)
135169
/// Indicate whether two channel names are equal.
136170
/// </summary>
137171
/// <param name="other">The <see cref="RedisChannel"/> to compare to.</param>
138-
public bool Equals(RedisChannel other) => IsPatternBased == other.IsPatternBased && RedisValue.Equals(Value, other.Value);
172+
public bool Equals(RedisChannel other) => _isPatternBased == other._isPatternBased && RedisValue.Equals(Value, other.Value);
139173

140174
/// <inheritdoc/>
141-
public override int GetHashCode() => RedisValue.GetHashCode(Value) + (IsPatternBased ? 1 : 0);
175+
public override int GetHashCode() => RedisValue.GetHashCode(Value) + (_isPatternBased ? 1 : 0);
142176

143177
/// <summary>
144178
/// Obtains a string representation of the channel name.
@@ -159,7 +193,16 @@ internal void AssertNotNull()
159193
if (IsNull) throw new ArgumentException("A null key is not valid in this context");
160194
}
161195

162-
internal RedisChannel Clone() => (byte[]?)Value?.Clone() ?? default;
196+
internal RedisChannel Clone()
197+
{
198+
if (Value is null || Value.Length == 0)
199+
{
200+
// no need to duplicate anything
201+
return this;
202+
}
203+
var copy = (byte[])Value.Clone(); // defensive array copy
204+
return new RedisChannel(copy, _isPatternBased);
205+
}
163206

164207
/// <summary>
165208
/// The matching pattern for this channel.
@@ -184,33 +227,35 @@ public enum PatternMode
184227
/// Create a channel name from a <see cref="string"/>.
185228
/// </summary>
186229
/// <param name="key">The string to get a channel from.</param>
230+
[Obsolete("It is preferable to explicitly specify a " + nameof(PatternMode) + ", or use the " + nameof(Literal) + "/" + nameof(Pattern) + " methods", error: false)]
187231
public static implicit operator RedisChannel(string key)
188232
{
189233
if (key == null) return default;
190-
return new RedisChannel(Encoding.UTF8.GetBytes(key), PatternMode.Auto);
234+
return new RedisChannel(Encoding.UTF8.GetBytes(key), s_DefaultPatternMode);
191235
}
192236

193237
/// <summary>
194238
/// Create a channel name from a <see cref="T:byte[]"/>.
195239
/// </summary>
196240
/// <param name="key">The byte array to get a channel from.</param>
241+
[Obsolete("It is preferable to explicitly specify a " + nameof(PatternMode) + ", or use the " + nameof(Literal) + "/" + nameof(Pattern) + " methods", error: false)]
197242
public static implicit operator RedisChannel(byte[]? key)
198243
{
199244
if (key == null) return default;
200-
return new RedisChannel(key, PatternMode.Auto);
245+
return new RedisChannel(key, s_DefaultPatternMode);
201246
}
202247

203248
/// <summary>
204249
/// Obtain the channel name as a <see cref="T:byte[]"/>.
205250
/// </summary>
206251
/// <param name="key">The channel to get a byte[] from.</param>
207-
public static implicit operator byte[]? (RedisChannel key) => key.Value;
252+
public static implicit operator byte[]?(RedisChannel key) => key.Value;
208253

209254
/// <summary>
210255
/// Obtain the channel name as a <see cref="string"/>.
211256
/// </summary>
212257
/// <param name="key">The channel to get a string from.</param>
213-
public static implicit operator string? (RedisChannel key)
258+
public static implicit operator string?(RedisChannel key)
214259
{
215260
var arr = key.Value;
216261
if (arr == null)
@@ -226,5 +271,15 @@ public static implicit operator RedisChannel(byte[]? key)
226271
return BitConverter.ToString(arr);
227272
}
228273
}
274+
275+
#if DEBUG
276+
// these exist *purely* to ensure that we never add them later *without*
277+
// giving due consideration to the default pattern mode (UseImplicitAutoPattern)
278+
// (since we don't ship them, we don't need them in release)
279+
[Obsolete("Watch for " + nameof(UseImplicitAutoPattern), error: true)]
280+
private RedisChannel(string value) => throw new NotSupportedException();
281+
[Obsolete("Watch for " + nameof(UseImplicitAutoPattern), error: true)]
282+
private RedisChannel(byte[]? value) => throw new NotSupportedException();
283+
#endif
229284
}
230285
}

src/StackExchange.Redis/RedisSubscriber.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public Subscription(CommandFlags flags)
159159
/// </summary>
160160
internal Message GetMessage(RedisChannel channel, SubscriptionAction action, CommandFlags flags, bool internalCall)
161161
{
162-
var isPattern = channel.IsPatternBased;
162+
var isPattern = channel._isPatternBased;
163163
var command = action switch
164164
{
165165
SubscriptionAction.Subscribe when isPattern => RedisCommand.PSUBSCRIBE,

0 commit comments

Comments
 (0)