diff --git a/include/mbgl/actor/scheduler.hpp b/include/mbgl/actor/scheduler.hpp index 4ebb017cb13..000235d4aca 100644 --- a/include/mbgl/actor/scheduler.hpp +++ b/include/mbgl/actor/scheduler.hpp @@ -39,6 +39,8 @@ class Scheduler { /// Enqueues a function for execution. virtual void schedule(std::function&&) = 0; + virtual void schedule(const void*, std::function&&) = 0; + /// Makes a weak pointer to this Scheduler. virtual mapbox::base::WeakPtr makeWeakPtr() = 0; /// Enqueues a function for execution on the render thread. @@ -69,8 +71,7 @@ class Scheduler { /// Wait until there's nothing pending or in process /// Must not be called from a task provided to this scheduler. - /// @param timeout Time to wait, or zero to wait forever. - virtual std::size_t waitForEmpty(Milliseconds timeout = Milliseconds{0}) = 0; + virtual void waitForEmpty(const void* tag = nullptr) = 0; /// Set/Get the current Scheduler for this thread static Scheduler* GetCurrent(); @@ -116,4 +117,26 @@ class Scheduler { std::function handler; }; +/// @brief A TaggedScheduler pairs a scheduler with a memory address. Tasklets submitted via a TaggedScheduler +/// are bucketed with the tag address to enable queries on tasks related to that tag. This allows multiple map +/// instances to all use the same scheduler and await processing of all their tasks prior to map deletion. +class TaggedScheduler { +public: + TaggedScheduler() = delete; + TaggedScheduler(std::shared_ptr scheduler_, const void* tagAddr_) + : scheduler(std::move(scheduler_)), + tagAddr(tagAddr_) {} + + /// @brief Get the wrapped scheduler + /// @return + const std::shared_ptr& get() const noexcept { return scheduler; } + + void schedule(std::function&& fn) { scheduler->schedule(tagAddr, std::move(fn)); } + void waitForEmpty() const noexcept { scheduler->waitForEmpty(tagAddr); } + +private: + std::shared_ptr scheduler; + const void* tagAddr; +}; + } // namespace mbgl diff --git a/include/mbgl/util/run_loop.hpp b/include/mbgl/util/run_loop.hpp index 841814638ab..ee230314ede 100644 --- a/include/mbgl/util/run_loop.hpp +++ b/include/mbgl/util/run_loop.hpp @@ -79,9 +79,10 @@ class RunLoop : public Scheduler, private util::noncopyable { } void schedule(std::function&& fn) override { invoke(std::move(fn)); } + void schedule(const void*, std::function&& fn) override { schedule(std::move(fn)); } ::mapbox::base::WeakPtr makeWeakPtr() override { return weakFactory.makeWeakPtr(); } - std::size_t waitForEmpty(Milliseconds timeout) override; + void waitForEmpty(const void* tag = nullptr) override; class Impl; diff --git a/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp b/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp index 472b96d14c2..0af25b6e100 100644 --- a/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/map_renderer.cpp @@ -84,20 +84,17 @@ void MapRenderer::schedule(std::function&& scheduled) { } } -std::size_t MapRenderer::waitForEmpty(Milliseconds timeout) { +void MapRenderer::waitForEmpty([[maybe_unused]] const void* tag) { try { android::UniqueEnv _env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*_env); - static auto waitForEmpty = javaClass.GetMethod(*_env, "waitForEmpty"); + static auto waitForEmpty = javaClass.GetMethod(*_env, "waitForEmpty"); if (auto weakReference = javaPeer.get(*_env)) { - return weakReference.Call(*_env, waitForEmpty, static_cast(timeout.count())); + return weakReference.Call(*_env, waitForEmpty); } - // If the peer is already cleaned up, there's nothing to wait for - return 0; } catch (...) { Log::Error(Event::Android, "MapRenderer::waitForEmpty failed"); jni::ThrowJavaError(*android::AttachEnv(), std::current_exception()); - return 0; } } diff --git a/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp b/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp index 4ecb8f5d94b..40d09fb9f03 100644 --- a/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/map_renderer.hpp @@ -67,11 +67,12 @@ class MapRenderer : public Scheduler { // From Scheduler. Schedules by using callbacks to the // JVM to process the mailbox on the right thread. void schedule(std::function&& scheduled) override; + void schedule(const void*, std::function&& fn) override { schedule(std::move(fn)); }; + mapbox::base::WeakPtr makeWeakPtr() override { return weakFactory.makeWeakPtr(); } // Wait for the queue to be empty - // A timeout of zero results in an unbounded wait - std::size_t waitForEmpty(Milliseconds timeout) override; + void waitForEmpty(const void*) override; void requestRender(); diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java index 72c539040de..7be40232479 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRenderer.java @@ -120,11 +120,6 @@ void queueEvent(MapRendererRunnable runnable) { this.queueEvent((Runnable) runnable); } - /// Wait indefinitely for the queue to become empty - public void waitForEmpty() { - waitForEmpty(0); - } - private native void nativeInitialize(MapRenderer self, float pixelRatio, String localIdeographFontFamily); diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRendererScheduler.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRendererScheduler.java index f1e17e658f7..15184736f53 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRendererScheduler.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/MapRendererScheduler.java @@ -16,7 +16,4 @@ public interface MapRendererScheduler { @Keep void waitForEmpty(); - - @Keep - long waitForEmpty(long timeoutMillis); } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/GLSurfaceViewMapRenderer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/GLSurfaceViewMapRenderer.java index a8a3c3fb41c..b47fa38e35d 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/GLSurfaceViewMapRenderer.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/GLSurfaceViewMapRenderer.java @@ -120,7 +120,7 @@ public void queueEvent(Runnable runnable) { * {@inheritDoc} */ @Override - public long waitForEmpty(long timeoutMillis) { - return glSurfaceView.waitForEmpty(timeoutMillis); + public void waitForEmpty() { + glSurfaceView.waitForEmpty(); } } \ No newline at end of file diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/MapLibreGLSurfaceView.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/MapLibreGLSurfaceView.java index a78a8a907a4..a58ba011307 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/MapLibreGLSurfaceView.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/glsurfaceview/MapLibreGLSurfaceView.java @@ -322,8 +322,8 @@ public void queueEvent(Runnable r) { * @param timeoutMillis Timeout in milliseconds * @return Number of queue items remaining */ - public long waitForEmpty(long timeoutMillis) { - return glThread.waitForEmpty(timeoutMillis); + public void waitForEmpty() { + glThread.waitForEmpty(); } @@ -1038,31 +1038,16 @@ public void queueEvent(@NonNull Runnable r) { * @param timeoutMillis Timeout in milliseconds, zero for indefinite wait * @return Number of queue items remaining */ - public int waitForEmpty(long timeoutMillis) { - final long startTime = System.nanoTime(); + public void waitForEmpty() { synchronized (glThreadManager) { // Wait for the queue to be empty while (!this.eventQueue.isEmpty()) { - if (timeoutMillis > 0) { - final long elapsedMillis = (System.nanoTime() - startTime) / 1000 / 1000; - if (elapsedMillis < timeoutMillis) { - try { - glThreadManager.wait(timeoutMillis - elapsedMillis); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } else { - break; - } - } else { - try { - glThreadManager.wait(); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } + try { + glThreadManager.wait(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); } } - return this.eventQueue.size(); } } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java index ee20aae6089..22a9415a6e0 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewMapRenderer.java @@ -91,8 +91,8 @@ public void queueEvent(Runnable runnable) { * {@inheritDoc} */ @Override - public long waitForEmpty(long timeoutMillis) { - return renderThread.waitForEmpty(timeoutMillis); + public void waitForEmpty() { + renderThread.waitForEmpty(); } /** diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewRenderThread.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewRenderThread.java index 51598b967ee..11d8a8d918f 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewRenderThread.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/renderer/textureview/TextureViewRenderThread.java @@ -141,31 +141,16 @@ void queueEvent(@NonNull Runnable runnable) { * @return The number of items remaining in the queue */ @UiThread - int waitForEmpty(long timeoutMillis) { - final long startTime = System.nanoTime(); + void waitForEmpty() { synchronized (lock) { // Wait for the queue to be empty while (!this.eventQueue.isEmpty()) { - if (timeoutMillis > 0) { - final long elapsedMillis = (System.nanoTime() - startTime) / 1000 / 1000; - if (elapsedMillis < timeoutMillis) { - try { - lock.wait(timeoutMillis - elapsedMillis); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } else { - break; - } - } else { - try { - lock.wait(0); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } + try { + lock.wait(0); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); } } - return this.eventQueue.size(); } } diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt index 307bd4a8611..49e2bb3f824 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/maps/NativeMapViewTest.kt @@ -443,9 +443,8 @@ class NativeMapViewTest : AppCenter() { // no-op } - override fun waitForEmpty(timeoutMillis: Long): Long { + override fun waitForEmpty() { // no-op - return 0 } } } diff --git a/platform/android/src/run_loop.cpp b/platform/android/src/run_loop.cpp index a26c91ead0d..f8adf1ed0a2 100644 --- a/platform/android/src/run_loop.cpp +++ b/platform/android/src/run_loop.cpp @@ -216,8 +216,7 @@ Milliseconds RunLoop::Impl::processRunnables() { return timeout; } -std::size_t RunLoop::Impl::waitForEmpty(Milliseconds timeout) { - const auto startTime = mbgl::util::MonotonicTimer::now(); +void RunLoop::Impl::waitForEmpty() { while (true) { std::size_t remaining; { @@ -225,10 +224,8 @@ std::size_t RunLoop::Impl::waitForEmpty(Milliseconds timeout) { remaining = runnables.size(); } - const auto elapsed = mbgl::util::MonotonicTimer::now() - startTime; - const auto elapsedMillis = std::chrono::duration_cast(elapsed); - if (remaining == 0 || (Milliseconds::zero() < timeout && timeout <= elapsedMillis)) { - return remaining; + if (remaining == 0) { + return; } runLoop->runOnce(); @@ -257,8 +254,8 @@ void RunLoop::wake() { impl->wake(); } -std::size_t RunLoop::waitForEmpty(std::chrono::milliseconds timeout) { - return impl->waitForEmpty(timeout); +void RunLoop::waitForEmpty([[maybe_unused]] const void* tag) { + impl->waitForEmpty(); } void RunLoop::run() { diff --git a/platform/android/src/run_loop_impl.hpp b/platform/android/src/run_loop_impl.hpp index ff2420cd02e..4f0eff4103a 100644 --- a/platform/android/src/run_loop_impl.hpp +++ b/platform/android/src/run_loop_impl.hpp @@ -42,7 +42,7 @@ class RunLoop::Impl { Milliseconds processRunnables(); - std::size_t waitForEmpty(Milliseconds timeout); + void waitForEmpty(); ALooper* loop = nullptr; RunLoop* runLoop = nullptr; diff --git a/platform/darwin/src/run_loop.cpp b/platform/darwin/src/run_loop.cpp index bf5bf71d0bb..8243c48e680 100644 --- a/platform/darwin/src/run_loop.cpp +++ b/platform/darwin/src/run_loop.cpp @@ -47,8 +47,7 @@ void RunLoop::stop() { invoke([&] { CFRunLoopStop(CFRunLoopGetCurrent()); }); } -std::size_t RunLoop::waitForEmpty(Milliseconds timeout) { - const auto startTime = mbgl::util::MonotonicTimer::now(); +void RunLoop::waitForEmpty([[maybe_unused]] const void* tag) { while (true) { std::size_t remaining; { @@ -56,10 +55,8 @@ std::size_t RunLoop::waitForEmpty(Milliseconds timeout) { remaining = defaultQueue.size() + highPriorityQueue.size(); } - const auto elapsed = mbgl::util::MonotonicTimer::now() - startTime; - const auto elapsedMillis = std::chrono::duration_cast(elapsed); - if (remaining == 0 || (Milliseconds::zero() < timeout && timeout <= elapsedMillis)) { - return remaining; + if (remaining == 0) { + return; } runOnce(); diff --git a/platform/default/src/mbgl/util/run_loop.cpp b/platform/default/src/mbgl/util/run_loop.cpp index 39d31d87b74..f69b830a9dc 100644 --- a/platform/default/src/mbgl/util/run_loop.cpp +++ b/platform/default/src/mbgl/util/run_loop.cpp @@ -149,8 +149,7 @@ void RunLoop::stop() { invoke([&] { uv_unref(impl->holderHandle()); }); } -std::size_t RunLoop::waitForEmpty(Milliseconds timeout) { - const auto startTime = mbgl::util::MonotonicTimer::now(); +void RunLoop::waitForEmpty([[maybe_unused]] const void* tag) { while (true) { std::size_t remaining; { @@ -158,10 +157,8 @@ std::size_t RunLoop::waitForEmpty(Milliseconds timeout) { remaining = defaultQueue.size() + highPriorityQueue.size(); } - const auto elapsed = mbgl::util::MonotonicTimer::now() - startTime; - const auto elapsedMillis = std::chrono::duration_cast(elapsed); - if (remaining == 0 || (Milliseconds::zero() < timeout && timeout <= elapsedMillis)) { - return remaining; + if (remaining == 0) { + return; } runOnce(); diff --git a/platform/qt/src/mbgl/run_loop.cpp b/platform/qt/src/mbgl/run_loop.cpp index c978bd81ea5..3f26c806e6e 100644 --- a/platform/qt/src/mbgl/run_loop.cpp +++ b/platform/qt/src/mbgl/run_loop.cpp @@ -91,8 +91,7 @@ void RunLoop::runOnce() { } } -std::size_t RunLoop::waitForEmpty(std::chrono::milliseconds timeout) { - const auto startTime = mbgl::util::MonotonicTimer::now(); +void RunLoop::waitForEmpty([[maybe_unused]] const void* tag) { while (true) { std::size_t remaining; { @@ -100,10 +99,8 @@ std::size_t RunLoop::waitForEmpty(std::chrono::milliseconds timeout) { remaining = defaultQueue.size() + highPriorityQueue.size(); } - const auto elapsed = mbgl::util::MonotonicTimer::now() - startTime; - const auto elapsedMillis = std::chrono::duration_cast(elapsed); - if (remaining == 0 || timeout <= elapsedMillis) { - return remaining; + if (remaining == 0) { + return; } runOnce(); diff --git a/platform/qt/src/utils/scheduler.cpp b/platform/qt/src/utils/scheduler.cpp index 45e83926151..4d8157c0985 100644 --- a/platform/qt/src/utils/scheduler.cpp +++ b/platform/qt/src/utils/scheduler.cpp @@ -42,23 +42,19 @@ void Scheduler::processEvents() { cvEmpty.notify_all(); } -std::size_t Scheduler::waitForEmpty(std::chrono::milliseconds timeout) { +void Scheduler::waitForEmpty([[maybe_unused]] const void* tag) { MBGL_VERIFY_THREAD(tid); - const auto startTime = mbgl::util::MonotonicTimer::now(); std::unique_lock lock(m_taskQueueMutex); const auto isDone = [&] { return m_taskQueue.empty() && pendingItems == 0; }; + while (!isDone()) { - const auto elapsed = mbgl::util::MonotonicTimer::now() - startTime; - if (timeout <= elapsed || !cvEmpty.wait_for(lock, timeout - elapsed, isDone)) { - assert(isDone()); - break; - } + cvEmpty.wait(lock); } - return m_taskQueue.size() + pendingItems; + assert(m_taskQueue.size() + pendingItems == 0); } } // namespace QMapLibre diff --git a/platform/qt/src/utils/scheduler.hpp b/platform/qt/src/utils/scheduler.hpp index 56f9fd954d7..e23f0b589a1 100644 --- a/platform/qt/src/utils/scheduler.hpp +++ b/platform/qt/src/utils/scheduler.hpp @@ -22,7 +22,7 @@ class Scheduler : public QObject, public mbgl::Scheduler { // mbgl::Scheduler implementation. void schedule(std::function&& function) final; - std::size_t waitForEmpty(std::chrono::milliseconds timeout) override; + void waitForEmpty(const void* tag = nullptr) override; mapbox::base::WeakPtr makeWeakPtr() override { return weakFactory.makeWeakPtr(); } diff --git a/src/mbgl/annotation/render_annotation_source.cpp b/src/mbgl/annotation/render_annotation_source.cpp index ec60eecbe68..10421d62dea 100644 --- a/src/mbgl/annotation/render_annotation_source.cpp +++ b/src/mbgl/annotation/render_annotation_source.cpp @@ -10,8 +10,8 @@ namespace mbgl { using namespace style; RenderAnnotationSource::RenderAnnotationSource(Immutable impl_, - std::shared_ptr threadPool_) - : RenderTileSource(std::move(impl_), std::move(threadPool_)) { + const TaggedScheduler& threadPool_) + : RenderTileSource(std::move(impl_), threadPool_) { assert(LayerManager::annotationsEnabled); tilePyramid.setObserver(this); } diff --git a/src/mbgl/annotation/render_annotation_source.hpp b/src/mbgl/annotation/render_annotation_source.hpp index d195ea9cb27..f8065b9221a 100644 --- a/src/mbgl/annotation/render_annotation_source.hpp +++ b/src/mbgl/annotation/render_annotation_source.hpp @@ -7,7 +7,7 @@ namespace mbgl { class RenderAnnotationSource final : public RenderTileSource { public: - explicit RenderAnnotationSource(Immutable, std::shared_ptr); + explicit RenderAnnotationSource(Immutable, const TaggedScheduler&); void update(Immutable, const std::vector>&, diff --git a/src/mbgl/renderer/render_orchestrator.cpp b/src/mbgl/renderer/render_orchestrator.cpp index feb92035917..d4ef2906e63 100644 --- a/src/mbgl/renderer/render_orchestrator.cpp +++ b/src/mbgl/renderer/render_orchestrator.cpp @@ -122,7 +122,7 @@ RenderOrchestrator::RenderOrchestrator(bool backgroundLayerAsColor_, const std:: layerImpls(makeMutable>>()), renderLight(makeMutable()), backgroundLayerAsColor(backgroundLayerAsColor_), - threadPool(Scheduler::GetBackground()) { + threadPool(Scheduler::GetBackground(), static_cast(this)) { glyphManager->setObserver(this); imageManager->setObserver(this); } @@ -142,9 +142,7 @@ RenderOrchestrator::~RenderOrchestrator() { // Wait for any deferred cleanup tasks to complete before releasing and potentially // destroying the scheduler. Those cleanup tasks must not hold the final reference // to the scheduler because it cannot be destroyed from one of its own pool threads. - constexpr auto deferredCleanupTimeout = Milliseconds{10000}; - [[maybe_unused]] const auto remaining = threadPool->waitForEmpty(deferredCleanupTimeout); - assert(remaining == 0); + threadPool.waitForEmpty(); } void RenderOrchestrator::setObserver(RendererObserver* observer_) { diff --git a/src/mbgl/renderer/render_orchestrator.hpp b/src/mbgl/renderer/render_orchestrator.hpp index 69ff4667e6b..aff3ee722fe 100644 --- a/src/mbgl/renderer/render_orchestrator.hpp +++ b/src/mbgl/renderer/render_orchestrator.hpp @@ -210,7 +210,7 @@ class RenderOrchestrator final : public GlyphManagerObserver, public ImageManage RenderLayerReferences orderedLayers; RenderLayerReferences layersNeedPlacement; - std::shared_ptr threadPool; + TaggedScheduler threadPool; #if MLN_DRAWABLE_RENDERER std::vector> pendingChanges; diff --git a/src/mbgl/renderer/render_source.cpp b/src/mbgl/renderer/render_source.cpp index 2eb5ce900e2..8c0481e38b4 100644 --- a/src/mbgl/renderer/render_source.cpp +++ b/src/mbgl/renderer/render_source.cpp @@ -21,7 +21,7 @@ namespace mbgl { using namespace style; std::unique_ptr RenderSource::create(const Immutable& impl, - std::shared_ptr threadPool_) { + const TaggedScheduler& threadPool_) { switch (impl->type) { case SourceType::Vector: return std::make_unique(staticImmutableCast(impl), diff --git a/src/mbgl/renderer/render_source.hpp b/src/mbgl/renderer/render_source.hpp index 4b46e07756b..d113f22745c 100644 --- a/src/mbgl/renderer/render_source.hpp +++ b/src/mbgl/renderer/render_source.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -48,7 +49,7 @@ using RenderTiles = std::shared_ptr create(const Immutable&, std::shared_ptr); + static std::unique_ptr create(const Immutable&, const TaggedScheduler&); ~RenderSource() override; bool isEnabled() const; diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp index 7f054bfa2c2..13377ef42fd 100644 --- a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp @@ -8,8 +8,8 @@ namespace mbgl { using namespace style; RenderCustomGeometrySource::RenderCustomGeometrySource(Immutable impl_, - std::shared_ptr threadPool_) - : RenderTileSource(std::move(impl_), std::move(threadPool_)) { + const TaggedScheduler& threadPool_) + : RenderTileSource(std::move(impl_), threadPool_) { tilePyramid.setObserver(this); } diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.hpp b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp index 4c77c6a1973..9ed8dc5f6b7 100644 --- a/src/mbgl/renderer/sources/render_custom_geometry_source.hpp +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp @@ -7,7 +7,7 @@ namespace mbgl { class RenderCustomGeometrySource final : public RenderTileSource { public: - explicit RenderCustomGeometrySource(Immutable, std::shared_ptr); + explicit RenderCustomGeometrySource(Immutable, const TaggedScheduler&); void update(Immutable, const std::vector>&, diff --git a/src/mbgl/renderer/sources/render_geojson_source.cpp b/src/mbgl/renderer/sources/render_geojson_source.cpp index 633df708879..1e91e5f8c7b 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.cpp +++ b/src/mbgl/renderer/sources/render_geojson_source.cpp @@ -66,8 +66,8 @@ MAPBOX_ETERNAL_CONSTEXPR const auto extensionGetters = } // namespace RenderGeoJSONSource::RenderGeoJSONSource(Immutable impl_, - std::shared_ptr threadPool_) - : RenderTileSource(std::move(impl_), std::move(threadPool_)) {} + const TaggedScheduler& threadPool_) + : RenderTileSource(std::move(impl_), threadPool_) {} RenderGeoJSONSource::~RenderGeoJSONSource() = default; diff --git a/src/mbgl/renderer/sources/render_geojson_source.hpp b/src/mbgl/renderer/sources/render_geojson_source.hpp index f0c41c2ab50..d7c9c4b8cda 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.hpp +++ b/src/mbgl/renderer/sources/render_geojson_source.hpp @@ -11,7 +11,7 @@ class GeoJSONData; class RenderGeoJSONSource final : public RenderTileSource { public: - explicit RenderGeoJSONSource(Immutable, std::shared_ptr); + explicit RenderGeoJSONSource(Immutable, const TaggedScheduler&); ~RenderGeoJSONSource() override; void update(Immutable, diff --git a/src/mbgl/renderer/sources/render_raster_dem_source.cpp b/src/mbgl/renderer/sources/render_raster_dem_source.cpp index 6f41426141e..8cb322ea20f 100644 --- a/src/mbgl/renderer/sources/render_raster_dem_source.cpp +++ b/src/mbgl/renderer/sources/render_raster_dem_source.cpp @@ -11,8 +11,8 @@ namespace mbgl { using namespace style; RenderRasterDEMSource::RenderRasterDEMSource(Immutable impl_, - std::shared_ptr threadPool_) - : RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {} + const TaggedScheduler& threadPool_) + : RenderTileSetSource(std::move(impl_), threadPool_) {} const style::RasterSource::Impl& RenderRasterDEMSource::impl() const { return static_cast(*baseImpl); diff --git a/src/mbgl/renderer/sources/render_raster_dem_source.hpp b/src/mbgl/renderer/sources/render_raster_dem_source.hpp index 1599b76ef5b..8531c6be86c 100644 --- a/src/mbgl/renderer/sources/render_raster_dem_source.hpp +++ b/src/mbgl/renderer/sources/render_raster_dem_source.hpp @@ -7,7 +7,7 @@ namespace mbgl { class RenderRasterDEMSource final : public RenderTileSetSource { public: - explicit RenderRasterDEMSource(Immutable, std::shared_ptr); + explicit RenderRasterDEMSource(Immutable, const TaggedScheduler&); std::unordered_map> queryRenderedFeatures( const ScreenLineString& geometry, diff --git a/src/mbgl/renderer/sources/render_raster_source.cpp b/src/mbgl/renderer/sources/render_raster_source.cpp index db99831e02c..30bf341512a 100644 --- a/src/mbgl/renderer/sources/render_raster_source.cpp +++ b/src/mbgl/renderer/sources/render_raster_source.cpp @@ -8,9 +8,8 @@ namespace mbgl { using namespace style; -RenderRasterSource::RenderRasterSource(Immutable impl_, - std::shared_ptr threadPool_) - : RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {} +RenderRasterSource::RenderRasterSource(Immutable impl_, const TaggedScheduler& threadPool_) + : RenderTileSetSource(std::move(impl_), threadPool_) {} inline const style::RasterSource::Impl& RenderRasterSource::impl() const { return static_cast(*baseImpl); diff --git a/src/mbgl/renderer/sources/render_raster_source.hpp b/src/mbgl/renderer/sources/render_raster_source.hpp index c827c8a7e0f..d650e974eee 100644 --- a/src/mbgl/renderer/sources/render_raster_source.hpp +++ b/src/mbgl/renderer/sources/render_raster_source.hpp @@ -7,7 +7,7 @@ namespace mbgl { class RenderRasterSource final : public RenderTileSetSource { public: - explicit RenderRasterSource(Immutable, std::shared_ptr); + explicit RenderRasterSource(Immutable, const TaggedScheduler&); private: void prepare(const SourcePrepareParameters&) final; diff --git a/src/mbgl/renderer/sources/render_tile_source.cpp b/src/mbgl/renderer/sources/render_tile_source.cpp index ae44ff3f777..efc9787e231 100644 --- a/src/mbgl/renderer/sources/render_tile_source.cpp +++ b/src/mbgl/renderer/sources/render_tile_source.cpp @@ -383,9 +383,9 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr } #endif -RenderTileSource::RenderTileSource(Immutable impl_, std::shared_ptr threadPool_) +RenderTileSource::RenderTileSource(Immutable impl_, const TaggedScheduler& threadPool_) : RenderSource(std::move(impl_)), - tilePyramid(std::move(threadPool_)), + tilePyramid(threadPool_), renderTiles(makeMutable>()) { tilePyramid.setObserver(this); } @@ -504,8 +504,8 @@ void RenderTileSource::dumpDebugLogs() const { // RenderTileSetSource implementation -RenderTileSetSource::RenderTileSetSource(Immutable impl_, std::shared_ptr threadPool_) - : RenderTileSource(std::move(impl_), std::move(threadPool_)) {} +RenderTileSetSource::RenderTileSetSource(Immutable impl_, const TaggedScheduler& threadPool_) + : RenderTileSource(std::move(impl_), threadPool_) {} RenderTileSetSource::~RenderTileSetSource() = default; diff --git a/src/mbgl/renderer/sources/render_tile_source.hpp b/src/mbgl/renderer/sources/render_tile_source.hpp index 160ed9541da..a755bdd41bc 100644 --- a/src/mbgl/renderer/sources/render_tile_source.hpp +++ b/src/mbgl/renderer/sources/render_tile_source.hpp @@ -51,7 +51,7 @@ class RenderTileSource : public RenderSource { void dumpDebugLogs() const override; protected: - RenderTileSource(Immutable, std::shared_ptr); + RenderTileSource(Immutable, const TaggedScheduler&); TilePyramid tilePyramid; Immutable> renderTiles; mutable RenderTiles filteredRenderTiles; @@ -67,7 +67,7 @@ class RenderTileSource : public RenderSource { */ class RenderTileSetSource : public RenderTileSource { protected: - RenderTileSetSource(Immutable, std::shared_ptr); + RenderTileSetSource(Immutable, const TaggedScheduler&); ~RenderTileSetSource() override; virtual void updateInternal(const Tileset&, diff --git a/src/mbgl/renderer/sources/render_vector_source.cpp b/src/mbgl/renderer/sources/render_vector_source.cpp index 212338db670..c53447eb839 100644 --- a/src/mbgl/renderer/sources/render_vector_source.cpp +++ b/src/mbgl/renderer/sources/render_vector_source.cpp @@ -8,9 +8,8 @@ namespace mbgl { using namespace style; -RenderVectorSource::RenderVectorSource(Immutable impl_, - std::shared_ptr threadPool_) - : RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {} +RenderVectorSource::RenderVectorSource(Immutable impl_, const TaggedScheduler& threadPool_) + : RenderTileSetSource(std::move(impl_), threadPool_) {} const std::optional& RenderVectorSource::getTileset() const { return static_cast(*baseImpl).tileset; diff --git a/src/mbgl/renderer/sources/render_vector_source.hpp b/src/mbgl/renderer/sources/render_vector_source.hpp index 326b392b698..fff332c4529 100644 --- a/src/mbgl/renderer/sources/render_vector_source.hpp +++ b/src/mbgl/renderer/sources/render_vector_source.hpp @@ -8,7 +8,7 @@ namespace mbgl { class RenderVectorSource final : public RenderTileSetSource { public: - explicit RenderVectorSource(Immutable, std::shared_ptr); + explicit RenderVectorSource(Immutable, const TaggedScheduler&); private: void updateInternal(const Tileset&, diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp index 55466aa3ada..82753ccc473 100644 --- a/src/mbgl/renderer/tile_pyramid.cpp +++ b/src/mbgl/renderer/tile_pyramid.cpp @@ -24,8 +24,8 @@ using namespace style; static TileObserver nullObserver; -TilePyramid::TilePyramid(std::shared_ptr threadPool_) - : cache(std::move(threadPool_)), +TilePyramid::TilePyramid(const TaggedScheduler& threadPool_) + : cache(threadPool_), observer(&nullObserver) {} TilePyramid::~TilePyramid() = default; diff --git a/src/mbgl/renderer/tile_pyramid.hpp b/src/mbgl/renderer/tile_pyramid.hpp index bfae55f0952..5068093ffcb 100644 --- a/src/mbgl/renderer/tile_pyramid.hpp +++ b/src/mbgl/renderer/tile_pyramid.hpp @@ -29,7 +29,7 @@ class SourcePrepareParameters; class TilePyramid { public: - TilePyramid(std::shared_ptr threadPool_); + TilePyramid(const TaggedScheduler& threadPool_); ~TilePyramid(); bool isLoaded() const; diff --git a/src/mbgl/tile/tile_cache.cpp b/src/mbgl/tile/tile_cache.cpp index b4d65990fcf..4dfa603a24a 100644 --- a/src/mbgl/tile/tile_cache.cpp +++ b/src/mbgl/tile/tile_cache.cpp @@ -49,7 +49,7 @@ void TileCache::deferredRelease(std::unique_ptr&& tile) { std::function func{[tile_{CaptureWrapper{std::move(tile)}}]() { }}; - threadPool->schedule(std::move(func)); + threadPool.schedule(std::move(func)); } void TileCache::add(const OverscaledTileID& key, std::unique_ptr&& tile) { diff --git a/src/mbgl/tile/tile_cache.hpp b/src/mbgl/tile/tile_cache.hpp index fe7f41f7b9f..02ad51258b1 100644 --- a/src/mbgl/tile/tile_cache.hpp +++ b/src/mbgl/tile/tile_cache.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -9,12 +10,10 @@ namespace mbgl { -class Scheduler; - class TileCache { public: - TileCache(std::shared_ptr threadPool_, size_t size_ = 0) - : threadPool(std::move(threadPool_)), + TileCache(const TaggedScheduler& threadPool_, size_t size_ = 0) + : threadPool(threadPool_), size(size_) {} /// Change the maximum size of the cache. @@ -38,7 +37,7 @@ class TileCache { private: std::map> tiles; std::list orderedKeys; - std::shared_ptr threadPool; + TaggedScheduler threadPool; size_t size; }; diff --git a/src/mbgl/util/thread_pool.cpp b/src/mbgl/util/thread_pool.cpp index d02963bf8f9..d7835e1dda8 100644 --- a/src/mbgl/util/thread_pool.cpp +++ b/src/mbgl/util/thread_pool.cpp @@ -15,7 +15,7 @@ void ThreadedSchedulerBase::terminate() { runRenderJobs(); { - std::lock_guard lock(mutex); + std::lock_guard lock(workerMutex); terminated = true; } @@ -36,49 +36,67 @@ std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) { owningThreadPool.set(this); + bool didWork = true; while (true) { - std::unique_lock lock(mutex); - if (queue.empty() && !pendingItems) { - cvEmpty.notify_all(); + std::unique_lock conditionLock(workerMutex); + if (!terminated && !didWork) { + cvAvailable.wait(conditionLock); } - - cvAvailable.wait(lock, [this] { return !queue.empty() || terminated; }); - if (terminated) { platform::detachThread(); - return; + break; } - auto function = std::move(queue.front()); - queue.pop(); + // Let other threads run + conditionLock.unlock(); - if (function) { - pendingItems++; + didWork = false; + std::vector> pending; + { + // 1. Gather buckets for us to visit this iteration + std::lock_guard lock(taggedQueueLock); + for (const auto& [tag, queue] : taggedQueue) { + pending.push_back(queue); + } } - lock.unlock(); + // 2. Visit a task from each + for (auto& q : pending) { + std::function tasklet; + { + std::lock_guard lock(q->lock); + if (q->queue.size()) { + tasklet = std::move(q->queue.front()); + q->queue.pop(); + } + if (!tasklet) continue; + } + + q->runningCount++; - if (function) { try { - function(); - - // destroy the function and release its captures before unblocking `waitForEmpty` - function = {}; - if (!--pendingItems) { - std::unique_lock inner_lock(mutex); - if (queue.empty()) { - cvEmpty.notify_all(); + // Indicate some processing was done this iteration. We'll try and do more work + // on the following loop until we run out of work, at which point we wait. + didWork = true; + + tasklet(); + tasklet = {}; // destroy the function and release its captures before unblocking `waitForEmpty` + + if (!--q->runningCount) { + std::lock_guard lock(q->lock); + if (q->queue.empty()) { + q->cv.notify_all(); } } } catch (...) { - std::unique_lock inner_lock(mutex); + std::lock_guard lock(q->lock); if (handler) { handler(std::current_exception()); } - function = {}; - if (!--pendingItems && queue.empty()) { - cvEmpty.notify_all(); + tasklet = {}; + if (!--q->runningCount && q->queue.empty()) { + q->cv.notify_all(); } if (handler) { @@ -92,47 +110,62 @@ std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) { } void ThreadedSchedulerBase::schedule(std::function&& fn) { + schedule(static_cast(this), std::move(fn)); +} + +void ThreadedSchedulerBase::schedule(const void* tag, std::function&& fn) { assert(fn); - if (fn) { - { - // We need to block if adding adding a new task from a thread not controlled by this - // pool. Tasks are added by other tasks, so we must not block a thread we do control - // or `waitForEmpty` will deadlock. - std::unique_lock addLock(addMutex, std::defer_lock); - if (!thisThreadIsOwned()) { - addLock.lock(); - } - std::lock_guard lock(mutex); - queue.push(std::move(fn)); + if (!fn) return; + + std::shared_ptr q; + { + std::lock_guard lock(taggedQueueLock); + auto it = taggedQueue.find(tag); + if (it == taggedQueue.end()) { + q = std::make_shared(); + taggedQueue.insert({tag, q}); + } else { + q = it->second; } - cvAvailable.notify_one(); } + + { + std::lock_guard lock(q->lock); + q->queue.push(std::move(fn)); + } + + cvAvailable.notify_one(); } -std::size_t ThreadedSchedulerBase::waitForEmpty(Milliseconds timeout) { +void ThreadedSchedulerBase::waitForEmpty(const void* tag) { // Must not be called from a thread in our pool, or we would deadlock assert(!thisThreadIsOwned()); if (!thisThreadIsOwned()) { - const auto startTime = util::MonotonicTimer::now(); - const auto isDone = [&] { - return queue.empty() && pendingItems == 0; - }; - // Block any other threads from adding new items - std::scoped_lock addLock(addMutex); - std::unique_lock lock(mutex); - while (!isDone()) { - if (timeout > Milliseconds::zero()) { - const auto elapsed = util::MonotonicTimer::now() - startTime; - if (timeout <= elapsed || !cvEmpty.wait_for(lock, timeout - elapsed, isDone)) { - break; - } - } else { - cvEmpty.wait(lock, isDone); + if (!tag) { + tag = static_cast(this); + } + + std::shared_ptr q; + { + std::lock_guard lock(taggedQueueLock); + auto it = taggedQueue.find(tag); + if (it == taggedQueue.end()) { + return; } + q = it->second; + } + + std::unique_lock queueLock(q->lock); + while (q->queue.size() + q->runningCount) { + q->cv.wait(queueLock); + } + + // After waiting for the queue to empty, go ahead and erase it from the map. + { + std::lock_guard lock(taggedQueueLock); + taggedQueue.erase(tag); } - return queue.size() + pendingItems; } - return 0; } } // namespace mbgl diff --git a/src/mbgl/util/thread_pool.hpp b/src/mbgl/util/thread_pool.hpp index ee0735082c9..f24438aceff 100644 --- a/src/mbgl/util/thread_pool.hpp +++ b/src/mbgl/util/thread_pool.hpp @@ -3,9 +3,11 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -15,7 +17,15 @@ namespace mbgl { class ThreadedSchedulerBase : public Scheduler { public: - void schedule(std::function&&) override; + /// @brief Schedule a generic task not assigned to any particular owner. + /// The scheduler itself will own the task. + /// @param fn Task to run + void schedule(std::function&& fn) override; + + /// @brief Schedule a task assigned to the given owner `tag`. + /// @param tag Address of any object used to identify ownership of `fn` + /// @param fn Task to run + void schedule(const void* tag, std::function&& fn) override; protected: ThreadedSchedulerBase() = default; @@ -24,28 +34,30 @@ class ThreadedSchedulerBase : public Scheduler { void terminate(); std::thread makeSchedulerThread(size_t index); - /// Wait until there's nothing pending or in process + /// @brief Wait until there's nothing pending or in process /// Must not be called from a task provided to this scheduler. - /// @param timeout Time to wait, or zero to wait forever. - std::size_t waitForEmpty(Milliseconds timeout) override; + /// @param tag Address of the owner identifying the collection of tasks to + // wait for. Waiting on nullptr waits on tasks owned by the scheduler. + void waitForEmpty(const void* tag = nullptr) override; /// Returns true if called from a thread managed by the scheduler bool thisThreadIsOwned() const { return owningThreadPool.get() == this; } - std::queue> queue; - // protects `queue` - std::mutex mutex; - // Used to block addition of new items while waiting - std::mutex addMutex; // Signal when an item is added to the queue std::condition_variable cvAvailable; - // Signal when the queue becomes empty - std::condition_variable cvEmpty; - // Count of functions removed from the queue but still executing - std::atomic pendingItems{0}; - // Points to the owning pool in owned threads + std::mutex workerMutex; + std::mutex taggedQueueLock; util::ThreadLocal owningThreadPool; bool terminated{false}; + + // Task queues bucketed by tag address + struct Queue { + std::atomic runningCount; /* running tasks */ + std::condition_variable cv; /* queue empty condition */ + std::mutex lock; /* lock */ + std::queue> queue; /* pending task queue */ + }; + mbgl::unordered_map> taggedQueue; }; /** diff --git a/test/actor/actor.test.cpp b/test/actor/actor.test.cpp index 68193007996..bb5fb8cef55 100644 --- a/test/actor/actor.test.cpp +++ b/test/actor/actor.test.cpp @@ -91,10 +91,7 @@ TEST(Actor, DestructionBlocksOnSend) { ~TestScheduler() override { EXPECT_TRUE(waited.load()); } - std::size_t waitForEmpty(Milliseconds) override { - assert(false); - return 0; - } + void waitForEmpty(const void*) override { assert(false); } void schedule(std::function&&) final { promise.set_value(); @@ -102,6 +99,9 @@ TEST(Actor, DestructionBlocksOnSend) { std::this_thread::sleep_for(1ms); waited = true; } + + void schedule(const void*, std::function&& fn) final { schedule(std::move(fn)); } + mapbox::base::WeakPtr makeWeakPtr() override { return weakFactory.makeWeakPtr(); } }; diff --git a/test/include/mbgl/test/vector_tile_test.hpp b/test/include/mbgl/test/vector_tile_test.hpp index 63b45aba639..7911573e506 100644 --- a/test/include/mbgl/test/vector_tile_test.hpp +++ b/test/include/mbgl/test/vector_tile_test.hpp @@ -27,7 +27,7 @@ class VectorTileTest { Tileset tileset{{"https://example.com"}, {0, 22}, "none"}; - const std::shared_ptr threadPool = Scheduler::GetBackground(); + TaggedScheduler threadPool; TileParameters tileParameters{1.0, MapDebugOptions(), @@ -39,10 +39,13 @@ class VectorTileTest { glyphManager, 0}; + VectorTileTest() + : threadPool(Scheduler::GetBackground(), this) {} + ~VectorTileTest() { // Ensure that deferred releases are complete before cleaning up - EXPECT_EQ(0, loop.waitForEmpty(Milliseconds::zero())); - EXPECT_EQ(0, threadPool->waitForEmpty()); + loop.waitForEmpty(); + threadPool.waitForEmpty(); } }; diff --git a/test/style/source.test.cpp b/test/style/source.test.cpp index 8be5ed6747a..66632d4ce64 100644 --- a/test/style/source.test.cpp +++ b/test/style/source.test.cpp @@ -64,7 +64,7 @@ class SourceTest { AnnotationManager annotationManager{style}; std::shared_ptr imageManager = std::make_shared(); std::shared_ptr glyphManager = std::make_shared(); - std::shared_ptr threadPool = Scheduler::GetBackground(); + TaggedScheduler threadPool; TileParameters tileParameters(MapMode mapMode = MapMode::Continuous) { return {1.0, @@ -78,7 +78,8 @@ class SourceTest { 0}; }; - SourceTest() { + SourceTest() + : threadPool(Scheduler::GetBackground(), this) { // Squelch logging. Log::setObserver(std::make_unique()); @@ -88,7 +89,7 @@ class SourceTest { transformState = transform.getState(); } - ~SourceTest() { threadPool->waitForEmpty(); } + ~SourceTest() { threadPool.waitForEmpty(); } void run() { loop.run(); } @@ -783,8 +784,8 @@ class FakeTileSource : public RenderTileSetSource { MOCK_METHOD1(tileSetNecessity, void(TileNecessity)); MOCK_METHOD1(tileSetMinimumUpdateInterval, void(Duration)); - explicit FakeTileSource(Immutable impl_, std::shared_ptr threadPool_) - : RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {} + explicit FakeTileSource(Immutable impl_, const TaggedScheduler& threadPool_) + : RenderTileSetSource(std::move(impl_), threadPool_) {} void updateInternal(const Tileset& tileset, const std::vector>& layers, const bool needsRendering, @@ -887,8 +888,8 @@ TEST(Source, RenderTileSetSourceUpdate) { class FakeRenderTileSetSource : public RenderTileSetSource { public: - explicit FakeRenderTileSetSource(Immutable impl_, std::shared_ptr threadPool_) - : RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {} + explicit FakeRenderTileSetSource(Immutable impl_, TaggedScheduler threadPool_) + : RenderTileSetSource(std::move(impl_), threadPool_) {} MOCK_METHOD0(mockedUpdateInternal, void()); diff --git a/test/tile/tile_cache.test.cpp b/test/tile/tile_cache.test.cpp index 51c0c92ef8f..825ce087ae0 100644 --- a/test/tile/tile_cache.test.cpp +++ b/test/tile/tile_cache.test.cpp @@ -45,7 +45,8 @@ class VectorTileMock : public VectorTile { TEST(TileCache, Smoke) { VectorTileTest test; - TileCache cache(Scheduler::GetBackground(), 1); + TaggedScheduler scheduler(Scheduler::GetBackground(), &test); + TileCache cache(scheduler, 1); const OverscaledTileID id(0, 0, 0); auto tile = std::make_unique(id, "source", test.tileParameters, test.tileset); diff --git a/test/util/thread.test.cpp b/test/util/thread.test.cpp index 01bfce2304b..c647dfbf1bf 100644 --- a/test/util/thread.test.cpp +++ b/test/util/thread.test.cpp @@ -343,7 +343,7 @@ TEST(Thread, PoolWait) { pool->schedule([&] { std::this_thread::sleep_for(Milliseconds(100)); }); } - EXPECT_EQ(0, pool->waitForEmpty()); + pool->waitForEmpty(); } TEST(Thread, PoolWaitRecursiveAdd) { @@ -358,7 +358,7 @@ TEST(Thread, PoolWaitRecursiveAdd) { std::this_thread::sleep_for(Milliseconds(10)); }); - EXPECT_EQ(0, pool->waitForEmpty()); + pool->waitForEmpty(); } TEST(Thread, PoolWaitAdd) { @@ -385,25 +385,10 @@ TEST(Thread, PoolWaitAdd) { // more items would be added by the sequential task if not blocked pool->schedule([&] { std::this_thread::sleep_for(Milliseconds(100)); }); - EXPECT_EQ(0, pool->waitForEmpty()); + pool->waitForEmpty(); addActive = false; - EXPECT_EQ(0, pool->waitForEmpty()); -} - -TEST(Thread, PoolWaitTimeout) { - auto pool = Scheduler::GetBackground(); - - std::mutex mutex; - { - std::lock_guard outerLock(mutex); - pool->schedule([&] { std::lock_guard innerLock(mutex); }); - - // should always time out - EXPECT_EQ(1, pool->waitForEmpty(Milliseconds(100))); - } - - EXPECT_EQ(0, pool->waitForEmpty()); + pool->waitForEmpty(); } TEST(Thread, PoolWaitException) { @@ -425,7 +410,7 @@ TEST(Thread, PoolWaitException) { } // Exceptions shouldn't cause deadlocks by, e.g., abandoning locks. - EXPECT_EQ(0, pool->waitForEmpty()); + pool->waitForEmpty(); EXPECT_EQ(threadCount, caught); } @@ -434,8 +419,8 @@ TEST(Thread, WrongThread) { auto pool = Scheduler::GetBackground(); // Asserts in debug builds, silently ignored in release. - pool->schedule([&] { EXPECT_EQ(0, pool->waitForEmpty()); }); + pool->schedule([&] { pool->waitForEmpty(); }); - EXPECT_EQ(0, pool->waitForEmpty()); + pool->waitForEmpty(); } #endif