Bug #1809
closedMemory leak when closing the session in the middle of a POST request: propagate connection closing
0%
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_);
Updated by Koen Deforche over 11 years ago
- Status changed from New to InProgress
- Assignee set to Koen Deforche
- Target version set to 3.3.0
Updated by Koen Deforche over 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
Updated by Stanislav Vorobiov over 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 ?
Updated by Koen Deforche over 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
Updated by Stanislav Vorobiov over 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...
Updated by Koen Deforche over 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
Updated by Koen Deforche about 11 years ago
- Target version changed from 3.3.1 to 3.3.2
Updated by Koen Deforche almost 11 years ago
- Status changed from InProgress to Resolved
Connection closing is not propagated up.
Updated by Koen Deforche almost 11 years ago
- Status changed from Resolved to Closed