Description
Hi,
Either I do something wrong or there's a deadlock. I've implemented simple REST server using cpprestsdk. Basically it just serves a couple of endpoints and static content. Here's an implementation for static serving:
class StaticServer : public Endpoint {
public:
StaticServer(): Endpoint(web::http::methods::GET, "/",
std::bind(&StaticServer::serve, this, std::placeholders::_1)) {}
private:
pplx::task<void> serve(web::http::http_request request) {
const std::string file = file_from_request(request.request_uri());
if (access(file.c_str(), F_OK) != 0) {
return request.reply(status_codes::NotFound);
}
if (access(file.c_str(), R_OK) != 0) {
return request.reply(status_codes::Forbidden);
}
web::http::http_response response(status_codes::OK);
response.headers().set_content_type(file_mime(file));
response.headers().set_cache_control(file_cache(file));
return concurrency::streams::fstream::open_istream(file, std::ios_base::in | std::ios_base::binary)
.then([=](concurrency::streams::istream stream) mutable {
response.set_body(stream);
return request.reply(response);
});
}
};
And here's how listeners are created:
virtual void listen() {
for (auto endpoint: endpoints_) {
for (auto method: endpoint->methods()) {
auto listener = createListener(endpoint->route()); // returns std::unique_ptr<http_listener>
listener->support(method, [=](http_request request) {
endpoint->handler()(request).then([=] { // endpoint->handler() returns std::function<...> which handles this endpoint. See StaticServer::serve
LOG << "request finished: " << request.request_uri().to_string() << std::endl;
});
});
listener->open().wait();
listeners_.push_back(std::move(listener));
}
}
}
I've restricted threadpool used in cpprestsdk from 40 to 4 threads because server is running on embedded device. Main server's thread calls listen()
and then just sleeps in an infinite loop. When I open the browser and send the request (just http://localhost:port/
) it works for a couple of times and then locks. Here's the backtrace of one of the threads:
==10080== at 0x55A2DA0: pthread_cond_wait@@GLIBC_2.3.2 (pthread_cond_wait.S:185)
==10080== by 0x5EAFCDB: std::condition_variable::wait(std::unique_lock<std::mutex>&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==10080== by 0x432F12: void std::condition_variable::wait<pplx::details::event_impl::wait(unsigned int)::{lambda()#1}>(std::unique_lock<std::mutex>&, pplx::details::event_impl::wait(unsigned int)::{lambda()#1}) (condition_variable:98)
==10080== by 0x428A1E: pplx::details::event_impl::wait(unsigned int) (pplxlinux.h:117)
==10080== by 0x428AF2: pplx::details::event_impl::wait() (pplxlinux.h:133)
==10080== by 0x42A06B: pplx::details::_TaskCollectionImpl::_Wait() (pplx.h:182)
==10080== by 0x42A051: pplx::details::_TaskCollectionImpl::_RunAndWait() (pplx.h:177)
==10080== by 0x42AD49: pplx::details::_Task_impl_base::_Wait() (pplxtasks.h:1778)
==10080== by 0x4347E9: pplx::task<unsigned char>::wait() const (pplxtasks.h:3519)
==10080== by 0x42C253: pplx::task<void>::wait() const (pplxtasks.h:4428)
==10080== by 0x46D406: Concurrency::streams::details::basic_file_buffer<unsigned char>::~basic_file_buffer() (filestream.h:111)
==10080== by 0x46D4AD: Concurrency::streams::details::basic_file_buffer<unsigned char>::~basic_file_buffer() (filestream.h:118)
==10080== by 0x47A88B: std::_Sp_counted_ptr<Concurrency::streams::details::basic_file_buffer<unsigned char>*, (__gnu_cxx::_Lock_policy)2>::_M_dispose()
==10080== by 0x4388F5: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (shared_ptr_base.h:149)
==10080== by 0x432DFA: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() (shared_ptr_base.h:666)
==10080== by 0x435573: std::__shared_ptr<Concurrency::streams::details::basic_streambuf<unsigned char>, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() (shared_ptr_base.h:914)
==10080== by 0x4355B3: std::shared_ptr<Concurrency::streams::details::basic_streambuf<unsigned char> >::~shared_ptr() (shared_ptr.h:93)
==10080== by 0x43AEEE: Concurrency::streams::streambuf<unsigned char>::~streambuf()
==10080== by 0x46BF3F: Concurrency::streams::details::basic_istream_helper<unsigned char>::~basic_istream_helper() (streams.h:59)
==10080== by 0x4672E5: void __gnu_cxx::new_allocator<Concurrency::streams::details::basic_istream_helper<unsigned char> >::destroy<Concurrency::streams::details::basic_istream_helper<unsigned char> >(Concurrency::streams::details::basic_istream_helper<unsigned char>*) (new_allocator.h:124)
==10080== by 0x462AC6: std::enable_if<std::__and_<std::allocator_traits<std::allocator<Concurrency::streams::details::basic_istream_helper<unsigned char> > >::__destroy_helper<Concurrency::streams::details::basic_istream_helper<unsigned char> >::type>::value, void>::type std::allocator_traits<std::allocator<Concurrency::streams::details::basic_istream_helper<unsigned char> > >::_S_destroy<Concurrency::streams::details::basic_istream_helper<unsigned char> >(std::allocator<Concurrency::streams::details::basic_istream_helper<unsigned char> >&, Concurrency::streams::details::basic_istream_helper<unsigned char>*) (alloc_traits.h:282)
==10080== by 0x45F0F4: void std::allocator_traits<std::allocator<Concurrency::streams::details::basic_istream_helper<unsigned char> > >::destroy<Concurrency::streams::details::basic_istream_helper<unsigned char> >(std::allocator<Concurrency::streams::details::basic_istream_helper<unsigned char> >&, Concurrency::streams::details::basic_istream_helper<unsigned char>*) (alloc_traits.h:411)
==10080== by 0x458EE0: std::_Sp_counted_ptr_inplace<Concurrency::streams::details::basic_istream_helper<unsigned char>, std::allocator<Concurrency::streams::details::basic_istream_helper<unsigned char> >, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr_base.h:524)
==10080== by 0x4388F5: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (shared_ptr_base.h:149)
==10080== by 0x4F62ADD: web::http::details::_http_response::~_http_response()
==10080== by 0x4677F4: void __gnu_cxx::new_allocator<web::http::details::_http_response>::destroy<web::http::details::_http_response>(web::http::details::_http_response*) (new_allocator.h:124)
==10080== by 0x462E1D: std::enable_if<std::__and_<std::allocator_traits<std::allocator<web::http::details::_http_response> >::__destroy_helper<web::http::details::_http_response>::type>::value, void>::type std::allocator_traits<std::allocator<web::http::details::_http_response> >::_S_destroy<web::http::details::_http_response>(std::allocator<web::http::details::_http_response>&, web::http::details::_http_response*) (alloc_traits.h:282)
==10080== by 0x45F44E: void std::allocator_traits<std::allocator<web::http::details::_http_response> >::destroy<web::http::details::_http_response>(std::allocator<web::http::details::_http_response>&, web::http::details::_http_response*) (alloc_traits.h:411)
==10080== by 0x45972A: std::_Sp_counted_ptr_inplace<web::http::details::_http_response, std::allocator<web::http::details::_http_response>, (__gnu_cxx::_Lock_policy)2>::_M_dispose() (shared_ptr_base.h:524)
==10080== by 0x4388F5: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (shared_ptr_base.h:149)
==10080== by 0x4F73A44: std::_Sp_counted_ptr_inplace<pplx::details::_Task_completion_event_impl<web::http::http_response>, std::allocator<pplx::details::_Task_completion_event_impl<web::http::http_response> >, (__gnu_cxx::_Lock_policy)2>::_M_dispose()
==10080== by 0x4388F5: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (shared_ptr_base.h:149)
==10080== by 0x4F62C17: web::http::details::_http_request::~_http_request()
==10080== by 0x4388F5: std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() (shared_ptr_base.h:149)
==10080== by 0x50979E1: web::http::experimental::listener::details::connection::handle_http_line(boost::system::error_code const&)
==10080== by 0x509A301: boost::asio::detail::read_until_match_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::stream_socket_service<boost::asio::ip::tcp> >, std::allocator<char>, web::http::experimental::listener::details::crlf_nonascii_searcher_t, web::http::experimental::listener::details::connection::start_request_response()::{lambda(boost::system::error_code const&, unsigned long)#2}>::operator()(boost::system::error_code const&, unsigned long, int)
==10080== by 0x509BD86: boost::asio::detail::reactive_socket_recv_op<boost::asio::mutable_buffers_1, boost::asio::detail::read_until_match_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::stream_socket_service<boost::asio::ip::tcp> >, std::allocator<char>, web::http::experimental::listener::details::crlf_nonascii_searcher_t, web::http::experimental::listener::details::connection::start_request_response()::{lambda(boost::system::error_code const&, unsigned long)#2}> >::do_complete(boost::asio::detail::task_io_service*, boost::asio::detail::task_io_service_operation*, boost::system::error_code const&, unsigned long)
==10080== by 0x4FDC694: boost::asio::detail::epoll_reactor::descriptor_state::do_complete(boost::asio::detail::task_io_service*, boost::asio::detail::task_io_service_operation*, boost::system::error_code const&, unsigned long)
==10080== by 0x4FDC322: boost::asio::detail::task_io_service::run(boost::system::error_code&)
==10080== by 0x505BD25: crossplat::threadpool::thread_start(void*)
==10080== by 0x559D6A9: start_thread (pthread_create.c:333)
==10080== by 0x649BE9C: clone (clone.S:109)
All 4 threads in a threadpool are locked in this condition (all have the same backtrace). So am I doing something wrong? All 4 threads locked waiting for a signal. Here's the relevant code (include/cpprest/filestream.h
):
virtual ~basic_file_buffer()
{
if( this->can_read() )
{
this->_close_read().wait(); // Line 111: locks here
}
if (this->can_write())
{
this->_close_write().wait();
}
}
So, all 4 threads are wait
ing and no free threads are in threadpool to schedule a task which will fire set
(this is just my guess). The ~basic_file_buffer
is called because I set concurrency::streams::istream
as a body for response. So when response is destructed the stream is destructed too causing destruction of the underlying buffer.
The server (and so cpprestsdk) is compiled and running on x86_64 linux (ubuntu 15.10). gcc version is 4.9.3. I can reproduce this in 100% of the cases. It serves the files for 2-3 times (sending around 20-30 files each time) and then locks.
Thanks in advance.