I have some confusion with regards on how to use CompletionQueue for async C++ client. My server is in C#, so my question here is purely around the client sending a request in an async manner to the server.
For reference, here is how I setup my client code to make an async request:
template<typename ResponseType, typename AsyncOpExecutor>
bool DoAsyncOp(const AsyncOpExecutor& op, const unsigned int deadlineMs, ResponseType& response)
{
grpc::ClientContext ctx;
grpc::CompletionQueue queue;
const std::unique_ptr<grpc::ClientAsyncResponseReaderInterface<ResponseType>> asyncOpResponse = op(ctx, queue);
grpc::Status status;
int requestTag = 1;
asyncOpResponse->Finish(&response, &status, (void*)requestTag);
bool result = false;
bool gotEvent = false;
do
{
const std::chrono::time_point<std::chrono::system_clock> deadline = std::chrono::system_clock::now() + std::chrono::milliseconds(deadlineMs);
void* got_tag;
bool ok = false;
const grpc::CompletionQueue::NextStatus nextStatus = queue.AsyncNext(&got_tag, &ok, deadline);
switch (nextStatus)
{
case grpc::CompletionQueue::NextStatus::TIMEOUT:
continue;
case grpc::CompletionQueue::NextStatus::GOT_EVENT:
assert(got_tag == (void*)requestTag);
// ok is always true even if I close the server while request is in progress.
assert(ok);
result = status.ok();
gotEvent = true;
break;
// Given that I am creating a new CompletionQueue per request (not using a shared one), is this flag likely to occur?
case grpc::CompletionQueue::NextStatus::SHUTDOWN:
result = false;
gotEvent = false;
break;
default:
result = false;
gotEvent = false;
break;
}
} while (!gotEvent);
return result;
}
My first confusion is around the best way to setup the CompletionQueue. This answer seems to suggest that a single CompletionQueue can be used across requests. How will this behave if multiple threads use the same queue to make requests. Let's say that I change my code above to use a shared queue rather than creating a fresh one per request.
How will one thread know that the response it receives is intended for it, rather than for a different thread?
Do I need to assign a unique tag per thread, and on each thread check that the received tag from the queue matches the one I originally sent?
If thread A receives a tag intended for thread B, does that mean thread B will be able to query for its tag later, or is that tag lost because thread A saw it first?
Is there actually a major issue of using a fresh queue per request, rather than sharing?
My second confusion is around grpc::CompletionQueue::NextStatus::SHUTDOWN result. If I use a fresh queue per request, and not explicitly call shutdown on the queue, is this result ever likely to occur? If yes, what will trigger it? One test I carried out is closing the server while a request is in progress, however rather than getting the shutdown result I got the grpc::CompletionQueue::NextStatus::GOT_EVENT result, with status set as UNAVAILABLE.
My final confusion is around the ok flag. I have read this answer, however it's still not very clear. Given the use case and code posted above, if the result I get from the queue is grpc::CompletionQueue::NextStatus::GOT_EVENT, can the ok flag be false, and if yes, what will cause it to be false? Again this is purely around the client and not how CompletionQueue is handles on the server.