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:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
23
src/mbgl/storage/asset_context.hpp
Normal file
23
src/mbgl/storage/asset_context.hpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
30
src/mbgl/storage/http_context.cpp
Normal file
30
src/mbgl/storage/http_context.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -23,6 +23,7 @@ public:
|
||||
|
||||
virtual ~RequestBase() = default;
|
||||
virtual void cancel() = 0;
|
||||
virtual void retry() {};
|
||||
|
||||
protected:
|
||||
const Resource& resource;
|
||||
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user