From 36581f3d3015d525db92248004e9dc7477705694 Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Mon, 31 Aug 2015 14:40:09 +0300 Subject: [core] Do not pass uv_loop_t around This should be abstracted by util::RunLoop --- include/mbgl/storage/default_file_source.hpp | 2 +- include/mbgl/storage/file_source.hpp | 4 +- include/mbgl/util/run_loop.hpp | 159 +++++++++++++++++++ include/mbgl/util/uv_detail.hpp | 220 +++++++++++++++++++++++++++ include/mbgl/util/work_request.hpp | 24 +++ include/mbgl/util/work_task.hpp | 21 +++ platform/node/src/node_file_source.cpp | 5 +- platform/node/src/node_file_source.hpp | 2 +- src/mbgl/map/map_context.cpp | 2 +- src/mbgl/map/raster_tile_data.cpp | 2 +- src/mbgl/map/source.cpp | 3 +- src/mbgl/map/vector_tile.cpp | 3 +- src/mbgl/sprite/sprite_store.cpp | 4 +- src/mbgl/storage/default_file_source.cpp | 6 +- src/mbgl/text/glyph_pbf.cpp | 2 +- src/mbgl/text/glyph_store.hpp | 1 - src/mbgl/util/run_loop.hpp | 159 ------------------- src/mbgl/util/uv_detail.hpp | 220 --------------------------- src/mbgl/util/work_request.hpp | 24 --- src/mbgl/util/work_task.hpp | 21 --- test/fixtures/mock_file_source.cpp | 4 +- test/fixtures/mock_file_source.hpp | 2 +- test/storage/cache_response.cpp | 40 ++--- test/storage/cache_revalidate.cpp | 19 ++- test/storage/directory_reading.cpp | 7 +- test/storage/file_reading.cpp | 16 +- test/storage/http_cancel.cpp | 12 +- test/storage/http_coalescing.cpp | 17 ++- test/storage/http_error.cpp | 30 ++-- test/storage/http_header_parsing.cpp | 22 ++- test/storage/http_issue_1369.cpp | 7 +- test/storage/http_load.cpp | 5 +- test/storage/http_other_loop.cpp | 5 +- test/storage/http_reading.cpp | 48 ++++-- test/storage/http_retry_network_status.cpp | 9 +- test/storage/http_timeout.cpp | 5 +- 36 files changed, 617 insertions(+), 515 deletions(-) create mode 100644 include/mbgl/util/run_loop.hpp create mode 100644 include/mbgl/util/uv_detail.hpp create mode 100644 include/mbgl/util/work_request.hpp create mode 100644 include/mbgl/util/work_task.hpp delete mode 100644 src/mbgl/util/run_loop.hpp delete mode 100644 src/mbgl/util/uv_detail.hpp delete mode 100644 src/mbgl/util/work_request.hpp delete mode 100644 src/mbgl/util/work_task.hpp diff --git a/include/mbgl/storage/default_file_source.hpp b/include/mbgl/storage/default_file_source.hpp index 8cfae03a96..5d018b720e 100644 --- a/include/mbgl/storage/default_file_source.hpp +++ b/include/mbgl/storage/default_file_source.hpp @@ -19,7 +19,7 @@ public: std::string getAccessToken() const { return accessToken; } // FileSource API - Request* request(const Resource&, uv_loop_t*, Callback) override; + Request* request(const Resource&, Callback) override; void cancel(Request*) override; public: diff --git a/include/mbgl/storage/file_source.hpp b/include/mbgl/storage/file_source.hpp index 3b19e00788..a53bf31c2c 100644 --- a/include/mbgl/storage/file_source.hpp +++ b/include/mbgl/storage/file_source.hpp @@ -9,8 +9,6 @@ #include -typedef struct uv_loop_s uv_loop_t; - namespace mbgl { class Request; @@ -26,7 +24,7 @@ public: // These can be called from any thread. The callback will be invoked in the loop. // You can only cancel a request from the same thread it was created in. - virtual Request* request(const Resource&, uv_loop_t*, Callback) = 0; + virtual Request* request(const Resource&, Callback) = 0; virtual void cancel(Request*) = 0; }; diff --git a/include/mbgl/util/run_loop.hpp b/include/mbgl/util/run_loop.hpp new file mode 100644 index 0000000000..6113ac2215 --- /dev/null +++ b/include/mbgl/util/run_loop.hpp @@ -0,0 +1,159 @@ +#ifndef MBGL_UTIL_RUN_LOOP +#define MBGL_UTIL_RUN_LOOP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace mbgl { +namespace util { + +class RunLoop : private util::noncopyable { +public: + RunLoop(uv_loop_t*); + ~RunLoop(); + + static RunLoop* Get() { + return current.get(); + } + + static uv_loop_t* getLoop() { + return current.get()->get(); + } + + void stop(); + + // Invoke fn(args...) on this RunLoop. + template + void invoke(Fn&& fn, Args&&... args) { + auto tuple = std::make_tuple(std::move(args)...); + auto task = std::make_shared>( + std::move(fn), + std::move(tuple)); + + withMutex([&] { queue.push(task); }); + async.send(); + } + + // Post the cancellable work fn(args...) to this RunLoop. + template + std::unique_ptr + invokeCancellable(Fn&& fn, Args&&... args) { + auto flag = std::make_shared>(); + *flag = false; + + auto tuple = std::make_tuple(std::move(args)...); + auto task = std::make_shared>( + std::move(fn), + std::move(tuple), + flag); + + withMutex([&] { queue.push(task); }); + async.send(); + + return std::make_unique(task); + } + + // Invoke fn(args...) on this RunLoop, then invoke callback(results...) on the current RunLoop. + template + std::unique_ptr + invokeWithCallback(Fn&& fn, Cb&& callback, Args&&... args) { + auto flag = std::make_shared>(); + *flag = false; + + // Create a lambda L1 that invokes another lambda L2 on the current RunLoop R, that calls + // the callback C. Both lambdas check the flag before proceeding. L1 needs to check the flag + // because if the request was cancelled, then R might have been destroyed. L2 needs to check + // the flag because the request may have been cancelled after L2 was invoked but before it + // began executing. + auto after = [flag, current = RunLoop::current.get(), callback1 = std::move(callback)] (auto&&... results1) { + if (!*flag) { + current->invoke([flag, callback2 = std::move(callback1)] (auto&&... results2) { + if (!*flag) { + callback2(std::move(results2)...); + } + }, std::move(results1)...); + } + }; + + auto tuple = std::make_tuple(std::move(args)..., after); + auto task = std::make_shared>( + std::move(fn), + std::move(tuple), + flag); + + withMutex([&] { queue.push(task); }); + async.send(); + + return std::make_unique(task); + } + + uv_loop_t* get() { return async.get()->loop; } + +private: + template + class Invoker : public WorkTask { + public: + Invoker(F&& f, P&& p, std::shared_ptr> canceled_ = nullptr) + : canceled(canceled_), + func(std::move(f)), + params(std::move(p)) { + } + + void operator()() override { + // Lock the mutex while processing so that cancel() will block. + std::lock_guard lock(mutex); + if (!canceled || !*canceled) { + invoke(std::make_index_sequence::value>{}); + } + } + + // If the task has not yet begun, this will cancel it. + // If the task is in progress, this will block until it completed. (Currently + // necessary because of shared state, but should be removed.) It will also + // cancel the after callback. + // If the task has completed, but the after callback has not executed, this + // will cancel the after callback. + // If the task has completed and the after callback has executed, this will + // do nothing. + void cancel() override { + std::lock_guard lock(mutex); + *canceled = true; + } + + private: + template + void invoke(std::index_sequence) { + func(std::get(std::forward

(params))...); + } + + std::recursive_mutex mutex; + std::shared_ptr> canceled; + + F func; + P params; + }; + + using Queue = std::queue>; + + void withMutex(std::function&&); + void process(); + + Queue queue; + std::mutex mutex; + uv::async async; + + static uv::tls current; +}; + +} +} + +#endif diff --git a/include/mbgl/util/uv_detail.hpp b/include/mbgl/util/uv_detail.hpp new file mode 100644 index 0000000000..86a64d33f2 --- /dev/null +++ b/include/mbgl/util/uv_detail.hpp @@ -0,0 +1,220 @@ +#ifndef MBGL_UTIL_UV_DETAIL +#define MBGL_UTIL_UV_DETAIL + +#include +#include + +#include + +// XXX: uv.h will include that will +// polute the namespace by defining "B0" which +// will conflict with boost macros. +#ifdef B0 +#undef B0 +#endif + +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 +#define UV_ASYNC_PARAMS(handle) uv_async_t *handle, int +#define UV_TIMER_PARAMS(timer) uv_timer_t *timer, int +#else +#define UV_ASYNC_PARAMS(handle) uv_async_t *handle +#define UV_TIMER_PARAMS(timer) uv_timer_t *timer +#endif + +#include +#include +#include +#include + +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 + +// Add thread local storage to libuv API: +// https://github.com/joyent/libuv/commit/5d2434bf71e47802841bad218d521fa254d1ca2d + +typedef pthread_key_t uv_key_t; + +UV_EXTERN int uv_key_create(uv_key_t* key); +UV_EXTERN void uv_key_delete(uv_key_t* key); +UV_EXTERN void* uv_key_get(uv_key_t* key); +UV_EXTERN void uv_key_set(uv_key_t* key, void* value); + +#endif + + +namespace uv { + +class loop : public mbgl::util::noncopyable { +public: + inline loop() { +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 + l = uv_loop_new(); + if (l == nullptr) { +#else + l = new uv_loop_t; + if (uv_loop_init(l) != 0) { +#endif + throw std::runtime_error("failed to initialize loop"); + } + } + + inline ~loop() { +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 + uv_loop_delete(l); +#else + uv_loop_close(l); + delete l; +#endif + } + + inline void run() { + uv_run(l, UV_RUN_DEFAULT); + } + + inline uv_loop_t* operator*() { + return l; + } + + inline uv_loop_t* get() { + return l; + } + +private: + uv_loop_t *l = nullptr; +}; + +template +class handle : public mbgl::util::noncopyable { +public: + inline handle() : t(reinterpret_cast(new T)) { + t->data = this; + } + + inline ~handle() { + uv_close(t.release(), [](uv_handle_t* h) { + delete reinterpret_cast(h); + }); + } + + inline void ref() { + uv_ref(t.get()); + } + + inline void unref() { + uv_unref(t.get()); + } + + inline T* get() { + return reinterpret_cast(t.get()); + } + +private: + std::unique_ptr t; +}; + +class async : public handle { +public: + inline async(uv_loop_t* loop, std::function fn_) + : fn(fn_) { + if (uv_async_init(loop, get(), async_cb) != 0) { + throw std::runtime_error("failed to initialize async"); + } + } + + inline void send() { + if (uv_async_send(get()) != 0) { + throw std::runtime_error("failed to async send"); + } + } + +private: + static void async_cb(UV_ASYNC_PARAMS(handle)) { + reinterpret_cast(handle->data)->fn(); + } + + std::function fn; +}; + +class timer : public handle { +public: + inline timer(uv_loop_t* loop) { + if (uv_timer_init(loop, get()) != 0) { + throw std::runtime_error("failed to initialize timer"); + } + } + + inline void start(uint64_t timeout, uint64_t repeat, std::function fn_) { + fn = fn_; + if (uv_timer_start(get(), timer_cb, timeout, repeat) != 0) { + throw std::runtime_error("failed to start timer"); + } + } + + inline void stop() { + fn = nullptr; + if (uv_timer_stop(get()) != 0) { + throw std::runtime_error("failed to stop timer"); + } + } + +private: + static void timer_cb(UV_TIMER_PARAMS(t)) { + reinterpret_cast(t->data)->fn(); + } + + std::function fn; +}; + +class mutex : public mbgl::util::noncopyable { +public: + inline mutex() { + if (uv_mutex_init(&mtx) != 0) { + throw std::runtime_error("failed to initialize mutex lock"); + } + } + inline ~mutex() { uv_mutex_destroy(&mtx); } + inline void lock() { uv_mutex_lock(&mtx); } + inline void unlock() { uv_mutex_unlock(&mtx); } +private: + uv_mutex_t mtx; +}; + +class rwlock : public mbgl::util::noncopyable { +public: + inline rwlock() { + if (uv_rwlock_init(&mtx) != 0) { + throw std::runtime_error("failed to initialize read-write lock"); + } + } + inline ~rwlock() { uv_rwlock_destroy(&mtx); } + inline void rdlock() { uv_rwlock_rdlock(&mtx); } + inline void wrlock() { uv_rwlock_wrlock(&mtx); } + inline void rdunlock() { uv_rwlock_rdunlock(&mtx); } + inline void wrunlock() { uv_rwlock_wrunlock(&mtx); } + +private: + uv_rwlock_t mtx; +}; + +template +class tls : public mbgl::util::noncopyable { +public: + inline tls(T* val) { + tls(); + set(val); + } + inline tls() { + if (uv_key_create(&key) != 0) { + throw std::runtime_error("failed to initialize thread local storage key"); + } + } + inline ~tls() { uv_key_delete(&key); } + inline T* get() { return reinterpret_cast(uv_key_get(&key)); } + inline void set(T* val) { uv_key_set(&key, val); } + +private: + uv_key_t key; +}; + +} + +#endif diff --git a/include/mbgl/util/work_request.hpp b/include/mbgl/util/work_request.hpp new file mode 100644 index 0000000000..f2aa2bbacc --- /dev/null +++ b/include/mbgl/util/work_request.hpp @@ -0,0 +1,24 @@ +#ifndef MBGL_UTIL_WORK_REQUEST +#define MBGL_UTIL_WORK_REQUEST + +#include + +#include + +namespace mbgl { + +class WorkTask; + +class WorkRequest : public util::noncopyable { +public: + using Task = std::shared_ptr; + WorkRequest(Task); + ~WorkRequest(); + +private: + std::shared_ptr task; +}; + +} + +#endif diff --git a/include/mbgl/util/work_task.hpp b/include/mbgl/util/work_task.hpp new file mode 100644 index 0000000000..2224b211c4 --- /dev/null +++ b/include/mbgl/util/work_task.hpp @@ -0,0 +1,21 @@ +#ifndef MBGL_UTIL_WORK_TASK +#define MBGL_UTIL_WORK_TASK + +#include + +namespace mbgl { + +// A movable type-erasing function wrapper. This allows to store arbitrary invokable +// things (like std::function<>, or the result of a movable-only std::bind()) in the queue. +// Source: http://stackoverflow.com/a/29642072/331379 +class WorkTask : private util::noncopyable { +public: + virtual ~WorkTask() = default; + + virtual void operator()() = 0; + virtual void cancel() = 0; +}; + +} + +#endif diff --git a/platform/node/src/node_file_source.cpp b/platform/node/src/node_file_source.cpp index 5a5a34f0ce..8f88919638 100644 --- a/platform/node/src/node_file_source.cpp +++ b/platform/node/src/node_file_source.cpp @@ -3,6 +3,7 @@ #include "util/async_queue.hpp" #include +#include namespace node_mbgl { @@ -33,8 +34,8 @@ NodeFileSource::~NodeFileSource() { options.Reset(); } -mbgl::Request* NodeFileSource::request(const mbgl::Resource& resource, uv_loop_t* loop, Callback callback) { - auto req = new mbgl::Request(resource, loop, std::move(callback)); +mbgl::Request* NodeFileSource::request(const mbgl::Resource& resource, Callback callback) { + auto req = new mbgl::Request(resource, mbgl::util::RunLoop::getLoop(), std::move(callback)); std::lock_guard lock(observersMutex); diff --git a/platform/node/src/node_file_source.hpp b/platform/node/src/node_file_source.hpp index 0ebdb541c9..dc1d06a3b0 100644 --- a/platform/node/src/node_file_source.hpp +++ b/platform/node/src/node_file_source.hpp @@ -22,7 +22,7 @@ public: NodeFileSource(v8::Local); ~NodeFileSource(); - mbgl::Request* request(const mbgl::Resource&, uv_loop_t*, Callback); + mbgl::Request* request(const mbgl::Resource&, Callback); void cancel(mbgl::Request*); // visiblity? diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index 031fbb57ca..f97cbb4dc7 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -105,7 +105,7 @@ void MapContext::setStyleURL(const std::string& url) { } FileSource* fs = util::ThreadContext::getFileSource(); - styleRequest = fs->request({ Resource::Kind::Style, styleURL }, util::RunLoop::getLoop(), [this, base](const Response &res) { + styleRequest = fs->request({ Resource::Kind::Style, styleURL }, [this, base](const Response &res) { if (res.stale) { // Only handle fresh responses. return; diff --git a/src/mbgl/map/raster_tile_data.cpp b/src/mbgl/map/raster_tile_data.cpp index f29fa86b8e..2ba03f0f0e 100644 --- a/src/mbgl/map/raster_tile_data.cpp +++ b/src/mbgl/map/raster_tile_data.cpp @@ -30,7 +30,7 @@ void RasterTileData::request(float pixelRatio, state = State::loading; FileSource* fs = util::ThreadContext::getFileSource(); - req = fs->request({ Resource::Kind::Tile, url }, util::RunLoop::getLoop(), [url, callback, this](const Response &res) { + req = fs->request({ Resource::Kind::Tile, url }, [url, callback, this](const Response &res) { if (res.stale) { // Only handle fresh responses. return; diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index a6fd9665bb..6a97d00f2d 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -148,7 +147,7 @@ void Source::load() { } FileSource* fs = util::ThreadContext::getFileSource(); - req = fs->request({ Resource::Kind::Source, info.url }, util::RunLoop::getLoop(), [this](const Response &res) { + req = fs->request({ Resource::Kind::Source, info.url }, [this](const Response &res) { if (res.stale) { // Only handle fresh responses. return; diff --git a/src/mbgl/map/vector_tile.cpp b/src/mbgl/map/vector_tile.cpp index b044250f8c..3cffed13ac 100644 --- a/src/mbgl/map/vector_tile.cpp +++ b/src/mbgl/map/vector_tile.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include @@ -183,7 +182,7 @@ VectorTileMonitor::VectorTileMonitor(const SourceInfo& source, const TileID& id, } Request* VectorTileMonitor::monitorTile(std::function)> callback) { - return util::ThreadContext::getFileSource()->request({ Resource::Kind::Tile, url }, util::RunLoop::getLoop(), [callback, this](const Response& res) { + return util::ThreadContext::getFileSource()->request({ Resource::Kind::Tile, url }, [callback, this](const Response& res) { if (res.data && data == res.data) { // We got the same data again. Abort early. return; diff --git a/src/mbgl/sprite/sprite_store.cpp b/src/mbgl/sprite/sprite_store.cpp index 27b33e51e3..723204a916 100644 --- a/src/mbgl/sprite/sprite_store.cpp +++ b/src/mbgl/sprite/sprite_store.cpp @@ -40,7 +40,7 @@ void SpriteStore::setURL(const std::string& url) { loader = std::make_unique(); FileSource* fs = util::ThreadContext::getFileSource(); - loader->jsonRequest = fs->request({ Resource::Kind::SpriteJSON, jsonURL }, util::RunLoop::getLoop(), + loader->jsonRequest = fs->request({ Resource::Kind::SpriteJSON, jsonURL }, [this, jsonURL](const Response& res) { if (res.stale) { // Only handle fresh responses. @@ -60,7 +60,7 @@ void SpriteStore::setURL(const std::string& url) { }); loader->spriteRequest = - fs->request({ Resource::Kind::SpriteImage, spriteURL }, util::RunLoop::getLoop(), + fs->request({ Resource::Kind::SpriteImage, spriteURL }, [this, spriteURL](const Response& res) { if (res.stale) { // Only handle fresh responses. diff --git a/src/mbgl/storage/default_file_source.cpp b/src/mbgl/storage/default_file_source.cpp index bfab5bfcaf..47873f76f4 100644 --- a/src/mbgl/storage/default_file_source.cpp +++ b/src/mbgl/storage/default_file_source.cpp @@ -39,9 +39,7 @@ DefaultFileSource::~DefaultFileSource() { MBGL_VERIFY_THREAD(tid); } -Request* DefaultFileSource::request(const Resource& resource, uv_loop_t* l, Callback callback) { - assert(l); - +Request* DefaultFileSource::request(const Resource& resource, Callback callback) { if (!callback) { throw util::MisuseException("FileSource callback can't be empty"); } @@ -70,7 +68,7 @@ Request* DefaultFileSource::request(const Resource& resource, uv_loop_t* l, Call url = resource.url; } - auto req = new Request({ resource.kind, url }, l, std::move(callback)); + auto req = new Request({ resource.kind, url }, util::RunLoop::getLoop(), std::move(callback)); thread->invoke(&Impl::add, req); return req; } diff --git a/src/mbgl/text/glyph_pbf.cpp b/src/mbgl/text/glyph_pbf.cpp index 66327b333f..635c434a80 100644 --- a/src/mbgl/text/glyph_pbf.cpp +++ b/src/mbgl/text/glyph_pbf.cpp @@ -92,7 +92,7 @@ GlyphPBF::GlyphPBF(GlyphStore* store, }; FileSource* fs = util::ThreadContext::getFileSource(); - req = fs->request({ Resource::Kind::Glyphs, url }, util::RunLoop::getLoop(), requestCallback); + req = fs->request({ Resource::Kind::Glyphs, url }, requestCallback); } GlyphPBF::~GlyphPBF() = default; diff --git a/src/mbgl/text/glyph_store.hpp b/src/mbgl/text/glyph_store.hpp index f8de6cbaf5..1f569664f2 100644 --- a/src/mbgl/text/glyph_store.hpp +++ b/src/mbgl/text/glyph_store.hpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include diff --git a/src/mbgl/util/run_loop.hpp b/src/mbgl/util/run_loop.hpp deleted file mode 100644 index 6113ac2215..0000000000 --- a/src/mbgl/util/run_loop.hpp +++ /dev/null @@ -1,159 +0,0 @@ -#ifndef MBGL_UTIL_RUN_LOOP -#define MBGL_UTIL_RUN_LOOP - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace mbgl { -namespace util { - -class RunLoop : private util::noncopyable { -public: - RunLoop(uv_loop_t*); - ~RunLoop(); - - static RunLoop* Get() { - return current.get(); - } - - static uv_loop_t* getLoop() { - return current.get()->get(); - } - - void stop(); - - // Invoke fn(args...) on this RunLoop. - template - void invoke(Fn&& fn, Args&&... args) { - auto tuple = std::make_tuple(std::move(args)...); - auto task = std::make_shared>( - std::move(fn), - std::move(tuple)); - - withMutex([&] { queue.push(task); }); - async.send(); - } - - // Post the cancellable work fn(args...) to this RunLoop. - template - std::unique_ptr - invokeCancellable(Fn&& fn, Args&&... args) { - auto flag = std::make_shared>(); - *flag = false; - - auto tuple = std::make_tuple(std::move(args)...); - auto task = std::make_shared>( - std::move(fn), - std::move(tuple), - flag); - - withMutex([&] { queue.push(task); }); - async.send(); - - return std::make_unique(task); - } - - // Invoke fn(args...) on this RunLoop, then invoke callback(results...) on the current RunLoop. - template - std::unique_ptr - invokeWithCallback(Fn&& fn, Cb&& callback, Args&&... args) { - auto flag = std::make_shared>(); - *flag = false; - - // Create a lambda L1 that invokes another lambda L2 on the current RunLoop R, that calls - // the callback C. Both lambdas check the flag before proceeding. L1 needs to check the flag - // because if the request was cancelled, then R might have been destroyed. L2 needs to check - // the flag because the request may have been cancelled after L2 was invoked but before it - // began executing. - auto after = [flag, current = RunLoop::current.get(), callback1 = std::move(callback)] (auto&&... results1) { - if (!*flag) { - current->invoke([flag, callback2 = std::move(callback1)] (auto&&... results2) { - if (!*flag) { - callback2(std::move(results2)...); - } - }, std::move(results1)...); - } - }; - - auto tuple = std::make_tuple(std::move(args)..., after); - auto task = std::make_shared>( - std::move(fn), - std::move(tuple), - flag); - - withMutex([&] { queue.push(task); }); - async.send(); - - return std::make_unique(task); - } - - uv_loop_t* get() { return async.get()->loop; } - -private: - template - class Invoker : public WorkTask { - public: - Invoker(F&& f, P&& p, std::shared_ptr> canceled_ = nullptr) - : canceled(canceled_), - func(std::move(f)), - params(std::move(p)) { - } - - void operator()() override { - // Lock the mutex while processing so that cancel() will block. - std::lock_guard lock(mutex); - if (!canceled || !*canceled) { - invoke(std::make_index_sequence::value>{}); - } - } - - // If the task has not yet begun, this will cancel it. - // If the task is in progress, this will block until it completed. (Currently - // necessary because of shared state, but should be removed.) It will also - // cancel the after callback. - // If the task has completed, but the after callback has not executed, this - // will cancel the after callback. - // If the task has completed and the after callback has executed, this will - // do nothing. - void cancel() override { - std::lock_guard lock(mutex); - *canceled = true; - } - - private: - template - void invoke(std::index_sequence) { - func(std::get(std::forward

(params))...); - } - - std::recursive_mutex mutex; - std::shared_ptr> canceled; - - F func; - P params; - }; - - using Queue = std::queue>; - - void withMutex(std::function&&); - void process(); - - Queue queue; - std::mutex mutex; - uv::async async; - - static uv::tls current; -}; - -} -} - -#endif diff --git a/src/mbgl/util/uv_detail.hpp b/src/mbgl/util/uv_detail.hpp deleted file mode 100644 index 86a64d33f2..0000000000 --- a/src/mbgl/util/uv_detail.hpp +++ /dev/null @@ -1,220 +0,0 @@ -#ifndef MBGL_UTIL_UV_DETAIL -#define MBGL_UTIL_UV_DETAIL - -#include -#include - -#include - -// XXX: uv.h will include that will -// polute the namespace by defining "B0" which -// will conflict with boost macros. -#ifdef B0 -#undef B0 -#endif - -#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 -#define UV_ASYNC_PARAMS(handle) uv_async_t *handle, int -#define UV_TIMER_PARAMS(timer) uv_timer_t *timer, int -#else -#define UV_ASYNC_PARAMS(handle) uv_async_t *handle -#define UV_TIMER_PARAMS(timer) uv_timer_t *timer -#endif - -#include -#include -#include -#include - -#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 - -// Add thread local storage to libuv API: -// https://github.com/joyent/libuv/commit/5d2434bf71e47802841bad218d521fa254d1ca2d - -typedef pthread_key_t uv_key_t; - -UV_EXTERN int uv_key_create(uv_key_t* key); -UV_EXTERN void uv_key_delete(uv_key_t* key); -UV_EXTERN void* uv_key_get(uv_key_t* key); -UV_EXTERN void uv_key_set(uv_key_t* key, void* value); - -#endif - - -namespace uv { - -class loop : public mbgl::util::noncopyable { -public: - inline loop() { -#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 - l = uv_loop_new(); - if (l == nullptr) { -#else - l = new uv_loop_t; - if (uv_loop_init(l) != 0) { -#endif - throw std::runtime_error("failed to initialize loop"); - } - } - - inline ~loop() { -#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 - uv_loop_delete(l); -#else - uv_loop_close(l); - delete l; -#endif - } - - inline void run() { - uv_run(l, UV_RUN_DEFAULT); - } - - inline uv_loop_t* operator*() { - return l; - } - - inline uv_loop_t* get() { - return l; - } - -private: - uv_loop_t *l = nullptr; -}; - -template -class handle : public mbgl::util::noncopyable { -public: - inline handle() : t(reinterpret_cast(new T)) { - t->data = this; - } - - inline ~handle() { - uv_close(t.release(), [](uv_handle_t* h) { - delete reinterpret_cast(h); - }); - } - - inline void ref() { - uv_ref(t.get()); - } - - inline void unref() { - uv_unref(t.get()); - } - - inline T* get() { - return reinterpret_cast(t.get()); - } - -private: - std::unique_ptr t; -}; - -class async : public handle { -public: - inline async(uv_loop_t* loop, std::function fn_) - : fn(fn_) { - if (uv_async_init(loop, get(), async_cb) != 0) { - throw std::runtime_error("failed to initialize async"); - } - } - - inline void send() { - if (uv_async_send(get()) != 0) { - throw std::runtime_error("failed to async send"); - } - } - -private: - static void async_cb(UV_ASYNC_PARAMS(handle)) { - reinterpret_cast(handle->data)->fn(); - } - - std::function fn; -}; - -class timer : public handle { -public: - inline timer(uv_loop_t* loop) { - if (uv_timer_init(loop, get()) != 0) { - throw std::runtime_error("failed to initialize timer"); - } - } - - inline void start(uint64_t timeout, uint64_t repeat, std::function fn_) { - fn = fn_; - if (uv_timer_start(get(), timer_cb, timeout, repeat) != 0) { - throw std::runtime_error("failed to start timer"); - } - } - - inline void stop() { - fn = nullptr; - if (uv_timer_stop(get()) != 0) { - throw std::runtime_error("failed to stop timer"); - } - } - -private: - static void timer_cb(UV_TIMER_PARAMS(t)) { - reinterpret_cast(t->data)->fn(); - } - - std::function fn; -}; - -class mutex : public mbgl::util::noncopyable { -public: - inline mutex() { - if (uv_mutex_init(&mtx) != 0) { - throw std::runtime_error("failed to initialize mutex lock"); - } - } - inline ~mutex() { uv_mutex_destroy(&mtx); } - inline void lock() { uv_mutex_lock(&mtx); } - inline void unlock() { uv_mutex_unlock(&mtx); } -private: - uv_mutex_t mtx; -}; - -class rwlock : public mbgl::util::noncopyable { -public: - inline rwlock() { - if (uv_rwlock_init(&mtx) != 0) { - throw std::runtime_error("failed to initialize read-write lock"); - } - } - inline ~rwlock() { uv_rwlock_destroy(&mtx); } - inline void rdlock() { uv_rwlock_rdlock(&mtx); } - inline void wrlock() { uv_rwlock_wrlock(&mtx); } - inline void rdunlock() { uv_rwlock_rdunlock(&mtx); } - inline void wrunlock() { uv_rwlock_wrunlock(&mtx); } - -private: - uv_rwlock_t mtx; -}; - -template -class tls : public mbgl::util::noncopyable { -public: - inline tls(T* val) { - tls(); - set(val); - } - inline tls() { - if (uv_key_create(&key) != 0) { - throw std::runtime_error("failed to initialize thread local storage key"); - } - } - inline ~tls() { uv_key_delete(&key); } - inline T* get() { return reinterpret_cast(uv_key_get(&key)); } - inline void set(T* val) { uv_key_set(&key, val); } - -private: - uv_key_t key; -}; - -} - -#endif diff --git a/src/mbgl/util/work_request.hpp b/src/mbgl/util/work_request.hpp deleted file mode 100644 index f2aa2bbacc..0000000000 --- a/src/mbgl/util/work_request.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef MBGL_UTIL_WORK_REQUEST -#define MBGL_UTIL_WORK_REQUEST - -#include - -#include - -namespace mbgl { - -class WorkTask; - -class WorkRequest : public util::noncopyable { -public: - using Task = std::shared_ptr; - WorkRequest(Task); - ~WorkRequest(); - -private: - std::shared_ptr task; -}; - -} - -#endif diff --git a/src/mbgl/util/work_task.hpp b/src/mbgl/util/work_task.hpp deleted file mode 100644 index 2224b211c4..0000000000 --- a/src/mbgl/util/work_task.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef MBGL_UTIL_WORK_TASK -#define MBGL_UTIL_WORK_TASK - -#include - -namespace mbgl { - -// A movable type-erasing function wrapper. This allows to store arbitrary invokable -// things (like std::function<>, or the result of a movable-only std::bind()) in the queue. -// Source: http://stackoverflow.com/a/29642072/331379 -class WorkTask : private util::noncopyable { -public: - virtual ~WorkTask() = default; - - virtual void operator()() = 0; - virtual void cancel() = 0; -}; - -} - -#endif diff --git a/test/fixtures/mock_file_source.cpp b/test/fixtures/mock_file_source.cpp index 5049b5c001..33d7397d54 100644 --- a/test/fixtures/mock_file_source.cpp +++ b/test/fixtures/mock_file_source.cpp @@ -145,8 +145,8 @@ void MockFileSource::setOnRequestDelayedCallback(std::function callb thread_->invokeSync(&Impl::setOnRequestDelayedCallback, callback); } -Request* MockFileSource::request(const Resource& resource, uv_loop_t* loop, Callback callback) { - Request* req = new Request(resource, loop, std::move(callback)); +Request* MockFileSource::request(const Resource& resource, Callback callback) { + Request* req = new Request(resource, util::RunLoop::getLoop(), std::move(callback)); thread_->invoke(&Impl::handleRequest, req); return req; diff --git a/test/fixtures/mock_file_source.hpp b/test/fixtures/mock_file_source.hpp index 6bee95dcf8..716b0672e3 100644 --- a/test/fixtures/mock_file_source.hpp +++ b/test/fixtures/mock_file_source.hpp @@ -47,7 +47,7 @@ public: void setOnRequestDelayedCallback(std::function callback); // FileSource implementation. - Request* request(const Resource&, uv_loop_t*, Callback) override; + Request* request(const Resource&, Callback) override; void cancel(Request*) override; private: diff --git a/test/storage/cache_response.cpp b/test/storage/cache_response.cpp index f70557e6e0..306abc9f01 100644 --- a/test/storage/cache_response.cpp +++ b/test/storage/cache_response.cpp @@ -4,6 +4,7 @@ #include #include +#include TEST_F(Storage, CacheResponse) { SCOPED_TEST(CacheResponse); @@ -12,12 +13,16 @@ TEST_F(Storage, CacheResponse) { SQLiteCache cache(":memory:"); DefaultFileSource fs(&cache); + util::RunLoop loop(uv_default_loop()); const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/cache" }; Response response; - Request* req = fs.request(resource, uv_default_loop(), [&](const Response &res) { - fs.cancel(req); + Request* req1 = nullptr; + Request* req2 = nullptr; + + req1 = fs.request(resource, [&](const Response &res) { + fs.cancel(req1); EXPECT_EQ(nullptr, res.error); EXPECT_EQ(false, res.stale); ASSERT_TRUE(res.data.get()); @@ -26,23 +31,22 @@ TEST_F(Storage, CacheResponse) { EXPECT_EQ(0, res.modified); EXPECT_EQ("", res.etag); response = res; - }); - - uv_run(uv_default_loop(), UV_RUN_DEFAULT); - - // Now test that we get the same values as in the previous request. If we'd go to the server - // again, we'd get different values. - req = fs.request(resource, uv_default_loop(), [&](const Response &res) { - fs.cancel(req); - EXPECT_EQ(response.error, res.error); - EXPECT_EQ(response.stale, res.stale); - ASSERT_TRUE(res.data.get()); - EXPECT_EQ(*response.data, *res.data); - EXPECT_EQ(response.expires, res.expires); - EXPECT_EQ(response.modified, res.modified); - EXPECT_EQ(response.etag, res.etag); - CacheResponse.finish(); + // Now test that we get the same values as in the previous request. If we'd go to the server + // again, we'd get different values. + req2 = fs.request(resource, [&](const Response &res2) { + fs.cancel(req2); + EXPECT_EQ(response.error, res2.error); + EXPECT_EQ(response.stale, res2.stale); + ASSERT_TRUE(res2.data.get()); + EXPECT_EQ(*response.data, *res2.data); + EXPECT_EQ(response.expires, res2.expires); + EXPECT_EQ(response.modified, res2.modified); + EXPECT_EQ(response.etag, res2.etag); + + loop.stop(); + CacheResponse.finish(); + }); }); uv_run(uv_default_loop(), UV_RUN_DEFAULT); diff --git a/test/storage/cache_revalidate.cpp b/test/storage/cache_revalidate.cpp index 6d9d30a3ec..c137b13da0 100644 --- a/test/storage/cache_revalidate.cpp +++ b/test/storage/cache_revalidate.cpp @@ -4,6 +4,7 @@ #include #include +#include TEST_F(Storage, CacheRevalidateSame) { SCOPED_TEST(CacheRevalidateSame) @@ -12,11 +13,12 @@ TEST_F(Storage, CacheRevalidateSame) { SQLiteCache cache(":memory:"); DefaultFileSource fs(&cache); + util::RunLoop loop(uv_default_loop()); const Resource revalidateSame { Resource::Unknown, "http://127.0.0.1:3000/revalidate-same" }; Request* req1 = nullptr; Request* req2 = nullptr; - req1 = fs.request(revalidateSame, uv_default_loop(), [&](const Response &res) { + req1 = fs.request(revalidateSame, [&](const Response &res) { // This callback can get triggered multiple times. We only care about the first invocation. // It will get triggered again when refreshing the req2 (see below). static bool first = true; @@ -33,7 +35,7 @@ TEST_F(Storage, CacheRevalidateSame) { EXPECT_EQ(0, res.modified); EXPECT_EQ("snowfall", res.etag); - req2 = fs.request(revalidateSame, uv_default_loop(), [&, res](const Response &res2) { + req2 = fs.request(revalidateSame, [&, res](const Response &res2) { if (res2.stale) { // Discard stale responses, if any. return; @@ -58,6 +60,7 @@ TEST_F(Storage, CacheRevalidateSame) { // We're not sending the ETag in the 304 reply, but it should still be there. EXPECT_EQ("snowfall", res2.etag); + loop.stop(); CacheRevalidateSame.finish(); }); }); @@ -72,12 +75,13 @@ TEST_F(Storage, CacheRevalidateModified) { SQLiteCache cache(":memory:"); DefaultFileSource fs(&cache); + util::RunLoop loop(uv_default_loop()); const Resource revalidateModified{ Resource::Unknown, "http://127.0.0.1:3000/revalidate-modified" }; Request* req1 = nullptr; Request* req2 = nullptr; - req1 = fs.request(revalidateModified, uv_default_loop(), [&](const Response& res) { + req1 = fs.request(revalidateModified, [&](const Response& res) { // This callback can get triggered multiple times. We only care about the first invocation. // It will get triggered again when refreshing the req2 (see below). static bool first = true; @@ -94,7 +98,7 @@ TEST_F(Storage, CacheRevalidateModified) { EXPECT_EQ(1420070400, res.modified); EXPECT_EQ("", res.etag); - req2 = fs.request(revalidateModified, uv_default_loop(), [&, res](const Response &res2) { + req2 = fs.request(revalidateModified, [&, res](const Response &res2) { if (res2.stale) { // Discard stale responses, if any. return; @@ -118,6 +122,7 @@ TEST_F(Storage, CacheRevalidateModified) { EXPECT_EQ(1420070400, res2.modified); EXPECT_EQ("", res2.etag); + loop.stop(); CacheRevalidateModified.finish(); }); }); @@ -132,11 +137,12 @@ TEST_F(Storage, CacheRevalidateEtag) { SQLiteCache cache(":memory:"); DefaultFileSource fs(&cache); + util::RunLoop loop(uv_default_loop()); const Resource revalidateEtag { Resource::Unknown, "http://127.0.0.1:3000/revalidate-etag" }; Request* req1 = nullptr; Request* req2 = nullptr; - req1 = fs.request(revalidateEtag, uv_default_loop(), [&](const Response &res) { + req1 = fs.request(revalidateEtag, [&](const Response &res) { // This callback can get triggered multiple times. We only care about the first invocation. // It will get triggered again when refreshing the req2 (see below). static bool first = true; @@ -153,7 +159,7 @@ TEST_F(Storage, CacheRevalidateEtag) { EXPECT_EQ(0, res.modified); EXPECT_EQ("response-1", res.etag); - req2 = fs.request(revalidateEtag, uv_default_loop(), [&, res](const Response &res2) { + req2 = fs.request(revalidateEtag, [&, res](const Response &res2) { if (res2.stale) { // Discard stale responses, if any. return; @@ -176,6 +182,7 @@ TEST_F(Storage, CacheRevalidateEtag) { EXPECT_EQ(0, res2.modified); EXPECT_EQ("response-2", res2.etag); + loop.stop(); CacheRevalidateEtag.finish(); }); }); diff --git a/test/storage/directory_reading.cpp b/test/storage/directory_reading.cpp index c30f504f3d..1bd56a9a22 100644 --- a/test/storage/directory_reading.cpp +++ b/test/storage/directory_reading.cpp @@ -3,6 +3,7 @@ #include #include +#include TEST_F(Storage, AssetReadDirectory) { SCOPED_TEST(ReadDirectory) @@ -15,7 +16,9 @@ TEST_F(Storage, AssetReadDirectory) { DefaultFileSource fs(nullptr); #endif - Request* req = fs.request({ Resource::Unknown, "asset://TEST_DATA/fixtures/storage" }, uv_default_loop(), + util::RunLoop loop(uv_default_loop()); + + Request* req = fs.request({ Resource::Unknown, "asset://TEST_DATA/fixtures/storage" }, [&](const Response &res) { fs.cancel(req); ASSERT_NE(nullptr, res.error); @@ -30,6 +33,8 @@ TEST_F(Storage, AssetReadDirectory) { #elif MBGL_ASSET_FS EXPECT_EQ("illegal operation on a directory", res.error->message); #endif + + loop.stop(); ReadDirectory.finish(); }); diff --git a/test/storage/file_reading.cpp b/test/storage/file_reading.cpp index c1b7f27a98..9e9d5b21a6 100644 --- a/test/storage/file_reading.cpp +++ b/test/storage/file_reading.cpp @@ -4,6 +4,7 @@ #include #include +#include TEST_F(Storage, AssetEmptyFile) { SCOPED_TEST(EmptyFile) @@ -16,7 +17,9 @@ TEST_F(Storage, AssetEmptyFile) { DefaultFileSource fs(nullptr); #endif - Request* req = fs.request({ Resource::Unknown, "asset://TEST_DATA/fixtures/storage/empty" }, uv_default_loop(), + util::RunLoop loop(uv_default_loop()); + + Request* req = fs.request({ Resource::Unknown, "asset://TEST_DATA/fixtures/storage/empty" }, [&](const Response &res) { fs.cancel(req); EXPECT_EQ(nullptr, res.error); @@ -26,6 +29,7 @@ TEST_F(Storage, AssetEmptyFile) { EXPECT_EQ(0, res.expires); EXPECT_LT(1420000000, res.modified); EXPECT_NE("", res.etag); + loop.stop(); EmptyFile.finish(); }); @@ -43,8 +47,10 @@ TEST_F(Storage, AssetNonEmptyFile) { DefaultFileSource fs(nullptr); #endif + util::RunLoop loop(uv_default_loop()); + Request* req = fs.request({ Resource::Unknown, "asset://TEST_DATA/fixtures/storage/nonempty" }, - uv_default_loop(), [&](const Response &res) { + [&](const Response &res) { fs.cancel(req); EXPECT_EQ(nullptr, res.error); EXPECT_EQ(false, res.stale); @@ -55,6 +61,7 @@ TEST_F(Storage, AssetNonEmptyFile) { EXPECT_NE("", res.etag); ASSERT_TRUE(res.data.get()); EXPECT_EQ("content is here\n", *res.data); + loop.stop(); NonEmptyFile.finish(); }); @@ -72,8 +79,10 @@ TEST_F(Storage, AssetNonExistentFile) { DefaultFileSource fs(nullptr); #endif + util::RunLoop loop(uv_default_loop()); + Request* req = fs.request({ Resource::Unknown, "asset://TEST_DATA/fixtures/storage/does_not_exist" }, - uv_default_loop(), [&](const Response &res) { + [&](const Response &res) { fs.cancel(req); ASSERT_NE(nullptr, res.error); EXPECT_EQ(Response::Error::Reason::NotFound, res.error->reason); @@ -87,6 +96,7 @@ TEST_F(Storage, AssetNonExistentFile) { #elif MBGL_ASSET_FS EXPECT_EQ("no such file or directory", res.error->message); #endif + loop.stop(); NonExistentFile.finish(); }); diff --git a/test/storage/http_cancel.cpp b/test/storage/http_cancel.cpp index cbe273e05d..af378162ee 100644 --- a/test/storage/http_cancel.cpp +++ b/test/storage/http_cancel.cpp @@ -4,6 +4,7 @@ #include #include +#include #include @@ -13,15 +14,16 @@ TEST_F(Storage, HTTPCancel) { using namespace mbgl; DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); auto req = - fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, [&](const Response &) { ADD_FAILURE() << "Callback should not be called"; }); fs.cancel(req); HTTPCancel.finish(); - uv_run(uv_default_loop(), UV_RUN_DEFAULT); + uv_run(uv_default_loop(), UV_RUN_ONCE); } TEST_F(Storage, HTTPCancelMultiple) { @@ -30,13 +32,14 @@ TEST_F(Storage, HTTPCancelMultiple) { using namespace mbgl; DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/test" }; - auto req2 = fs.request(resource, uv_default_loop(), [&](const Response &) { + auto req2 = fs.request(resource, [&](const Response &) { ADD_FAILURE() << "Callback should not be called"; }); - Request* req = fs.request(resource, uv_default_loop(), [&](const Response &res) { + Request* req = fs.request(resource, [&](const Response &res) { fs.cancel(req); EXPECT_EQ(nullptr, res.error); EXPECT_EQ(false, res.stale); @@ -45,6 +48,7 @@ TEST_F(Storage, HTTPCancelMultiple) { EXPECT_EQ(0, res.expires); EXPECT_EQ(0, res.modified); EXPECT_EQ("", res.etag); + loop.stop(); HTTPCancelMultiple.finish(); }); fs.cancel(req2); diff --git a/test/storage/http_coalescing.cpp b/test/storage/http_coalescing.cpp index ab776eebfb..4255787285 100644 --- a/test/storage/http_coalescing.cpp +++ b/test/storage/http_coalescing.cpp @@ -3,6 +3,7 @@ #include #include +#include TEST_F(Storage, HTTPCoalescing) { SCOPED_TEST(HTTPCoalescing) @@ -13,6 +14,7 @@ TEST_F(Storage, HTTPCoalescing) { using namespace mbgl; DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); static const Response *reference = nullptr; @@ -34,6 +36,7 @@ TEST_F(Storage, HTTPCoalescing) { EXPECT_EQ("", res.etag); if (counter >= total) { + loop.stop(); HTTPCoalescing.finish(); } }; @@ -42,7 +45,7 @@ TEST_F(Storage, HTTPCoalescing) { Request* reqs[total]; for (int i = 0; i < total; i++) { - reqs[i] = fs.request(resource, uv_default_loop(), [&complete, &fs, &reqs, i] (const Response &res) { + reqs[i] = fs.request(resource, [&complete, &fs, &reqs, i] (const Response &res) { fs.cancel(reqs[i]); complete(res); }); @@ -57,13 +60,14 @@ TEST_F(Storage, HTTPMultiple) { using namespace mbgl; DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); const Response *reference = nullptr; const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/test?expires=2147483647" }; Request* req1 = nullptr; Request* req2 = nullptr; - req1 = fs.request(resource, uv_default_loop(), [&] (const Response &res) { + req1 = fs.request(resource, [&] (const Response &res) { EXPECT_EQ(nullptr, reference); reference = &res; @@ -76,7 +80,7 @@ TEST_F(Storage, HTTPMultiple) { EXPECT_EQ("", res.etag); // Start a second request for the same resource after the first one has been completed. - req2 = fs.request(resource, uv_default_loop(), [&] (const Response &res2) { + req2 = fs.request(resource, [&] (const Response &res2) { // Make sure we get the same object ID as before. EXPECT_EQ(reference, &res2); @@ -91,6 +95,7 @@ TEST_F(Storage, HTTPMultiple) { EXPECT_EQ(0, res2.modified); EXPECT_EQ("", res2.etag); + loop.stop(); HTTPMultiple.finish(); }); }); @@ -105,6 +110,7 @@ TEST_F(Storage, HTTPStale) { using namespace mbgl; DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); int updates = 0; int stale = 0; @@ -112,7 +118,7 @@ TEST_F(Storage, HTTPStale) { const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/test" }; Request* req1 = nullptr; Request* req2 = nullptr; - req1 = fs.request(resource, uv_default_loop(), [&] (const Response &res) { + req1 = fs.request(resource, [&] (const Response &res) { // Do not cancel the request right away. EXPECT_EQ(nullptr, res.error); ASSERT_TRUE(res.data.get()); @@ -130,7 +136,7 @@ TEST_F(Storage, HTTPStale) { updates++; // Start a second request for the same resource after the first one has been completed. - req2 = fs.request(resource, uv_default_loop(), [&] (const Response &res2) { + req2 = fs.request(resource, [&] (const Response &res2) { EXPECT_EQ(nullptr, res2.error); ASSERT_TRUE(res2.data.get()); EXPECT_EQ("Hello World!", *res2.data); @@ -145,6 +151,7 @@ TEST_F(Storage, HTTPStale) { // Now cancel both requests after both have been notified. fs.cancel(req1); fs.cancel(req2); + loop.stop(); HTTPStale.finish(); } }); diff --git a/test/storage/http_error.cpp b/test/storage/http_error.cpp index 94a3f29a4e..fd0a847a61 100644 --- a/test/storage/http_error.cpp +++ b/test/storage/http_error.cpp @@ -4,26 +4,21 @@ #include #include +#include #include -#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 -#define UV_TIMER_PARAMS uv_timer_t*, int -#else -#define UV_TIMER_PARAMS uv_timer_t* -#endif - -TEST_F(Storage, HTTPError) { +TEST_F(Storage, HTTPTemporaryError) { SCOPED_TEST(HTTPTemporaryError) - SCOPED_TEST(HTTPConnectionError) using namespace mbgl; DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); const auto start = uv_hrtime(); - Request* req1 = fs.request({ Resource::Unknown, "http://127.0.0.1:3000/temporary-error" }, uv_default_loop(), + Request* req1 = fs.request({ Resource::Unknown, "http://127.0.0.1:3000/temporary-error" }, [&](const Response &res) { static int counter = 0; switch (counter++) { @@ -52,12 +47,26 @@ TEST_F(Storage, HTTPError) { EXPECT_EQ(0, res.expires); EXPECT_EQ(0, res.modified); EXPECT_EQ("", res.etag); + loop.stop(); HTTPTemporaryError.finish(); } break; } }); - Request* req2 = fs.request({ Resource::Unknown, "http://127.0.0.1:3001/" }, uv_default_loop(), + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} + +TEST_F(Storage, HTTPConnectionError) { + SCOPED_TEST(HTTPConnectionError) + + using namespace mbgl; + + DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); + + const auto start = uv_hrtime(); + + Request* req2 = fs.request({ Resource::Unknown, "http://127.0.0.1:3001/" }, [&](const Response &res) { static int counter = 0; static int wait = 0; @@ -85,6 +94,7 @@ TEST_F(Storage, HTTPError) { if (counter == 2) { fs.cancel(req2); + loop.stop(); HTTPConnectionError.finish(); } wait += (1 << counter); diff --git a/test/storage/http_header_parsing.cpp b/test/storage/http_header_parsing.cpp index aab8711ea0..415a232e92 100644 --- a/test/storage/http_header_parsing.cpp +++ b/test/storage/http_header_parsing.cpp @@ -4,20 +4,21 @@ #include #include +#include #include -TEST_F(Storage, HTTPHeaderParsing) { +TEST_F(Storage, HTTPExpiresParsing) { SCOPED_TEST(HTTPExpiresTest) - SCOPED_TEST(HTTPCacheControlTest) using namespace mbgl; DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); Request* req1 = fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test?modified=1420794326&expires=1420797926&etag=foo" }, - uv_default_loop(), [&](const Response &res) { + [&](const Response &res) { fs.cancel(req1); EXPECT_EQ(nullptr, res.error); EXPECT_EQ(false, res.stale); @@ -26,14 +27,26 @@ TEST_F(Storage, HTTPHeaderParsing) { EXPECT_EQ(1420797926, res.expires); EXPECT_EQ(1420794326, res.modified); EXPECT_EQ("foo", res.etag); + loop.stop(); HTTPExpiresTest.finish(); }); + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} + +TEST_F(Storage, HTTPCacheControlParsing) { + SCOPED_TEST(HTTPCacheControlTest) + + using namespace mbgl; + + DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); + int64_t now = std::chrono::duration_cast( SystemClock::now().time_since_epoch()).count(); Request* req2 = fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test?cachecontrol=max-age=120" }, - uv_default_loop(), [&](const Response &res) { + [&](const Response &res) { fs.cancel(req2); EXPECT_EQ(nullptr, res.error); EXPECT_EQ(false, res.stale); @@ -42,6 +55,7 @@ TEST_F(Storage, HTTPHeaderParsing) { EXPECT_GT(2, std::abs(res.expires - now - 120)) << "Expiration date isn't about 120 seconds in the future"; EXPECT_EQ(0, res.modified); EXPECT_EQ("", res.etag); + loop.stop(); HTTPCacheControlTest.finish(); }); diff --git a/test/storage/http_issue_1369.cpp b/test/storage/http_issue_1369.cpp index c6f0929feb..3d7165d9be 100644 --- a/test/storage/http_issue_1369.cpp +++ b/test/storage/http_issue_1369.cpp @@ -4,6 +4,7 @@ #include #include +#include // Test for https://github.com/mapbox/mapbox-gl-native/issue/1369 // @@ -23,14 +24,15 @@ TEST_F(Storage, HTTPIssue1369) { SQLiteCache cache; DefaultFileSource fs(&cache); + util::RunLoop loop(uv_default_loop()); const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/test" }; - auto req = fs.request(resource, uv_default_loop(), [&](const Response&) { + auto req = fs.request(resource, [&](const Response&) { ADD_FAILURE() << "Callback should not be called"; }); fs.cancel(req); - req = fs.request(resource, uv_default_loop(), [&](const Response &res) { + req = fs.request(resource, [&](const Response &res) { fs.cancel(req); EXPECT_EQ(nullptr, res.error); EXPECT_EQ(false, res.stale); @@ -39,6 +41,7 @@ TEST_F(Storage, HTTPIssue1369) { EXPECT_EQ(0, res.expires); EXPECT_EQ(0, res.modified); EXPECT_EQ("", res.etag); + loop.stop(); HTTPIssue1369.finish(); }); diff --git a/test/storage/http_load.cpp b/test/storage/http_load.cpp index ece859c6d3..be385afe99 100644 --- a/test/storage/http_load.cpp +++ b/test/storage/http_load.cpp @@ -3,6 +3,7 @@ #include #include +#include TEST_F(Storage, HTTPLoad) { SCOPED_TEST(HTTPLoad) @@ -10,6 +11,7 @@ TEST_F(Storage, HTTPLoad) { using namespace mbgl; DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); const int concurrency = 50; const int max = 10000; @@ -21,7 +23,7 @@ TEST_F(Storage, HTTPLoad) { const auto current = number++; reqs[i] = fs.request({ Resource::Unknown, std::string("http://127.0.0.1:3000/load/") + std::to_string(current) }, - uv_default_loop(), [&, i, current](const Response &res) { + [&, i, current](const Response &res) { fs.cancel(reqs[i]); reqs[i] = nullptr; EXPECT_EQ(nullptr, res.error); @@ -35,6 +37,7 @@ TEST_F(Storage, HTTPLoad) { if (number <= max) { req(i); } else if (current == max) { + loop.stop(); HTTPLoad.finish(); } }); diff --git a/test/storage/http_other_loop.cpp b/test/storage/http_other_loop.cpp index b00d54eb7b..3f50134de5 100644 --- a/test/storage/http_other_loop.cpp +++ b/test/storage/http_other_loop.cpp @@ -3,6 +3,7 @@ #include #include +#include TEST_F(Storage, HTTPOtherLoop) { SCOPED_TEST(HTTPOtherLoop) @@ -11,8 +12,9 @@ TEST_F(Storage, HTTPOtherLoop) { // This file source launches a separate thread to do the processing. DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); - Request* req = fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), + Request* req = fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, [&](const Response &res) { fs.cancel(req); EXPECT_EQ(nullptr, res.error); @@ -22,6 +24,7 @@ TEST_F(Storage, HTTPOtherLoop) { EXPECT_EQ(0, res.expires); EXPECT_EQ(0, res.modified); EXPECT_EQ("", res.etag); + loop.stop(); HTTPOtherLoop.finish(); }); diff --git a/test/storage/http_reading.cpp b/test/storage/http_reading.cpp index 1c0df9df73..b7ddc270d8 100644 --- a/test/storage/http_reading.cpp +++ b/test/storage/http_reading.cpp @@ -4,21 +4,21 @@ #include #include +#include #include -TEST_F(Storage, HTTPReading) { +TEST_F(Storage, HTTPTest) { SCOPED_TEST(HTTPTest) - SCOPED_TEST(HTTP404) - SCOPED_TEST(HTTP500) using namespace mbgl; DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); const auto mainThread = uv_thread_self(); - Request* req1 = fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), + Request* req1 = fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, [&](const Response &res) { fs.cancel(req1); EXPECT_EQ(uv_thread_self(), mainThread); @@ -29,10 +29,24 @@ TEST_F(Storage, HTTPReading) { EXPECT_EQ(0, res.expires); EXPECT_EQ(0, res.modified); EXPECT_EQ("", res.etag); + loop.stop(); HTTPTest.finish(); }); - Request* req2 = fs.request({ Resource::Unknown, "http://127.0.0.1:3000/doesnotexist" }, uv_default_loop(), + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} + +TEST_F(Storage, HTTP404) { + SCOPED_TEST(HTTP404) + + using namespace mbgl; + + DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); + + const auto mainThread = uv_thread_self(); + + Request* req2 = fs.request({ Resource::Unknown, "http://127.0.0.1:3000/doesnotexist" }, [&](const Response &res) { fs.cancel(req2); EXPECT_EQ(uv_thread_self(), mainThread); @@ -45,10 +59,24 @@ TEST_F(Storage, HTTPReading) { EXPECT_EQ(0, res.expires); EXPECT_EQ(0, res.modified); EXPECT_EQ("", res.etag); + loop.stop(); HTTP404.finish(); }); - Request* req3 = fs.request({ Resource::Unknown, "http://127.0.0.1:3000/permanent-error" }, uv_default_loop(), + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} + +TEST_F(Storage, HTTP500) { + SCOPED_TEST(HTTP500) + + using namespace mbgl; + + DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); + + const auto mainThread = uv_thread_self(); + + Request* req3 = fs.request({ Resource::Unknown, "http://127.0.0.1:3000/permanent-error" }, [&](const Response &res) { fs.cancel(req3); EXPECT_EQ(uv_thread_self(), mainThread); @@ -61,6 +89,7 @@ TEST_F(Storage, HTTPReading) { EXPECT_EQ(0, res.expires); EXPECT_EQ(0, res.modified); EXPECT_EQ("", res.etag); + loop.stop(); HTTP500.finish(); }); @@ -68,14 +97,15 @@ TEST_F(Storage, HTTPReading) { } TEST_F(Storage, HTTPNoCallback) { - SCOPED_TEST(HTTPTest) + SCOPED_TEST(HTTPNoCallback) using namespace mbgl; DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); try { - fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, nullptr); } catch (const util::MisuseException& ex) { EXPECT_EQ(std::string(ex.what()), "FileSource callback can't be empty"); @@ -83,5 +113,5 @@ TEST_F(Storage, HTTPNoCallback) { EXPECT_TRUE(false) << "Unhandled exception."; } - HTTPTest.finish(); + HTTPNoCallback.finish(); } diff --git a/test/storage/http_retry_network_status.cpp b/test/storage/http_retry_network_status.cpp index 3f8c469cf9..131186b5d7 100644 --- a/test/storage/http_retry_network_status.cpp +++ b/test/storage/http_retry_network_status.cpp @@ -4,6 +4,7 @@ #include #include +#include // Test for https://github.com/mapbox/mapbox-gl-native/issues/2123 @@ -18,11 +19,12 @@ TEST_F(Storage, HTTPNetworkStatusChange) { using namespace mbgl; DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/delayed" }; // This request takes 200 milliseconds to answer. - Request* req = fs.request(resource, uv_default_loop(), [&](const Response& res) { + Request* req = fs.request(resource, [&](const Response& res) { fs.cancel(req); EXPECT_EQ(nullptr, res.error); EXPECT_EQ(false, res.stale); @@ -31,6 +33,7 @@ TEST_F(Storage, HTTPNetworkStatusChange) { EXPECT_EQ(0, res.expires); EXPECT_EQ(0, res.modified); EXPECT_EQ("", res.etag); + loop.stop(); HTTPNetworkStatusChange.finish(); }); @@ -56,11 +59,12 @@ TEST_F(Storage, HTTPNetworkStatusChangePreempt) { using namespace mbgl; DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); const auto start = uv_hrtime(); const Resource resource{ Resource::Unknown, "http://127.0.0.1:3001/test" }; - Request* req = fs.request(resource, uv_default_loop(), [&](const Response& res) { + Request* req = fs.request(resource, [&](const Response& res) { static int counter = 0; const auto duration = double(uv_hrtime() - start) / 1e9; if (counter == 0) { @@ -92,6 +96,7 @@ TEST_F(Storage, HTTPNetworkStatusChangePreempt) { if (counter++ == 1) { fs.cancel(req); + loop.stop(); HTTPNetworkStatusChangePreempt.finish(); } }); diff --git a/test/storage/http_timeout.cpp b/test/storage/http_timeout.cpp index 92a6671d7e..5855bdc4f2 100644 --- a/test/storage/http_timeout.cpp +++ b/test/storage/http_timeout.cpp @@ -4,6 +4,7 @@ #include #include +#include TEST_F(Storage, HTTPTimeout) { @@ -12,11 +13,12 @@ TEST_F(Storage, HTTPTimeout) { using namespace mbgl; DefaultFileSource fs(nullptr); + util::RunLoop loop(uv_default_loop()); int counter = 0; const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/test?cachecontrol=max-age=1" }; - Request* req = fs.request(resource, uv_default_loop(), [&](const Response &res) { + Request* req = fs.request(resource, [&](const Response &res) { counter++; EXPECT_EQ(nullptr, res.error); EXPECT_EQ(false, res.stale); @@ -27,6 +29,7 @@ TEST_F(Storage, HTTPTimeout) { EXPECT_EQ("", res.etag); if (counter == 4) { fs.cancel(req); + loop.stop(); HTTPTimeout.finish(); } }); -- cgit v1.2.1