Windows: v2.1 Breaks http_request Queuing

Jun 15, 2014 at 12:16 PM
Edited Jun 16, 2014 at 8:42 AM
This is a Windows VS 2013 issue. I implemented a request queuing scheme that worked prior to v2.1. No longer works with v2.1. Is it a bug in C++ Rest SDK? What is the workaround?

The idea is to defer reply() processing by queuing incoming requests inside listener.support() lamda. Some brief time later the queued request is popped in the main() thread and a reply() is issued. The issue is that wait() after close() always hangs. This issue looks similar to Simple Listener Program Faulting on Linux.

What's the correct way of deferring/queuing incoming requests? Isn't there an issue if a request comes in a split second before close(), before listener.support reply is executed, thus causing wait() to hang forever?
#include "cpprest/http_client.h"
#include "cpprest/http_listener.h"
#include "concurrent_queue.h"

Concurrency::concurrent_queue<web::http::http_request> cq;

int main()
{
    try
    {
        web::http::experimental::listener::http_listener listener(U("http://192.168.1.104:3901"));
        listener.support([=](web::http::http_request request) { cq.push(request); /*request.reply(web::http::status_codes::OK, U("Hello, World!"));*/ });
        listener.open().wait();
        std::string line;
        std::cout << "listening. hit enter to initiate shutdown." << std::endl;
        std::getchar();
        std::cout << "popping requests" << std::endl;
        web::http::http_request request;
        while (cq.try_pop(request))
            request.reply(web::http::status_codes::ServiceUnavailable);
        std::cout << "closing" << std::endl;
        Concurrency::task<void> task = listener.close();
        std::cout << "waiting" << std::endl;
        task.wait();
        std::cout << "ending" << std::endl;
    }
    catch (...)
    {
        std::cout << "caught error" << std::endl;
    }
    return(0);
}
Coordinator
Jun 18, 2014 at 1:41 AM
Hi BSalita,

Let me explain first about how the close semantics work with our http_listener. Most uses of our http_listener follow the typical pattern of responding to requests directly in the support handler callback. When doing so you don't need to really know any more. If you are going to start saving requests off for responding to later then you need to be careful.

The way to think about it is for each http_request a reference count is incremented on the listener. Once the request has been responded to and there are no other references to the http_request object the reference count is decremented. If close is called we wait for the reference count to go to zero before signaling the returned task. This is what is causing the hang you are seeing. Close isn't being completed because there are still some http_request objects alive. So if you are saving requests to respond later, the task from close won't be completed until you respond to each request AND destroy the http_request objects.

I'm not sure who is still holding onto an http_request object in your code, but one thing I noticed is in the support lambda you are capturing the concurrent_queue by value with is going to copy the queue. I don't think you want this. As a starting point try out the following code, which works fine for me using a std::queue:
    web::http::experimental::listener::http_listener listener(U("http://localhost:3901"));

    std::queue<::web::http::http_request> requests;
    listener.support([&](::web::http::http_request request) mutable
    {
        requests.push(request);
    });
    listener.open().wait();
    std::cout << "listening. hit enter to initiate shutdown." << std::endl;
    std::getchar();
    std::cout << "popping requests" << std::endl;
    while (!requests.empty())
    {
        requests.front().reply(web::http::status_codes::ServiceUnavailable).wait();
        requests.pop();
    }

    std::cout << "closing" << std::endl;
    Concurrency::task<void> task = listener.close();
    std::cout << "waiting" << std::endl;
    task.wait();
    std::cout << "ending" << std::endl;
Thanks,
Steve
Jun 21, 2014 at 4:42 PM
Steve,

I've found the issue and a workaround.

The following fragment of code uses a very common coding pattern. I'm using Concurrency namespace here.
        web::http::http_request request;
        while (cq.try_pop(request))
            request.reply(web::http::status_codes::ServiceUnavailable).wait();
However, for purposes of the SDK, it's wrong. The issue is that after the while() completes, request still holds an object which causes close().wait() to hang. Instead, the following code causes request to go out of scope and solves the issue. I'm using boost::lockfree namespace here.
while (!queue.empty())
{
    web::http::http_request request;
    if (queue.pop(request))
        request.reply(web::http::status_codes::ServiceUnavailable).wait();
}
I'm speculating there's some issue with http_request object's copy or assignment methods that contributes to the issue. It's not clear to me 1) that the first example should create a hang. 2) the issue is so darn hard to see, even for an expert, that something should be done to avoid it from happening such as a compiler or runtime message, or some change to copy or assignment methods.

I posted this issue to http://stackoverflow.com/questions/24341792/c-not-understanding-object-destruction-rules

Your above post actually helped me to find the issue. Using std::queue is wrong as it isn't thread safe. However, I eventually realized that your example worked because it never created a temporary request variable.

Robert
Coordinator
Jul 1, 2014 at 6:07 PM
Hi Robert,

Like I mentioned in the earlier our current listener semantics are that you have to respond to all request, and allow all http_request and http_response objects to be destroyed before the task returned from close completes. That is why you are seeing a hang.

I made some improvements and now you no longer have to do any of this for the close task to complete. The updates have been made in the development branch, and your concurrent_queue case works now. Try it out.

Steve
Jul 1, 2014 at 10:39 PM
I can confirm that the test program now works using the development branch. Bravo and thanks.