diff options
41 files changed, 485 insertions, 201 deletions
diff --git a/.clang-format b/.clang-format index 86d562e74e..109b562b59 100644 --- a/.clang-format +++ b/.clang-format @@ -2,7 +2,7 @@ Standard: Cpp11 IndentWidth: 4 AccessModifierOffset: -4 UseTab: Never -BinPackParameters: false +BinPackParameters: true AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AllowShortBlocksOnASingleLine: false diff --git a/gyp/http-curl.gypi b/gyp/http-curl.gypi index bfb2054d55..c97ad370b5 100644 --- a/gyp/http-curl.gypi +++ b/gyp/http-curl.gypi @@ -14,6 +14,25 @@ '../include', ], + 'variables': { + 'cflags_cc': [ + '<@(uv_cflags)', + '<@(curl_cflags)', + '<@(boost_cflags)', + ], + 'ldflags': [ + '<@(uv_ldflags)', + '<@(curl_ldflags)', + ], + 'libraries': [ + '<@(uv_static_libs)', + '<@(curl_static_libs)', + ], + 'defines': [ + '-DMBGL_HTTP_CURL' + ], + }, + 'conditions': [ ['host == "android"', { 'variables': { @@ -32,19 +51,17 @@ }], ], - 'variables': { - 'cflags_cc': [ - '<@(uv_cflags)', - '<@(curl_cflags)', - '<@(boost_cflags)', - ], - 'ldflags': [ - '<@(uv_ldflags)', - '<@(curl_ldflags)', - ], - 'libraries': [ - '<@(uv_static_libs)', - '<@(curl_static_libs)', + 'direct_dependent_settings': { + 'conditions': [ + ['OS == "mac"', { + 'xcode_settings': { + 'OTHER_CFLAGS': [ '<@(defines)' ], + 'OTHER_CPLUSPLUSFLAGS': [ '<@(defines)' ], + } + }, { + 'cflags': [ '<@(defines)' ], + 'cflags_cc': [ '<@(defines)' ], + }] ], }, diff --git a/gyp/http-nsurl.gypi b/gyp/http-nsurl.gypi index 4205f59d81..5a079fdeeb 100644 --- a/gyp/http-nsurl.gypi +++ b/gyp/http-nsurl.gypi @@ -25,6 +25,9 @@ 'libraries': [ '<@(uv_static_libs)', ], + 'defines': [ + '-DMBGL_HTTP_NSURL' + ], }, 'xcode_settings': { @@ -32,6 +35,13 @@ 'CLANG_ENABLE_OBJC_ARC': 'NO', }, + 'direct_dependent_settings': { + 'xcode_settings': { + 'OTHER_CFLAGS': [ '<@(defines)' ], + 'OTHER_CPLUSPLUSFLAGS': [ '<@(defines)' ], + } + }, + 'link_settings': { 'libraries': [ '<@(libraries)' ], 'xcode_settings': { diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 8711aa3d08..00719aa382 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -36,11 +36,7 @@ class View; class GlyphAtlas; class SpriteAtlas; class LineAtlas; - -struct exception : std::runtime_error { - inline exception(const char *msg) : std::runtime_error(msg) { - } -}; +class Environment; class Map : private util::noncopyable { friend class View; @@ -182,8 +178,8 @@ private: Mode mode = Mode::None; -public: // TODO: make private again - std::unique_ptr<uv::loop> loop; + const std::unique_ptr<Environment> env; + View &view; private: std::unique_ptr<uv::worker> workers; @@ -214,12 +210,8 @@ private: // Stores whether the map thread has been stopped already. std::atomic_bool isStopped; - View &view; - -#ifdef DEBUG const std::thread::id mainThread; std::thread::id mapThread; -#endif Transform transform; TransformState state; diff --git a/include/mbgl/storage/default/request.hpp b/include/mbgl/storage/default/request.hpp index 648585f304..b686d1fe90 100644 --- a/include/mbgl/storage/default/request.hpp +++ b/include/mbgl/storage/default/request.hpp @@ -15,13 +15,14 @@ typedef struct uv_loop_s uv_loop_t; namespace mbgl { class Response; +class Environment; class Request : private util::noncopyable { MBGL_STORE_THREAD(tid) public: using Callback = std::function<void(const Response &)>; - Request(const Resource &resource, uv_loop_t *loop, Callback callback); + Request(const Resource &resource, uv_loop_t *loop, const Environment &env, Callback callback); public: // May be called from any thread. @@ -45,6 +46,11 @@ private: public: const Resource resource; + + // The environment ref is used to associate requests with a particular environment. This allows + // us to only terminate requests associated with that environment, e.g. when the map the env + // belongs to is discarded. + const Environment &env; }; } diff --git a/include/mbgl/storage/default/shared_request_base.hpp b/include/mbgl/storage/default/shared_request_base.hpp index 2d56615608..59e38efc2f 100644 --- a/include/mbgl/storage/default/shared_request_base.hpp +++ b/include/mbgl/storage/default/shared_request_base.hpp @@ -4,11 +4,13 @@ #include <mbgl/storage/resource.hpp> #include <mbgl/storage/file_cache.hpp> #include <mbgl/storage/default_file_source.hpp> +#include <mbgl/storage/default/request.hpp> #include <mbgl/util/util.hpp> #include <mbgl/util/noncopyable.hpp> #include <string> #include <set> +#include <vector> #include <cassert> typedef struct uv_loop_s uv_loop_t; @@ -45,20 +47,12 @@ public: observers.insert(request); } - void unsubscribeAll() { - MBGL_VERIFY_THREAD(tid); - - source = nullptr; - observers.clear(); - cancel(); - } - void unsubscribe(Request *request) { MBGL_VERIFY_THREAD(tid); observers.erase(request); - if (observers.empty()) { + if (abandoned()) { // There are no observers anymore. We are initiating cancelation. if (source) { // First, remove this SharedRequestBase from the source. @@ -70,6 +64,29 @@ public: } } + bool abandoned() const { + return observers.empty(); + } + + std::vector<Request *> removeAllInEnvironment(const Environment &env) { + MBGL_VERIFY_THREAD(tid); + + std::vector<Request *> result; + + // Removes all Requests in the supplied environment and returns a list + // of them. + util::erase_if(observers, [&](Request *req) -> bool { + if (&req->env == &env) { + result.push_back(req); + return true; + } else { + return false; + } + }); + + return result; + } + protected: virtual ~SharedRequestBase() { MBGL_VERIFY_THREAD(tid); diff --git a/include/mbgl/storage/default_file_source.hpp b/include/mbgl/storage/default_file_source.hpp index 86e2414041..14b4db6eff 100644 --- a/include/mbgl/storage/default_file_source.hpp +++ b/include/mbgl/storage/default_file_source.hpp @@ -20,11 +20,14 @@ class DefaultFileSource : public FileSource { public: DefaultFileSource(FileCache *cache, const std::string &root = ""); DefaultFileSource(FileCache *cache, uv_loop_t *loop, const std::string &root = ""); - ~DefaultFileSource(); + ~DefaultFileSource() override; - Request *request(const Resource &resource, uv_loop_t *loop, Callback callback); - void cancel(Request *request); - void request(const Resource &resource, Callback callback); + Request *request(const Resource &resource, uv_loop_t *loop, const Environment &env, + Callback callback) override; + void cancel(Request *request) override; + void request(const Resource &resource, const Environment &env, Callback callback) override; + + void abort(const Environment &env) override; enum class CacheHint : uint8_t { Full, Refresh, No }; void notify(SharedRequestBase *sharedRequest, const std::set<Request *> &observers, @@ -39,14 +42,16 @@ private: struct RemoveRequestAction; struct ResultAction; struct StopAction; - using Action = - mapbox::util::variant<AddRequestAction, RemoveRequestAction, ResultAction, StopAction>; + struct AbortAction; + using Action = mapbox::util::variant<AddRequestAction, RemoveRequestAction, ResultAction, + StopAction, AbortAction>; using Queue = util::AsyncQueue<Action>; void process(AddRequestAction &action); void process(RemoveRequestAction &action); void process(ResultAction &action); void process(StopAction &action); + void process(AbortAction &action); SharedRequestBase *find(const Resource &resource); diff --git a/include/mbgl/storage/file_source.hpp b/include/mbgl/storage/file_source.hpp index 8517d6e4a6..30e88c39f6 100644 --- a/include/mbgl/storage/file_source.hpp +++ b/include/mbgl/storage/file_source.hpp @@ -15,6 +15,7 @@ typedef struct uv_loop_s uv_loop_t; namespace mbgl { class Request; +class Environment; class FileSource : private util::noncopyable { protected: @@ -27,12 +28,19 @@ 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 &resource, uv_loop_t *loop, Callback callback) = 0; + virtual Request *request(const Resource &resource, uv_loop_t *loop, const Environment &env, + Callback callback) = 0; virtual void cancel(Request *request) = 0; // These can be called from any thread. The callback will be invoked in an arbitrary other thread. // You cannot cancel these requests. - virtual void request(const Resource &resource, Callback callback) = 0; + virtual void request(const Resource &resource, const Environment &env, Callback callback) = 0; + + // This can be called from any thread. All requests with the environment pointer env should be + // notified as errored. Note that this is /different/ from canceling requests; a canceled + // request's callback is never called, while an aborted request's callback is called with + // a error message. + virtual void abort(const Environment &env) = 0; }; } diff --git a/platform/darwin/http_request_nsurl.mm b/platform/darwin/http_request_nsurl.mm index 83c010f8b8..638b5062d4 100644 --- a/platform/darwin/http_request_nsurl.mm +++ b/platform/darwin/http_request_nsurl.mm @@ -188,6 +188,7 @@ void HTTPRequestImpl::handleResponse() { } context->removeRequest(request); + request->ptr = nullptr; delete request; request = nullptr; } @@ -206,6 +207,8 @@ void HTTPRequestImpl::cancel() { [task cancel]; [task release]; task = nullptr; + } else { + delete this; } } diff --git a/src/mbgl/map/environment.cpp b/src/mbgl/map/environment.cpp new file mode 100644 index 0000000000..ee13d33ea7 --- /dev/null +++ b/src/mbgl/map/environment.cpp @@ -0,0 +1,39 @@ +#include <mbgl/map/environment.hpp> +#include <mbgl/storage/file_source.hpp> + +#include <uv.h> + +#include <cassert> + +namespace mbgl { + +Environment::Environment(FileSource &fs) : fileSource(fs), loop(uv_loop_new()) { +} + +void Environment::setup() { + mapThread = std::this_thread::get_id(); +} + +bool Environment::inMapThread() const { + return std::this_thread::get_id() == mapThread; +} + +void Environment::requestAsync(const Resource &resource, std::function<void(const Response &)> callback) { + fileSource.request(resource, *this, std::move(callback)); +} + +Request *Environment::request(const Resource &resource, std::function<void(const Response &)> callback) { + assert(inMapThread()); + return fileSource.request(resource, loop, *this, std::move(callback)); +} + +void Environment::cancelRequest(Request *req) { + assert(inMapThread()); + fileSource.cancel(req); +} + +void Environment::terminate() { + fileSource.abort(*this); +} + +} diff --git a/src/mbgl/map/environment.hpp b/src/mbgl/map/environment.hpp new file mode 100644 index 0000000000..b68a7b9e2f --- /dev/null +++ b/src/mbgl/map/environment.hpp @@ -0,0 +1,44 @@ +#ifndef MBGL_MAP_MAP_ENVIRONMENT +#define MBGL_MAP_MAP_ENVIRONMENT + +#include <mbgl/util/noncopyable.hpp> +#include <mbgl/util/util.hpp> + +#include <thread> +#include <functional> + +typedef struct uv_loop_s uv_loop_t; + +namespace mbgl { + +class FileSource; +class Request; +class Response; +struct Resource; + +class Environment : private util::noncopyable { +public: + Environment(FileSource &); + + void setup(); + + bool inMapThread() const; + + void requestAsync(const Resource &, std::function<void(const Response &)>); + Request *request(const Resource &, std::function<void(const Response &)>); + void cancelRequest(Request *); + + // Request to terminate the environment. + void terminate(); + +private: + FileSource &fileSource; + std::thread::id mapThread; + +public: + uv_loop_t *const loop; +}; + +} + +#endif diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 518bc5bb96..0cd7d3621e 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -1,4 +1,5 @@ #include <mbgl/map/map.hpp> +#include <mbgl/map/environment.hpp> #include <mbgl/map/view.hpp> #include <mbgl/platform/platform.hpp> #include <mbgl/map/source.hpp> @@ -25,6 +26,7 @@ #include <mbgl/util/string.hpp> #include <mbgl/util/uv.hpp> #include <mbgl/util/mapbox.hpp> +#include <mbgl/util/exception.hpp> #include <algorithm> #include <iostream> @@ -57,16 +59,14 @@ const static bool uvVersionCheck = []() { using namespace mbgl; Map::Map(View& view_, FileSource& fileSource_) - : loop(util::make_unique<uv::loop>()), + : env(util::make_unique<Environment>(fileSource_)), view(view_), -#ifdef DEBUG mainThread(std::this_thread::get_id()), mapThread(mainThread), -#endif transform(view_), fileSource(fileSource_), glyphAtlas(util::make_unique<GlyphAtlas>(1024, 1024)), - glyphStore(std::make_shared<GlyphStore>(fileSource)), + glyphStore(std::make_shared<GlyphStore>(*env)), spriteAtlas(util::make_unique<SpriteAtlas>(512, 512)), lineAtlas(util::make_unique<LineAtlas>(512, 512)), texturePool(std::make_shared<TexturePool>()), @@ -92,7 +92,7 @@ Map::~Map() { texturePool.reset(); workers.reset(); - uv_run(**loop, UV_RUN_DEFAULT); + uv_run(env->loop, UV_RUN_DEFAULT); } uv::worker &Map::getWorker() { @@ -112,7 +112,7 @@ void Map::start(bool startPaused) { isStopped = false; // Setup async notifications - asyncTerminate = util::make_unique<uv::async>(**loop, [this]() { + asyncTerminate = util::make_unique<uv::async>(env->loop, [this]() { assert(std::this_thread::get_id() == mapThread); // Remove all of these to make sure they are destructed in the correct thread. @@ -127,7 +127,7 @@ void Map::start(bool startPaused) { asyncTerminate.reset(); }); - asyncRender = util::make_unique<uv::async>(**loop, [this]() { + asyncRender = util::make_unique<uv::async>(env->loop, [this]() { assert(std::this_thread::get_id() == mapThread); if (state.hasSize()) { @@ -207,7 +207,7 @@ void Map::pause(bool waitForPause) { pausing = true; mutexRun.unlock(); - uv_stop(**loop); + uv_stop(env->loop); rerender(); // Needed to ensure uv_stop is seen and uv_run exits, otherwise we deadlock on wait_for_pause if (waitForPause) { @@ -242,12 +242,14 @@ void Map::run() { } if (mode == Mode::Static && !style && styleURL.empty()) { - throw exception("Style is not set"); + throw util::Exception("Style is not set"); } view.activate(); - workers = util::make_unique<uv::worker>(**loop, 4, "Tile Worker"); + workers = util::make_unique<uv::worker>(env->loop, 4, "Tile Worker"); + + env->setup(); setup(); prepare(); @@ -255,15 +257,15 @@ void Map::run() { if (mode == Mode::Continuous) { terminating = false; while(!terminating) { - uv_run(**loop, UV_RUN_DEFAULT); + uv_run(env->loop, UV_RUN_DEFAULT); checkForPause(); } } else { - uv_run(**loop, UV_RUN_DEFAULT); + uv_run(env->loop, UV_RUN_DEFAULT); } // Run the event loop once more to make sure our async delete handlers are called. - uv_run(**loop, UV_RUN_ONCE); + uv_run(env->loop, UV_RUN_ONCE); // If the map rendering wasn't started asynchronously, we perform one render // *after* all events have been processed. @@ -376,7 +378,7 @@ util::ptr<Sprite> Map::getSprite() { const float pixelRatio = state.getPixelRatio(); const std::string &sprite_url = style->getSpriteURL(); if (!sprite || sprite->pixelRatio != pixelRatio) { - sprite = Sprite::Create(sprite_url, pixelRatio, fileSource); + sprite = Sprite::Create(sprite_url, pixelRatio, *env); } return sprite; @@ -621,7 +623,7 @@ void Map::updateSources() { if (source->enabled) { if (!source->source) { source->source = std::make_shared<Source>(source->info); - source->source->load(*this, fileSource); + source->source->load(*this, *env); } } else { source->source.reset(); @@ -648,10 +650,8 @@ void Map::updateSources(const util::ptr<StyleLayerGroup> &group) { void Map::updateTiles() { for (const auto& source : activeSources) { - source->source->update(*this, getWorker(), - style, *glyphAtlas, *glyphStore, - *spriteAtlas, getSprite(), - *texturePool, fileSource, ***loop, [this](){ update(); }); + source->source->update(*this, *env, getWorker(), style, *glyphAtlas, *glyphStore, + *spriteAtlas, getSprite(), *texturePool, [this]() { update(); }); } } @@ -659,7 +659,7 @@ void Map::prepare() { if (!style) { style = std::make_shared<Style>(); - fileSource.request({ Resource::Kind::JSON, styleURL}, **loop, [&](const Response &res) { + env->request({ Resource::Kind::JSON, styleURL}, [&](const Response &res) { if (res.status == Response::Successful) { // Calculate the base const size_t pos = styleURL.rfind('/'); diff --git a/src/mbgl/map/raster_tile_data.cpp b/src/mbgl/map/raster_tile_data.cpp index 1ca9ef8041..4cd7fc2b5e 100644 --- a/src/mbgl/map/raster_tile_data.cpp +++ b/src/mbgl/map/raster_tile_data.cpp @@ -4,10 +4,9 @@ using namespace mbgl; - -RasterTileData::RasterTileData(Tile::ID const& id_, TexturePool& texturePool, const SourceInfo& source_, FileSource& fileSource_) - : TileData(id_, source_, fileSource_), - bucket(texturePool, layout) { +RasterTileData::RasterTileData(Tile::ID const &id_, TexturePool &texturePool, + const SourceInfo &source_, Environment &env_) + : TileData(id_, source_, env_), bucket(texturePool, layout) { } RasterTileData::~RasterTileData() { diff --git a/src/mbgl/map/raster_tile_data.hpp b/src/mbgl/map/raster_tile_data.hpp index 9a8578a61e..2413e13fb0 100644 --- a/src/mbgl/map/raster_tile_data.hpp +++ b/src/mbgl/map/raster_tile_data.hpp @@ -17,7 +17,7 @@ class RasterTileData : public TileData { friend class TileParser; public: - RasterTileData(Tile::ID const& id, TexturePool&, const SourceInfo&, FileSource &); + RasterTileData(Tile::ID const &id, TexturePool &, const SourceInfo &, Environment &); ~RasterTileData(); void parse() override; diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index d9236b19b3..ec3a1b8e00 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -1,12 +1,14 @@ #include <mbgl/map/source.hpp> #include <mbgl/map/map.hpp> +#include <mbgl/map/environment.hpp> #include <mbgl/map/transform.hpp> #include <mbgl/renderer/painter.hpp> #include <mbgl/util/constants.hpp> #include <mbgl/util/raster.hpp> #include <mbgl/util/string.hpp> #include <mbgl/util/texture_pool.hpp> -#include <mbgl/storage/file_source.hpp> +#include <mbgl/storage/resource.hpp> +#include <mbgl/storage/response.hpp> #include <mbgl/util/vec.hpp> #include <mbgl/util/math.hpp> #include <mbgl/util/std.hpp> @@ -32,7 +34,7 @@ Source::Source(SourceInfo& info_) // Note: This is a separate function that must be called exactly once after creation // The reason this isn't part of the constructor is that calling shared_from_this() in // the constructor fails. -void Source::load(Map& map, FileSource& fileSource) { +void Source::load(Map &map, Environment &env) { if (info.url.empty()) { loaded = true; return; @@ -41,7 +43,7 @@ void Source::load(Map& map, FileSource& fileSource) { util::ptr<Source> source = shared_from_this(); const std::string url = util::mapbox::normalizeSourceURL(info.url, map.getAccessToken()); - fileSource.request({ Resource::Kind::JSON, url }, **map.loop, [source, &map](const Response &res) { + env.request({ Resource::Kind::JSON, url }, [source, &map](const Response &res) { if (res.status != Response::Successful) { Log::Warning(Event::General, "Failed to load source TileJSON: %s", res.message.c_str()); return; @@ -153,13 +155,11 @@ TileData::State Source::hasTile(const Tile::ID& id) { return TileData::State::invalid; } -TileData::State Source::addTile(Map& map, uv::worker& worker, - util::ptr<Style> style, - GlyphAtlas& glyphAtlas, GlyphStore& glyphStore, - SpriteAtlas& spriteAtlas, util::ptr<Sprite> sprite, - FileSource& fileSource, uv_loop_t &loop, TexturePool& texturePool, - const Tile::ID& id, - std::function<void ()> callback) { +TileData::State Source::addTile(Map &map, Environment &env, uv::worker &worker, + util::ptr<Style> style, GlyphAtlas &glyphAtlas, + GlyphStore &glyphStore, SpriteAtlas &spriteAtlas, + util::ptr<Sprite> sprite, TexturePool &texturePool, + const Tile::ID &id, std::function<void()> callback) { const TileData::State state = hasTile(id); if (state != TileData::State::invalid) { @@ -190,14 +190,14 @@ TileData::State Source::addTile(Map& map, uv::worker& worker, new_tile.data = std::make_shared<VectorTileData>(normalized_id, map.getMaxZoom(), style, glyphAtlas, glyphStore, spriteAtlas, sprite, - info, fileSource); + info, env); } else if (info.type == SourceType::Raster) { - new_tile.data = std::make_shared<RasterTileData>(normalized_id, texturePool, info, fileSource); + new_tile.data = std::make_shared<RasterTileData>(normalized_id, texturePool, info, env); } else { throw std::runtime_error("source type not implemented"); } - new_tile.data->request(worker, loop, map.getState().getPixelRatio(), callback); + new_tile.data->request(worker, map.getState().getPixelRatio(), callback); tile_data.emplace(new_tile.data->id, new_tile.data); } @@ -283,12 +283,16 @@ bool Source::findLoadedParent(const Tile::ID& id, int32_t minCoveringZoom, std:: return false; } -void Source::update(Map& map, uv::worker& worker, +void Source::update(Map &map, + Environment &env, + uv::worker &worker, util::ptr<Style> style, - GlyphAtlas& glyphAtlas, GlyphStore& glyphStore, - SpriteAtlas& spriteAtlas, util::ptr<Sprite> sprite, - TexturePool& texturePool, FileSource& fileSource, uv_loop_t& loop, - std::function<void ()> callback) { + GlyphAtlas &glyphAtlas, + GlyphStore &glyphStore, + SpriteAtlas &spriteAtlas, + util::ptr<Sprite> sprite, + TexturePool &texturePool, + std::function<void()> callback) { if (!loaded || map.getTime() <= updated) return; @@ -308,11 +312,8 @@ void Source::update(Map& map, uv::worker& worker, // Add existing child/parent tiles if the actual tile is not yet loaded for (const Tile::ID& id : required) { - const TileData::State state = addTile(map, worker, style, - glyphAtlas, glyphStore, - spriteAtlas, sprite, - fileSource, loop, texturePool, - id, callback); + const TileData::State state = addTile(map, env, worker, style, glyphAtlas, glyphStore, + spriteAtlas, sprite, texturePool, id, callback); if (state != TileData::State::parsed) { // The tile we require is not yet loaded. Try to find a parent or diff --git a/src/mbgl/map/source.hpp b/src/mbgl/map/source.hpp index 2c0e444225..061908b89d 100644 --- a/src/mbgl/map/source.hpp +++ b/src/mbgl/map/source.hpp @@ -18,11 +18,11 @@ namespace mbgl { class Map; +class Environment; class GlyphAtlas; class GlyphStore; class SpriteAtlas; class Sprite; -class FileSource; class TexturePool; class Style; class Painter; @@ -34,13 +34,9 @@ class Source : public std::enable_shared_from_this<Source>, private util::noncop public: Source(SourceInfo&); - void load(Map&, FileSource&); - void update(Map&, uv::worker&, - util::ptr<Style>, - GlyphAtlas&, GlyphStore&, - SpriteAtlas&, util::ptr<Sprite>, - TexturePool&, FileSource&, uv_loop_t& loop, - std::function<void ()> callback); + void load(Map &, Environment &); + void update(Map &, Environment &, uv::worker &, util::ptr<Style>, GlyphAtlas &, GlyphStore &, + SpriteAtlas &, util::ptr<Sprite>, TexturePool &, std::function<void()> callback); void updateMatrices(const mat4 &projMatrix, const TransformState &transform); void drawClippingMasks(Painter &painter); @@ -59,13 +55,9 @@ private: int32_t coveringZoomLevel(const TransformState&) const; std::forward_list<Tile::ID> coveringTiles(const TransformState&) const; - TileData::State addTile(Map&, uv::worker&, - util::ptr<Style>, - GlyphAtlas&, GlyphStore&, - SpriteAtlas&, util::ptr<Sprite>, - FileSource&, uv_loop_t &, TexturePool&, - const Tile::ID&, - std::function<void ()> callback); + TileData::State addTile(Map &, Environment &, uv::worker &, util::ptr<Style>, GlyphAtlas &, + GlyphStore &, SpriteAtlas &, util::ptr<Sprite>, TexturePool &, + const Tile::ID &, std::function<void()> callback); TileData::State hasTile(const Tile::ID& id); diff --git a/src/mbgl/map/sprite.cpp b/src/mbgl/map/sprite.cpp index 9543fe083a..4be92ff73f 100644 --- a/src/mbgl/map/sprite.cpp +++ b/src/mbgl/map/sprite.cpp @@ -5,7 +5,9 @@ #include <string> #include <mbgl/platform/platform.hpp> -#include <mbgl/storage/file_source.hpp> +#include <mbgl/map/environment.hpp> +#include <mbgl/storage/resource.hpp> +#include <mbgl/storage/response.hpp> #include <mbgl/util/uv_detail.hpp> #include <mbgl/util/std.hpp> @@ -22,9 +24,9 @@ SpritePosition::SpritePosition(uint16_t x_, uint16_t y_, uint16_t width_, uint16 sdf(sdf_) { } -util::ptr<Sprite> Sprite::Create(const std::string& base_url, float pixelRatio, FileSource& fileSource) { +util::ptr<Sprite> Sprite::Create(const std::string &base_url, float pixelRatio, Environment &env) { util::ptr<Sprite> sprite(std::make_shared<Sprite>(Key(), base_url, pixelRatio)); - sprite->load(fileSource); + sprite->load(env); return sprite; } @@ -51,7 +53,7 @@ Sprite::operator bool() const { // Note: This is a separate function that must be called exactly once after creation // The reason this isn't part of the constructor is that calling shared_from_this() in // the constructor fails. -void Sprite::load(FileSource& fileSource) { +void Sprite::load(Environment &env) { if (!valid) { // Treat a non-existent sprite as a successfully loaded empty sprite. @@ -63,29 +65,25 @@ void Sprite::load(FileSource& fileSource) { util::ptr<Sprite> sprite = shared_from_this(); - fileSource.request({ Resource::Kind::JSON, jsonURL }, [sprite](const Response &res) { + env.request({ Resource::Kind::JSON, jsonURL }, [sprite](const Response &res) { if (res.status == Response::Successful) { sprite->body = res.data; sprite->parseJSON(); sprite->complete(); } else { Log::Warning(Event::Sprite, "Failed to load sprite info: %s", res.message.c_str()); - if (!sprite->future.valid()) { - sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message))); - } + sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message))); } }); - fileSource.request({ Resource::Kind::Image, spriteURL }, [sprite](const Response &res) { + env.request({ Resource::Kind::Image, spriteURL }, [sprite](const Response &res) { if (res.status == Response::Successful) { sprite->image = res.data; sprite->parseImage(); sprite->complete(); } else { Log::Warning(Event::Sprite, "Failed to load sprite image: %s", res.message.c_str()); - if (!sprite->future.valid()) { - sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message))); - } + sprite->promise.set_exception(std::make_exception_ptr(std::runtime_error(res.message))); } }); } diff --git a/src/mbgl/map/sprite.hpp b/src/mbgl/map/sprite.hpp index d4b54ba1b5..cb0c274dee 100644 --- a/src/mbgl/map/sprite.hpp +++ b/src/mbgl/map/sprite.hpp @@ -14,7 +14,7 @@ namespace mbgl { -class FileSource; +class Environment; class SpritePosition { public: @@ -34,11 +34,12 @@ public: class Sprite : public std::enable_shared_from_this<Sprite>, private util::noncopyable { private: struct Key {}; - void load(FileSource& fileSource); + void load(Environment &env); public: Sprite(const Key &, const std::string& base_url, float pixelRatio); - static util::ptr<Sprite> Create(const std::string& base_url, float pixelRatio, FileSource& fileSource); + static util::ptr<Sprite> + Create(const std::string &base_url, float pixelRatio, Environment &env); const SpritePosition &getSpritePosition(const std::string& name) const; diff --git a/src/mbgl/map/tile_data.cpp b/src/mbgl/map/tile_data.cpp index 9d48041239..53d36dfe8b 100644 --- a/src/mbgl/map/tile_data.cpp +++ b/src/mbgl/map/tile_data.cpp @@ -1,5 +1,6 @@ #include <mbgl/map/tile_data.hpp> #include <mbgl/map/map.hpp> +#include <mbgl/map/environment.hpp> #include <mbgl/style/style_source.hpp> #include <mbgl/util/token.hpp> @@ -10,12 +11,12 @@ using namespace mbgl; -TileData::TileData(Tile::ID const& id_, const SourceInfo& source_, FileSource& fileSource_) +TileData::TileData(Tile::ID const& id_, const SourceInfo& source_, Environment& env_) : id(id_), name(id), state(State::initial), source(source_), - fileSource(fileSource_), + env(env_), debugBucket(debugFontBuffer) { // Initialize tile debug coordinates debugFontBuffer.addText(name.c_str(), 50, 200, 5); @@ -23,7 +24,7 @@ TileData::TileData(Tile::ID const& id_, const SourceInfo& source_, FileSource& f TileData::~TileData() { if (req) { - fileSource.cancel(req); + env.cancelRequest(req); } } @@ -31,8 +32,7 @@ const std::string TileData::toString() const { return std::string { "[tile " } + name + "]"; } -void TileData::request(uv::worker &worker, uv_loop_t &loop, - float pixelRatio, std::function<void()> callback) { +void TileData::request(uv::worker &worker, float pixelRatio, std::function<void()> callback) { if (source.tiles.empty()) return; @@ -53,9 +53,8 @@ void TileData::request(uv::worker &worker, uv_loop_t &loop, state = State::loading; - // Note: Somehow this feels slower than the change to request_http() std::weak_ptr<TileData> weak_tile = shared_from_this(); - req = fileSource.request({ Resource::Kind::Tile, url }, &loop, [weak_tile, url, callback, &worker](const Response &res) { + req = env.request({ Resource::Kind::Tile, url }, [weak_tile, url, callback, &worker](const Response &res) { util::ptr<TileData> tile = weak_tile.lock(); if (!tile || tile->state == State::obsolete) { // noop. Tile is obsolete and we're now just waiting for the refcount @@ -84,7 +83,7 @@ void TileData::cancel() { state = State::obsolete; } if (req) { - fileSource.cancel(req); + env.cancelRequest(req); req = nullptr; } } diff --git a/src/mbgl/map/tile_data.hpp b/src/mbgl/map/tile_data.hpp index fcd741a1f8..66c0fbba2e 100644 --- a/src/mbgl/map/tile_data.hpp +++ b/src/mbgl/map/tile_data.hpp @@ -23,7 +23,7 @@ typedef struct uv_loop_s uv_loop_t; namespace mbgl { class Map; -class FileSource; +class Environment; class Painter; class SourceInfo; class StyleLayer; @@ -48,10 +48,10 @@ public: }; public: - TileData(Tile::ID const& id, const SourceInfo&, FileSource&); + TileData(Tile::ID const &id, const SourceInfo &, Environment &); ~TileData(); - void request(uv::worker&, uv_loop_t&, float pixelRatio, std::function<void ()> callback); + void request(uv::worker&, float pixelRatio, std::function<void ()> callback); void reparse(uv::worker&, std::function<void ()> callback); void cancel(); const std::string toString() const; @@ -72,7 +72,7 @@ public: public: const SourceInfo& source; - FileSource& fileSource; + Environment &env; protected: Request *req = nullptr; diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index 68974aadf4..66fed0706b 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -14,8 +14,8 @@ VectorTileData::VectorTileData(Tile::ID const& id_, float mapMaxZoom, util::ptr<Style> style_, GlyphAtlas& glyphAtlas_, GlyphStore& glyphStore_, SpriteAtlas& spriteAtlas_, util::ptr<Sprite> sprite_, - const SourceInfo& source_, FileSource &fileSource_) - : TileData(id_, source_, fileSource_), + const SourceInfo& source_, Environment &env_) + : TileData(id_, source_, env_), glyphAtlas(glyphAtlas_), glyphStore(glyphStore_), spriteAtlas(spriteAtlas_), diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index feb3c6238b..1c48fc5f23 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -30,11 +30,8 @@ class VectorTileData : public TileData { friend class TileParser; public: - VectorTileData(Tile::ID const&, - float mapMaxZoom, util::ptr<Style>, - GlyphAtlas&, GlyphStore&, - SpriteAtlas&, util::ptr<Sprite>, - const SourceInfo&, FileSource &); + VectorTileData(Tile::ID const &, float mapMaxZoom, util::ptr<Style>, GlyphAtlas &, GlyphStore &, + SpriteAtlas &, util::ptr<Sprite>, const SourceInfo &, Environment &); ~VectorTileData(); void parse() override; diff --git a/src/mbgl/storage/default_file_source.cpp b/src/mbgl/storage/default_file_source.cpp index c6b201b559..9f70ac9943 100644 --- a/src/mbgl/storage/default_file_source.cpp +++ b/src/mbgl/storage/default_file_source.cpp @@ -50,6 +50,10 @@ struct DefaultFileSource::ResultAction { struct DefaultFileSource::StopAction { }; +struct DefaultFileSource::AbortAction { + const Environment &env; +}; + DefaultFileSource::DefaultFileSource(FileCache *cache_, const std::string &root) : assetRoot(root.empty() ? platform::assetRoot() : root), @@ -105,8 +109,9 @@ SharedRequestBase *DefaultFileSource::find(const Resource &resource) { return nullptr; } -Request *DefaultFileSource::request(const Resource &resource, uv_loop_t *l, Callback callback) { - auto req = new Request(resource, l, std::move(callback)); +Request *DefaultFileSource::request(const Resource &resource, uv_loop_t *l, const Environment &env, + Callback callback) { + auto req = new Request(resource, l, env, std::move(callback)); // This function can be called from any thread. Make sure we're executing the actual call in the // file source loop by sending it over the queue. It will be processed in processAction(). @@ -114,8 +119,9 @@ Request *DefaultFileSource::request(const Resource &resource, uv_loop_t *l, Call return req; } -void DefaultFileSource::request(const Resource &resource, Callback callback) { - auto req = new Request(resource, nullptr, std::move(callback)); +void DefaultFileSource::request(const Resource &resource, const Environment &env, + Callback callback) { + auto req = new Request(resource, nullptr, env, std::move(callback)); // This function can be called from any thread. Make sure we're executing the actual call in the // file source loop by sending it over the queue. It will be processed in processAction(). @@ -130,6 +136,11 @@ void DefaultFileSource::cancel(Request *req) { queue->send(RemoveRequestAction{ req }); } +void DefaultFileSource::abort(const Environment &env) { + queue->send(AbortAction{ env }); +} + + void DefaultFileSource::process(AddRequestAction &action) { const Resource &resource = action.request->resource; @@ -209,18 +220,45 @@ void DefaultFileSource::process(ResultAction &action) { } } +// A stop action means the file source is about to be destructed. We need to cancel all requests +// for all environments. void DefaultFileSource::process(StopAction &) { - // Cancel all remaining requests. - for (auto it : pending) { - it.second->unsubscribeAll(); - } - pending.clear(); - + // There may not be any pending requests in this file source anymore. You must terminate all + // Map objects before deleting the FileSource. + assert(pending.empty()); assert(queue); queue->stop(); queue = nullptr; } +// Aborts all requests that are part of the current environment. +void DefaultFileSource::process(AbortAction &action) { + // Construct a cancellation response. + auto res = util::make_unique<Response>(); + res->status = Response::Error; + res->message = "Environment is terminating"; + std::shared_ptr<const Response> response = std::move(res); + + // Iterate through all pending requests and remove them in case they're abandoned. + util::erase_if(pending, [&](const std::pair<Resource, SharedRequestBase *> &it) -> bool { + // Obtain all pending requests that are in the current environment. + const auto aborted = it.second->removeAllInEnvironment(action.env); + + // Notify all observers. + for (auto req : aborted) { + req->notify(response); + } + + // Finally, remove all requests that are now abandoned. + if (it.second->abandoned()) { + it.second->cancel(); + return true; + } else { + return false; + } + }); +} + void DefaultFileSource::notify(SharedRequestBase *sharedRequest, const std::set<Request *> &observers, std::shared_ptr<const Response> response, FileCache::Hint hint) { @@ -235,8 +273,8 @@ void DefaultFileSource::notify(SharedRequestBase *sharedRequest, } // Notify all observers. - for (auto it : observers) { - it->notify(response); + for (auto req : observers) { + req->notify(response); } } diff --git a/src/mbgl/storage/request.cpp b/src/mbgl/storage/request.cpp index de18138ec2..48b5b774d2 100644 --- a/src/mbgl/storage/request.cpp +++ b/src/mbgl/storage/request.cpp @@ -13,8 +13,8 @@ namespace mbgl { // Note: This requires that loop is running in the current thread (or not yet running). -Request::Request(const Resource &resource_, uv_loop_t *loop, Callback callback_) - : callback(callback_), resource(resource_) { +Request::Request(const Resource &resource_, uv_loop_t *loop, const Environment &env_, Callback callback_) + : callback(callback_), resource(resource_), env(env_) { // When there is no loop supplied (== nullptr), the callback will be fired in an arbitrary // thread (the thread notify() is called from) rather than kicking back to the calling thread. if (loop) { diff --git a/src/mbgl/text/glyph_store.cpp b/src/mbgl/text/glyph_store.cpp index f89f42e909..ab1776c04b 100644 --- a/src/mbgl/text/glyph_store.cpp +++ b/src/mbgl/text/glyph_store.cpp @@ -1,5 +1,6 @@ #include <mbgl/text/glyph_store.hpp> +#include <mbgl/map/environment.hpp> #include <mbgl/util/std.hpp> #include <mbgl/util/string.hpp> #include <mbgl/util/utf.hpp> @@ -137,9 +138,11 @@ void FontStack::lineWrap(Shaping &shaping, const float lineHeight, const float m align(shaping, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, line); } -GlyphPBF::GlyphPBF(const std::string &glyphURL, const std::string &fontStack, GlyphRange glyphRange, FileSource& fileSource) - : future(promise.get_future().share()) -{ +GlyphPBF::GlyphPBF(const std::string &glyphURL, + const std::string &fontStack, + GlyphRange glyphRange, + Environment &env) + : future(promise.get_future().share()) { // Load the glyph set URL std::string url = util::replaceTokens(glyphURL, [&](const std::string &name) -> std::string { if (name == "fontstack") return util::percentEncode(fontStack); @@ -148,7 +151,7 @@ GlyphPBF::GlyphPBF(const std::string &glyphURL, const std::string &fontStack, Gl }); // The prepare call jumps back to the main thread. - fileSource.request({ Resource::Kind::Glyphs, url }, [&, url](const Response &res) { + env.requestAsync({ Resource::Kind::Glyphs, url }, [&, url](const Response &res) { if (res.status != Response::Successful) { // Something went wrong with loading the glyph pbf. Pass on the error to the future listeners. const std::string msg = std::string { "[ERROR] failed to load glyphs: " } + res.message; @@ -223,7 +226,7 @@ void GlyphPBF::parse(FontStack &stack) { data.clear(); } -GlyphStore::GlyphStore(FileSource& fileSource_) : fileSource(fileSource_), mtx(util::make_unique<uv::mutex>()) {} +GlyphStore::GlyphStore(Environment& env_) : env(env_), mtx(util::make_unique<uv::mutex>()) {} void GlyphStore::setURL(const std::string &url) { glyphURL = url; @@ -265,7 +268,7 @@ std::shared_future<GlyphPBF &> GlyphStore::loadGlyphRange(const std::string &fon auto range_it = rangeSets.find(range); if (range_it == rangeSets.end()) { // We don't have this glyph set yet for this font stack. - range_it = rangeSets.emplace(range, util::make_unique<GlyphPBF>(glyphURL, fontStack, range, fileSource)).first; + range_it = rangeSets.emplace(range, util::make_unique<GlyphPBF>(glyphURL, fontStack, range, env)).first; } return range_it->second->getFuture(); diff --git a/src/mbgl/text/glyph_store.hpp b/src/mbgl/text/glyph_store.hpp index 6839045d61..406234241d 100644 --- a/src/mbgl/text/glyph_store.hpp +++ b/src/mbgl/text/glyph_store.hpp @@ -17,6 +17,7 @@ namespace mbgl { class FileSource; +class Environment; class SDFGlyph { public: @@ -49,7 +50,10 @@ private: class GlyphPBF { public: - GlyphPBF(const std::string &glyphURL, const std::string &fontStack, GlyphRange glyphRange, FileSource& fileSource); + GlyphPBF(const std::string &glyphURL, + const std::string &fontStack, + GlyphRange glyphRange, + Environment &env); private: GlyphPBF(const GlyphPBF &) = delete; @@ -72,7 +76,7 @@ private: // Manages Glyphrange PBF loading. class GlyphStore { public: - GlyphStore(FileSource& fileSource); + GlyphStore(Environment &); // Block until all specified GlyphRanges of the specified font stack are loaded. void waitForGlyphRanges(const std::string &fontStack, const std::set<GlyphRange> &glyphRanges); @@ -88,7 +92,7 @@ private: FontStack &createFontStack(const std::string &fontStack); std::string glyphURL; - FileSource& fileSource; + Environment &env; std::unordered_map<std::string, std::map<GlyphRange, std::unique_ptr<GlyphPBF>>> ranges; std::unordered_map<std::string, std::unique_ptr<FontStack>> stacks; std::unique_ptr<uv::mutex> mtx; diff --git a/test/storage/cache_response.cpp b/test/storage/cache_response.cpp index a0b5ba31c1..ac0dc4c565 100644 --- a/test/storage/cache_response.cpp +++ b/test/storage/cache_response.cpp @@ -14,8 +14,9 @@ TEST_F(Storage, CacheResponse) { DefaultFileSource fs(&cache, uv_default_loop()); const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/cache" }; + auto &env = *static_cast<const Environment *>(nullptr); - fs.request(resource, uv_default_loop(), [&](const Response &res) { + fs.request(resource, uv_default_loop(), env, [&](const Response &res) { EXPECT_EQ(Response::Successful, res.status); EXPECT_EQ("Response 1", res.data); EXPECT_LT(0, res.expires); @@ -23,7 +24,7 @@ TEST_F(Storage, CacheResponse) { EXPECT_EQ("", res.etag); EXPECT_EQ("", res.message); - fs.request(resource, uv_default_loop(), [&, res](const Response &res2) { + fs.request(resource, uv_default_loop(), env, [&, res](const Response &res2) { EXPECT_EQ(res.status, res2.status); EXPECT_EQ(res.data, res2.data); EXPECT_EQ(res.expires, res2.expires); diff --git a/test/storage/cache_revalidate.cpp b/test/storage/cache_revalidate.cpp index 530b7325b5..bd32042b94 100644 --- a/test/storage/cache_revalidate.cpp +++ b/test/storage/cache_revalidate.cpp @@ -15,8 +15,10 @@ TEST_F(Storage, CacheRevalidate) { SQLiteCache cache(":memory:"); DefaultFileSource fs(&cache); + auto &env = *static_cast<const Environment *>(nullptr); + const Resource revalidateSame { Resource::Unknown, "http://127.0.0.1:3000/revalidate-same" }; - fs.request(revalidateSame, uv_default_loop(), [&](const Response &res) { + fs.request(revalidateSame, uv_default_loop(), env, [&](const Response &res) { EXPECT_EQ(Response::Successful, res.status); EXPECT_EQ("Response", res.data); EXPECT_EQ(0, res.expires); @@ -24,7 +26,7 @@ TEST_F(Storage, CacheRevalidate) { EXPECT_EQ("snowfall", res.etag); EXPECT_EQ("", res.message); - fs.request(revalidateSame, uv_default_loop(), [&, res](const Response &res2) { + fs.request(revalidateSame, uv_default_loop(), env, [&, res](const Response &res2) { EXPECT_EQ(Response::Successful, res2.status); EXPECT_EQ("Response", res2.data); // We use this to indicate that a 304 reply came back. @@ -38,8 +40,9 @@ TEST_F(Storage, CacheRevalidate) { }); }); - const Resource revalidateModified { Resource::Unknown, "http://127.0.0.1:3000/revalidate-modified" }; - fs.request(revalidateModified, uv_default_loop(), [&](const Response &res) { + const Resource revalidateModified{ Resource::Unknown, + "http://127.0.0.1:3000/revalidate-modified" }; + fs.request(revalidateModified, uv_default_loop(), env, [&](const Response &res) { EXPECT_EQ(Response::Successful, res.status); EXPECT_EQ("Response", res.data); EXPECT_EQ(0, res.expires); @@ -47,7 +50,7 @@ TEST_F(Storage, CacheRevalidate) { EXPECT_EQ("", res.etag); EXPECT_EQ("", res.message); - fs.request(revalidateModified, uv_default_loop(), [&, res](const Response &res2) { + fs.request(revalidateModified, uv_default_loop(), env, [&, res](const Response &res2) { EXPECT_EQ(Response::Successful, res2.status); EXPECT_EQ("Response", res2.data); // We use this to indicate that a 304 reply came back. @@ -61,7 +64,7 @@ TEST_F(Storage, CacheRevalidate) { }); const Resource revalidateEtag { Resource::Unknown, "http://127.0.0.1:3000/revalidate-etag" }; - fs.request(revalidateEtag, uv_default_loop(), [&](const Response &res) { + fs.request(revalidateEtag, uv_default_loop(), env, [&](const Response &res) { EXPECT_EQ(Response::Successful, res.status); EXPECT_EQ("Response 1", res.data); EXPECT_EQ(0, res.expires); @@ -69,7 +72,7 @@ TEST_F(Storage, CacheRevalidate) { EXPECT_EQ("response-1", res.etag); EXPECT_EQ("", res.message); - fs.request(revalidateEtag, uv_default_loop(), [&, res](const Response &res2) { + fs.request(revalidateEtag, uv_default_loop(), env, [&, res](const Response &res2) { EXPECT_EQ(Response::Successful, res2.status); EXPECT_EQ("Response 2", res2.data); EXPECT_EQ(0, res2.expires); diff --git a/test/storage/directory_reading.cpp b/test/storage/directory_reading.cpp index 3ee4dd1721..a955648462 100644 --- a/test/storage/directory_reading.cpp +++ b/test/storage/directory_reading.cpp @@ -15,7 +15,10 @@ TEST_F(Storage, AssetReadDirectory) { DefaultFileSource fs(nullptr, uv_default_loop()); #endif - fs.request({ Resource::Unknown, "asset://TEST_DATA/fixtures/storage" }, uv_default_loop(), [&](const Response &res) { + auto &env = *static_cast<const Environment *>(nullptr); + + fs.request({ Resource::Unknown, "asset://TEST_DATA/fixtures/storage" }, uv_default_loop(), + env, [&](const Response &res) { EXPECT_EQ(Response::Error, res.status); EXPECT_EQ(0ul, res.data.size()); EXPECT_EQ(0, res.expires); @@ -24,7 +27,7 @@ TEST_F(Storage, AssetReadDirectory) { #ifdef MBGL_ASSET_ZIP EXPECT_EQ("No such file", res.message); #elif MBGL_ASSET_FS - EXPECT_EQ("illegal operation on a directory", res.message); + EXPECT_EQ("illegal operation on a directory", res.message); #endif ReadDirectory.finish(); }); diff --git a/test/storage/file_reading.cpp b/test/storage/file_reading.cpp index 7e14fdcb15..cca072b27a 100644 --- a/test/storage/file_reading.cpp +++ b/test/storage/file_reading.cpp @@ -16,9 +16,10 @@ TEST_F(Storage, AssetEmptyFile) { DefaultFileSource fs(nullptr, uv_default_loop()); #endif - fs.request({ Resource::Unknown, "asset://TEST_DATA/fixtures/storage/empty" }, - uv_default_loop(), - [&](const Response &res) { + auto &env = *static_cast<const Environment *>(nullptr); + + fs.request({ Resource::Unknown, "asset://TEST_DATA/fixtures/storage/empty" }, uv_default_loop(), + env, [&](const Response &res) { EXPECT_EQ(Response::Successful, res.status); EXPECT_EQ(0ul, res.data.size()); EXPECT_EQ(0, res.expires); @@ -42,9 +43,10 @@ TEST_F(Storage, AssetNonEmptyFile) { DefaultFileSource fs(nullptr, uv_default_loop()); #endif + auto &env = *static_cast<const Environment *>(nullptr); + fs.request({ Resource::Unknown, "asset://TEST_DATA/fixtures/storage/nonempty" }, - uv_default_loop(), - [&](const Response &res) { + uv_default_loop(), env, [&](const Response &res) { EXPECT_EQ(Response::Successful, res.status); EXPECT_EQ(16ul, res.data.size()); EXPECT_EQ(0, res.expires); @@ -69,9 +71,10 @@ TEST_F(Storage, AssetNonExistentFile) { DefaultFileSource fs(nullptr, uv_default_loop()); #endif + auto &env = *static_cast<const Environment *>(nullptr); + fs.request({ Resource::Unknown, "asset://TEST_DATA/fixtures/storage/does_not_exist" }, - uv_default_loop(), - [&](const Response &res) { + uv_default_loop(), env, [&](const Response &res) { EXPECT_EQ(Response::Error, res.status); EXPECT_EQ(0ul, res.data.size()); EXPECT_EQ(0, res.expires); diff --git a/test/storage/http_cancel.cpp b/test/storage/http_cancel.cpp index 7e485ec42d..d0dac8ccdb 100644 --- a/test/storage/http_cancel.cpp +++ b/test/storage/http_cancel.cpp @@ -14,9 +14,11 @@ TEST_F(Storage, HTTPCancel) { DefaultFileSource fs(nullptr, uv_default_loop()); - auto req = fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), [&](const Response &) { - ADD_FAILURE() << "Callback should not be called"; - }); + auto &env = *static_cast<const Environment *>(nullptr); + + auto req = + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), env, + [&](const Response &) { ADD_FAILURE() << "Callback should not be called"; }); fs.cancel(req); HTTPCancel.finish(); @@ -31,11 +33,13 @@ TEST_F(Storage, HTTPCancelMultiple) { DefaultFileSource fs(nullptr, uv_default_loop()); + auto &env = *static_cast<const Environment *>(nullptr); 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, uv_default_loop(), env, [&](const Response &) { ADD_FAILURE() << "Callback should not be called"; }); - fs.request(resource, uv_default_loop(), [&](const Response &res) { + fs.request(resource, uv_default_loop(), env, [&](const Response &res) { EXPECT_EQ(Response::Successful, res.status); EXPECT_EQ("Hello World!", res.data); EXPECT_EQ(0, res.expires); diff --git a/test/storage/http_coalescing.cpp b/test/storage/http_coalescing.cpp index 592c65f8d6..9eaea70e11 100644 --- a/test/storage/http_coalescing.cpp +++ b/test/storage/http_coalescing.cpp @@ -14,6 +14,8 @@ TEST_F(Storage, HTTPCoalescing) { DefaultFileSource fs(nullptr, uv_default_loop()); + auto &env = *static_cast<const Environment *>(nullptr); + static const Response *reference = nullptr; const auto complete = [&](const Response &res) { @@ -41,7 +43,7 @@ TEST_F(Storage, HTTPCoalescing) { const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/test" }; for (int i = 0; i < total; i++) { - fs.request(resource, uv_default_loop(), complete); + fs.request(resource, uv_default_loop(), env, complete); } uv_run(uv_default_loop(), UV_RUN_DEFAULT); diff --git a/test/storage/http_environment.cpp b/test/storage/http_environment.cpp new file mode 100644 index 0000000000..efd7b78ad4 --- /dev/null +++ b/test/storage/http_environment.cpp @@ -0,0 +1,54 @@ +#include "storage.hpp" + +#include <uv.h> + +#include <mbgl/storage/default_file_source.hpp> +#include <mbgl/storage/network_status.hpp> + +#include <cmath> + +TEST_F(Storage, HTTPCancelEnvironment) { + SCOPED_TEST(HTTPRetainedEnvironment) + SCOPED_TEST(HTTPCanceledEnvironment) + + using namespace mbgl; + + DefaultFileSource fs(nullptr, uv_default_loop()); + + // Create two fake environment pointers. The FileSource implementation treats these as opaque + // pointers and doesn't reach into them. + auto &env1 = *reinterpret_cast<const Environment *>(1); + auto &env2 = *reinterpret_cast<const Environment *>(2); + + const Resource resource { Resource::Unknown, "http://127.0.0.1:3000/delayed" }; + + // Environment 1 + fs.request(resource, uv_default_loop(), env1, [&](const Response &res) { + // This environment gets aborted below. This means the request is marked as failing and + // will return an error here. + EXPECT_EQ(Response::Error, res.status); + EXPECT_EQ("", res.data); + EXPECT_EQ(0, res.expires); + EXPECT_EQ(0, res.modified); + EXPECT_EQ("", res.etag); + EXPECT_EQ("Environment is terminating", res.message); + HTTPCanceledEnvironment.finish(); + }); + + // Environment 2 + fs.request(resource, uv_default_loop(), env2, [&](const Response &res) { + // The same request as above, but in a different environment which doesn't get aborted. This + // means the request should succeed. + EXPECT_EQ(Response::Successful, res.status); + EXPECT_EQ("Response", res.data); + EXPECT_EQ(0, res.expires); + EXPECT_EQ(0, res.modified); + EXPECT_EQ("", res.etag); + EXPECT_EQ("", res.message); + HTTPRetainedEnvironment.finish(); + }); + + fs.abort(env1); + + uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} diff --git a/test/storage/http_error.cpp b/test/storage/http_error.cpp index 498f1eae6b..abaeff5396 100644 --- a/test/storage/http_error.cpp +++ b/test/storage/http_error.cpp @@ -22,9 +22,12 @@ TEST_F(Storage, HTTPError) { DefaultFileSource fs(nullptr, uv_default_loop()); + auto &env = *static_cast<const Environment *>(nullptr); + auto start = uv_hrtime(); - fs.request({ Resource::Unknown, "http://127.0.0.1:3000/temporary-error" }, uv_default_loop(), [&](const Response &res) { + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/temporary-error" }, uv_default_loop(), + env, [&](const Response &res) { const auto duration = double(uv_hrtime() - start) / 1e9; EXPECT_LT(1, duration) << "Backoff timer didn't wait 1 second"; EXPECT_GT(1.2, duration) << "Backoff timer fired too late"; @@ -38,13 +41,21 @@ TEST_F(Storage, HTTPError) { HTTPTemporaryError.finish(); }); - fs.request({ Resource::Unknown, "http://127.0.0.1:3001/" }, uv_default_loop(), [&](const Response &res) { + fs.request({ Resource::Unknown, "http://127.0.0.1:3001/" }, uv_default_loop(), env, + [&](const Response &res) { const auto duration = double(uv_hrtime() - start) / 1e9; // 1.5 seconds == 4 retries, with a 500ms timeout (see above). EXPECT_LT(1.5, duration) << "Resource wasn't retried the correct number of times"; EXPECT_GT(1.7, duration) << "Resource wasn't retried the correct number of times"; EXPECT_EQ(Response::Error, res.status); - EXPECT_TRUE(res.message == "Couldn't connect to server: couldn't connect to host" || res.message == "The operation couldn’t be completed. (NSURLErrorDomain error -1004.)") << res.message; +#ifdef MBGL_HTTP_NSURL + EXPECT_STREQ(res.message.c_str(), "The operation couldn’t be completed. (NSURLErrorDomain error -1004.)"); +#elif MBGL_HTTP_CURL + const std::string prefix { "Couldn't connect to server: " }; + EXPECT_STREQ(prefix.c_str(), res.message.substr(0, prefix.size()).c_str()) << "Full message is: \"" << res.message << "\""; +#else + FAIL(); +#endif EXPECT_EQ("", res.data); EXPECT_EQ(0, res.expires); EXPECT_EQ(0, res.modified); @@ -52,7 +63,6 @@ TEST_F(Storage, HTTPError) { HTTPConnectionError.finish(); }); - uv_run(uv_default_loop(), UV_RUN_DEFAULT); uv_timer_stop(&statusChange); diff --git a/test/storage/http_header_parsing.cpp b/test/storage/http_header_parsing.cpp index 655348c877..271460387c 100644 --- a/test/storage/http_header_parsing.cpp +++ b/test/storage/http_header_parsing.cpp @@ -14,7 +14,11 @@ TEST_F(Storage, HTTPHeaderParsing) { DefaultFileSource fs(nullptr, uv_default_loop()); - fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test?modified=1420794326&expires=1420797926&etag=foo" }, uv_default_loop(), [&](const Response &res) { + auto &env = *static_cast<const Environment *>(nullptr); + + fs.request({ Resource::Unknown, + "http://127.0.0.1:3000/test?modified=1420794326&expires=1420797926&etag=foo" }, + uv_default_loop(), env, [&](const Response &res) { EXPECT_EQ(Response::Successful, res.status); EXPECT_EQ("Hello World!", res.data); EXPECT_EQ(1420797926, res.expires); @@ -24,11 +28,11 @@ TEST_F(Storage, HTTPHeaderParsing) { HTTPExpiresTest.finish(); }); - int64_t now = std::chrono::duration_cast<std::chrono::seconds>( std::chrono::system_clock::now().time_since_epoch()).count(); - fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test?cachecontrol=max-age=120" }, uv_default_loop(), [&](const Response &res) { + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test?cachecontrol=max-age=120" }, + uv_default_loop(), env, [&](const Response &res) { EXPECT_EQ(Response::Successful, res.status); EXPECT_EQ("Hello World!", res.data); EXPECT_GT(2, std::abs(res.expires - now - 120)) << "Expiration date isn't about 120 seconds in the future"; diff --git a/test/storage/http_load.cpp b/test/storage/http_load.cpp index fff662da29..2680daf93b 100644 --- a/test/storage/http_load.cpp +++ b/test/storage/http_load.cpp @@ -11,13 +11,17 @@ TEST_F(Storage, HTTPLoad) { DefaultFileSource fs(nullptr, uv_default_loop()); + auto &env = *static_cast<const Environment *>(nullptr); + const int concurrency = 50; const int max = 10000; int number = 1; std::function<void()> req = [&]() { const auto current = number++; - fs.request({ Resource::Unknown, std::string("http://127.0.0.1:3000/load/") + std::to_string(current) }, uv_default_loop(), [&, current](const Response &res) { + fs.request({ Resource::Unknown, + std::string("http://127.0.0.1:3000/load/") + std::to_string(current) }, + uv_default_loop(), env, [&, current](const Response &res) { EXPECT_EQ(Response::Successful, res.status); EXPECT_EQ(std::string("Request ") + std::to_string(current), res.data); EXPECT_EQ(0, res.expires); diff --git a/test/storage/http_noloop.cpp b/test/storage/http_noloop.cpp index c71fd0bc26..cd4ebeb2c4 100644 --- a/test/storage/http_noloop.cpp +++ b/test/storage/http_noloop.cpp @@ -12,6 +12,8 @@ TEST_F(Storage, HTTPNoLoop) { DefaultFileSource fs(nullptr); + auto &env = *static_cast<const Environment *>(nullptr); + const auto mainThread = uv_thread_self(); // Async handle that keeps the main loop alive until the thread finished @@ -20,7 +22,8 @@ TEST_F(Storage, HTTPNoLoop) { uv::close(as); }); - fs.request({ Resource::Unknown, "http://127.0.0.1:3000/temporary-error" }, [&](const Response &res) { + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/temporary-error" }, nullptr, env, + [&](const Response &res) { EXPECT_NE(uv_thread_self(), mainThread) << "Response was called in the same thread"; EXPECT_EQ(Response::Successful, res.status); EXPECT_EQ("Hello World!", res.data); diff --git a/test/storage/http_other_loop.cpp b/test/storage/http_other_loop.cpp index 06cc6f7476..9ad673cf79 100644 --- a/test/storage/http_other_loop.cpp +++ b/test/storage/http_other_loop.cpp @@ -12,7 +12,10 @@ TEST_F(Storage, HTTPOtherLoop) { // This file source launches a separate thread to do the processing. DefaultFileSource fs(nullptr); - fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), [&](const Response &res) { + auto &env = *static_cast<const Environment *>(nullptr); + + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), env, + [&](const Response &res) { EXPECT_EQ(Response::Successful, res.status); EXPECT_EQ("Hello World!", res.data); EXPECT_EQ(0, res.expires); diff --git a/test/storage/http_reading.cpp b/test/storage/http_reading.cpp index ad87db15ff..a6a5775825 100644 --- a/test/storage/http_reading.cpp +++ b/test/storage/http_reading.cpp @@ -12,9 +12,12 @@ TEST_F(Storage, HTTPReading) { DefaultFileSource fs(nullptr, uv_default_loop()); + auto &env = *static_cast<const Environment *>(nullptr); + const auto mainThread = uv_thread_self(); - fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), [&](const Response &res) { + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), env, + [&](const Response &res) { EXPECT_EQ(uv_thread_self(), mainThread); EXPECT_EQ(Response::Successful, res.status); EXPECT_EQ("Hello World!", res.data); @@ -25,7 +28,8 @@ TEST_F(Storage, HTTPReading) { HTTPTest.finish(); }); - fs.request({ Resource::Unknown, "http://127.0.0.1:3000/doesnotexist" }, uv_default_loop(), [&](const Response &res) { + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/doesnotexist" }, uv_default_loop(), + env, [&](const Response &res) { EXPECT_EQ(uv_thread_self(), mainThread); EXPECT_EQ(Response::Error, res.status); EXPECT_EQ("HTTP status code 404", res.message); @@ -45,7 +49,10 @@ TEST_F(Storage, HTTPNoCallback) { DefaultFileSource fs(nullptr, uv_default_loop()); - fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), nullptr); + auto &env = *static_cast<const Environment *>(nullptr); + + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, uv_default_loop(), env, + nullptr); uv_run(uv_default_loop(), UV_RUN_DEFAULT); @@ -59,7 +66,9 @@ TEST_F(Storage, HTTPNoCallbackNoLoop) { DefaultFileSource fs(nullptr, uv_default_loop()); - fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, nullptr); + auto &env = *static_cast<const Environment *>(nullptr); + + fs.request({ Resource::Unknown, "http://127.0.0.1:3000/test" }, env, nullptr); uv_run(uv_default_loop(), UV_RUN_DEFAULT); diff --git a/test/storage/server.js b/test/storage/server.js index 33d5a82985..1ee5363196 100755 --- a/test/storage/server.js +++ b/test/storage/server.js @@ -85,6 +85,13 @@ app.get('/temporary-error', function(req, res) { temporaryErrorCounter++; }); +app.get('/delayed', function(req, res) { + setTimeout(function() { + res.status(200).send('Response'); + }, 200); +}); + + app.get('/load/:number(\\d+)', function(req, res) { res.send('Request ' + req.params.number); }); diff --git a/test/test.gyp b/test/test.gyp index 1d80a4f830..7759caced8 100644 --- a/test/test.gyp +++ b/test/test.gyp @@ -56,6 +56,7 @@ 'storage/file_reading.cpp', 'storage/http_cancel.cpp', 'storage/http_coalescing.cpp', + 'storage/http_environment.cpp', 'storage/http_error.cpp', 'storage/http_header_parsing.cpp', 'storage/http_load.cpp', |