Restructure *Request and Context

The previous implementation, based on thread-local storage, did not
ensure that the context was destructed before the FileSource run loop.
This resulted in implementations attempting to uv_close handles for a
loop that had already been destroyed.

This change also fixes #1262.
This commit is contained in:
John Firebaugh
2015-04-28 14:31:00 -04:00
parent 2c3952af66
commit 481fa0e69f
14 changed files with 312 additions and 504 deletions

View File

@@ -1,9 +1,8 @@
#include <mbgl/storage/http_request.hpp>
#include <mbgl/storage/http_context.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/util/uv.hpp>
#include <mbgl/util/std.hpp>
#include <mbgl/util/time.hpp>
#include <mbgl/util/parsedate.h>
@@ -46,25 +45,29 @@ enum class ResponseStatus : uint8_t {
class HTTPNSURLContext;
class HTTPRequestImpl {
class HTTPRequest : public RequestBase {
public:
HTTPRequestImpl(HTTPRequest *request, uv_loop_t *loop, std::shared_ptr<const Response> response);
~HTTPRequestImpl();
HTTPRequest(HTTPNSURLContext*,
const Resource&,
Callback,
uv_loop_t*,
std::shared_ptr<const Response>);
~HTTPRequest();
void cancel();
void cancel() override;
void retry() override;
private:
void cancelTimer();
void start();
void handleResult(NSData *data, NSURLResponse *res, NSError *error);
void handleResponse();
void retry(uint64_t timeout);
void retryImmediately();
static void restart(uv_timer_t *timer, int);
private:
HTTPNSURLContext *context = nullptr;
HTTPRequest *request = nullptr;
bool cancelled = false;
NSURLSessionDataTask *task = nullptr;
std::unique_ptr<Response> response;
const std::shared_ptr<const Response> existingResponse;
@@ -79,18 +82,20 @@ private:
// -------------------------------------------------------------------------------------------------
class HTTPNSURLContext : public HTTPContext<HTTPNSURLContext> {
class HTTPNSURLContext : public HTTPContext {
public:
HTTPNSURLContext(uv_loop_t *loop);
~HTTPNSURLContext();
RequestBase* createRequest(const Resource&,
RequestBase::Callback,
uv_loop_t*,
std::shared_ptr<const Response>) override;
NSURLSession *session = nil;
NSString *userAgent = nil;
};
template<> pthread_key_t ThreadContext<HTTPNSURLContext>::key{};
template<> pthread_once_t ThreadContext<HTTPNSURLContext>::once = PTHREAD_ONCE_INIT;
HTTPNSURLContext::HTTPNSURLContext(uv_loop_t *loop_) : HTTPContext(loop_) {
@autoreleasepool {
NSURLSessionConfiguration *sessionConfig =
@@ -116,34 +121,51 @@ HTTPNSURLContext::~HTTPNSURLContext() {
userAgent = nullptr;
}
RequestBase* HTTPNSURLContext::createRequest(const Resource& resource,
RequestBase::Callback callback,
uv_loop_t* loop,
std::shared_ptr<const Response> response) {
return new HTTPRequest(this, resource, callback, loop, response);
}
// -------------------------------------------------------------------------------------------------
HTTPRequestImpl::HTTPRequestImpl(HTTPRequest *request_, uv_loop_t *loop,
std::shared_ptr<const Response> existingResponse_)
: context(HTTPNSURLContext::Get(loop)),
request(request_),
HTTPRequest::HTTPRequest(HTTPNSURLContext* context_, const Resource& resource_, Callback callback_, uv_loop_t *loop, std::shared_ptr<const Response> existingResponse_)
: RequestBase(resource_, callback_),
context(context_),
existingResponse(existingResponse_),
async(new uv_async_t) {
assert(request);
context->addRequest(request);
context->addRequest(this);
async->data = this;
uv_async_init(loop, async, [](uv_async_t *as, int) {
auto impl = reinterpret_cast<HTTPRequestImpl *>(as->data);
auto impl = reinterpret_cast<HTTPRequest *>(as->data);
impl->handleResponse();
});
start();
}
void HTTPRequestImpl::start() {
HTTPRequest::~HTTPRequest() {
assert(!task);
assert(async);
// Stop the backoff timer to avoid re-triggering this request.
cancelTimer();
uv::close(async);
context->removeRequest(this);
}
void HTTPRequest::start() {
assert(!task);
attempts++;
@autoreleasepool {
NSMutableString *url = [[NSMutableString alloc] initWithString:@(request->resource.url.c_str())];
NSMutableString *url = [[NSMutableString alloc] initWithString:@(resource.url.c_str())];
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"mapbox_metrics_disabled"] == nil) {
if ([url rangeOfString:@"?"].location == NSNotFound) {
[url appendString:@"?"];
@@ -175,13 +197,13 @@ void HTTPRequestImpl::start() {
}
}
void HTTPRequestImpl::handleResponse() {
void HTTPRequest::handleResponse() {
if (task) {
[task release];
task = nullptr;
}
if (request) {
if (!cancelled) {
if (status == ResponseStatus::TemporaryError && attempts < maxAttempts) {
strategy = ExponentialBackoff;
return retry((1 << (attempts - 1)) * 1000);
@@ -194,23 +216,18 @@ void HTTPRequestImpl::handleResponse() {
// Actually return the response.
if (status == ResponseStatus::NotModified) {
request->notify(std::move(response), FileCache::Hint::Refresh);
notify(std::move(response), FileCache::Hint::Refresh);
} else {
request->notify(std::move(response), FileCache::Hint::Full);
notify(std::move(response), FileCache::Hint::Full);
}
context->removeRequest(request);
request->impl = nullptr;
delete request;
request = nullptr;
}
delete this;
}
void HTTPRequestImpl::cancel() {
context->removeRequest(request);
request = nullptr;
void HTTPRequest::cancel() {
context->removeRequest(this);
cancelled = true;
// Stop the backoff timer to avoid re-triggering this request.
cancelTimer();
@@ -224,7 +241,7 @@ void HTTPRequestImpl::cancel() {
}
}
void HTTPRequestImpl::cancelTimer() {
void HTTPRequest::cancelTimer() {
if (timer) {
uv_timer_stop(timer);
uv::close(timer);
@@ -232,21 +249,6 @@ void HTTPRequestImpl::cancelTimer() {
}
}
HTTPRequestImpl::~HTTPRequestImpl() {
assert(!task);
assert(async);
// Stop the backoff timer to avoid re-triggering this request.
cancelTimer();
uv::close(async);
if (request) {
context->removeRequest(request);
request->impl = nullptr;
}
}
int64_t parseCacheControl(const char *value) {
if (value) {
unsigned long long seconds = 0;
@@ -262,7 +264,7 @@ int64_t parseCacheControl(const char *value) {
return 0;
}
void HTTPRequestImpl::handleResult(NSData *data, NSURLResponse *res, NSError *error) {
void HTTPRequest::handleResult(NSData *data, NSURLResponse *res, NSError *error) {
if (error) {
if ([error code] == NSURLErrorCancelled) {
status = ResponseStatus::Canceled;
@@ -365,7 +367,7 @@ void HTTPRequestImpl::handleResult(NSData *data, NSURLResponse *res, NSError *er
uv_async_send(async);
}
void HTTPRequestImpl::retry(uint64_t timeout) {
void HTTPRequest::retry(uint64_t timeout) {
response.reset();
assert(!timer);
@@ -375,7 +377,7 @@ void HTTPRequestImpl::retry(uint64_t timeout) {
uv_timer_start(timer, restart, timeout, 0);
}
void HTTPRequestImpl::retryImmediately() {
void HTTPRequest::retry() {
// All batons get notified when the network status changed, but some of them
// might not actually wait for the network to become available again.
if (timer && strategy == PreemptImmediately) {
@@ -385,9 +387,9 @@ void HTTPRequestImpl::retryImmediately() {
}
}
void HTTPRequestImpl::restart(uv_timer_t *timer, int) {
void HTTPRequest::restart(uv_timer_t *timer, int) {
// Restart the request.
auto impl = reinterpret_cast<HTTPRequestImpl *>(timer->data);
auto impl = reinterpret_cast<HTTPRequest *>(timer->data);
// Get rid of the timer.
impl->timer = nullptr;
@@ -396,28 +398,8 @@ void HTTPRequestImpl::restart(uv_timer_t *timer, int) {
impl->start();
}
// -------------------------------------------------------------------------------------------------
HTTPRequest::HTTPRequest(const Resource& resource, Callback callback,
uv_loop_t* loop, std::shared_ptr<const Response> response)
: RequestBase(resource, callback)
, impl(new HTTPRequestImpl(this, loop, response)) {
}
HTTPRequest::~HTTPRequest() {
if (impl) {
impl->cancel();
}
}
void HTTPRequest::retryImmediately() {
if (impl) {
impl->retryImmediately();
}
}
void HTTPRequest::cancel() {
delete this;
std::unique_ptr<HTTPContext> HTTPContext::createContext(uv_loop_t* loop) {
return util::make_unique<HTTPNSURLContext>(loop);
}
}

View File

@@ -1,4 +1,4 @@
#include <mbgl/storage/asset_request.hpp>
#include <mbgl/storage/asset_context.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/util/std.hpp>
@@ -7,27 +7,19 @@
#include <uv.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
#ifndef __clang__
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#endif
#include <boost/algorithm/string.hpp>
#pragma GCC diagnostic pop
#include <cassert>
namespace algo = boost::algorithm;
#include <limits>
namespace mbgl {
class AssetRequestImpl {
class AssetRequest : public RequestBase {
MBGL_STORE_THREAD(tid)
public:
AssetRequestImpl(AssetRequest*, uv_loop_t*, const std::string& assetRoot);
~AssetRequestImpl();
AssetRequest(const Resource&, Callback, uv_loop_t*, const std::string& assetRoot);
~AssetRequest();
void cancel() override;
static void fileOpened(uv_fs_t *req);
static void fileStated(uv_fs_t *req);
@@ -36,7 +28,6 @@ public:
static void notifyError(uv_fs_t *req);
static void cleanup(uv_fs_t *req);
AssetRequest *request = nullptr;
std::string assetRoot;
bool canceled = false;
uv_fs_t req;
@@ -45,20 +36,25 @@ public:
std::unique_ptr<Response> response;
};
AssetRequestImpl::~AssetRequestImpl() {
MBGL_VERIFY_THREAD(tid);
if (request) {
request->impl = nullptr;
class AssetFSContext : public AssetContext {
RequestBase* createRequest(const Resource& resource,
RequestBase::Callback callback,
uv_loop_t* loop,
const std::string& assetRoot) override {
return new AssetRequest(resource, callback, loop, assetRoot);
}
};
AssetRequest::~AssetRequest() {
MBGL_VERIFY_THREAD(tid);
}
AssetRequestImpl::AssetRequestImpl(AssetRequest* request_, uv_loop_t* loop, const std::string& assetRoot_)
: request(request_)
, assetRoot(assetRoot_) {
AssetRequest::AssetRequest(const Resource& resource_, Callback callback_, uv_loop_t* loop, const std::string& assetRoot_)
: RequestBase(resource_, callback_),
assetRoot(assetRoot_) {
req.data = this;
const auto &url = request->resource.url;
const auto &url = resource.url;
std::string path;
if (url.size() <= 8 || url[8] == '/') {
// This is an empty or absolute path.
@@ -71,9 +67,9 @@ AssetRequestImpl::AssetRequestImpl(AssetRequest* request_, uv_loop_t* loop, cons
uv_fs_open(loop, &req, path.c_str(), O_RDONLY, S_IRUSR, fileOpened);
}
void AssetRequestImpl::fileOpened(uv_fs_t *req) {
void AssetRequest::fileOpened(uv_fs_t *req) {
assert(req->data);
auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
auto self = reinterpret_cast<AssetRequest *>(req->data);
MBGL_VERIFY_THREAD(self->tid);
if (req->result < 0) {
@@ -96,9 +92,9 @@ void AssetRequestImpl::fileOpened(uv_fs_t *req) {
}
}
void AssetRequestImpl::fileStated(uv_fs_t *req) {
void AssetRequest::fileStated(uv_fs_t *req) {
assert(req->data);
auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
auto self = reinterpret_cast<AssetRequest *>(req->data);
MBGL_VERIFY_THREAD(self->tid);
if (req->result != 0 || self->canceled) {
@@ -124,9 +120,7 @@ void AssetRequestImpl::fileStated(uv_fs_t *req) {
#else
response->message = uv_strerror(UV_EFBIG);
#endif
assert(self->request);
self->request->notify(std::move(response), FileCache::Hint::No);
delete self->request;
self->notify(std::move(response), FileCache::Hint::No);
uv_fs_req_cleanup(req);
uv_fs_close(req->loop, req, self->fd, fileClosed);
@@ -151,9 +145,9 @@ void AssetRequestImpl::fileStated(uv_fs_t *req) {
}
}
void AssetRequestImpl::fileRead(uv_fs_t *req) {
void AssetRequest::fileRead(uv_fs_t *req) {
assert(req->data);
auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
auto self = reinterpret_cast<AssetRequest *>(req->data);
MBGL_VERIFY_THREAD(self->tid);
if (req->result < 0 || self->canceled) {
@@ -163,18 +157,16 @@ void AssetRequestImpl::fileRead(uv_fs_t *req) {
} else {
// File was successfully read.
self->response->status = Response::Successful;
assert(self->request);
self->request->notify(std::move(self->response), FileCache::Hint::No);
delete self->request;
self->notify(std::move(self->response), FileCache::Hint::No);
}
uv_fs_req_cleanup(req);
uv_fs_close(req->loop, req, self->fd, fileClosed);
}
void AssetRequestImpl::fileClosed(uv_fs_t *req) {
void AssetRequest::fileClosed(uv_fs_t *req) {
assert(req->data);
auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
auto self = reinterpret_cast<AssetRequest *>(req->data);
MBGL_VERIFY_THREAD(self->tid);
(void(self)); // Silence unused variable error in Release mode
@@ -185,51 +177,37 @@ void AssetRequestImpl::fileClosed(uv_fs_t *req) {
cleanup(req);
}
void AssetRequestImpl::notifyError(uv_fs_t *req) {
void AssetRequest::notifyError(uv_fs_t *req) {
assert(req->data);
auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
auto self = reinterpret_cast<AssetRequest *>(req->data);
MBGL_VERIFY_THREAD(self->tid);
if (req->result < 0 && !self->canceled && req->result != UV_ECANCELED) {
auto response = util::make_unique<Response>();
response->status = Response::Error;
response->message = uv::getFileRequestError(req);
assert(self->request);
self->request->notify(std::move(response), FileCache::Hint::No);
delete self->request;
self->notify(std::move(response), FileCache::Hint::No);
}
}
void AssetRequestImpl::cleanup(uv_fs_t *req) {
void AssetRequest::cleanup(uv_fs_t *req) {
assert(req->data);
auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
auto self = reinterpret_cast<AssetRequest *>(req->data);
MBGL_VERIFY_THREAD(self->tid);
uv_fs_req_cleanup(req);
delete self;
}
// -------------------------------------------------------------------------------------------------
AssetRequest::AssetRequest(const Resource& resource_, Callback callback_,
uv_loop_t* loop, const std::string& assetRoot)
: RequestBase(resource_, callback_)
, impl(new AssetRequestImpl(this, loop, assetRoot)) {
assert(algo::starts_with(resource.url, "asset://"));
// Note: the AssetRequestImpl deletes itself.
}
AssetRequest::~AssetRequest() {
if (impl) {
impl->request = nullptr;
}
}
void AssetRequest::cancel() {
impl->canceled = true;
canceled = true;
// uv_cancel fails frequently when the request has already been started.
// In that case, we have to let it complete and check the canceled bool
// instead. The cancelation callback will delete the AssetRequest object.
uv_cancel((uv_req_t *)&impl->req);
uv_cancel((uv_req_t *)&req);
}
std::unique_ptr<AssetContext> AssetContext::createContext(uv_loop_t*) {
return util::make_unique<AssetFSContext>();
}
}

View File

@@ -1,46 +1,40 @@
#include <mbgl/storage/asset_request.hpp>
#include <mbgl/storage/thread_context.hpp>
#include <mbgl/storage/asset_context.hpp>
#include <mbgl/android/jni.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/platform/log.hpp>
#include <mbgl/util/std.hpp>
#include <mbgl/util/util.hpp>
#include <mbgl/util/uv.hpp>
#include <uv.h>
#include "uv_zip.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
#ifndef __clang__
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#endif
#include <boost/algorithm/string.hpp>
#pragma GCC diagnostic pop
#include <map>
#include <cassert>
#include <forward_list>
namespace algo = boost::algorithm;
namespace mbgl {
class AssetZipContext : public ThreadContext<AssetZipContext> {
class AssetZipContext : public AssetContext {
public:
AssetZipContext(uv_loop_t *loop);
~AssetZipContext();
RequestBase* createRequest(const Resource& resource,
RequestBase::Callback callback,
uv_loop_t* loop,
const std::string& assetRoot) override;
uv_zip_t *getHandle(const std::string &path);
void returnHandle(const std::string &path, uv_zip_t *zip);
private:
// A list of resuable uv_zip handles to avoid creating and destroying them all the time.
std::map<std::string, std::forward_list<uv_zip_t *>> handles;
uv_loop_t *loop;
};
// -------------------------------------------------------------------------------------------------
template<> pthread_key_t ThreadContext<AssetZipContext>::key{};
template<> pthread_once_t ThreadContext<AssetZipContext>::once = PTHREAD_ONCE_INIT;
AssetZipContext::AssetZipContext(uv_loop_t *loop_) : ThreadContext(loop_) {
AssetZipContext::AssetZipContext(uv_loop_t *loop_) : loop(loop_) {
}
uv_zip_t *AssetZipContext::getHandle(const std::string &path) {
@@ -71,20 +65,18 @@ AssetZipContext::~AssetZipContext() {
handles.clear();
}
// -------------------------------------------------------------------------------------------------
class AssetRequestImpl {
class AssetRequest : public RequestBase {
MBGL_STORE_THREAD(tid)
public:
AssetRequestImpl(AssetRequest*, uv_loop_t*, const std::string& assetRoot);
~AssetRequestImpl();
AssetRequest(AssetZipContext&, const Resource&, Callback, const std::string& assetRoot);
~AssetRequest();
void cancel();
void cancel() override;
private:
AssetZipContext &context;
AssetRequest *request = nullptr;
bool cancelled = false;
const std::string root;
const std::string path;
std::unique_ptr<Response> response;
@@ -102,21 +94,18 @@ private:
void notifyError(const char *message);
};
// -------------------------------------------------------------------------------------------------
AssetRequestImpl::~AssetRequestImpl() {
MBGL_VERIFY_THREAD(tid);
if (request) {
request->impl = nullptr;
}
RequestBase* AssetZipContext::createRequest(const Resource& resource,
RequestBase::Callback callback,
uv_loop_t*,
const std::string& assetRoot) {
return new AssetRequest(*this, resource, callback, assetRoot);
}
AssetRequestImpl::AssetRequestImpl(AssetRequest* request_, uv_loop_t* loop, const std::string& assetRoot_)
: context(*AssetZipContext::Get(loop)),
request(request_),
AssetRequest::AssetRequest(AssetZipContext& context_, const Resource& resource_, Callback callback_, const std::string& assetRoot_)
: RequestBase(resource_, callback_),
context(context_),
root(assetRoot_),
path(std::string { "assets/" } + request->resource.url.substr(8)) {
path(std::string { "assets/" } + resource.url.substr(8)) {
auto zip = context.getHandle(root);
if (zip) {
archiveOpened(zip);
@@ -125,17 +114,19 @@ AssetRequestImpl::AssetRequestImpl(AssetRequest* request_, uv_loop_t* loop, cons
}
}
void AssetRequestImpl::openZipArchive() {
AssetRequest::~AssetRequest() {
MBGL_VERIFY_THREAD(tid);
}
void AssetRequest::openZipArchive() {
uv_fs_t *req = new uv_fs_t();
req->data = this;
assert(request);
// We're using uv_fs_open first to obtain a file descriptor. Then, uv_zip_fdopen will operate
// on a read-only file.
uv_fs_open(context.loop, req, root.c_str(), O_RDONLY, S_IRUSR, [](uv_fs_t *fsReq) {
if (fsReq->result < 0) {
auto impl = reinterpret_cast<AssetRequestImpl *>(fsReq->data);
auto impl = reinterpret_cast<AssetRequest *>(fsReq->data);
impl->notifyError(uv::getFileRequestError(fsReq));
delete impl;
} else {
@@ -143,7 +134,7 @@ void AssetRequestImpl::openZipArchive() {
uv_zip_init(zip);
zip->data = fsReq->data;
uv_zip_fdopen(fsReq->loop, zip, uv_file(fsReq->result), 0, [](uv_zip_t *openZip) {
auto impl = reinterpret_cast<AssetRequestImpl *>(openZip->data);
auto impl = reinterpret_cast<AssetRequest *>(openZip->data);
if (openZip->result < 0) {
impl->notifyError(openZip->message);
delete openZip;
@@ -163,20 +154,20 @@ void AssetRequestImpl::openZipArchive() {
#define INVOKE_MEMBER(name) \
[](uv_zip_t *zip_) { \
assert(zip_->data); \
reinterpret_cast<AssetRequestImpl *>(zip_->data)->name(zip_); \
reinterpret_cast<AssetRequest *>(zip_->data)->name(zip_); \
}
void AssetRequestImpl::archiveOpened(uv_zip_t *zip) {
void AssetRequest::archiveOpened(uv_zip_t *zip) {
MBGL_VERIFY_THREAD(tid);
zip->data = this;
uv_zip_stat(context.loop, zip, path.c_str(), 0, INVOKE_MEMBER(fileStated));
}
void AssetRequestImpl::fileStated(uv_zip_t *zip) {
void AssetRequest::fileStated(uv_zip_t *zip) {
MBGL_VERIFY_THREAD(tid);
if (!request || zip->result < 0) {
if (cancelled || zip->result < 0) {
// Stat failed, probably because the file doesn't exist.
if (zip->result < 0) {
notifyError(zip->message);
@@ -206,14 +197,14 @@ void AssetRequestImpl::fileStated(uv_zip_t *zip) {
}
}
void AssetRequestImpl::fileOpened(uv_zip_t *zip) {
void AssetRequest::fileOpened(uv_zip_t *zip) {
MBGL_VERIFY_THREAD(tid);
if (zip->result < 0) {
// Opening failed.
notifyError(zip->message);
cleanup(zip);
} else if (!request) {
} else if (cancelled) {
// The request was canceled. Close the file again.
uv_zip_fclose(context.loop, zip, zip->file, INVOKE_MEMBER(fileClosed));
} else {
@@ -221,23 +212,21 @@ void AssetRequestImpl::fileOpened(uv_zip_t *zip) {
}
}
void AssetRequestImpl::fileRead(uv_zip_t *zip) {
void AssetRequest::fileRead(uv_zip_t *zip) {
MBGL_VERIFY_THREAD(tid);
if (zip->result < 0) {
// Reading failed. We still have an open file handle though.
notifyError(zip->message);
} else if (request) {
} else if (!cancelled) {
response->status = Response::Successful;
request->notify(std::move(response), FileCache::Hint::No);
delete request;
assert(request == nullptr);
notify(std::move(response), FileCache::Hint::No);
}
uv_zip_fclose(context.loop, zip, zip->file, INVOKE_MEMBER(fileClosed));
}
void AssetRequestImpl::fileClosed(uv_zip_t *zip) {
void AssetRequest::fileClosed(uv_zip_t *zip) {
MBGL_VERIFY_THREAD(tid);
if (zip->result < 0) {
@@ -247,53 +236,32 @@ void AssetRequestImpl::fileClosed(uv_zip_t *zip) {
cleanup(zip);
}
void AssetRequestImpl::cleanup(uv_zip_t *zip) {
void AssetRequest::cleanup(uv_zip_t *zip) {
MBGL_VERIFY_THREAD(tid);
context.returnHandle(root, zip);
delete this;
}
void AssetRequestImpl::notifyError(const char *message) {
void AssetRequest::notifyError(const char *message) {
MBGL_VERIFY_THREAD(tid);
if (request) {
if (!cancelled) {
response = util::make_unique<Response>();
response->status = Response::Error;
response->message = message;
request->notify(std::move(response), FileCache::Hint::No);
delete request;
assert(request == nullptr);
notify(std::move(response), FileCache::Hint::No);
} else {
// The request was already canceled and deleted.
}
}
void AssetRequestImpl::cancel() {
request = nullptr;
}
// -------------------------------------------------------------------------------------------------
AssetRequest::AssetRequest(const Resource& resource_, Callback callback_,
uv_loop_t* loop, const std::string& assetRoot)
: RequestBase(resource_, callback_)
, impl(new AssetRequestImpl(this, loop, assetRoot)) {
assert(algo::starts_with(resource.url, "asset://"));
}
AssetRequest::~AssetRequest() {
if (impl) {
impl->cancel();
}
}
void AssetRequest::cancel() {
if (impl) {
impl->cancel();
}
cancelled = true;
}
delete this;
std::unique_ptr<AssetContext> AssetContext::createContext(uv_loop_t* loop) {
return util::make_unique<AssetZipContext>(loop);
}
}

View File

@@ -1,4 +1,3 @@
#include <mbgl/storage/http_request.hpp>
#include <mbgl/storage/http_context.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/response.hpp>
@@ -6,6 +5,7 @@
#include <mbgl/platform/log.hpp>
#include <mbgl/util/time.hpp>
#include <mbgl/util/util.hpp>
#include <curl/curl.h>
@@ -54,14 +54,20 @@ enum class ResponseStatus : int8_t {
NotModified,
};
class HTTPRequestImpl;
class HTTPRequest;
class HTTPCURLContext : public HTTPContext<HTTPCURLContext> {
class HTTPCURLContext : public HTTPContext {
MBGL_STORE_THREAD(tid)
public:
HTTPCURLContext(uv_loop_t *loop);
~HTTPCURLContext();
RequestBase* createRequest(const Resource&,
RequestBase::Callback,
uv_loop_t*,
std::shared_ptr<const Response>) override;
static int handleSocket(CURL *handle, curl_socket_t s, int action, void *userp, void *socketp);
static void perform(uv_poll_t *req, int status, int events);
static int startTimeout(CURLM *multi, long timeout_ms, void *userp);
@@ -75,7 +81,8 @@ public:
void returnHandle(CURL *handle);
void checkMultiInfo();
public:
uv_loop_t *loop = nullptr;
// Used as the CURL timer function to periodically check for socket updates.
uv_timer_t *timeout = nullptr;
@@ -91,17 +98,21 @@ public:
std::queue<CURL *> handles;
};
class HTTPRequestImpl {
class HTTPRequest : public RequestBase {
MBGL_STORE_THREAD(tid)
public:
HTTPRequestImpl(HTTPRequest *request, uv_loop_t *loop, std::shared_ptr<const Response> response);
~HTTPRequestImpl();
HTTPRequest(HTTPCURLContext*,
const Resource&,
Callback,
uv_loop_t*,
std::shared_ptr<const Response>);
~HTTPRequest();
void cancel() override;
void retry() override;
void handleResult(CURLcode code);
void abandon();
void retryImmediately();
private:
static size_t headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp);
@@ -116,9 +127,8 @@ private:
void finish(ResponseStatus status);
void start();
private:
HTTPCURLContext *context = nullptr;
HTTPRequest *request = nullptr;
bool cancelled = false;
// Will store the current response.
std::unique_ptr<Response> response;
@@ -176,10 +186,9 @@ private:
// -------------------------------------------------------------------------------------------------
template<> pthread_key_t ThreadContext<HTTPCURLContext>::key{};
template<> pthread_once_t ThreadContext<HTTPCURLContext>::once = PTHREAD_ONCE_INIT;
HTTPCURLContext::HTTPCURLContext(uv_loop_t *loop_) : HTTPContext(loop_) {
HTTPCURLContext::HTTPCURLContext(uv_loop_t *loop_)
: HTTPContext(loop_),
loop(loop_) {
if (curl_global_init(CURL_GLOBAL_ALL)) {
throw std::runtime_error("Could not init cURL");
}
@@ -208,6 +217,13 @@ HTTPCURLContext::~HTTPCURLContext() {
uv::close(timeout);
}
RequestBase* HTTPCURLContext::createRequest(const Resource& resource,
RequestBase::Callback callback,
uv_loop_t* loop_,
std::shared_ptr<const Response> response) {
return new HTTPRequest(this, resource, callback, loop_, response);
}
CURL *HTTPCURLContext::getHandle() {
if (!handles.empty()) {
auto handle = handles.front();
@@ -231,7 +247,7 @@ void HTTPCURLContext::checkMultiInfo() {
while ((message = curl_multi_info_read(multi, &pending))) {
switch (message->msg) {
case CURLMSG_DONE: {
HTTPRequestImpl *baton = nullptr;
HTTPRequest *baton = nullptr;
curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton);
assert(baton);
baton->handleResult(message->data.result);
@@ -426,13 +442,12 @@ static CURLcode sslctx_function(CURL * /* curl */, void *sslctx, void * /* parm
}
#endif
HTTPRequestImpl::HTTPRequestImpl(HTTPRequest *request_, uv_loop_t *loop, std::shared_ptr<const Response> response_)
: context(HTTPCURLContext::Get(loop)),
request(request_),
HTTPRequest::HTTPRequest(HTTPCURLContext* context_, const Resource& resource_, Callback callback_, uv_loop_t*, std::shared_ptr<const Response> response_)
: RequestBase(resource_, callback_),
context(context_),
existingResponse(response_),
handle(context->getHandle()) {
assert(request);
context->addRequest(request);
context->addRequest(this);
// Zero out the error buffer.
memset(error, 0, sizeof(error));
@@ -463,7 +478,7 @@ HTTPRequestImpl::HTTPRequestImpl(HTTPRequest *request_, uv_loop_t *loop, std::sh
handleError(curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt"));
#endif
handleError(curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1));
handleError(curl_easy_setopt(handle, CURLOPT_URL, request->resource.url.c_str()));
handleError(curl_easy_setopt(handle, CURLOPT_URL, resource.url.c_str()));
handleError(curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback));
handleError(curl_easy_setopt(handle, CURLOPT_WRITEDATA, this));
handleError(curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, headerCallback));
@@ -475,28 +490,10 @@ HTTPRequestImpl::HTTPRequestImpl(HTTPRequest *request_, uv_loop_t *loop, std::sh
start();
}
void HTTPRequestImpl::abandon() {
if (request) {
context->removeRequest(request);
request = nullptr;
}
}
void HTTPRequestImpl::start() {
// Count up the attempts.
attempts++;
// Start requesting the information.
handleError(curl_multi_add_handle(context->multi, handle));
}
HTTPRequestImpl::~HTTPRequestImpl() {
HTTPRequest::~HTTPRequest() {
MBGL_VERIFY_THREAD(tid);
if (request) {
context->removeRequest(request);
request->impl = nullptr;
}
context->removeRequest(this);
handleError(curl_multi_remove_handle(context->multi, handle));
context->returnHandle(handle);
@@ -515,11 +512,23 @@ HTTPRequestImpl::~HTTPRequestImpl() {
}
}
void HTTPRequest::cancel() {
delete this;
}
void HTTPRequest::start() {
// Count up the attempts.
attempts++;
// Start requesting the information.
handleError(curl_multi_add_handle(context->multi, handle));
}
// 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 HTTPRequestImpl::writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp) {
size_t HTTPRequest::writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp) {
assert(userp);
auto impl = reinterpret_cast<HTTPRequestImpl *>(userp);
auto impl = reinterpret_cast<HTTPRequest *>(userp);
MBGL_VERIFY_THREAD(impl->tid);
if (!impl->response) {
@@ -561,9 +570,9 @@ int64_t parseCacheControl(const char *value) {
return 0;
}
size_t HTTPRequestImpl::headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp) {
size_t HTTPRequest::headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp) {
assert(userp);
auto baton = reinterpret_cast<HTTPRequestImpl *>(userp);
auto baton = reinterpret_cast<HTTPRequest *>(userp);
MBGL_VERIFY_THREAD(baton->tid);
if (!baton->response) {
@@ -590,8 +599,7 @@ size_t HTTPRequestImpl::headerCallback(char *const buffer, const size_t size, co
return length;
}
void HTTPRequestImpl::retry(uint64_t timeout) {
void HTTPRequest::retry(uint64_t timeout) {
handleError(curl_multi_remove_handle(context->multi, handle));
response.reset();
@@ -603,7 +611,7 @@ void HTTPRequestImpl::retry(uint64_t timeout) {
uv_timer_start(timer, restart, timeout, 0);
}
void HTTPRequestImpl::retryImmediately() {
void HTTPRequest::retry() {
// All batons get notified when the network status changed, but some of them
// might not actually wait for the network to become available again.
if (timer && strategy == PreemptImmediately) {
@@ -614,12 +622,12 @@ void HTTPRequestImpl::retryImmediately() {
}
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
void HTTPRequestImpl::restart(uv_timer_t *timer, int) {
void HTTPRequest::restart(uv_timer_t *timer, int) {
#else
void HTTPRequestImpl::restart(uv_timer_t *timer) {
void HTTPRequest::restart(uv_timer_t *timer) {
#endif
// Restart the request.
auto baton = reinterpret_cast<HTTPRequestImpl *>(timer->data);
auto baton = reinterpret_cast<HTTPRequest *>(timer->data);
// Get rid of the timer.
baton->timer = nullptr;
@@ -628,7 +636,7 @@ void HTTPRequestImpl::restart(uv_timer_t *timer) {
baton->start();
}
void HTTPRequestImpl::finish(ResponseStatus status) {
void HTTPRequest::finish(ResponseStatus status) {
if (status == ResponseStatus::TemporaryError && attempts < maxAttempts) {
strategy = ExponentialBackoff;
return retry((1 << (attempts - 1)) * 1000);
@@ -641,19 +649,18 @@ void HTTPRequestImpl::finish(ResponseStatus status) {
// Actually return the response.
if (status == ResponseStatus::NotModified) {
request->notify(std::move(response), FileCache::Hint::Refresh);
notify(std::move(response), FileCache::Hint::Refresh);
} else {
request->notify(std::move(response), FileCache::Hint::Full);
notify(std::move(response), FileCache::Hint::Full);
}
delete request;
delete this;
}
void HTTPRequestImpl::handleResult(CURLcode code) {
void HTTPRequest::handleResult(CURLcode code) {
MBGL_VERIFY_THREAD(tid);
if (!request) {
if (cancelled) {
// In this case, it doesn't make sense to even process the response even further since
// the request was canceled anyway.
delete this;
@@ -721,30 +728,8 @@ void HTTPRequestImpl::handleResult(CURLcode code) {
throw std::runtime_error("Response hasn't been handled");
}
// -------------------------------------------------------------------------------------------------
HTTPRequest::HTTPRequest(const Resource& resource_, Callback callback_,
uv_loop_t* loop, std::shared_ptr<const Response> response)
: RequestBase(resource_, callback_)
, impl(new HTTPRequestImpl(this, loop, response)) {
}
HTTPRequest::~HTTPRequest() {
if (impl) {
impl->abandon();
}
}
void HTTPRequest::retryImmediately() {
if (impl) {
impl->retryImmediately();
}
}
void HTTPRequest::cancel() {
delete impl;
impl = nullptr;
delete this;
std::unique_ptr<HTTPContext> HTTPContext::createContext(uv_loop_t* loop) {
return util::make_unique<HTTPCURLContext>(loop);
}
}

View File

@@ -0,0 +1,23 @@
#ifndef MBGL_STORAGE_DEFAULT_ASSET_CONTEXT
#define MBGL_STORAGE_DEFAULT_ASSET_CONTEXT
#include <mbgl/storage/request_base.hpp>
typedef struct uv_loop_s uv_loop_t;
namespace mbgl {
class AssetContext {
public:
static std::unique_ptr<AssetContext> createContext(uv_loop_t*);
virtual ~AssetContext() = default;
virtual RequestBase* createRequest(const Resource&,
RequestBase::Callback,
uv_loop_t*,
const std::string& assetRoot) = 0;
};
}
#endif

View File

@@ -1,27 +0,0 @@
#ifndef MBGL_STORAGE_DEFAULT_ASSET_REQUEST
#define MBGL_STORAGE_DEFAULT_ASSET_REQUEST
#include "request_base.hpp"
typedef struct uv_loop_s uv_loop_t;
namespace mbgl {
class AssetRequestImpl;
class AssetRequest : public RequestBase {
public:
AssetRequest(const Resource&, Callback, uv_loop_t*, const std::string& assetRoot);
void cancel() override;
private:
~AssetRequest();
AssetRequestImpl* impl;
friend class AssetRequestImpl;
};
}
#endif

View File

@@ -1,7 +1,7 @@
#include <mbgl/storage/default_file_source_impl.hpp>
#include <mbgl/storage/request.hpp>
#include <mbgl/storage/asset_request.hpp>
#include <mbgl/storage/http_request.hpp>
#include <mbgl/storage/asset_context.hpp>
#include <mbgl/storage/http_context.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/platform/platform.hpp>
@@ -64,7 +64,9 @@ void DefaultFileSource::cancel(Request *req) {
DefaultFileSource::Impl::Impl(uv_loop_t* loop_, FileCache* cache_, const std::string& root)
: loop(loop_),
cache(cache_),
assetRoot(root.empty() ? platform::assetRoot() : root) {
assetRoot(root.empty() ? platform::assetRoot() : root),
assetContext(AssetContext::createContext(loop_)),
httpContext(HTTPContext::createContext(loop_)) {
}
DefaultFileRequest* DefaultFileSource::Impl::find(const Resource& resource) {
@@ -130,9 +132,9 @@ void DefaultFileSource::Impl::startRealRequest(const Resource& resource, std::sh
};
if (algo::starts_with(resource.url, "asset://")) {
request->request = new AssetRequest(resource, callback, loop, assetRoot);
request->request = assetContext->createRequest(resource, callback, loop, assetRoot);
} else {
request->request = new HTTPRequest(resource, callback, loop, response);
request->request = httpContext->createRequest(resource, callback, loop, response);
}
}

View File

@@ -2,6 +2,8 @@
#define MBGL_STORAGE_DEFAULT_DEFAULT_FILE_SOURCE_IMPL
#include <mbgl/storage/default_file_source.hpp>
#include <mbgl/storage/asset_context.hpp>
#include <mbgl/storage/http_context.hpp>
#include <set>
#include <unordered_map>
@@ -39,6 +41,8 @@ private:
uv_loop_t* loop = nullptr;
FileCache* cache = nullptr;
const std::string assetRoot;
std::unique_ptr<AssetContext> assetContext;
std::unique_ptr<HTTPContext> httpContext;
};
}

View File

@@ -0,0 +1,30 @@
#include <mbgl/storage/http_context.hpp>
namespace mbgl {
HTTPContext::HTTPContext(uv_loop_t* loop_)
: reachability(loop_, [this] { retryRequests(); }) {
NetworkStatus::Subscribe(reachability.get());
reachability.unref();
}
HTTPContext::~HTTPContext() {
assert(requests.empty());
NetworkStatus::Unsubscribe(reachability.get());
}
void HTTPContext::addRequest(RequestBase* request) {
requests.insert(request);
}
void HTTPContext::removeRequest(RequestBase* request) {
requests.erase(request);
}
void HTTPContext::retryRequests() {
for (auto request : requests) {
request->retry();
}
}
}

View File

@@ -1,76 +1,40 @@
#ifndef MBGL_STORAGE_DEFAULT_HTTP_CONTEXT
#define MBGL_STORAGE_DEFAULT_HTTP_CONTEXT
#include "thread_context.hpp"
#include <mbgl/storage/request_base.hpp>
#include <mbgl/storage/network_status.hpp>
#include <mbgl/util/uv_detail.hpp>
#include <set>
namespace mbgl {
class HTTPRequest;
// This is a template class that provides a per-thread Context object. It can be used by HTTP
// implementations to store global state. It also implements the NetworkStatus mechanism and
// triggers immediate retries on all requests waiting for network status changes.
template <typename Context>
class HTTPContext : public ThreadContext<Context> {
class HTTPContext {
public:
HTTPContext(uv_loop_t *loop);
~HTTPContext();
static std::unique_ptr<HTTPContext> createContext(uv_loop_t*);
void addRequest(HTTPRequest *request);
void removeRequest(HTTPRequest *baton);
HTTPContext(uv_loop_t*);
virtual ~HTTPContext();
virtual RequestBase* createRequest(const Resource&,
RequestBase::Callback,
uv_loop_t*,
std::shared_ptr<const Response>) = 0;
void addRequest(RequestBase*);
void removeRequest(RequestBase*);
private:
void retryRequests();
public:
// Will be fired when the network status becomes reachable.
uv_async_t *reachability = nullptr;
uv::async reachability;
// A list of all pending HTTPRequestImpls that we need to notify when the network status
// changes.
std::set<HTTPRequest *> requests;
std::set<RequestBase*> requests;
};
template <typename Context>
HTTPContext<Context>::HTTPContext(uv_loop_t *loop_)
: ThreadContext<Context>(loop_) {
reachability = new uv_async_t;
reachability->data = this;
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
uv_async_init(loop_, reachability, [](uv_async_t *async, int) {
#else
uv_async_init(loop_, reachability, [](uv_async_t *async) {
#endif
for (auto request : reinterpret_cast<Context *>(async->data)->requests) {
request->retryImmediately();
}
});
// Allow the loop to quit even though this handle is still active.
uv_unref(reinterpret_cast<uv_handle_t *>(reachability));
NetworkStatus::Subscribe(reachability);
}
template <typename Context>
HTTPContext<Context>::~HTTPContext() {
MBGL_VERIFY_THREAD(HTTPContext<Context>::tid);
assert(requests.empty());
NetworkStatus::Unsubscribe(reachability);
uv::close(reachability);
}
template <typename Context>
void HTTPContext<Context>::addRequest(HTTPRequest *request) {
requests.insert(request);
}
template <typename Context>
void HTTPContext<Context>::removeRequest(HTTPRequest *request) {
requests.erase(request);
}
}
#endif

View File

@@ -1,28 +0,0 @@
#ifndef MBGL_STORAGE_DEFAULT_HTTP_REQUEST
#define MBGL_STORAGE_DEFAULT_HTTP_REQUEST
#include "request_base.hpp"
typedef struct uv_loop_s uv_loop_t;
namespace mbgl {
class HTTPRequestImpl;
class HTTPRequest : public RequestBase {
public:
HTTPRequest(const Resource&, Callback, uv_loop_t*, std::shared_ptr<const Response> = nullptr);
void cancel() override;
void retryImmediately();
private:
~HTTPRequest();
HTTPRequestImpl* impl;
friend class HTTPRequestImpl;
};
}
#endif

View File

@@ -23,6 +23,7 @@ public:
virtual ~RequestBase() = default;
virtual void cancel() = 0;
virtual void retry() {};
protected:
const Resource& resource;

View File

@@ -1,78 +0,0 @@
#ifndef MBGL_STORAGE_DEFAULT_THREAD_CONTEXT
#define MBGL_STORAGE_DEFAULT_THREAD_CONTEXT
#include <mbgl/util/noncopyable.hpp>
#include <mbgl/util/std.hpp>
#include <mbgl/util/util.hpp>
#include <mbgl/util/uv.hpp>
#include <uv.h>
#include <pthread.h>
#include <map>
#include <cassert>
namespace mbgl {
// This is a template class that provides a per-thread and per-loop Context object. It can be used
// by implementations to store global state.
template <typename Context>
class ThreadContext : private util::noncopyable {
protected:
MBGL_STORE_THREAD(tid)
using Map = std::map<uv_loop_t *, std::unique_ptr<Context>>;
public:
static Context *Get(uv_loop_t *loop);
private:
static pthread_key_t key;
static pthread_once_t once;
public:
ThreadContext(uv_loop_t *loop);
~ThreadContext();
public:
uv_loop_t *loop;
};
template <typename Context>
Context *ThreadContext<Context>::Get(uv_loop_t *loop) {
pthread_once(&once, []() {
pthread_key_create(&key, [](void *ptr) {
assert(ptr);
delete reinterpret_cast<Map *>(ptr);
});
});
auto contexts = reinterpret_cast<Map *>(pthread_getspecific(key));
if (!contexts) {
contexts = new Map();
pthread_setspecific(key, contexts);
}
// Now find a ThreadContext that matches the requested loop.
auto it = contexts->find(loop);
if (it == contexts->end()) {
auto result = contexts->emplace(loop, util::make_unique<Context>(loop));
assert(result.second); // Make sure it was actually inserted.
return result.first->second.get();
} else {
return it->second.get();
}
}
template <typename Context>
ThreadContext<Context>::ThreadContext(uv_loop_t *loop_) : loop(loop_) {
}
template <typename Context>
ThreadContext<Context>::~ThreadContext() {
MBGL_VERIFY_THREAD(tid);
}
}
#endif

View File

@@ -104,6 +104,10 @@ public:
uv_unref(reinterpret_cast<uv_handle_t*>(a.get()));
}
inline uv_async_t* get() {
return a.get();
}
private:
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
static void async_cb(uv_async_t* a, int) {