Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Compatibility] Added CLIENT UNBLOCK command #886

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

Vijay-Nirmal
Copy link
Contributor

Adding the CLIENT UNBLOCK command to garnet

  • Add CLIENT UNBLOCK commands
  • Add Integration Test cases and ACL Test
  • Add documentation

Copy link
Contributor

@TalZaccai TalZaccai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Please see my comments.

libs/server/Resp/ClientCommands.cs Show resolved Hide resolved
libs/server/Resp/ClientCommands.cs Show resolved Hide resolved
}
}

while (!RespWriteUtils.WriteInteger(1, ref dcurr, dend))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client may be unblocked between you checking if it's blocked and attempting to unblock it (in which case you should return 0). In other words, you should get some feedback from the observer that indicates if you initiated the unblocking or not.

Copy link
Contributor Author

@Vijay-Nirmal Vijay-Nirmal Jan 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have moved this inside the if, I think that should work as expected, because only why this like can execute observer.CancellationTokenSource.Cancel() or observer.ResultFoundSemaphore.Release() is called. Let me know, If I understood it wrongly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can still have a context switch happen here. What you need to do is have something similar to observer.HandleSetResult (say, observer.TryForceUnblock) that locks the ObserverStatusLock, and returns a boolean that indicates if the observer was force unblocked or not.

libs/server/Objects/ItemBroker/CollectionItemBroker.cs Outdated Show resolved Hide resolved
try
{
// Wait for either the result found notification or the timeout to expire
await observer.ResultFoundSemaphore.WaitAsync(timeout, observer.CancellationTokenSource.Token);
}
catch (OperationCanceledException)
catch (OperationCanceledException) when (observer.CancellationTokenSource.IsCancellationRequested)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will also be true if the session was disposed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should cause any issue right? earlier its just an empty catch, Now I am assigning a bool. Do you see any issue with that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, because if the session was disposed you wouldn't need to return a CollectionItemResult.Error

libs/server/Objects/ItemBroker/CollectionItemResult.cs Outdated Show resolved Hide resolved
test/Garnet.test/RespTests.cs Outdated Show resolved Hide resolved
[Test]
[TestCase("ERROR", Description = "Multiple unblock attempts with ERROR")]
[TestCase("TIMEOUT", Description = "Multiple unblock attempts with TIMEOUT")]
public async Task ClientUnblockMultipleAttemptsTest(string mode)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can add a couple more tests here, for example -
1 client blocks, send 1 force unblock and 1 add in parallel - check that either client gets the data or gets unblocked and data stays in collection, can repeat test multiple times.
Multiple clients blocking, multiple calls for force unblock & add in parallel - check that no added data gets lost (i.e. either stays in the collection or being returned to clients).

Copy link
Contributor Author

@Vijay-Nirmal Vijay-Nirmal Jan 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test concurrency, everything is working as expected. One finding is that, there could be multiple clients get the response as 1 if they are unblocking the same client. I would say it's expected as there is no point in having transactions for unblocking client ID.

Another interesting things I found is there is an issue with the list push command where if we push multiple items in then somehow all items values are just the same as last item's value. I will find the root cause and fix it as part of a separate PR

for (int i = 0; i < 20; i++)
{
    addTasks.Add(Task.Run(() => redis.GetDatabase(0).ListLeftPush(key, $"{value}{i}")));
}
await Task.Delay(1000)
var resultInScreenshot = db.ListRange(key).Select(x => (string)x).ToArray();

image

Edit: Even though I am not able to create any issues with concurrent add command, blocking command and unblock command. I am pretty sure if I try hard I can create an issue here because the observer is not part of the transaction. But I don't think this should be an issue for Garnet as it doesn't corrupt any original data.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I think that only 1 client should get a result of 1, this happens do to the thread-unsafety in ClientCommands (see my comment there).
  • That's a good find - please let me know if you need any help here, this should be fixed promptly as it corrupts the data.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another interesting things I found is there is an issue with the list push command where if we push multiple items in then somehow all items values are just the same as last item's value. I will find the root cause and fix it as part of a separate PR

for (int i = 0; i < 20; i++)
{
    addTasks.Add(Task.Run(() => redis.GetDatabase(0).ListLeftPush(key, $"{value}{i}")));
}
await Task.Delay(1000)
var resultInScreenshot = db.ListRange(key).Select(x => (string)x).ToArray();

This is a bug in your client code, not the server. You need to capture the i values correctly - something like this:

            for (int i = 0; i < 20; i++)
            {
                var _i = i;
                addTasks.Add(Task.Run(() => redis.GetDatabase(0).ListLeftPush(key, $"{value}{_i}")));
            }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@badrishc Ohh yaa, a rookie mistake from my end

@Vijay-Nirmal
Copy link
Contributor Author

Fixed the review commands

Copy link
Contributor

@TalZaccai TalZaccai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Please see remaining comments :)

@@ -1036,6 +1036,43 @@
"Type": "String"
}
]
},
{
"Command": "CLIENT_UNBLOCK",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here about running CommentInfoUpdater - #864 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants