Skip to content

Commit 695d0c9

Browse files
committed
Add support for BLPOP and BRPOP commands
Issues redis#249 and redis#250
1 parent ade0ac4 commit 695d0c9

File tree

5 files changed

+242
-10
lines changed

5 files changed

+242
-10
lines changed

src/NRedisStack/CoreCommands/CoreCommandBuilder.cs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,25 @@ public static SerializedCommand BZMPop(double timeout, RedisKey[] keys, MinMaxMo
4646

4747
public static SerializedCommand BZPopMin(RedisKey[] keys, double timeout)
4848
{
49-
if (keys.Length == 0)
50-
{
51-
throw new ArgumentException("At least one key must be provided.");
52-
}
49+
return BlockingCommandWithKeysAndTimeout(RedisCoreCommands.BZPOPMIN, keys, timeout);
50+
}
5351

54-
List<object> args = new List<object>();
55-
args.AddRange(keys.Cast<object>());
56-
args.Add(timeout);
52+
public static SerializedCommand BZPopMax(RedisKey[] keys, double timeout)
53+
{
54+
return BlockingCommandWithKeysAndTimeout(RedisCoreCommands.BZPOPMAX, keys, timeout);
55+
}
56+
57+
public static SerializedCommand BLPop(RedisKey[] keys, double timeout)
58+
{
59+
return BlockingCommandWithKeysAndTimeout(RedisCoreCommands.BLPOP, keys, timeout);
60+
}
5761

58-
return new SerializedCommand(RedisCoreCommands.BZPOPMIN, args);
62+
public static SerializedCommand BRPop(RedisKey[] keys, double timeout)
63+
{
64+
return BlockingCommandWithKeysAndTimeout(RedisCoreCommands.BRPOP, keys, timeout);
5965
}
6066

61-
public static SerializedCommand BZPopMax(RedisKey[] keys, double timeout)
67+
private static SerializedCommand BlockingCommandWithKeysAndTimeout(String command, RedisKey[] keys, double timeout)
6268
{
6369
if (keys.Length == 0)
6470
{
@@ -69,7 +75,7 @@ public static SerializedCommand BZPopMax(RedisKey[] keys, double timeout)
6975
args.AddRange(keys.Cast<object>());
7076
args.Add(timeout);
7177

72-
return new SerializedCommand(RedisCoreCommands.BZPOPMAX, args);
78+
return new SerializedCommand(command, args);
7379
}
7480
}
7581
}

src/NRedisStack/CoreCommands/CoreCommands.cs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,5 +175,101 @@ public static bool ClientSetInfo(this IDatabase db, SetInfoAttr attr, string val
175175
{
176176
return BZPopMax(db, new[] { key }, timeout);
177177
}
178+
179+
/// <summary>
180+
/// The BLPOP command.
181+
/// <p/>
182+
/// Removes and returns an entry from the head (left side) of the first non-empty list in <paramref name="keys"/>.
183+
/// If none of the lists contain elements, the call blocks on the server until elements
184+
/// become available, or the given <paramref name="timeout"/> expires. A <paramref name="timeout"/> of <c>0</c>
185+
/// means to wait indefinitely server-side. Returns <c>null</c> if the server timeout expires.
186+
/// <p/>
187+
/// When using this, pay attention to the timeout configured in the client, on the
188+
/// <see cref="ConnectionMultiplexer"/>, which by default can be too small:
189+
/// <code>
190+
/// ConfigurationOptions configurationOptions = new ConfigurationOptions();
191+
/// configurationOptions.SyncTimeout = 120000; // set a meaningful value here
192+
/// configurationOptions.EndPoints.Add("localhost");
193+
/// ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(configurationOptions);
194+
/// </code>
195+
/// If the connection multiplexer timeout expires in the client, a <c>StackExchange.Redis.RedisTimeoutException</c>
196+
/// is thrown.
197+
/// <p/>
198+
/// This is an extension method added to the <see cref="IDatabase"/> class, for convenience.
199+
/// </summary>
200+
/// <param name="db">The <see cref="IDatabase"/> class where this extension method is applied.</param>
201+
/// <param name="keys">The keys to check.</param>
202+
/// <param name="timeout">Server-side timeout for the wait. A value of <c>0</c> means to wait indefinitely.</param>
203+
/// <returns>A value, together with the key it was popped from, or <c>null</c> if the server timeout
204+
/// expires.</returns>
205+
/// <remarks><seealso href="https://redis.io/commands/blpop"/></remarks>
206+
public static Tuple<RedisKey, RedisValue>? BLPop(this IDatabase db, RedisKey[] keys, double timeout)
207+
{
208+
var command = CoreCommandBuilder.BLPop(keys, timeout);
209+
return db.Execute(command).ToListPopResult();
210+
}
211+
212+
/// <summary>
213+
/// Syntactic sugar for <see cref="BLPop(StackExchange.Redis.IDatabase,StackExchange.Redis.RedisKey[],double)"/>,
214+
/// where only one key is used.
215+
/// </summary>
216+
/// <param name="db">The <see cref="IDatabase"/> class where this extension method is applied.</param>
217+
/// <param name="key">The key to check.</param>
218+
/// <param name="timeout">Server-side timeout for the wait. A value of <c>0</c> means to wait indefinitely.</param>
219+
/// <returns>A value, together with the key it was popped from, or <c>null</c> if the server timeout
220+
/// expires.</returns>
221+
/// <remarks><seealso href="https://redis.io/commands/blpop"/></remarks>
222+
public static Tuple<RedisKey, RedisValue>? BLPop(this IDatabase db, RedisKey key, double timeout)
223+
{
224+
return BLPop(db, new[] { key }, timeout);
225+
}
226+
227+
/// <summary>
228+
/// The BRPOP command.
229+
/// <p/>
230+
/// Removes and returns an entry from the tail (right side) of the first non-empty list in <paramref name="keys"/>.
231+
/// If none of the lists contain elements, the call blocks on the server until elements
232+
/// become available, or the given <paramref name="timeout"/> expires. A <paramref name="timeout"/> of <c>0</c>
233+
/// means to wait indefinitely server-side. Returns <c>null</c> if the server timeout expires.
234+
/// <p/>
235+
/// When using this, pay attention to the timeout configured in the client, on the
236+
/// <see cref="ConnectionMultiplexer"/>, which by default can be too small:
237+
/// <code>
238+
/// ConfigurationOptions configurationOptions = new ConfigurationOptions();
239+
/// configurationOptions.SyncTimeout = 120000; // set a meaningful value here
240+
/// configurationOptions.EndPoints.Add("localhost");
241+
/// ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(configurationOptions);
242+
/// </code>
243+
/// If the connection multiplexer timeout expires in the client, a <c>StackExchange.Redis.RedisTimeoutException</c>
244+
/// is thrown.
245+
/// <p/>
246+
/// This is an extension method added to the <see cref="IDatabase"/> class, for convenience.
247+
/// </summary>
248+
/// <param name="db">The <see cref="IDatabase"/> class where this extension method is applied.</param>
249+
/// <param name="keys">The keys to check.</param>
250+
/// <param name="timeout">Server-side timeout for the wait. A value of <c>0</c> means to wait indefinitely.</param>
251+
/// <returns>A value, together with the key it was popped from, or <c>null</c> if the server timeout
252+
/// expires.</returns>
253+
/// <remarks><seealso href="https://redis.io/commands/brpop"/></remarks>
254+
public static Tuple<RedisKey, RedisValue>? BRPop(this IDatabase db, RedisKey[] keys, double timeout)
255+
{
256+
var command = CoreCommandBuilder.BRPop(keys, timeout);
257+
return db.Execute(command).ToListPopResult();
258+
}
259+
260+
/// <summary>
261+
/// Syntactic sugar for <see cref="BLPop(StackExchange.Redis.IDatabase,StackExchange.Redis.RedisKey[],double)"/>,
262+
/// where only one key is used.
263+
/// </summary>
264+
/// <param name="db">The <see cref="IDatabase"/> class where this extension method is applied.</param>
265+
/// <param name="key">The key to check.</param>
266+
/// <param name="timeout">Server-side timeout for the wait. A value of <c>0</c> means to wait indefinitely.</param>
267+
/// <returns>A value, together with the key it was popped from, or <c>null</c> if the server timeout
268+
/// expires.</returns>
269+
/// <remarks><seealso href="https://redis.io/commands/brpop"/></remarks>
270+
public static Tuple<RedisKey, RedisValue>? BRPop(this IDatabase db, RedisKey key, double timeout)
271+
{
272+
return BRPop(db, new[] { key }, timeout);
273+
}
178274
}
179275
}

src/NRedisStack/CoreCommands/Literals/Commands.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ namespace NRedisStack.Core.Literals
55
/// </summary>
66
internal static class RedisCoreCommands
77
{
8+
public const string BLPOP = "BLPOP";
9+
public const string BRPOP = "BRPOP";
810
public const string BZMPOP = "BZMPOP";
911
public const string BZPOPMAX = "BZPOPMAX";
1012
public const string BZPOPMIN = "BZPOPMIN";

src/NRedisStack/ResponseParser.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,5 +766,19 @@ public static Dictionary<string, RedisResult>[] ToDictionarys(this RedisResult r
766766

767767
return new Tuple<RedisKey, List<RedisValueWithScore>>(resultKey, valuesWithScores);
768768
}
769+
770+
public static Tuple<RedisKey, RedisValue>? ToListPopResult(this RedisResult result)
771+
{
772+
if (result.IsNull)
773+
{
774+
return null;
775+
}
776+
777+
var resultArray = (RedisResult[])result!;
778+
var resultKey = resultArray[0].ToRedisKey();
779+
var value = resultArray[1].ToRedisValue();
780+
781+
return new Tuple<RedisKey, RedisValue>(resultKey, value);
782+
}
769783
}
770784
}

tests/NRedisStack.Tests/Core Commands/CoreTests.cs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,4 +406,118 @@ public void TestBZPopMaxMultipleSets()
406406
Assert.Equal("set-one", result!.Item1);
407407
Assert.Equal("b", result.Item2.Value.ToString());
408408
}
409+
410+
[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "2.0.0")]
411+
public void TestBLPop()
412+
{
413+
var redis = ConnectionMultiplexer.Connect("localhost");
414+
415+
var db = redis.GetDatabase(null);
416+
db.Execute("FLUSHALL");
417+
418+
db.ListRightPush("my-list", "a");
419+
db.ListRightPush("my-list", "b");
420+
421+
var result = db.BLPop("my-list", 0);
422+
423+
Assert.NotNull(result);
424+
Assert.Equal("my-list", result!.Item1);
425+
Assert.Equal("a", result.Item2.ToString());
426+
}
427+
428+
[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "2.0.0")]
429+
public void TestBLPopNull()
430+
{
431+
var redis = ConnectionMultiplexer.Connect("localhost");
432+
433+
var db = redis.GetDatabase(null);
434+
db.Execute("FLUSHALL");
435+
436+
// Nothing in the set, and a short server timeout, which yields null.
437+
var result = db.BLPop("my-set", 0.5);
438+
439+
Assert.Null(result);
440+
}
441+
442+
[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "2.0.0")]
443+
public void TestBLPopMultipleLists()
444+
{
445+
var redis = ConnectionMultiplexer.Connect("localhost");
446+
447+
var db = redis.GetDatabase(null);
448+
db.Execute("FLUSHALL");
449+
450+
db.ListRightPush("list-one", "a");
451+
db.ListRightPush("list-one", "b");
452+
db.ListRightPush("list-two", "e");
453+
454+
var result = db.BLPop(new[] { new RedisKey("list-two"), new RedisKey("list-one") }, 0);
455+
456+
Assert.NotNull(result);
457+
Assert.Equal("list-two", result!.Item1);
458+
Assert.Equal("e", result.Item2.ToString());
459+
460+
result = db.BLPop(new[] { new RedisKey("list-two"), new RedisKey("list-one") }, 0);
461+
462+
Assert.NotNull(result);
463+
Assert.Equal("list-one", result!.Item1);
464+
Assert.Equal("a", result.Item2.ToString());
465+
}
466+
467+
[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "2.0.0")]
468+
public void TestBRPop()
469+
{
470+
var redis = ConnectionMultiplexer.Connect("localhost");
471+
472+
var db = redis.GetDatabase(null);
473+
db.Execute("FLUSHALL");
474+
475+
db.ListRightPush("my-list", "a");
476+
db.ListRightPush("my-list", "b");
477+
478+
var result = db.BRPop("my-list", 0);
479+
480+
Assert.NotNull(result);
481+
Assert.Equal("my-list", result!.Item1);
482+
Assert.Equal("b", result.Item2.ToString());
483+
}
484+
485+
[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "2.0.0")]
486+
public void TestBRPopNull()
487+
{
488+
var redis = ConnectionMultiplexer.Connect("localhost");
489+
490+
var db = redis.GetDatabase(null);
491+
db.Execute("FLUSHALL");
492+
493+
// Nothing in the set, and a short server timeout, which yields null.
494+
var result = db.BRPop("my-set", 0.5);
495+
496+
Assert.Null(result);
497+
}
498+
499+
[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "2.0.0")]
500+
public void TestBRPopMultipleLists()
501+
{
502+
var redis = ConnectionMultiplexer.Connect("localhost");
503+
504+
var db = redis.GetDatabase(null);
505+
db.Execute("FLUSHALL");
506+
507+
db.ListRightPush("list-one", "a");
508+
db.ListRightPush("list-one", "b");
509+
db.ListRightPush("list-two", "e");
510+
511+
var result = db.BRPop(new[] { new RedisKey("list-two"), new RedisKey("list-one") }, 0);
512+
513+
Assert.NotNull(result);
514+
Assert.Equal("list-two", result!.Item1);
515+
Assert.Equal("e", result.Item2.ToString());
516+
517+
result = db.BRPop(new[] { new RedisKey("list-two"), new RedisKey("list-one") }, 0);
518+
519+
Assert.NotNull(result);
520+
Assert.Equal("list-one", result!.Item1);
521+
Assert.Equal("b", result.Item2.ToString());
522+
}
409523
}

0 commit comments

Comments
 (0)