Project

General

Profile

Actions

Bug #1809

closed

Memory leak when closing the session in the middle of a POST request: propagate connection closing

Added by Stanislav Vorobiov about 11 years ago. Updated about 10 years ago.

Status:
Closed
Priority:
Normal
Assignee:
Target version:
Start date:
04/02/2013
Due date:
% Done:

0%

Estimated time:

Description

When you close the session in the middle of a POST request you get a memory leak at src/http/WtReply.C:consumeRequestBody

...
httpRequest_ = new HTTPRequest(boost::dynamic_pointer_cast<WtReply>
                       (shared_from_this()), &entryPoint_);
...

WtReply class has a member "HTTPRequest* httpRequest_" which is a internally owned pointer. HTTPRequest constructor is:

HTTPRequest::HTTPRequest(WtReplyPtr reply, const Wt::EntryPoint *entryPoint)

i.e. it takes a shared_ptr to WtReply, thus, causing a reference loop. The loop is broken when the browser finishes the request normally:

...
    if (!connection->server()->controller()->requestDataReceived
        (httpRequest_, bodyReceived_, request().contentLength)) {
      delete httpRequest_;
      httpRequest_ = 0;
...

However, when the browser is closed in the middle of a POST request WtReply gets unreferenced from http::server::Connection, however, httpRequest_ inside WtReply still holds a reference

to WtReply, thus, causing a memory leak.

The fix:

Since httpRequest_ is just a raw pointer which is owned by WtReply it's not necessary to pass a shared_ptr to it, normal pointer will suffice, just replace:

HTTPRequest::HTTPRequest(WtReplyPtr reply, const Wt::EntryPoint *entryPoint)

with:

HTTPRequest::HTTPRequest(WtReply* reply, const Wt::EntryPoint *entryPoint)

and

httpRequest_ = new HTTPRequest(boost::dynamic_pointer_cast<WtReply>
                       (shared_from_this()), &entryPoint_);

with:

httpRequest_ = new HTTPRequest(this, &entryPoint_);
Actions #1

Updated by Koen Deforche about 11 years ago

  • Status changed from New to InProgress
  • Assignee set to Koen Deforche
  • Target version set to 3.3.0
Actions #2

Updated by Koen Deforche about 11 years ago

Hey,

I follow your analysis and indeed this will lead to a memory leak.

IMO, your solution will not work because the shared pointer makes sure that reply stays alive as long as the http request object is passed around within Wt (this is until a flush(done) is called on it).

I believe the proper fix is to delete the httpRequest on Error, since in any case, consumeData() will be called with an Error state when the browser closes the connection.

Regards,

koen

Actions #3

Updated by Stanislav Vorobiov about 11 years ago

Hi,

IMO, your solution will not work because the shared pointer makes sure that reply stays alive as long as the http request object is passed around within Wt (this is until a flush(done) is called on it).

Do you mean that HTTPRequest that is stored inside WtReply can outlive WtReply itself ? Could you show me the exact line of code where that happens ?

Actions #4

Updated by Koen Deforche about 11 years ago

  • Subject changed from Memory leak when closing the session in the middle of a POST request to Memory leak when closing the session in the middle of a POST request: propagate connection closing
  • Target version changed from 3.3.0 to 3.3.1

Hey,

No, the HTTPRequest cannot outlive the WtReply, but without a shared ptr to WtReply from HTTPRequest, both could be deleted while the http request is still referenced and used within Wt (i.e. when the connection is being closed).

It does make me realize that if we would properly propagate connection closing up to the Wt layer which will then discard the request, we would not have these complications.

Regards,

koen

Actions #5

Updated by Stanislav Vorobiov about 11 years ago

Ok, but about:

both could be deleted while the http request is still referenced and used within Wt

If we look at WtReply.C, there're only 2 places where 'httpRequest_' private member gets out of WtReply:

    if (!connection->server()->controller()->requestDataReceived
        (httpRequest_, bodyReceived_, request().contentLength)) {
      delete httpRequest_;
      httpRequest_ = 0;

      setStatus(request_entity_too_large);
      setCloseConnection();
      state = Request::Error;
    }

and

    connection->server()->controller()->handleRequest(httpRequest_);

Now, let's look at first method - requestDataReceived:

bool WebController::requestDataReceived(WebRequest *request,
                    boost::uintmax_t current,
                    boost::uintmax_t total)
{
#ifdef WT_THREADED
  boost::mutex::scoped_lock lock(uploadProgressUrlsMutex_);
#endif // WT_THREADED

  if (uploadProgressUrls_.find(request->queryString())
      != uploadProgressUrls_.end()) {
#ifdef WT_THREADED
    lock.unlock();
#endif // WT_THREADED

    CgiParser cgi(conf_.maxRequestSize());

    try {
      cgi.parse(*request, CgiParser::ReadHeadersOnly);
    } catch (std::exception& e) {
      LOG_ERROR_S(&server_, "could not parse request: " << e.what());
      return false;
    }

    const std::string *wtdE = request->getParameter("wtd");
    if (!wtdE)
      return false;

    std::string sessionId = *wtdE;

    ApplicationEvent event(sessionId,
               boost::bind(&WebController::updateResourceProgress,
                       this, request, current, total));

    if (handleApplicationEvent(event))
      return !request->postDataExceeded();
    else
      return false;
  }

  return true;
}

and second - handleRequest:

void WebController::handleRequest(WebRequest *request)
{
... too long to quote ...
}

So, it looks like 'httpRequest*' is only being used "in place", it doesn't get stored anywhere, so, after those methods return we can safely delete 'httpRequest*'. Or am I missing something ? Sorry if I'm being annoying, I just want to understand why it's not possible to just make a raw pointer, I'm using Wt in my embedded project and I can't wait until this bug is officially fixed, it causes a lot of problems especially with large file uploads, due to this bug spool files remain in filesystem when they should have been deleted, thus, my embedded system can run low on disk space quickly...

Actions #6

Updated by Koen Deforche about 11 years ago

Hey,

I didn't want to mark the bug as resolved but latest git does resolve it. I want to fix it better by propagating the close event.

For asynchronous responses, the request is kept around (in WApplication and WResource continuation).

Regards,

Koen

Actions #7

Updated by Koen Deforche over 10 years ago

  • Target version changed from 3.3.1 to 3.3.2
Actions #8

Updated by Koen Deforche about 10 years ago

  • Status changed from InProgress to Resolved

Connection closing is not propagated up.

Actions #9

Updated by Koen Deforche about 10 years ago

  • Status changed from Resolved to Closed
Actions

Also available in: Atom PDF