Skip to content

Commit 8f3a60d

Browse files
committed
Merge remote-tracking branch 'github/staging' into development
2 parents dad24c1 + c966484 commit 8f3a60d

File tree

4 files changed

+113
-48
lines changed

4 files changed

+113
-48
lines changed

CONTRIBUTORS.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,6 @@ Cisco Systems
3636
Gergely Lukacsy (glukacsy)
3737

3838
thomasschaub
39+
40+
Trimble
41+
Tim Boundy (gigaplex)

Release/include/cpprest/details/http_server_httpsys.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ struct windows_request_context : http::details::_http_server_context
9999
// Dispatch request to the provided http_listener.
100100
void dispatch_request_to_listener(_In_ web::http::experimental::listener::details::http_listener_impl *pListener);
101101

102+
enum class ShouldWaitForBody
103+
{
104+
Wait,
105+
DontWait
106+
};
107+
// Initialise the response task callbacks. If the body has been requested, we should wait for it to avoid race conditions.
108+
void init_response_callbacks(ShouldWaitForBody shouldWait);
109+
102110
// Read in a portion of the request body.
103111
void read_request_body_chunk();
104112

Release/src/http/listener/http_listener_msg.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ pplx::task<void> details::_http_request::_reply_impl(http_response response)
4747
{
4848
// Add a task-based continuation so no exceptions thrown from the task go 'unobserved'.
4949
response._set_server_context(std::move(m_server_context));
50-
response_completed = experimental::details::http_server_api::server_api()->respond(response);
50+
response_completed = server_api->respond(response);
5151
response_completed.then([](pplx::task<void> t)
5252
{
5353
try { t.wait(); } catch(...) {}

Release/src/http/listener/http_server_httpsys.cpp

Lines changed: 101 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -465,13 +465,7 @@ void http_windows_server::receive_requests()
465465
pplx::task<void> http_windows_server::respond(http::http_response response)
466466
{
467467
windows_request_context * p_context = static_cast<windows_request_context *>(response._get_server_context());
468-
return pplx::create_task(p_context->m_response_completed).then([p_context](::pplx::task<void> t)
469-
{
470-
// After response is sent, break circular reference between http_response and the request context.
471-
// Otherwise http_listener::close() can hang.
472-
p_context->m_response._get_impl()->_set_server_context(nullptr);
473-
t.get();
474-
});
468+
return pplx::create_task(p_context->m_response_completed);
475469
}
476470

477471
windows_request_context::windows_request_context()
@@ -494,6 +488,12 @@ windows_request_context::~windows_request_context()
494488
// the lock then setting of the event has completed.
495489
std::unique_lock<std::mutex> lock(m_responseCompletedLock);
496490

491+
// Add a task-based continuation so no exceptions thrown from the task go 'unobserved'.
492+
pplx::create_task(m_response_completed).then([](pplx::task<void> t)
493+
{
494+
try { t.wait(); } catch(...) {}
495+
});
496+
497497
auto *pServer = static_cast<http_windows_server *>(http_server_api::server_api());
498498
if(--pServer->m_numOutstandingRequests == 0)
499499
{
@@ -530,6 +530,7 @@ void windows_request_context::async_process_request(HTTP_REQUEST_ID request_id,
530530
{
531531
CancelThreadpoolIo(pServer->m_threadpool_io);
532532
m_msg.reply(status_codes::InternalError);
533+
init_response_callbacks(ShouldWaitForBody::DontWait);
533534
}
534535
}
535536

@@ -541,6 +542,7 @@ void windows_request_context::read_headers_io_completion(DWORD error_code, DWORD
541542
if(error_code != NO_ERROR)
542543
{
543544
m_msg.reply(status_codes::InternalError);
545+
init_response_callbacks(ShouldWaitForBody::DontWait);
544546
}
545547
else
546548
{
@@ -574,6 +576,9 @@ void windows_request_context::read_headers_io_completion(DWORD error_code, DWORD
574576
else
575577
{
576578
m_msg.reply(status_codes::BadRequest, badRequestMsg);
579+
580+
// Even though we have a bad request, we should wait for the body otherwise we risk racing over m_overlapped
581+
init_response_callbacks(ShouldWaitForBody::Wait);
577582
}
578583
}
579584
}
@@ -649,46 +654,7 @@ void windows_request_context::dispatch_request_to_listener(_In_ web::http::exper
649654
// Save http_request copy to dispatch to user's handler in case content_ready() completes before.
650655
http_request request = m_msg;
651656

652-
// Wait until the content download finished before replying.
653-
request.content_ready().then([=](pplx::task<http_request> requestBody)
654-
{
655-
// If an exception occurred while processing the body then there is no reason
656-
// to even try sending the response, just re-surface the same exception.
657-
try
658-
{
659-
requestBody.wait();
660-
}
661-
catch (...)
662-
{
663-
m_msg = http_request();
664-
cancel_request(std::current_exception());
665-
return;
666-
}
667-
668-
// At this point the user entirely controls the lifetime of the http_request.
669-
m_msg = http_request();
670-
671-
request.get_response().then([this](pplx::task<http::http_response> responseTask)
672-
{
673-
// Don't let an exception from sending the response bring down the server.
674-
try
675-
{
676-
m_response = responseTask.get();
677-
}
678-
catch (const pplx::task_canceled &)
679-
{
680-
// This means the user didn't respond to the request, allowing the
681-
// http_request instance to be destroyed. There is nothing to do then
682-
// so don't send a response.
683-
return;
684-
}
685-
catch (...)
686-
{
687-
m_response = http::http_response(status_codes::InternalError);
688-
}
689-
async_process_response();
690-
});
691-
});
657+
init_response_callbacks(ShouldWaitForBody::Wait);
692658

693659
// Look up the lock for the http_listener.
694660
auto *pServer = static_cast<http_windows_server *>(http_server_api::server_api());
@@ -721,6 +687,94 @@ void windows_request_context::dispatch_request_to_listener(_In_ web::http::exper
721687
}
722688
}
723689

690+
void windows_request_context::init_response_callbacks(ShouldWaitForBody shouldWait)
691+
{
692+
// Use a proxy event so we're not causing a circular reference between the http_request and the response task
693+
pplx::task_completion_event<void> proxy_content_ready;
694+
695+
auto content_ready_task = m_msg.content_ready();
696+
auto get_response_task = m_msg.get_response();
697+
698+
content_ready_task.then([this, proxy_content_ready](pplx::task<http_request> requestBody)
699+
{
700+
// If an exception occurred while processing the body then there is no reason
701+
// to even try sending the response, just re-surface the same exception.
702+
try
703+
{
704+
requestBody.wait();
705+
}
706+
catch (...)
707+
{
708+
// Copy the request reference in case it's the last
709+
http_request request = m_msg;
710+
m_msg = http_request();
711+
auto exc = std::current_exception();
712+
proxy_content_ready.set_exception(exc);
713+
cancel_request(exc);
714+
return;
715+
}
716+
717+
// At this point the user entirely controls the lifetime of the http_request.
718+
m_msg = http_request();
719+
proxy_content_ready.set();
720+
});
721+
722+
get_response_task.then([this, proxy_content_ready](pplx::task<http::http_response> responseTask)
723+
{
724+
// Don't let an exception from sending the response bring down the server.
725+
try
726+
{
727+
m_response = responseTask.get();
728+
}
729+
catch (const pplx::task_canceled &)
730+
{
731+
// This means the user didn't respond to the request, allowing the
732+
// http_request instance to be destroyed. There is nothing to do then
733+
// so don't send a response.
734+
// Avoid unobserved exception handler
735+
pplx::create_task(proxy_content_ready).then([](pplx::task<void> t)
736+
{
737+
try { t.wait(); } catch (...) {}
738+
});
739+
return;
740+
}
741+
catch (...)
742+
{
743+
// Should never get here, if we do there's a chance that a circular reference will cause leaks,
744+
// or worse, undefined behaviour as we don't know who owns 'this' anymore
745+
_ASSERTE(false);
746+
m_response = http::http_response(status_codes::InternalError);
747+
}
748+
749+
pplx::create_task(m_response_completed).then([this](pplx::task<void> t)
750+
{
751+
// After response is sent, break circular reference between http_response and the request context.
752+
// Otherwise http_listener::close() can hang.
753+
m_response._get_impl()->_set_server_context(nullptr);
754+
});
755+
756+
// Wait until the content download finished before replying because m_overlapped is reused,
757+
// and we don't want to delete 'this' if the body is still downloading
758+
pplx::create_task(proxy_content_ready).then([this](pplx::task<void> t)
759+
{
760+
try
761+
{
762+
t.wait();
763+
async_process_response();
764+
}
765+
catch (...)
766+
{
767+
}
768+
}).wait();
769+
});
770+
771+
if (shouldWait == ShouldWaitForBody::DontWait)
772+
{
773+
// Fake a body completion so the content_ready() task doesn't keep the http_request alive forever
774+
m_msg._get_impl()->_complete(0);
775+
}
776+
}
777+
724778
void windows_request_context::async_process_response()
725779
{
726780
auto *pServer = static_cast<http_windows_server *>(http_server_api::server_api());

0 commit comments

Comments
 (0)