summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
authorKonstantin Käfer <mail@kkaefer.com>2014-11-05 18:32:11 +0100
committerKonstantin Käfer <mail@kkaefer.com>2014-11-06 13:05:51 +0100
commit3038cdbbf3bddc49f6a58dda710a8e080dcb9ec5 (patch)
treee7c1dc3445040852935c3ebf80d137c409da5dc5 /platform
parent44570c1d303ea169157f82034ef3af42f73d9e8d (diff)
downloadqtlocation-mapboxgl-3038cdbbf3bddc49f6a58dda710a8e080dcb9ec5.tar.gz
fix various issues in curl bindings related to tile cancelation
Diffstat (limited to 'platform')
-rw-r--r--platform/default/http_request_baton_curl.cpp304
1 files changed, 162 insertions, 142 deletions
diff --git a/platform/default/http_request_baton_curl.cpp b/platform/default/http_request_baton_curl.cpp
index 80eed27677..986191ef80 100644
--- a/platform/default/http_request_baton_curl.cpp
+++ b/platform/default/http_request_baton_curl.cpp
@@ -1,7 +1,5 @@
#include <mbgl/storage/http_request_baton.hpp>
#include <mbgl/util/uv-messenger.h>
-#include <mbgl/util/std.hpp>
-#include <mbgl/util/ptr.hpp>
#include <mbgl/util/time.hpp>
#include <uv.h>
@@ -20,7 +18,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@@ -74,90 +72,108 @@ static std::queue<CURL *> handles;
namespace mbgl {
-struct CURLContext {
- util::ptr<HTTPRequestBaton> baton;
- uv_poll_t *poll_handle = nullptr;
- curl_socket_t sockfd = 0;
+struct Context {
+ const util::ptr<HTTPRequestBaton> baton;
+ CURL *handle = nullptr;
curl_slist *headers = nullptr;
- CURLContext(const util::ptr<HTTPRequestBaton> &baton_) : baton(baton_) {
+ Context(const util::ptr<HTTPRequestBaton> &baton_) : baton(baton_) {
+ assert(baton);
+ baton->ptr = this;
+
+ if (!handles.empty()) {
+ handle = handles.front();
+ handles.pop();
+ } else {
+ handle = curl_easy_init();
+ }
}
- ~CURLContext() {
- // We are destructing the poll handle in a CURL callback already.
- assert(!poll_handle);
+ ~Context() {
+ baton->ptr = nullptr;
- // Once the CURLContext gets destroyed, CURL doesn't need any headers anymore.
if (headers) {
curl_slist_free_all(headers);
headers = nullptr;
}
- }
-};
-
-// Locks the CURL share handle
-void curl_share_lock(CURL *, curl_lock_data, curl_lock_access, void *) {
- uv_mutex_lock(&share_mutex);
-}
-// Unlocks the CURL share handle
-void curl_share_unlock(CURL *, curl_lock_data, void *) {
- uv_mutex_unlock(&share_mutex);
-}
-
-// This function must run in the CURL thread.
-// It is either called when the request is completed, or when we try to cancel the request.
-void finish_request(const util::ptr<HTTPRequestBaton> &baton) {
- assert(uv_thread_self() == thread_id);
- if (baton->ptr) {
- CURL *handle = (CURL *)baton->ptr;
CURLMcode error = curl_multi_remove_handle(multi, handle);
if (error != CURLM_OK) {
- baton->response = std::make_unique<Response>();
+ baton->response = std::unique_ptr<Response>(new Response());
baton->response->code = -1;
baton->response->message = curl_multi_strerror(error);
}
- // Destroy the shared pointer. We still have one pointing to it
- CURLContext *context = nullptr;
- curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char *)&context);
curl_easy_setopt(handle, CURLOPT_PRIVATE, nullptr);
- delete context;
-
- // TODO: delete the headers object again.
-
curl_easy_reset(handle);
handles.push(handle);
- baton->ptr = nullptr;
+ handle = nullptr;
+
+ if (baton->async) {
+ uv_async_send(baton->async);
+ baton->async = nullptr;
+ }
}
-}
+};
-void curl_perform(uv_poll_t *req, int, int events) {
- int running_handles;
- int flags = 0;
- CURLContext *context = (CURLContext *)req->data;
- CURLMsg *message;
- int pending;
+struct Socket {
+private:
+ uv_poll_t poll_handle;
- uv_timer_stop(&timeout);
+public:
+ const curl_socket_t sockfd = 0;
- if (events & UV_READABLE) {
- flags |= CURL_CSELECT_IN;
+public:
+ Socket(curl_socket_t sockfd_) : sockfd(sockfd_) {
+ uv_poll_init_socket(loop, &poll_handle, sockfd);
+ poll_handle.data = this;
}
- if (events & UV_WRITABLE) {
- flags |= CURL_CSELECT_OUT;
+
+ void start(int events, uv_poll_cb cb) {
+ uv_poll_start(&poll_handle, events, cb);
}
- curl_multi_socket_action(multi, context->sockfd, flags, &running_handles);
+ void stop() {
+ uv_poll_stop(&poll_handle);
+ uv_close((uv_handle_t *)&poll_handle, [](uv_handle_t *handle) {
+ delete (Socket *)handle->data;
+ });
+ }
+
+private:
+ // Make the destructor private to ensure that ew can only close the Socket
+ // with stop(), and disallow manual deletion.
+ ~Socket() {
+ assert(!poll_handle.data);
+ }
+};
+
+// Locks the CURL share handle
+void curl_share_lock(CURL *, curl_lock_data, curl_lock_access, void *) {
+ uv_mutex_lock(&share_mutex);
+}
+
+// Unlocks the CURL share handle
+void curl_share_unlock(CURL *, curl_lock_data, void *) {
+ uv_mutex_unlock(&share_mutex);
+}
+
+void check_multi_info() {
+ CURLMsg *message = nullptr;
+ int pending = 0;
while ((message = curl_multi_info_read(multi, &pending))) {
switch (message->msg) {
case CURLMSG_DONE: {
- CURLContext *ctx = nullptr;
- curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&ctx);
+ Context *context = nullptr;
+ curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&context);
+ assert(context);
+
+ auto baton = context->baton;
- // Make a copy so that the Baton stays around even after we are calling finish_request
- util::ptr<HTTPRequestBaton> baton = ctx->baton;
+ // This request is complete. We are removing the pointer to the CURL easy handle again
+ // to prevent this request from getting canceled.
+ context->baton->ptr = nullptr;
// Add human-readable error code
if (message->data.result != CURLE_OK) {
@@ -199,16 +215,8 @@ void curl_perform(uv_poll_t *req, int, int events) {
}
}
- // We're currently in the CURL request thread.
- finish_request(baton);
-
- if (baton->async) {
- uv_async_send(baton->async);
- baton->async = nullptr;
- }
-
- break;
- }
+ delete context;
+ } break;
default:
// This should never happen, because there are no other message types.
@@ -217,59 +225,75 @@ void curl_perform(uv_poll_t *req, int, int events) {
}
}
-int handle_socket(CURL *handle, curl_socket_t sockfd, int action, void *, void *socketp) {
- CURLContext *context = nullptr;
- curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char *)&context);
-
- if (!socketp && action != CURL_POLL_REMOVE) {
- // We haven't initialized the socket yet, so we need to do this now.
- assert(context->sockfd == 0);
- context->sockfd = sockfd;
- assert(!context->poll_handle);
- context->poll_handle = new uv_poll_t;
- uv_poll_init_socket(loop, context->poll_handle, sockfd);
- context->poll_handle->data = context;
- curl_multi_assign(multi, sockfd, context);
+void curl_perform(uv_poll_t *req, int /* status */, int events) {
+ int flags = 0;
+
+ uv_timer_stop(&timeout);
+
+ if (events & UV_READABLE) {
+ flags |= CURL_CSELECT_IN;
+ }
+ if (events & UV_WRITABLE) {
+ flags |= CURL_CSELECT_OUT;
}
- if (context) {
- if (action == CURL_POLL_IN || action == CURL_POLL_INOUT) {
- uv_poll_start(context->poll_handle, UV_READABLE, curl_perform);
- }
- if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT) {
- uv_poll_start(context->poll_handle, UV_WRITABLE, curl_perform);
+ Socket *context = (Socket *)req->data;
+ int running_handles = 0;
+ curl_multi_socket_action(multi, context->sockfd, flags, &running_handles);
+
+ check_multi_info();
+}
+
+int handle_socket(CURL * /* handle */, curl_socket_t s, int action, void * /* userp */, void *socketp) {
+ Socket *socket;
+ if (action == CURL_POLL_IN || action == CURL_POLL_OUT) {
+ if (socketp) {
+ socket = (Socket *)socketp;
+ } else {
+ socket = new Socket(s);
}
- if (action == CURL_POLL_REMOVE && socketp) {
- uv_poll_stop(context->poll_handle);
- uv_close((uv_handle_t *)context->poll_handle, [](uv_handle_t *poll_handle) {
- delete (uv_poll_t *)poll_handle;
- });
- context->poll_handle = nullptr;
- curl_multi_assign(multi, sockfd, NULL);
+ curl_multi_assign(multi, s, (void *)socket);
+ }
+
+ switch (action) {
+ case CURL_POLL_IN:
+ socket->start(UV_READABLE, curl_perform);
+ break;
+ case CURL_POLL_OUT:
+ socket->start(UV_WRITABLE, curl_perform);
+ break;
+ case CURL_POLL_REMOVE:
+ if (socketp) {
+ ((Socket *)socketp)->stop();
+ curl_multi_assign(multi, s, NULL);
}
+ break;
+ default:
+ abort();
}
return 0;
}
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
-void on_timeout(uv_timer_t *, int = 0) {
+void on_timeout(uv_timer_t * /* req */, int /* status */) {
#else
-void on_timeout(uv_timer_t *) {
+void on_timeout(uv_timer_t * /* req */) {
#endif
int running_handles;
CURLMcode error = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &running_handles);
if (error != CURLM_OK) {
throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error));
}
+
+ check_multi_info();
}
-void start_timeout(CURLM *, long timeout_ms, void *) {
+void start_timeout(CURLM * /* multi */, long timeout_ms, void * /* userp */) {
if (timeout_ms <= 0) {
- on_timeout(&timeout, 0);
- } else {
- uv_timer_start(&timeout, on_timeout, timeout_ms, 0);
+ timeout_ms = 1; /* 0 means directly call socket_action, but we'll do it in a bit */
}
+ uv_timer_start(&timeout, on_timeout, timeout_ms, 0);
}
void thread_init(void *) {
@@ -278,6 +302,10 @@ void thread_init(void *) {
#endif
thread_id = uv_thread_self();
+ if (curl_global_init(CURL_GLOBAL_ALL)) {
+ throw std::runtime_error("Could not init cURL");
+ }
+
uv_timer_init(loop, &timeout);
CURLSHcode share_error;
@@ -293,7 +321,6 @@ void thread_init(void *) {
throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error));
}
-
CURLMcode multi_error;
multi = curl_multi_init();
@@ -304,7 +331,6 @@ void thread_init(void *) {
multi_error = curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, start_timeout);
if (multi_error != CURLM_OK) {
throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error));
-
}
// Main event loop. This will not return until the request loop is terminated.
@@ -322,7 +348,9 @@ void thread_init(void *) {
// This function is called when we have new data for a request. We just append it to the string
// containing the previous data.
size_t curl_write_cb(void *const contents, const size_t size, const size_t nmemb, void *const userp) {
- ((std::string *)userp)->append((char *)contents, size * nmemb);
+ auto &response = *(std::unique_ptr<Response> *)userp;
+ assert(response);
+ response->data.append((char *)contents, size * nmemb);
return size * nmemb;
}
@@ -343,7 +371,8 @@ size_t header_matches(const char *const header, const char *const buffer, const
size_t curl_header_cb(char * const buffer, const size_t size, const size_t nmemb, void *const userp) {
const size_t length = size * nmemb;
- Response *response = static_cast<Response *>(userp);
+ auto &response = *(std::unique_ptr<Response> *)userp;
+ assert(response);
size_t begin = std::string::npos;
if ((begin = header_matches("last-modified: ", buffer, length)) != std::string::npos) {
@@ -364,72 +393,63 @@ size_t curl_header_cb(char * const buffer, const size_t size, const size_t nmemb
// This function must run in the CURL thread.
void start_request(void *const ptr) {
assert(uv_thread_self() == thread_id);
- std::unique_ptr<util::ptr<HTTPRequestBaton>> baton_guard { (util::ptr<HTTPRequestBaton> *)ptr };
- util::ptr<HTTPRequestBaton> &baton = *baton_guard.get();
- assert(baton);
-
- CURL *handle = nullptr;
- if (!handles.empty()) {
- handle = handles.front();
- handles.pop();
- } else {
- handle = curl_easy_init();
- }
-
- baton->ptr = handle;
- // Wrap this in a unique_ptr for now so that it destructs until we assign it the the CURL handle.
- std::unique_ptr<CURLContext> context = std::make_unique<CURLContext>(baton);
+ // The Context object stores information that we need to retain throughout the request, such
+ // as the actual CURL easy handle, the baton, and the list of headers. The Context itself is
+ // stored in both the CURL easy handle's PRIVATE field, and the baton's `ptr` field.
+ auto context = new Context(*(util::ptr<HTTPRequestBaton> *)ptr);
+ delete (util::ptr<HTTPRequestBaton> *)ptr;
- if (baton->response) {
- if (!baton->response->etag.empty()) {
- const std::string header = std::string("If-None-Match: ") + baton->response->etag;
+ if (context->baton->response) {
+ if (!context->baton->response->etag.empty()) {
+ const std::string header = std::string("If-None-Match: ") + context->baton->response->etag;
context->headers = curl_slist_append(context->headers, header.c_str());
- } else if (baton->response->modified) {
- const std::string time = std::string("If-Modified-Since: ") +
- util::rfc1123(baton->response->modified);
+ } else if (context->baton->response->modified) {
+ const std::string time =
+ std::string("If-Modified-Since: ") + util::rfc1123(context->baton->response->modified);
context->headers = curl_slist_append(context->headers, time.c_str());
}
}
if (context->headers) {
- curl_easy_setopt(handle, CURLOPT_HTTPHEADER, context->headers);
+ curl_easy_setopt(context->handle, CURLOPT_HTTPHEADER, context->headers);
}
- if (!baton->response) {
- baton->response = std::make_unique<Response>();
+ if (!context->baton->response) {
+ context->baton->response = std::unique_ptr<Response>(new Response());
}
// Carry on the shared pointer in the private information of the CURL handle.
- curl_easy_setopt(handle, CURLOPT_PRIVATE, context.release());
- curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt");
- curl_easy_setopt(handle, CURLOPT_URL, baton->path.c_str());
- curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curl_write_cb);
- curl_easy_setopt(handle, CURLOPT_WRITEDATA, &baton->response->data);
- curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, curl_header_cb);
- curl_easy_setopt(handle, CURLOPT_HEADERDATA, baton->response.get());
- curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate");
- curl_easy_setopt(handle, CURLOPT_SHARE, share);
+ curl_easy_setopt(context->handle, CURLOPT_PRIVATE, context);
+ curl_easy_setopt(context->handle, CURLOPT_CAINFO, "ca-bundle.crt");
+ curl_easy_setopt(context->handle, CURLOPT_FOLLOWLOCATION, 1);
+ curl_easy_setopt(context->handle, CURLOPT_URL, context->baton->path.c_str());
+ curl_easy_setopt(context->handle, CURLOPT_WRITEFUNCTION, curl_write_cb);
+ curl_easy_setopt(context->handle, CURLOPT_WRITEDATA, &context->baton->response);
+ curl_easy_setopt(context->handle, CURLOPT_HEADERFUNCTION, curl_header_cb);
+ curl_easy_setopt(context->handle, CURLOPT_HEADERDATA, &context->baton->response);
+ curl_easy_setopt(context->handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate");
+ curl_easy_setopt(context->handle, CURLOPT_SHARE, share);
// Start requesting the information.
- curl_multi_add_handle(multi, handle);
+ curl_multi_add_handle(multi, context->handle);
}
// This function must run in the CURL thread.
void stop_request(void *const ptr) {
assert(uv_thread_self() == thread_id);
- std::unique_ptr<util::ptr<HTTPRequestBaton>> baton_guard { (util::ptr<HTTPRequestBaton> *)ptr };
- util::ptr<HTTPRequestBaton> &baton = *baton_guard.get();
+ auto baton = *(util::ptr<HTTPRequestBaton> *)ptr;
+ delete (util::ptr<HTTPRequestBaton> *)ptr;
assert(baton);
if (baton->async) {
baton->type = HTTPResponseType::Canceled;
- // We can still stop the request because it is still in progress.
- finish_request(baton);
+ assert(baton->ptr);
- uv_async_send(baton->async);
- baton->async = nullptr;
+ // We can still stop the request because it is still in progress.
+ delete (Context *)baton->ptr;
+ assert(!baton->ptr);
} else {
// If the async handle is gone, it means that the actual request has been completed before
// we got a chance to cancel it. In this case, this is a no-op. It is likely that