diff --git a/libs/server/Objects/ItemBroker/CollectionItemBroker.cs b/libs/server/Objects/ItemBroker/CollectionItemBroker.cs
index f33fc61a4bb..0cec4dff5d5 100644
--- a/libs/server/Objects/ItemBroker/CollectionItemBroker.cs
+++ b/libs/server/Objects/ItemBroker/CollectionItemBroker.cs
@@ -235,7 +235,7 @@ private void InitializeObserver(CollectionItemObserver observer, byte[][] keys)
// If the key already has a non-empty observer queue, it does not have an item to retrieve
// Otherwise, try to retrieve next available item
if ((KeysToObservers.ContainsKey(key) && KeysToObservers[key].Count > 0) ||
- !TryGetResult(key, observer.Session.storageSession, observer.Command, observer.CommandArgs,
+ !TryGetResult(key, observer.Session.storageSession, observer.Command, observer.CommandArgs, true,
out _, out var result)) continue;
// An item was found - set the observer result and return
@@ -291,7 +291,7 @@ private bool TryAssignItemFromKey(byte[] key)
}
// Try to get next available item from object stored in key
- if (!TryGetResult(key, observer.Session.storageSession, observer.Command, observer.CommandArgs,
+ if (!TryGetResult(key, observer.Session.storageSession, observer.Command, observer.CommandArgs, false,
out var currCount, out var result))
{
// If unsuccessful getting next item but there is at least one item in the collection,
@@ -436,7 +436,13 @@ private static unsafe bool TryGetNextSetObjects(byte[] key, SortedSetObject sort
}
}
- private unsafe bool TryGetResult(byte[] key, StorageSession storageSession, RespCommand command, ArgSlice[] cmdArgs, out int currCount, out CollectionItemResult result)
+ ///
+ /// Try to get available item(s) from sorted set object based on command type and arguments
+ /// When run from initializer (initial = true), can return WRONGTYPE errors
+ ///
+ private unsafe bool TryGetResult(byte[] key, StorageSession storageSession,
+ RespCommand command, ArgSlice[] cmdArgs, bool initial,
+ out int currCount, out CollectionItemResult result)
{
currCount = default;
result = default;
@@ -449,6 +455,7 @@ private unsafe bool TryGetResult(byte[] key, StorageSession storageSession, Resp
_ => throw new NotSupportedException()
};
+ var asKey = storageSession.scratchBufferManager.CreateArgSlice(key);
ArgSlice dstKey = default;
if (command == RespCommand.BLMOVE)
{
@@ -460,32 +467,42 @@ private unsafe bool TryGetResult(byte[] key, StorageSession storageSession, Resp
{
Debug.Assert(storageSession.txnManager.state == TxnState.None);
createTransaction = true;
- var asKey = storageSession.scratchBufferManager.CreateArgSlice(key);
+ if (initial)
+ storageSession.txnManager.SaveKeyEntryToLock(asKey, false, LockType.Exclusive);
storageSession.txnManager.SaveKeyEntryToLock(asKey, true, LockType.Exclusive);
if (command == RespCommand.BLMOVE)
{
+ if (initial)
+ storageSession.txnManager.SaveKeyEntryToLock(dstKey, false, LockType.Exclusive);
storageSession.txnManager.SaveKeyEntryToLock(dstKey, true, LockType.Exclusive);
}
_ = storageSession.txnManager.Run(true);
}
+ var lockableContext = storageSession.txnManager.LockableContext;
var objectLockableContext = storageSession.txnManager.ObjectStoreLockableContext;
try
{
// Get the object stored at key
var statusOp = storageSession.GET(key, out var osObject, ref objectLockableContext);
- if (statusOp == GarnetStatus.NOTFOUND) return false;
-
- IGarnetObject dstObj = null;
- byte[] arrDstKey = default;
- if (command == RespCommand.BLMOVE)
+ if (statusOp == GarnetStatus.NOTFOUND)
{
- arrDstKey = dstKey.ToArray();
- var dstStatusOp = storageSession.GET(arrDstKey, out var osDstObject, ref objectLockableContext);
- if (dstStatusOp != GarnetStatus.NOTFOUND) dstObj = osDstObject.GarnetObject;
+ if (!initial)
+ return false;
+
+ // Check the string store as well to see if WRONGTYPE should be returned.
+ statusOp = storageSession.GET(asKey, out ArgSlice _, ref lockableContext);
+
+ if (statusOp != GarnetStatus.NOTFOUND)
+ {
+ result = new CollectionItemResult(GarnetStatus.WRONGTYPE);
+ return initial;
+ }
+
+ return false;
}
// Check for type match between the observer and the actual object type
@@ -494,29 +511,64 @@ private unsafe bool TryGetResult(byte[] key, StorageSession storageSession, Resp
{
case ListObject listObj:
currCount = listObj.LnkList.Count;
- if (objectType != GarnetObjectType.List) return false;
- if (currCount == 0) return false;
+ if (objectType != GarnetObjectType.List)
+ {
+ result = new CollectionItemResult(GarnetStatus.WRONGTYPE);
+ return initial;
+ }
+ if (currCount == 0)
+ return false;
+ var isSuccessful = false;
switch (command)
{
case RespCommand.BLPOP:
case RespCommand.BRPOP:
- var isSuccessful = TryGetNextListItem(listObj, command, out var nextItem);
+ isSuccessful = TryGetNextListItem(listObj, command, out var nextItem);
result = new CollectionItemResult(key, nextItem);
- return isSuccessful;
+ break;
case RespCommand.BLMOVE:
+ var arrDstKey = dstKey.ToArray();
+ var dstStatusOp = storageSession.GET(arrDstKey, out var osDstObject, ref objectLockableContext);
+
ListObject dstList;
var newObj = false;
- if (dstObj == null)
+
+ if (dstStatusOp != GarnetStatus.NOTFOUND)
{
- dstList = new ListObject();
- newObj = true;
+ var dstObj = osDstObject.GarnetObject;
+
+ if (dstObj == null)
+ {
+ dstList = new ListObject();
+ newObj = true;
+ }
+ else if (dstObj is ListObject tmpDstList)
+ {
+ dstList = tmpDstList;
+ }
+ else
+ {
+ result = new CollectionItemResult(GarnetStatus.WRONGTYPE);
+ return initial;
+ }
}
- else if (dstObj is ListObject tmpDstList)
+ else
{
- dstList = tmpDstList;
+ if (initial)
+ {
+ // Check string store for wrongtype errors on initial run.
+ dstStatusOp = storageSession.GET(dstKey, out ArgSlice _, ref lockableContext);
+ if (dstStatusOp != GarnetStatus.NOTFOUND)
+ {
+ result = new CollectionItemResult(GarnetStatus.WRONGTYPE);
+ return initial;
+ }
+ }
+
+ dstList = new ListObject();
+ newObj = true;
}
- else return false;
isSuccessful = TryMoveNextListItem(listObj, dstList, (OperationDirection)cmdArgs[1].ReadOnlySpan[0],
(OperationDirection)cmdArgs[2].ReadOnlySpan[0], out nextItem);
@@ -527,8 +579,7 @@ private unsafe bool TryGetResult(byte[] key, StorageSession storageSession, Resp
isSuccessful = storageSession.SET(arrDstKey, dstList, ref objectLockableContext) ==
GarnetStatus.OK;
}
-
- return isSuccessful;
+ break;
case RespCommand.BLMPOP:
var popDirection = (OperationDirection)cmdArgs[0].ReadOnlySpan[0];
var popCount = *(int*)(cmdArgs[1].ptr);
@@ -537,25 +588,44 @@ private unsafe bool TryGetResult(byte[] key, StorageSession storageSession, Resp
var items = new byte[popCount][];
for (var i = 0; i < popCount; i++)
{
- var _ = TryGetNextListItem(listObj, popDirection == OperationDirection.Left ? RespCommand.BLPOP : RespCommand.BRPOP, out items[i]); // Return can be ignored because it is guaranteed to return true
+ // Return can be ignored because it is guaranteed to return true
+ _ = TryGetNextListItem(listObj, popDirection == OperationDirection.Left ? RespCommand.BLPOP : RespCommand.BRPOP, out items[i]);
}
result = new CollectionItemResult(key, items);
- return true;
+ isSuccessful = true;
+ break;
default:
- return false;
+ result = new CollectionItemResult(GarnetStatus.WRONGTYPE);
+ return initial;
+ }
+
+ if (isSuccessful && listObj.LnkList.Count == 0)
+ {
+ _ = storageSession.EXPIRE(asKey, TimeSpan.Zero, out _, StoreType.Object, ExpireOption.None,
+ ref lockableContext, ref objectLockableContext);
}
+ return isSuccessful;
case SortedSetObject setObj:
currCount = setObj.Count();
if (objectType != GarnetObjectType.SortedSet)
- return false;
+ {
+ result = new CollectionItemResult(GarnetStatus.WRONGTYPE);
+ return initial;
+ }
if (currCount == 0)
return false;
- return TryGetNextSetObjects(key, setObj, currCount, command, cmdArgs, out result);
-
+ isSuccessful = TryGetNextSetObjects(key, setObj, currCount, command, cmdArgs, out result);
+ if (isSuccessful && setObj.Count() == 0)
+ {
+ _ = storageSession.EXPIRE(asKey, TimeSpan.Zero, out _, StoreType.Object, ExpireOption.None,
+ ref lockableContext, ref objectLockableContext);
+ }
+ return isSuccessful;
default:
- return false;
+ result = new CollectionItemResult(GarnetStatus.WRONGTYPE);
+ return initial;
}
}
finally
diff --git a/libs/server/Objects/ItemBroker/CollectionItemResult.cs b/libs/server/Objects/ItemBroker/CollectionItemResult.cs
index 4a980a8e639..dbcff00e2b5 100644
--- a/libs/server/Objects/ItemBroker/CollectionItemResult.cs
+++ b/libs/server/Objects/ItemBroker/CollectionItemResult.cs
@@ -8,30 +8,39 @@ namespace Garnet.server
///
internal readonly struct CollectionItemResult
{
+ public CollectionItemResult(GarnetStatus status)
+ {
+ Status = status;
+ }
+
public CollectionItemResult(byte[] key, byte[] item)
{
Key = key;
Item = item;
+ Status = key == default ? GarnetStatus.NOTFOUND : GarnetStatus.OK;
}
public CollectionItemResult(byte[] key, byte[][] items)
{
Key = key;
Items = items;
+ Status = key == default ? GarnetStatus.NOTFOUND : GarnetStatus.OK;
}
public CollectionItemResult(byte[] key, double score, byte[] item)
{
Key = key;
- Score = score;
Item = item;
+ Score = score;
+ Status = key == default ? GarnetStatus.NOTFOUND : GarnetStatus.OK;
}
public CollectionItemResult(byte[] key, double[] scores, byte[][] items)
{
Key = key;
- Scores = scores;
Items = items;
+ Scores = scores;
+ Status = key == default ? GarnetStatus.NOTFOUND : GarnetStatus.OK;
}
private CollectionItemResult(bool isForceUnblocked)
@@ -40,9 +49,9 @@ private CollectionItemResult(bool isForceUnblocked)
}
///
- /// True if item was found
+ /// Result status
///
- internal bool Found => Key != default;
+ internal GarnetStatus Status { get; }
///
/// Key of collection from which item was retrieved
diff --git a/libs/server/Resp/Objects/ListCommands.cs b/libs/server/Resp/Objects/ListCommands.cs
index 94f54ac9402..8bd0fb418a2 100644
--- a/libs/server/Resp/Objects/ListCommands.cs
+++ b/libs/server/Resp/Objects/ListCommands.cs
@@ -294,9 +294,12 @@ private bool ListBlockingPop(RespCommand command)
if (!parseState.TryGetDouble(parseState.Count - 1, out var timeout))
{
- while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_ERR_TIMEOUT_NOT_VALID_FLOAT, ref dcurr, dend))
- SendAndReset();
- return true;
+ return AbortWithErrorMessage(CmdStrings.RESP_ERR_TIMEOUT_NOT_VALID_FLOAT);
+ }
+
+ if (timeout < 0)
+ {
+ return AbortWithErrorMessage(CmdStrings.RESP_ERR_TIMEOUT_IS_NEGATIVE);
}
if (storeWrapper.itemBroker == null)
@@ -311,21 +314,27 @@ private bool ListBlockingPop(RespCommand command)
return true;
}
- if (!result.Found)
+ switch (result.Status)
{
- while (!RespWriteUtils.TryWriteNullArray(ref dcurr, dend))
- SendAndReset();
- }
- else
- {
- while (!RespWriteUtils.TryWriteArrayLength(2, ref dcurr, dend))
- SendAndReset();
+ case GarnetStatus.OK:
+ while (!RespWriteUtils.TryWriteArrayLength(2, ref dcurr, dend))
+ SendAndReset();
- while (!RespWriteUtils.TryWriteBulkString(new Span(result.Key), ref dcurr, dend))
- SendAndReset();
+ while (!RespWriteUtils.TryWriteBulkString(new Span(result.Key), ref dcurr, dend))
+ SendAndReset();
- while (!RespWriteUtils.TryWriteBulkString(new Span(result.Item), ref dcurr, dend))
- SendAndReset();
+ while (!RespWriteUtils.TryWriteBulkString(new Span(result.Item), ref dcurr, dend))
+ SendAndReset();
+ break;
+ case GarnetStatus.WRONGTYPE:
+ while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend))
+ SendAndReset();
+ break;
+ case GarnetStatus.NOTFOUND:
+ default:
+ while (!RespWriteUtils.TryWriteNullArray(ref dcurr, dend))
+ SendAndReset();
+ break;
}
return true;
@@ -345,9 +354,12 @@ private unsafe bool ListBlockingMove()
if (!parseState.TryGetDouble(4, out var timeout))
{
- while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_ERR_TIMEOUT_NOT_VALID_FLOAT, ref dcurr, dend))
- SendAndReset();
- return true;
+ return AbortWithErrorMessage(CmdStrings.RESP_ERR_TIMEOUT_NOT_VALID_FLOAT);
+ }
+
+ if (timeout < 0)
+ {
+ return AbortWithErrorMessage(CmdStrings.RESP_ERR_TIMEOUT_IS_NEGATIVE);
}
return ListBlockingMove(srcKey, dstKey, srcDir, dstDir, timeout);
@@ -371,9 +383,12 @@ private bool ListBlockingPopPush()
if (!parseState.TryGetDouble(2, out var timeout))
{
- while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_ERR_TIMEOUT_NOT_VALID_FLOAT, ref dcurr, dend))
- SendAndReset();
- return true;
+ return AbortWithErrorMessage(CmdStrings.RESP_ERR_TIMEOUT_NOT_VALID_FLOAT);
+ }
+
+ if (timeout < 0)
+ {
+ return AbortWithErrorMessage(CmdStrings.RESP_ERR_TIMEOUT_IS_NEGATIVE);
}
return ListBlockingMove(srcKey, dstKey, rightOption, leftOption, timeout);
@@ -405,14 +420,20 @@ private bool ListBlockingMove(ArgSlice srcKey, ArgSlice dstKey, ArgSlice srcDir,
var result = storeWrapper.itemBroker.MoveCollectionItemAsync(RespCommand.BLMOVE, srcKey.ToArray(), this, timeout,
cmdArgs).Result;
- if (!result.Found)
+ switch (result.Status)
{
- WriteNull();
- }
- else
- {
- while (!RespWriteUtils.TryWriteBulkString(new Span(result.Item), ref dcurr, dend))
- SendAndReset();
+ case GarnetStatus.OK:
+ while (!RespWriteUtils.TryWriteBulkString(new Span(result.Item), ref dcurr, dend))
+ SendAndReset();
+ break;
+ case GarnetStatus.WRONGTYPE:
+ while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend))
+ SendAndReset();
+ break;
+ case GarnetStatus.NOTFOUND:
+ default:
+ WriteNull();
+ break;
}
return true;
@@ -925,6 +946,11 @@ private unsafe bool ListBlockingPopMultiple()
return AbortWithErrorMessage(CmdStrings.RESP_ERR_TIMEOUT_NOT_VALID_FLOAT);
}
+ if (timeout < 0)
+ {
+ return AbortWithErrorMessage(CmdStrings.RESP_ERR_TIMEOUT_IS_NEGATIVE);
+ }
+
// Read count of keys
if (!parseState.TryGetInt(currTokenId++, out var numKeys))
{
@@ -987,28 +1013,36 @@ private unsafe bool ListBlockingPopMultiple()
{
while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_UNBLOCKED_CLIENT_VIA_CLIENT_UNBLOCK, ref dcurr, dend))
SendAndReset();
- }
-
- if (!result.Found)
- {
- WriteNull();
return true;
}
- while (!RespWriteUtils.TryWriteArrayLength(2, ref dcurr, dend))
- SendAndReset();
+ switch (result.Status)
+ {
+ case GarnetStatus.OK:
+ while (!RespWriteUtils.TryWriteArrayLength(2, ref dcurr, dend))
+ SendAndReset();
- while (!RespWriteUtils.TryWriteBulkString(result.Key, ref dcurr, dend))
- SendAndReset();
+ while (!RespWriteUtils.TryWriteBulkString(result.Key, ref dcurr, dend))
+ SendAndReset();
- var elements = result.Items;
- while (!RespWriteUtils.TryWriteArrayLength(elements.Length, ref dcurr, dend))
- SendAndReset();
+ var elements = result.Items;
+ while (!RespWriteUtils.TryWriteArrayLength(elements.Length, ref dcurr, dend))
+ SendAndReset();
- foreach (var element in elements)
- {
- while (!RespWriteUtils.TryWriteBulkString(element, ref dcurr, dend))
- SendAndReset();
+ foreach (var element in elements)
+ {
+ while (!RespWriteUtils.TryWriteBulkString(element, ref dcurr, dend))
+ SendAndReset();
+ }
+ break;
+ case GarnetStatus.WRONGTYPE:
+ while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend))
+ SendAndReset();
+ break;
+ case GarnetStatus.NOTFOUND:
+ default:
+ WriteNull();
+ break;
}
return true;
diff --git a/libs/server/Resp/Objects/SortedSetCommands.cs b/libs/server/Resp/Objects/SortedSetCommands.cs
index d0474a7c847..61859e5fa76 100644
--- a/libs/server/Resp/Objects/SortedSetCommands.cs
+++ b/libs/server/Resp/Objects/SortedSetCommands.cs
@@ -1550,11 +1550,16 @@ private unsafe bool SortedSetBlockingPop(RespCommand command)
return AbortWithWrongNumberOfArguments(command.ToString());
}
- if (!parseState.TryGetDouble(parseState.Count - 1, out var timeout) || (timeout < 0))
+ if (!parseState.TryGetDouble(parseState.Count - 1, out var timeout))
{
return AbortWithErrorMessage(CmdStrings.RESP_ERR_TIMEOUT_NOT_VALID_FLOAT);
}
+ if (timeout < 0)
+ {
+ return AbortWithErrorMessage(CmdStrings.RESP_ERR_TIMEOUT_IS_NEGATIVE);
+ }
+
var keysBytes = new byte[parseState.Count - 1][];
for (var i = 0; i < keysBytes.Length; i++)
@@ -1564,23 +1569,29 @@ private unsafe bool SortedSetBlockingPop(RespCommand command)
var result = storeWrapper.itemBroker.GetCollectionItemAsync(command, keysBytes, this, timeout).Result;
- if (!result.Found)
+ switch (result.Status)
{
- WriteNull();
- }
- else
- {
- while (!RespWriteUtils.TryWriteArrayLength(3, ref dcurr, dend))
- SendAndReset();
+ case GarnetStatus.OK:
+ while (!RespWriteUtils.TryWriteArrayLength(3, ref dcurr, dend))
+ SendAndReset();
- while (!RespWriteUtils.TryWriteBulkString(result.Key, ref dcurr, dend))
- SendAndReset();
+ while (!RespWriteUtils.TryWriteBulkString(result.Key, ref dcurr, dend))
+ SendAndReset();
- while (!RespWriteUtils.TryWriteBulkString(result.Item, ref dcurr, dend))
- SendAndReset();
+ while (!RespWriteUtils.TryWriteBulkString(result.Item, ref dcurr, dend))
+ SendAndReset();
- while (!RespWriteUtils.TryWriteDoubleBulkString(result.Score, ref dcurr, dend))
- SendAndReset();
+ while (!RespWriteUtils.TryWriteDoubleBulkString(result.Score, ref dcurr, dend))
+ SendAndReset();
+ break;
+ case GarnetStatus.WRONGTYPE:
+ while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend))
+ SendAndReset();
+ break;
+ case GarnetStatus.NOTFOUND:
+ default:
+ WriteNull();
+ break;
}
return true;
@@ -1668,30 +1679,37 @@ private unsafe bool SortedSetBlockingMPop()
var result = storeWrapper.itemBroker.GetCollectionItemAsync(RespCommand.BZMPOP, keysBytes, this, timeout, cmdArgs).Result;
- if (!result.Found)
+ switch (result.Status)
{
- WriteNull();
- return true;
- }
-
- // Write array with 2 elements: key and array of member-score pairs
- while (!RespWriteUtils.TryWriteArrayLength(2, ref dcurr, dend))
- SendAndReset();
+ case GarnetStatus.OK:
+ // Write array with 2 elements: key and array of member-score pairs
+ while (!RespWriteUtils.TryWriteArrayLength(2, ref dcurr, dend))
+ SendAndReset();
- while (!RespWriteUtils.TryWriteBulkString(result.Key, ref dcurr, dend))
- SendAndReset();
+ while (!RespWriteUtils.TryWriteBulkString(result.Key, ref dcurr, dend))
+ SendAndReset();
- while (!RespWriteUtils.TryWriteArrayLength(result.Items.Length, ref dcurr, dend))
- SendAndReset();
+ while (!RespWriteUtils.TryWriteArrayLength(result.Items.Length, ref dcurr, dend))
+ SendAndReset();
- for (var i = 0; i < result.Items.Length; ++i)
- {
- while (!RespWriteUtils.TryWriteArrayLength(2, ref dcurr, dend))
- SendAndReset();
- while (!RespWriteUtils.TryWriteBulkString(result.Items[i], ref dcurr, dend))
- SendAndReset();
- while (!RespWriteUtils.TryWriteDoubleBulkString(result.Scores[i], ref dcurr, dend))
- SendAndReset();
+ for (var i = 0; i < result.Items.Length; ++i)
+ {
+ while (!RespWriteUtils.TryWriteArrayLength(2, ref dcurr, dend))
+ SendAndReset();
+ while (!RespWriteUtils.TryWriteBulkString(result.Items[i], ref dcurr, dend))
+ SendAndReset();
+ while (!RespWriteUtils.TryWriteDoubleBulkString(result.Scores[i], ref dcurr, dend))
+ SendAndReset();
+ }
+ break;
+ case GarnetStatus.WRONGTYPE:
+ while (!RespWriteUtils.TryWriteError(CmdStrings.RESP_ERR_WRONG_TYPE, ref dcurr, dend))
+ SendAndReset();
+ break;
+ case GarnetStatus.NOTFOUND:
+ default:
+ WriteNull();
+ break;
}
return true;
diff --git a/test/Garnet.test/RespBlockingCollectionTests.cs b/test/Garnet.test/RespBlockingCollectionTests.cs
index 7b7f193be27..52af96274c9 100644
--- a/test/Garnet.test/RespBlockingCollectionTests.cs
+++ b/test/Garnet.test/RespBlockingCollectionTests.cs
@@ -414,6 +414,11 @@ public void BlmpopBlockingWithCountTest()
Task.WaitAll([blockingTask, pushingTask], TimeSpan.FromSeconds(10));
ClassicAssert.IsTrue(blockingTask.IsCompletedSuccessfully);
ClassicAssert.IsTrue(pushingTask.IsCompletedSuccessfully);
+
+ using var lightClientRequest = TestUtils.CreateRequest();
+ var response = lightClientRequest.SendCommand($"EXISTS {key}");
+ var expectedResponse = ":1\r\n";
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
}
[Test]
@@ -470,6 +475,9 @@ public void BzmpopReturnTest()
ClassicAssert.AreEqual(2, pop[1][1].Length);
ClassicAssert.AreEqual("two", pop[1][1][0].ToString());
ClassicAssert.AreEqual(2, (int)(RedisValue)pop[1][1][1]);
+
+ ClassicAssert.IsFalse(db.KeyExists("a"));
+ ClassicAssert.IsTrue(db.KeyExists("b"));
}
[Test]
@@ -608,5 +616,87 @@ public void BzpopMinMaxTimeoutTest(string command)
var expectedResponse = "$-1\r\n";
TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
}
+
+ [Test]
+ public void PopWrongTypeTest()
+ {
+ using var lightClientRequest = TestUtils.CreateRequest();
+ lightClientRequest.SendCommand("SET key 0");
+ lightClientRequest.SendCommand("RPUSH list 0");
+ lightClientRequest.SendCommand("ZADD set 0 first");
+
+ var response = lightClientRequest.SendCommand("BLMPOP 0 1 key RIGHT");
+ var expectedResponse = "-WRONGTYPE Operation against a key holding the wrong kind of value.\r\n";
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ response = lightClientRequest.SendCommand("BLMPOP 0 2 key list RIGHT");
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ response = lightClientRequest.SendCommand("BLMPOP 0 1 set RIGHT");
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ response = lightClientRequest.SendCommand("BZMPOP 0 1 key MAX");
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ response = lightClientRequest.SendCommand("BZMPOP 0 2 key set MAX");
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ response = lightClientRequest.SendCommand("BZMPOP 0 1 list MAX");
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ response = lightClientRequest.SendCommand("BZPOPMIN key 0");
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ response = lightClientRequest.SendCommand("BZPOPMAX list 0");
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ response = lightClientRequest.SendCommand("BLMOVE key foo RIGHT RIGHT 0");
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ response = lightClientRequest.SendCommand("BLMOVE set foo RIGHT RIGHT 0");
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ response = lightClientRequest.SendCommand("BLMOVE list set LEFT LEFT 0");
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ response = lightClientRequest.SendCommand("BLMOVE list key LEFT LEFT 0");
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ response = lightClientRequest.SendCommand("BRPOPLPUSH key foo 0");
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ response = lightClientRequest.SendCommand("BRPOPLPUSH list key 0");
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ response = lightClientRequest.SendCommand("BLPOP key 0");
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ response = lightClientRequest.SendCommand("BRPOP key 0");
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+
+ var blockingLTask = Task.Run(() =>
+ {
+ using var lcr = TestUtils.CreateRequest();
+ var response = lcr.SendCommand("BLMPOP 0 2 list key RIGHT");
+ var expectedResponse = "*2\r\n$4\r\nlist\r\n*1\r\n$1\r\n0\r\n";
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+ });
+
+ var blockingZTask = Task.Run(() =>
+ {
+ using var lcr = TestUtils.CreateRequest();
+ var response = lcr.SendCommand("BZMPOP 0 2 set key MAX");
+ var expectedResponse = "*2\r\n$3\r\nset\r\n*1\r\n*2\r\n$5\r\nfirst\r\n$1\r\n0";
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+ });
+
+ Task.WaitAll([blockingLTask, blockingZTask], TimeSpan.FromSeconds(5));
+ ClassicAssert.IsTrue(blockingLTask.IsCompletedSuccessfully);
+ ClassicAssert.IsTrue(blockingZTask.IsCompletedSuccessfully);
+
+ response = lightClientRequest.SendCommand("EXISTS list");
+ expectedResponse = ":0\r\n";
+ TestUtils.AssertEqualUpToExpectedLength(expectedResponse, response);
+ }
}
}
\ No newline at end of file