Add TaggedScheduler, couple lifetime of tasks and orchestrator (#2398)

This commit is contained in:
mwilsnd
2024-06-28 17:20:03 -04:00
committed by GitHub
parent b2fe5021b8
commit c4c75452b6
99 changed files with 759 additions and 489 deletions

View File

@@ -14,6 +14,8 @@
namespace mbgl {
class TaggedScheduler;
/**
An `Actor<O>` is an owning reference to an asynchronous object of type `O`:
an "actor". Communication with an actor happens via message passing: you send
@@ -61,6 +63,11 @@ public:
Actor(Scheduler& scheduler, Args&&... args)
: target(scheduler, parent, std::forward<Args>(args)...) {}
template <class... Args>
Actor(const TaggedScheduler& scheduler, Args&&... args)
: retainer(scheduler.get()),
target(scheduler, parent, std::forward<Args>(args)...) {}
template <class... Args>
Actor(std::shared_ptr<Scheduler> scheduler, Args&&... args)
: retainer(std::move(scheduler)),

View File

@@ -39,6 +39,17 @@ public:
parent.mailbox->open(scheduler);
}
// Construct the Object from a parameter pack `args` (i.e. `Object(args...)`)
template <typename U = Object,
class... Args,
typename std::enable_if_t<std::is_constructible_v<U, Args...> ||
std::is_constructible_v<U, ActorRef<U>, Args...>>* = nullptr>
EstablishedActor(const TaggedScheduler& scheduler, AspiringActor<Object>& parent_, Args&&... args)
: parent(parent_) {
emplaceObject(std::forward<Args>(args)...);
parent.mailbox->open(scheduler);
}
// Construct the `Object` from a tuple containing the constructor arguments
// (i.e. `Object(std::get<0>(args), std::get<1>(args), ...)`)
template <class ArgsTuple, std::size_t ArgCount = std::tuple_size<std::decay_t<ArgsTuple>>::value>
@@ -48,6 +59,11 @@ public:
parent.mailbox->open(scheduler);
}
template <class ArgsTuple, std::size_t ArgCount = std::tuple_size<std::decay_t<ArgsTuple>>::value>
EstablishedActor(const TaggedScheduler& scheduler, AspiringActor<Object>& parent_, ArgsTuple&& args) {
EstablishedActor(*scheduler.get(), parent_, std::forward<ArgsTuple>(args));
}
EstablishedActor(const EstablishedActor&) = delete;
~EstablishedActor() {

View File

@@ -6,6 +6,7 @@
#include <queue>
#include <mapbox/std/weak.hpp>
#include <mbgl/actor/scheduler.hpp>
namespace mbgl {
@@ -21,11 +22,13 @@ public:
Mailbox();
Mailbox(Scheduler&);
Mailbox(const TaggedScheduler&);
/// Attach the given scheduler to this mailbox and begin processing messages
/// sent to it. The mailbox must be a "holding" mailbox, as created by the
/// default constructor Mailbox().
void open(Scheduler& scheduler_);
void open(const TaggedScheduler& scheduler_);
void open(Scheduler&);
void close();
// Indicate this mailbox will no longer be checked for messages
@@ -46,6 +49,7 @@ private:
Abandoned
};
util::SimpleIdentity schedulerTag = util::SimpleIdentity::Empty;
mapbox::base::WeakPtr<Scheduler> weakScheduler;
std::recursive_mutex receivingMutex;

View File

@@ -1,6 +1,7 @@
#pragma once
#include <mbgl/util/chrono.hpp>
#include <mbgl/util/identity.hpp>
#include <mapbox/std/weak.hpp>
@@ -39,12 +40,17 @@ public:
/// Enqueues a function for execution.
virtual void schedule(std::function<void()>&&) = 0;
virtual void schedule(const util::SimpleIdentity, std::function<void()>&&) = 0;
/// Makes a weak pointer to this Scheduler.
virtual mapbox::base::WeakPtr<Scheduler> makeWeakPtr() = 0;
/// Enqueues a function for execution on the render thread.
virtual void runOnRenderThread(std::function<void()>&&) {};
virtual void runRenderJobs() {}
/// Enqueues a function for execution on the render thread owned by the given tag.
virtual void runOnRenderThread(const util::SimpleIdentity, std::function<void()>&&) {}
/// Run render thread jobs for the given tag
/// @param tag Tag of owner
/// @param closeQueue Runs all render jobs and then removes the internal queue.
virtual void runRenderJobs([[maybe_unused]] const util::SimpleIdentity tag,
[[maybe_unused]] bool closeQueue = false) {}
/// Returns a closure wrapping the given one.
///
/// When the returned closure is invoked for the first time, it schedules
@@ -62,15 +68,14 @@ public:
/// The |TaskFn| return type must be compatible with the |ReplyFn| argument
/// type. Note: the task result is copied and passed by value.
template <typename TaskFn, typename ReplyFn>
void scheduleAndReplyValue(const TaskFn& task, const ReplyFn& reply) {
void scheduleAndReplyValue(const util::SimpleIdentity tag, TaskFn&& task, ReplyFn&& reply) {
assert(GetCurrent());
scheduleAndReplyValue(task, reply, GetCurrent()->makeWeakPtr());
scheduleAndReplyValue(tag, task, reply, GetCurrent()->makeWeakPtr());
}
/// 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 util::SimpleIdentity = util::SimpleIdentity::Empty) = 0;
/// Set/Get the current Scheduler for this thread
static Scheduler* GetCurrent();
@@ -99,21 +104,50 @@ public:
protected:
template <typename TaskFn, typename ReplyFn>
void scheduleAndReplyValue(const TaskFn& task,
void scheduleAndReplyValue(const util::SimpleIdentity tag,
const TaskFn& task,
const ReplyFn& reply,
mapbox::base::WeakPtr<Scheduler> replyScheduler) {
auto scheduled = [replyScheduler = std::move(replyScheduler), task, reply] {
schedule(tag, [replyScheduler = std::move(replyScheduler), tag, task, reply] {
auto lock = replyScheduler.lock();
if (!replyScheduler) return;
auto scheduledReply = [reply, result = task()] {
reply(result);
};
replyScheduler->schedule(std::move(scheduledReply));
};
schedule(std::move(scheduled));
replyScheduler->schedule(tag, [reply, result = task()] { reply(result); });
});
}
std::function<void(const std::exception_ptr)> handler;
};
/// @brief A TaggedScheduler pairs a scheduler with an identifier. Tasklets submitted via a TaggedScheduler
/// are bucketed with the tag 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> scheduler_, const util::SimpleIdentity tag_)
: tag(tag_),
scheduler(std::move(scheduler_)) {}
TaggedScheduler(const TaggedScheduler&) = default;
/// @brief Get the wrapped scheduler
/// @return
const std::shared_ptr<Scheduler>& get() const noexcept { return scheduler; }
void schedule(std::function<void()>&& fn) { scheduler->schedule(tag, std::move(fn)); }
void runOnRenderThread(std::function<void()>&& fn) { scheduler->runOnRenderThread(tag, std::move(fn)); }
void runRenderJobs(bool closeQueue = false) { scheduler->runRenderJobs(tag, closeQueue); }
void waitForEmpty() const noexcept { scheduler->waitForEmpty(tag); }
/// type. Note: the task result is copied and passed by value.
template <typename TaskFn, typename ReplyFn>
void scheduleAndReplyValue(TaskFn&& task, ReplyFn&& reply) {
scheduler->scheduleAndReplyValue(tag, task, reply);
}
const mbgl::util::SimpleIdentity tag;
private:
std::shared_ptr<Scheduler> scheduler;
};
} // namespace mbgl

View File

@@ -54,8 +54,7 @@ using VertexAttributeArrayPtr = std::shared_ptr<VertexAttributeArray>;
class Context {
protected:
Context(uint32_t maximumVertexBindingCount_)
: maximumVertexBindingCount(maximumVertexBindingCount_),
backgroundScheduler(Scheduler::GetBackground()) {}
: maximumVertexBindingCount(maximumVertexBindingCount_) {}
public:
static constexpr const uint32_t minimumRequiredVertexBindingCount = 8;
@@ -174,8 +173,6 @@ protected:
virtual std::unique_ptr<DrawScopeResource> createDrawScopeResource() = 0;
gfx::RenderingStats stats;
std::shared_ptr<Scheduler> backgroundScheduler;
};
} // namespace gfx

View File

@@ -1,5 +1,6 @@
#pragma once
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/util/util.hpp>
#include <memory>
@@ -8,6 +9,7 @@
namespace mbgl {
class ProgramParameters;
class Map;
namespace gfx {
@@ -25,12 +27,16 @@ enum class ContextMode : bool {
class RendererBackend {
protected:
explicit RendererBackend(ContextMode);
RendererBackend(ContextMode, const TaggedScheduler&);
public:
virtual ~RendererBackend();
RendererBackend(const RendererBackend&) = delete;
RendererBackend& operator=(const RendererBackend&) = delete;
// Return the background thread pool assigned to this backend
TaggedScheduler& getThreadPool() noexcept { return threadPool; }
/// Returns the device's context.
Context& getContext();
@@ -48,6 +54,7 @@ public:
/// One-time shader initialization
virtual void initShaders(gfx::ShaderRegistry&, const ProgramParameters&) = 0;
#endif
const mbgl::util::SimpleIdentity uniqueID;
protected:
virtual std::unique_ptr<Context> createContext() = 0;
@@ -70,6 +77,7 @@ protected:
std::unique_ptr<Context> context;
const ContextMode contextMode;
std::once_flag initialized;
TaggedScheduler threadPool;
friend class BackendScope;
};

View File

@@ -17,6 +17,7 @@ using FramebufferID = uint32_t;
class RendererBackend : public gfx::RendererBackend {
public:
RendererBackend(gfx::ContextMode);
RendererBackend(gfx::ContextMode, const TaggedScheduler&);
~RendererBackend() override;
/// Called prior to rendering to update the internally assumed OpenGL state.

View File

@@ -4,6 +4,7 @@
#include <mbgl/gfx/drawable.hpp>
#endif
#include <mbgl/actor/scheduler.hpp>
#include <memory>
namespace mbgl {
@@ -30,6 +31,8 @@ public:
/// Coalescing updates is up to the implementer
virtual void update(std::shared_ptr<UpdateParameters>) = 0;
virtual const TaggedScheduler& getThreadPool() const = 0;
protected:
};

View File

@@ -41,7 +41,7 @@ public:
using TileFeatures = mapbox::feature::feature_collection<int16_t>;
using Features = mapbox::feature::feature_collection<double>;
static std::shared_ptr<GeoJSONData> create(const GeoJSON&,
std::shared_ptr<Scheduler> scheduler,
std::shared_ptr<Scheduler> sequencedScheduler,
const Immutable<GeoJSONOptions>& = GeoJSONOptions::defaultOptions());
virtual ~GeoJSONData() = default;
@@ -80,7 +80,6 @@ protected:
private:
std::optional<std::string> url;
std::unique_ptr<AsyncRequest> req;
std::shared_ptr<Scheduler> threadPool;
std::shared_ptr<Scheduler> sequencedScheduler;
mapbox::base::WeakPtrFactory<Source> weakFactory{this};
// Do not add members here, see `WeakPtrFactory`

View File

@@ -1,5 +1,6 @@
#pragma once
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/map/camera.hpp>
#include <mbgl/style/image.hpp>
#include <mbgl/style/transition_options.hpp>
@@ -22,7 +23,7 @@ class Layer;
class Style {
public:
Style(std::shared_ptr<FileSource>, float pixelRatio);
Style(std::shared_ptr<FileSource>, float pixelRatio, const TaggedScheduler& threadPool_);
~Style();
void loadJSON(const std::string&);

View File

@@ -27,7 +27,7 @@ public:
bool operator!=(const SimpleIdentity& other) const noexcept { return uniqueID != other.uniqueID; }
std::int64_t id() const noexcept { return uniqueID; }
bool isEmpty() const noexcept { return uniqueID != emptyID; }
bool isEmpty() const noexcept { return uniqueID == emptyID; }
operator bool() const noexcept { return isEmpty(); }
bool operator!() const noexcept { return !isEmpty(); }
@@ -58,3 +58,10 @@ inline std::string toString(const util::SimpleIdentity& ident) {
} // namespace util
} // namespace mbgl
template <>
struct std::hash<mbgl::util::SimpleIdentity> {
std::size_t operator()(const mbgl::util::SimpleIdentity& s) const noexcept {
return std::hash<std::int64_t>{}(s.id());
}
};

View File

@@ -79,9 +79,10 @@ public:
}
void schedule(std::function<void()>&& fn) override { invoke(std::move(fn)); }
void schedule(const util::SimpleIdentity, std::function<void()>&& fn) override { schedule(std::move(fn)); }
::mapbox::base::WeakPtr<Scheduler> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
std::size_t waitForEmpty(Milliseconds timeout) override;
void waitForEmpty(const util::SimpleIdentity = util::SimpleIdentity::Empty) override;
class Impl;

View File

@@ -28,7 +28,7 @@ private:
AndroidRendererBackend& backend;
};
AndroidRendererBackend::AndroidRendererBackend()
AndroidRendererBackend::AndroidRendererBackend(const TaggedScheduler& threadPool)
: gl::RendererBackend(gfx::ContextMode::Unique),
mbgl::gfx::Renderable({64, 64}, std::make_unique<AndroidGLRenderableResource>(*this)) {}

View File

@@ -8,7 +8,7 @@ namespace android {
class AndroidRendererBackend : public gl::RendererBackend, public mbgl::gfx::Renderable {
public:
AndroidRendererBackend();
AndroidRendererBackend(const TaggedScheduler& threadPool);
~AndroidRendererBackend() override;
void updateViewPort();

View File

@@ -82,6 +82,10 @@ void AndroidRendererFrontend::update(std::shared_ptr<UpdateParameters> params) {
updateAsyncTask->send();
}
const TaggedScheduler& AndroidRendererFrontend::getThreadPool() const {
return mapRenderer.getThreadPool();
}
void AndroidRendererFrontend::reduceMemoryUse() {
mapRenderer.actor().invoke(&Renderer::reduceMemoryUse);
}

View File

@@ -37,6 +37,8 @@ public:
void update(std::shared_ptr<UpdateParameters>) override;
const TaggedScheduler& getThreadPool() const override;
// Feature querying
std::vector<Feature> queryRenderedFeatures(const ScreenCoordinate&, const RenderedQueryOptions&) const;
std::vector<Feature> queryRenderedFeatures(const ScreenBox&, const RenderedQueryOptions&) const;

View File

@@ -22,6 +22,7 @@ MapRenderer::MapRenderer(jni::JNIEnv& _env,
pixelRatio(pixelRatio_),
localIdeographFontFamily(localIdeographFontFamily_ ? jni::Make<std::string>(_env, localIdeographFontFamily_)
: std::optional<std::string>{}),
threadPool(Scheduler::GetBackground(), {}),
mailboxData(this) {}
MapRenderer::MailboxData::MailboxData(Scheduler* scheduler_)
@@ -84,20 +85,17 @@ void MapRenderer::schedule(std::function<void()>&& scheduled) {
}
}
std::size_t MapRenderer::waitForEmpty(Milliseconds timeout) {
void MapRenderer::waitForEmpty([[maybe_unused]] const util::SimpleIdentity tag) {
try {
android::UniqueEnv _env = android::AttachEnv();
static auto& javaClass = jni::Class<MapRenderer>::Singleton(*_env);
static auto waitForEmpty = javaClass.GetMethod<jni::jint(jni::jlong)>(*_env, "waitForEmpty");
static auto waitForEmpty = javaClass.GetMethod<void()>(*_env, "waitForEmpty");
if (auto weakReference = javaPeer.get(*_env)) {
return weakReference.Call(*_env, waitForEmpty, static_cast<int64_t>(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;
}
}
@@ -221,7 +219,7 @@ void MapRenderer::onSurfaceCreated(JNIEnv&) {
backend.reset();
// Create the new backend and renderer
backend = std::make_unique<AndroidRendererBackend>();
backend = std::make_unique<AndroidRendererBackend>(threadPool);
renderer = std::make_unique<Renderer>(*backend, pixelRatio, localIdeographFontFamily);
rendererRef = std::make_unique<ActorRef<Renderer>>(*renderer, mailboxData.getMailbox());

View File

@@ -67,11 +67,12 @@ public:
// From Scheduler. Schedules by using callbacks to the
// JVM to process the mailbox on the right thread.
void schedule(std::function<void()>&& scheduled) override;
void schedule(const util::SimpleIdentity, std::function<void()>&& fn) override { schedule(std::move(fn)); };
mapbox::base::WeakPtr<Scheduler> 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 util::SimpleIdentity tag) override;
void requestRender();
@@ -79,6 +80,9 @@ public:
using SnapshotCallback = std::function<void(PremultipliedImage)>;
void requestSnapshot(SnapshotCallback);
AndroidRendererBackend& getRendererBackend() const { return *backend; }
const TaggedScheduler& getThreadPool() const { return threadPool; }
protected:
// Called from the GL Thread //
@@ -119,7 +123,7 @@ private:
float pixelRatio;
std::optional<std::string> localIdeographFontFamily;
std::shared_ptr<ThreadPool> threadPool;
TaggedScheduler threadPool;
const MailboxData mailboxData;
std::mutex initialisationMutex;

View File

@@ -120,11 +120,6 @@ public abstract class MapRenderer implements MapRendererScheduler {
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);

View File

@@ -16,7 +16,4 @@ public interface MapRendererScheduler {
@Keep
void waitForEmpty();
@Keep
long waitForEmpty(long timeoutMillis);
}

View File

@@ -120,7 +120,7 @@ public class GLSurfaceViewMapRenderer extends MapRenderer implements GLSurfaceVi
* {@inheritDoc}
*/
@Override
public long waitForEmpty(long timeoutMillis) {
return glSurfaceView.waitForEmpty(timeoutMillis);
public void waitForEmpty() {
glSurfaceView.waitForEmpty();
}
}

View File

@@ -322,8 +322,8 @@ public class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
* @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 class MapLibreGLSurfaceView extends SurfaceView implements SurfaceHolder.
* @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();
}
}

View File

@@ -91,8 +91,8 @@ public class TextureViewMapRenderer extends MapRenderer {
* {@inheritDoc}
*/
@Override
public long waitForEmpty(long timeoutMillis) {
return renderThread.waitForEmpty(timeoutMillis);
public void waitForEmpty() {
renderThread.waitForEmpty();
}
/**

View File

@@ -141,31 +141,16 @@ class TextureViewRenderThread extends Thread implements TextureView.SurfaceTextu
* @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();
}
}

View File

@@ -444,9 +444,8 @@ class NativeMapViewTest : AppCenter() {
// no-op
}
override fun waitForEmpty(timeoutMillis: Long): Long {
override fun waitForEmpty() {
// no-op
return 0
}
}
}

View File

@@ -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<Milliseconds>(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 SimpleIdentity tag) {
impl->waitForEmpty();
}
void RunLoop::run() {

View File

@@ -42,7 +42,7 @@ public:
Milliseconds processRunnables();
std::size_t waitForEmpty(Milliseconds timeout);
void waitForEmpty();
ALooper* loop = nullptr;
RunLoop* runLoop = nullptr;

View File

@@ -1,3 +1,4 @@
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/gfx/backend_scope.hpp>
#include <mbgl/gfx/renderer_backend.hpp>
#include <mbgl/renderer/renderer.hpp>
@@ -33,6 +34,10 @@ class MLNRenderFrontend : public mbgl::RendererFrontend {
}
}
const mbgl::TaggedScheduler& getThreadPool() const override {
return mbglBackend.getThreadPool();
}
void setObserver(mbgl::RendererObserver& observer) override {
if (!renderer) return;
renderer->setObserver(&observer);

View File

@@ -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 SimpleIdentity 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<std::chrono::milliseconds>(elapsed);
if (remaining == 0 || (Milliseconds::zero() < timeout && timeout <= elapsedMillis)) {
return remaining;
if (remaining == 0) {
return;
}
runOnce();

View File

@@ -37,6 +37,7 @@ public:
void reset() override;
void update(std::shared_ptr<UpdateParameters>) override;
const TaggedScheduler& getThreadPool() const override;
void setObserver(RendererObserver&) override;
double getFrameTime() const;

View File

@@ -48,6 +48,10 @@ void HeadlessFrontend::update(std::shared_ptr<UpdateParameters> updateParameters
}
}
const TaggedScheduler& HeadlessFrontend::getThreadPool() const {
return backend->getRendererBackend()->getThreadPool();
}
void HeadlessFrontend::setObserver(RendererObserver& observer_) {
assert(renderer);
renderer->setObserver(&observer_);

View File

@@ -118,6 +118,8 @@ public:
return std::move(stillImage);
}
const TaggedScheduler& getThreadPool() const { return frontend.getThreadPool(); }
private:
PremultipliedImage stillImage;
bool hasPendingStillImageRequest = false;
@@ -156,6 +158,10 @@ public:
PremultipliedImage takeImage() { return renderer->actor().ask(&SnapshotterRenderer::takeImage).get(); }
const mbgl::TaggedScheduler& getThreadPool() const override {
return renderer->actor().ask(&SnapshotterRenderer::getThreadPool).get();
}
private:
std::shared_ptr<UpdateParameters> updateParameters;
const std::unique_ptr<util::Thread<SnapshotterRenderer>> renderer;

View File

@@ -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 mbgl::util::SimpleIdentity 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<Milliseconds>(elapsed);
if (remaining == 0 || (Milliseconds::zero() < timeout && timeout <= elapsedMillis)) {
return remaining;
if (remaining == 0) {
return;
}
runOnce();

View File

@@ -2,6 +2,7 @@
#include <mbgl/renderer/renderer.hpp>
#include <mbgl/gfx/backend_scope.hpp>
#include <mbgl/gfx/renderer_backend.hpp>
#include <mbgl/util/instrumentation.hpp>
GLFWRendererFrontend::GLFWRendererFrontend(std::unique_ptr<mbgl::Renderer> renderer_, GLFWView& glfwView_)
@@ -27,6 +28,10 @@ void GLFWRendererFrontend::update(std::shared_ptr<mbgl::UpdateParameters> params
glfwView.invalidate();
}
const mbgl::TaggedScheduler& GLFWRendererFrontend::getThreadPool() const {
return glfwView.getRendererBackend().getThreadPool();
}
void GLFWRendererFrontend::render() {
MLN_TRACE_FUNC();

View File

@@ -18,6 +18,7 @@ public:
void setObserver(mbgl::RendererObserver&) override;
void update(std::shared_ptr<mbgl::UpdateParameters>) override;
const mbgl::TaggedScheduler& getThreadPool() const override;
void render();
mbgl::Renderer* getRenderer();

View File

@@ -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 mbgl::util::SimpleIdentity 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<std::chrono::milliseconds>(elapsed);
if (remaining == 0 || timeout <= elapsedMillis) {
return remaining;
if (remaining == 0) {
return;
}
runOnce();

View File

@@ -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 mbgl::util::SimpleIdentity tag) {
MBGL_VERIFY_THREAD(tid);
const auto startTime = mbgl::util::MonotonicTimer::now();
std::unique_lock<std::mutex> 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

View File

@@ -22,7 +22,7 @@ public:
// mbgl::Scheduler implementation.
void schedule(std::function<void()>&& function) final;
std::size_t waitForEmpty(std::chrono::milliseconds timeout) override;
void waitForEmpty(const mbgl::util::SimpleIdentity tag = mbgl::util::SimpleIdentity::Empty) override;
mapbox::base::WeakPtr<mbgl::Scheduler> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }

View File

@@ -12,6 +12,16 @@ Mailbox::Mailbox() = default;
Mailbox::Mailbox(Scheduler& scheduler_)
: weakScheduler(scheduler_.makeWeakPtr()) {}
Mailbox::Mailbox(const TaggedScheduler& scheduler_)
: schedulerTag(scheduler_.tag),
weakScheduler(scheduler_.get()->makeWeakPtr()) {}
void Mailbox::open(const TaggedScheduler& scheduler_) {
assert(!weakScheduler);
schedulerTag = scheduler_.tag;
return open(*scheduler_.get());
}
void Mailbox::open(Scheduler& scheduler_) {
assert(!weakScheduler);
@@ -90,7 +100,7 @@ void Mailbox::push(std::unique_ptr<Message> message) {
queue.push(std::move(message));
auto guard = weakScheduler.lock();
if (wasEmpty && weakScheduler) {
weakScheduler->schedule(makeClosure(shared_from_this()));
weakScheduler->schedule(schedulerTag, makeClosure(shared_from_this()));
}
}

View File

@@ -11,8 +11,8 @@ namespace mbgl {
using namespace style;
RenderAnnotationSource::RenderAnnotationSource(Immutable<AnnotationSource::Impl> impl_,
std::shared_ptr<Scheduler> threadPool_)
: RenderTileSource(std::move(impl_), std::move(threadPool_)) {
const TaggedScheduler& threadPool_)
: RenderTileSource(std::move(impl_), threadPool_) {
assert(LayerManager::annotationsEnabled);
tilePyramid.setObserver(this);
}

View File

@@ -7,7 +7,7 @@ namespace mbgl {
class RenderAnnotationSource final : public RenderTileSource {
public:
explicit RenderAnnotationSource(Immutable<AnnotationSource::Impl>, std::shared_ptr<Scheduler>);
explicit RenderAnnotationSource(Immutable<AnnotationSource::Impl>, const TaggedScheduler&);
void update(Immutable<style::Source::Impl>,
const std::vector<Immutable<style::LayerProperties>>&,

View File

@@ -6,7 +6,13 @@ namespace mbgl {
namespace gfx {
RendererBackend::RendererBackend(const ContextMode contextMode_)
: contextMode(contextMode_) {}
: contextMode(contextMode_),
threadPool(Scheduler::GetBackground(), uniqueID) {}
RendererBackend::RendererBackend(const ContextMode contextMode_, const TaggedScheduler& threadPool_)
: contextMode(contextMode_),
threadPool(threadPool_) {}
RendererBackend::~RendererBackend() = default;
gfx::Context& RendererBackend::getContext() {

View File

@@ -102,7 +102,7 @@ Context::Context(RendererBackend& backend_)
Context::~Context() noexcept {
if (cleanupOnDestruction) {
Scheduler::GetBackground()->runRenderJobs();
backend.getThreadPool().runRenderJobs(true /* closeQueue */);
reset();
#if !defined(NDEBUG)
@@ -115,7 +115,7 @@ Context::~Context() noexcept {
void Context::beginFrame() {
MLN_TRACE_FUNC();
Scheduler::GetBackground()->runRenderJobs();
backend.getThreadPool().runRenderJobs();
#if MLN_DRAWABLE_RENDERER
frameInFlightFence = std::make_shared<gl::Fence>();

View File

@@ -19,10 +19,14 @@ namespace gl {
RendererBackend::RendererBackend(const gfx::ContextMode contextMode_)
: gfx::RendererBackend(contextMode_) {}
RendererBackend::RendererBackend(const gfx::ContextMode contextMode_, const TaggedScheduler& threadPool_)
: gfx::RendererBackend(contextMode_, threadPool_) {}
std::unique_ptr<gfx::Context> RendererBackend::createContext() {
MLN_TRACE_FUNC();
auto result = std::make_unique<gl::Context>(*this);
auto result = std::make_unique<gl::Context>(
*this); // Tagged background thread pool will be owned by the RendererBackend
result->enableDebugging();
result->initializeExtensions(std::bind(&RendererBackend::getExtensionFunctionPointer, this, std::placeholders::_1));
return result;

View File

@@ -52,7 +52,7 @@ Map::Impl::Impl(RendererFrontend& frontend_,
pixelRatio(mapOptions.pixelRatio()),
crossSourceCollisions(mapOptions.crossSourceCollisions()),
fileSource(std::move(fileSource_)),
style(std::make_unique<style::Style>(fileSource, pixelRatio)),
style(std::make_unique<style::Style>(fileSource, pixelRatio, frontend_.getThreadPool())),
annotationManager(*style) {
transform.setNorthOrientation(mapOptions.northOrientation());
style->impl->setObserver(this);

View File

@@ -44,7 +44,7 @@ Context::Context(RendererBackend& backend_)
Context::~Context() noexcept {
if (cleanupOnDestruction) {
Scheduler::GetBackground()->runRenderJobs();
backend.getThreadPool().runRenderJobs(true /* closeQueue */);
performCleanup();
emptyVertexBuffer.reset();
@@ -68,7 +68,7 @@ Context::~Context() noexcept {
}
void Context::beginFrame() {
Scheduler::GetBackground()->runRenderJobs();
backend.getThreadPool().runRenderJobs();
}
void Context::endFrame() {}

View File

@@ -114,7 +114,9 @@ public:
} // namespace
RenderOrchestrator::RenderOrchestrator(bool backgroundLayerAsColor_, const std::optional<std::string>& localFontFamily_)
RenderOrchestrator::RenderOrchestrator(bool backgroundLayerAsColor_,
TaggedScheduler& threadPool_,
const std::optional<std::string>& localFontFamily_)
: observer(&nullObserver()),
glyphManager(std::make_unique<GlyphManager>(std::make_unique<LocalGlyphRasterizer>(localFontFamily_))),
imageManager(std::make_unique<ImageManager>()),
@@ -125,7 +127,7 @@ RenderOrchestrator::RenderOrchestrator(bool backgroundLayerAsColor_, const std::
layerImpls(makeMutable<std::vector<Immutable<style::Layer::Impl>>>()),
renderLight(makeMutable<Light::Impl>()),
backgroundLayerAsColor(backgroundLayerAsColor_),
threadPool(Scheduler::GetBackground()) {
threadPool(threadPool_) {
glyphManager->setObserver(this);
imageManager->setObserver(this);
}
@@ -147,14 +149,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);
// this assert is causing Android Instrumentation tests to fail
// since they need to run in on a debug build
// ignore it for now, see issue https://github.com/maplibre/maplibre-native/issues/2187
#ifndef __ANDROID__
assert(remaining == 0);
#endif
threadPool.waitForEmpty();
}
void RenderOrchestrator::setObserver(RendererObserver* observer_) {
@@ -201,7 +196,8 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(
updateParameters->annotationManager,
imageManager,
glyphManager,
updateParameters->prefetchZoomDelta};
updateParameters->prefetchZoomDelta,
threadPool};
glyphManager->setURL(updateParameters->glyphURL);

View File

@@ -2,6 +2,7 @@
#if MLN_DRAWABLE_RENDERER
#include <mbgl/renderer/layer_group.hpp>
#endif
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/renderer/renderer.hpp>
#include <mbgl/renderer/render_source_observer.hpp>
#include <mbgl/renderer/render_light.hpp>
@@ -55,7 +56,9 @@ using ImmutableLayer = Immutable<style::Layer::Impl>;
class RenderOrchestrator final : public GlyphManagerObserver, public ImageManagerObserver, public RenderSourceObserver {
public:
RenderOrchestrator(bool backgroundLayerAsColor_, const std::optional<std::string>& localFontFamily_);
RenderOrchestrator(bool backgroundLayerAsColor_,
TaggedScheduler& threadPool_,
const std::optional<std::string>& localFontFamily_);
~RenderOrchestrator() override;
void markContextLost() { contextLost = true; };
@@ -210,7 +213,7 @@ private:
RenderLayerReferences orderedLayers;
RenderLayerReferences layersNeedPlacement;
std::shared_ptr<Scheduler> threadPool;
TaggedScheduler threadPool;
#if MLN_DRAWABLE_RENDERER
std::vector<std::unique_ptr<ChangeRequest>> pendingChanges;

View File

@@ -21,7 +21,7 @@ namespace mbgl {
using namespace style;
std::unique_ptr<RenderSource> RenderSource::create(const Immutable<Source::Impl>& impl,
std::shared_ptr<Scheduler> threadPool_) {
const TaggedScheduler& threadPool_) {
switch (impl->type) {
case SourceType::Vector:
return std::make_unique<RenderVectorSource>(staticImmutableCast<VectorSource::Impl>(impl),

View File

@@ -1,5 +1,6 @@
#pragma once
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/map/mode.hpp>
#include <mbgl/tile/tile_id.hpp>
#include <mbgl/tile/tile_observer.hpp>
@@ -48,7 +49,7 @@ using RenderTiles = std::shared_ptr<const std::vector<std::reference_wrapper<con
class RenderSource : protected TileObserver {
public:
static std::unique_ptr<RenderSource> create(const Immutable<style::Source::Impl>&, std::shared_ptr<Scheduler>);
static std::unique_ptr<RenderSource> create(const Immutable<style::Source::Impl>&, const TaggedScheduler&);
~RenderSource() override;
bool isEnabled() const;

View File

@@ -59,7 +59,7 @@ RendererObserver& nullObserver() {
Renderer::Impl::Impl(gfx::RendererBackend& backend_,
float pixelRatio_,
const std::optional<std::string>& localFontFamily_)
: orchestrator(!backend_.contextIsShared(), localFontFamily_),
: orchestrator(!backend_.contextIsShared(), backend_.getThreadPool(), localFontFamily_),
backend(backend_),
observer(&nullObserver()),
pixelRatio(pixelRatio_) {}

View File

@@ -8,8 +8,8 @@ namespace mbgl {
using namespace style;
RenderCustomGeometrySource::RenderCustomGeometrySource(Immutable<style::CustomGeometrySource::Impl> impl_,
std::shared_ptr<Scheduler> threadPool_)
: RenderTileSource(std::move(impl_), std::move(threadPool_)) {
const TaggedScheduler& threadPool_)
: RenderTileSource(std::move(impl_), threadPool_) {
tilePyramid.setObserver(this);
}

View File

@@ -7,7 +7,7 @@ namespace mbgl {
class RenderCustomGeometrySource final : public RenderTileSource {
public:
explicit RenderCustomGeometrySource(Immutable<style::CustomGeometrySource::Impl>, std::shared_ptr<Scheduler>);
explicit RenderCustomGeometrySource(Immutable<style::CustomGeometrySource::Impl>, const TaggedScheduler&);
void update(Immutable<style::Source::Impl>,
const std::vector<Immutable<style::LayerProperties>>&,

View File

@@ -65,8 +65,8 @@ constexpr const auto extensionGetters = mapbox::eternal::hash_map<mapbox::eterna
} // namespace
RenderGeoJSONSource::RenderGeoJSONSource(Immutable<style::GeoJSONSource::Impl> impl_,
std::shared_ptr<Scheduler> threadPool_)
: RenderTileSource(std::move(impl_), std::move(threadPool_)) {}
const TaggedScheduler& threadPool_)
: RenderTileSource(std::move(impl_), threadPool_) {}
RenderGeoJSONSource::~RenderGeoJSONSource() = default;

View File

@@ -11,7 +11,7 @@ class GeoJSONData;
class RenderGeoJSONSource final : public RenderTileSource {
public:
explicit RenderGeoJSONSource(Immutable<style::GeoJSONSource::Impl>, std::shared_ptr<Scheduler>);
explicit RenderGeoJSONSource(Immutable<style::GeoJSONSource::Impl>, const TaggedScheduler&);
~RenderGeoJSONSource() override;
void update(Immutable<style::Source::Impl>,

View File

@@ -11,8 +11,8 @@ namespace mbgl {
using namespace style;
RenderRasterDEMSource::RenderRasterDEMSource(Immutable<style::RasterSource::Impl> impl_,
std::shared_ptr<Scheduler> 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<const style::RasterSource::Impl&>(*baseImpl);

View File

@@ -7,7 +7,7 @@ namespace mbgl {
class RenderRasterDEMSource final : public RenderTileSetSource {
public:
explicit RenderRasterDEMSource(Immutable<style::RasterSource::Impl>, std::shared_ptr<Scheduler>);
explicit RenderRasterDEMSource(Immutable<style::RasterSource::Impl>, const TaggedScheduler&);
std::unordered_map<std::string, std::vector<Feature>> queryRenderedFeatures(
const ScreenLineString& geometry,

View File

@@ -8,9 +8,8 @@ namespace mbgl {
using namespace style;
RenderRasterSource::RenderRasterSource(Immutable<style::RasterSource::Impl> impl_,
std::shared_ptr<Scheduler> threadPool_)
: RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {}
RenderRasterSource::RenderRasterSource(Immutable<style::RasterSource::Impl> impl_, const TaggedScheduler& threadPool_)
: RenderTileSetSource(std::move(impl_), threadPool_) {}
inline const style::RasterSource::Impl& RenderRasterSource::impl() const {
return static_cast<const style::RasterSource::Impl&>(*baseImpl);

View File

@@ -7,7 +7,7 @@ namespace mbgl {
class RenderRasterSource final : public RenderTileSetSource {
public:
explicit RenderRasterSource(Immutable<style::RasterSource::Impl>, std::shared_ptr<Scheduler>);
explicit RenderRasterSource(Immutable<style::RasterSource::Impl>, const TaggedScheduler&);
private:
void prepare(const SourcePrepareParameters&) final;

View File

@@ -383,9 +383,9 @@ void TileSourceRenderItem::updateDebugDrawables(DebugLayerGroupMap& debugLayerGr
}
#endif
RenderTileSource::RenderTileSource(Immutable<style::Source::Impl> impl_, std::shared_ptr<Scheduler> threadPool_)
RenderTileSource::RenderTileSource(Immutable<style::Source::Impl> impl_, const TaggedScheduler& threadPool_)
: RenderSource(std::move(impl_)),
tilePyramid(std::move(threadPool_)),
tilePyramid(threadPool_),
renderTiles(makeMutable<std::vector<RenderTile>>()) {
tilePyramid.setObserver(this);
}
@@ -504,8 +504,8 @@ void RenderTileSource::dumpDebugLogs() const {
// RenderTileSetSource implementation
RenderTileSetSource::RenderTileSetSource(Immutable<style::Source::Impl> impl_, std::shared_ptr<Scheduler> threadPool_)
: RenderTileSource(std::move(impl_), std::move(threadPool_)) {}
RenderTileSetSource::RenderTileSetSource(Immutable<style::Source::Impl> impl_, const TaggedScheduler& threadPool_)
: RenderTileSource(std::move(impl_), threadPool_) {}
RenderTileSetSource::~RenderTileSetSource() = default;

View File

@@ -51,7 +51,7 @@ public:
void dumpDebugLogs() const override;
protected:
RenderTileSource(Immutable<style::Source::Impl>, std::shared_ptr<Scheduler>);
RenderTileSource(Immutable<style::Source::Impl>, const TaggedScheduler&);
TilePyramid tilePyramid;
Immutable<std::vector<RenderTile>> renderTiles;
mutable RenderTiles filteredRenderTiles;
@@ -67,7 +67,7 @@ private:
*/
class RenderTileSetSource : public RenderTileSource {
protected:
RenderTileSetSource(Immutable<style::Source::Impl>, std::shared_ptr<Scheduler>);
RenderTileSetSource(Immutable<style::Source::Impl>, const TaggedScheduler&);
~RenderTileSetSource() override;
virtual void updateInternal(const Tileset&,

View File

@@ -8,9 +8,8 @@ namespace mbgl {
using namespace style;
RenderVectorSource::RenderVectorSource(Immutable<style::VectorSource::Impl> impl_,
std::shared_ptr<Scheduler> threadPool_)
: RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {}
RenderVectorSource::RenderVectorSource(Immutable<style::VectorSource::Impl> impl_, const TaggedScheduler& threadPool_)
: RenderTileSetSource(std::move(impl_), threadPool_) {}
const std::optional<Tileset>& RenderVectorSource::getTileset() const {
return static_cast<const style::VectorSource::Impl&>(*baseImpl).tileset;

View File

@@ -8,7 +8,7 @@ namespace mbgl {
class RenderVectorSource final : public RenderTileSetSource {
public:
explicit RenderVectorSource(Immutable<style::VectorSource::Impl>, std::shared_ptr<Scheduler>);
explicit RenderVectorSource(Immutable<style::VectorSource::Impl>, const TaggedScheduler&);
private:
void updateInternal(const Tileset&,

View File

@@ -1,6 +1,7 @@
#pragma once
#include <mbgl/map/mode.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <memory>
@@ -25,6 +26,7 @@ public:
std::shared_ptr<ImageManager> imageManager;
std::shared_ptr<GlyphManager> glyphManager;
const uint8_t prefetchZoomDelta;
TaggedScheduler threadPool;
};
} // namespace mbgl

View File

@@ -24,8 +24,8 @@ using namespace style;
static TileObserver nullObserver;
TilePyramid::TilePyramid(std::shared_ptr<Scheduler> threadPool_)
: cache(std::move(threadPool_)),
TilePyramid::TilePyramid(const TaggedScheduler& threadPool_)
: cache(threadPool_),
observer(&nullObserver) {}
TilePyramid::~TilePyramid() = default;

View File

@@ -29,7 +29,7 @@ class SourcePrepareParameters;
class TilePyramid {
public:
TilePyramid(std::shared_ptr<Scheduler> threadPool_);
TilePyramid(const TaggedScheduler& threadPool_);
~TilePyramid();
bool isLoaded() const;

View File

@@ -26,10 +26,10 @@ struct SpriteLoader::Data {
std::unique_ptr<AsyncRequest> spriteRequest;
};
SpriteLoader::SpriteLoader(float pixelRatio_)
SpriteLoader::SpriteLoader(float pixelRatio_, const TaggedScheduler& threadPool_)
: pixelRatio(pixelRatio_),
observer(&nullObserver),
threadPool(Scheduler::GetBackground()) {}
threadPool(threadPool_) {}
SpriteLoader::~SpriteLoader() = default;
@@ -93,25 +93,25 @@ void SpriteLoader::emitSpriteLoadedIfComplete(style::Sprite sprite) {
std::exception_ptr error;
};
auto parseClosure = [sprite = sprite, image = data->image, json = data->json]() -> ParseResult {
try {
return {parseSprite(sprite.id, *image, *json), nullptr};
} catch (...) {
return {{}, std::current_exception()};
}
};
threadPool.scheduleAndReplyValue(
/* parseClosure */
[sprite = sprite, image = data->image, json = data->json]() -> ParseResult {
try {
return {parseSprite(sprite.id, *image, *json), nullptr};
} catch (...) {
return {{}, std::current_exception()};
}
},
/* resultClosure */
[this, sprite = sprite, weak = weakFactory.makeWeakPtr()](ParseResult result) {
if (!weak) return; // This instance has been deleted.
auto resultClosure = [this, sprite = sprite, weak = weakFactory.makeWeakPtr()](ParseResult result) {
if (!weak) return; // This instance has been deleted.
if (result.error) {
observer->onSpriteError(std::optional(sprite), result.error);
return;
}
observer->onSpriteLoaded(std::optional(sprite), std::move(result.images));
};
threadPool->scheduleAndReplyValue(parseClosure, resultClosure);
if (result.error) {
observer->onSpriteError(std::optional(sprite), result.error);
return;
}
observer->onSpriteLoaded(std::optional(sprite), std::move(result.images));
});
}
void SpriteLoader::setObserver(SpriteLoaderObserver* observer_) {

View File

@@ -1,5 +1,6 @@
#pragma once
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/style/image.hpp>
#include <mbgl/style/sprite.hpp>
#include <mapbox/std/weak.hpp>
@@ -15,11 +16,10 @@ namespace mbgl {
class FileSource;
class SpriteLoaderObserver;
class Scheduler;
class SpriteLoader {
public:
SpriteLoader(float pixelRatio);
SpriteLoader(float pixelRatio, const TaggedScheduler& threadPool_);
~SpriteLoader();
void load(const std::optional<style::Sprite> sprite, FileSource&);
@@ -39,7 +39,7 @@ private:
std::mutex dataMapMutex;
SpriteLoaderObserver* observer = nullptr;
std::shared_ptr<Scheduler> threadPool;
TaggedScheduler threadPool;
mapbox::base::WeakPtrFactory<SpriteLoader> weakFactory{this};
// Do not add members here, see `WeakPtrFactory`
};

View File

@@ -9,6 +9,7 @@
#include <mbgl/util/async_request.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/thread_pool.hpp>
#include <mbgl/util/identity.hpp>
namespace mbgl {
namespace style {
@@ -21,7 +22,6 @@ Immutable<GeoJSONOptions> GeoJSONOptions::defaultOptions() {
GeoJSONSource::GeoJSONSource(std::string id, Immutable<GeoJSONOptions> options)
: Source(makeMutable<Impl>(std::move(id), std::move(options))),
threadPool(Scheduler::GetBackground()),
sequencedScheduler(Scheduler::GetSequenced()) {}
GeoJSONSource::~GeoJSONSource() = default;
@@ -77,32 +77,37 @@ void GeoJSONSource::loadDescription(FileSource& fileSource) {
} else if (res.noContent) {
observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error("unexpectedly empty GeoJSON")));
} else {
auto makeImplInBackground = [currentImpl = baseImpl,
data = res.data,
seqScheduler{sequencedScheduler}]() -> Immutable<Source::Impl> {
assert(data);
auto& current = static_cast<const Impl&>(*currentImpl);
conversion::Error error;
std::shared_ptr<GeoJSONData> geoJSONData;
if (std::optional<GeoJSON> geoJSON = conversion::convertJSON<GeoJSON>(*data, error)) {
geoJSONData = GeoJSONData::create(*geoJSON, std::move(seqScheduler), current.getOptions());
} else {
// Create an empty GeoJSON VT object to make sure we're not
// infinitely waiting for tiles to load.
Log::Error(Event::ParseStyle, "Failed to parse GeoJSON data: " + error.message);
}
return makeMutable<Impl>(current, std::move(geoJSONData));
};
auto onImplReady = [this, self = makeWeakPtr(), capturedReq = req.get()](Immutable<Source::Impl> newImpl) {
assert(capturedReq);
if (!self) return; // This source has been deleted.
if (capturedReq != req.get()) return; // A new request is being processed, ignore this impl.
// Note: This task appears to be safe enough to schedule on the generic background queue.
// This task does not reference other objects who's lifetimes are coupled with a map.
Scheduler::GetBackground()->scheduleAndReplyValue(
util::SimpleIdentity::Empty,
/* makeImplInBackground */
[currentImpl = baseImpl,
data = res.data,
seqScheduler{sequencedScheduler}]() -> Immutable<Source::Impl> {
assert(data);
auto& current = static_cast<const Impl&>(*currentImpl);
conversion::Error error;
std::shared_ptr<GeoJSONData> geoJSONData;
if (std::optional<GeoJSON> geoJSON = conversion::convertJSON<GeoJSON>(*data, error)) {
geoJSONData = GeoJSONData::create(*geoJSON, std::move(seqScheduler), current.getOptions());
} else {
// Create an empty GeoJSON VT object to make sure we're not
// infinitely waiting for tiles to load.
Log::Error(Event::ParseStyle, "Failed to parse GeoJSON data: " + error.message);
}
return makeMutable<Impl>(current, std::move(geoJSONData));
},
/* onImplReady */
[this, self = makeWeakPtr(), capturedReq = req.get()](Immutable<Source::Impl> newImpl) {
assert(capturedReq);
if (!self) return; // This source has been deleted.
if (capturedReq != req.get()) return; // A new request is being processed, ignore this impl.
baseImpl = std::move(newImpl);
loaded = true;
observer->onSourceLoaded(*this);
};
threadPool->scheduleAndReplyValue(makeImplInBackground, onImplReady);
baseImpl = std::move(newImpl);
loaded = true;
observer->onSourceLoaded(*this);
});
}
});
}

View File

@@ -4,6 +4,7 @@
#include <mbgl/util/feature.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/thread_pool.hpp>
#include <mbgl/util/identity.hpp>
#ifdef _MSC_VER
#pragma warning(push)
@@ -25,7 +26,8 @@ namespace style {
class GeoJSONVTData final : public GeoJSONData {
void getTile(const CanonicalTileID& id, const std::function<void(TileFeatures)>& fn) final {
assert(fn);
scheduler->scheduleAndReplyValue(
sequencedScheduler->scheduleAndReplyValue(
util::SimpleIdentity::Empty,
[id, geoJSONVT_impl = this->impl]() -> TileFeatures {
return geoJSONVT_impl->getTile(id.z, id.x, id.y).features;
},
@@ -41,14 +43,14 @@ class GeoJSONVTData final : public GeoJSONData {
friend GeoJSONData;
GeoJSONVTData(const GeoJSON& geoJSON,
const mapbox::geojsonvt::Options& options,
std::shared_ptr<Scheduler> scheduler_)
std::shared_ptr<Scheduler> sequencedScheduler_)
: impl(std::make_shared<mapbox::geojsonvt::GeoJSONVT>(geoJSON, options)),
scheduler(std::move(scheduler_)) {
assert(scheduler);
sequencedScheduler(std::move(sequencedScheduler_)) {
assert(sequencedScheduler);
}
std::shared_ptr<mapbox::geojsonvt::GeoJSONVT> impl; // Accessed on worker thread.
std::shared_ptr<Scheduler> scheduler;
std::shared_ptr<Scheduler> sequencedScheduler;
};
class SuperclusterData final : public GeoJSONData {
@@ -89,7 +91,7 @@ T evaluateFeature(const mapbox::feature::feature<double>& f,
// static
std::shared_ptr<GeoJSONData> GeoJSONData::create(const GeoJSON& geoJSON,
std::shared_ptr<Scheduler> scheduler,
std::shared_ptr<Scheduler> sequencedScheduler,
const Immutable<GeoJSONOptions>& options) {
constexpr double scale = util::EXTENT / util::tileSize_D;
if (options->cluster && geoJSON.is<Features>() && !geoJSON.get<Features>().empty()) {
@@ -126,7 +128,7 @@ std::shared_ptr<GeoJSONData> GeoJSONData::create(const GeoJSON& geoJSON,
vtOptions.buffer = static_cast<uint16_t>(::round(scale * options->buffer));
vtOptions.tolerance = scale * options->tolerance;
vtOptions.lineMetrics = options->lineMetrics;
return std::shared_ptr<GeoJSONData>(new GeoJSONVTData(geoJSON, vtOptions, std::move(scheduler)));
return std::shared_ptr<GeoJSONData>(new GeoJSONVTData(geoJSON, vtOptions, std::move(sequencedScheduler)));
}
GeoJSONSource::Impl::Impl(std::string id_, Immutable<GeoJSONOptions> options_)

View File

@@ -10,8 +10,8 @@
namespace mbgl {
namespace style {
Style::Style(std::shared_ptr<FileSource> fileSource, float pixelRatio)
: impl(std::make_unique<Impl>(std::move(fileSource), pixelRatio)) {}
Style::Style(std::shared_ptr<FileSource> fileSource, float pixelRatio, const TaggedScheduler& threadPool_)
: impl(std::make_unique<Impl>(std::move(fileSource), pixelRatio, threadPool_)) {}
Style::~Style() = default;

View File

@@ -30,9 +30,9 @@ namespace style {
static Observer nullObserver;
Style::Impl::Impl(std::shared_ptr<FileSource> fileSource_, float pixelRatio)
Style::Impl::Impl(std::shared_ptr<FileSource> fileSource_, float pixelRatio, const TaggedScheduler& threadPool_)
: fileSource(std::move(fileSource_)),
spriteLoader(std::make_unique<SpriteLoader>(pixelRatio)),
spriteLoader(std::make_unique<SpriteLoader>(pixelRatio, threadPool_)),
light(std::make_unique<Light>()),
observer(&nullObserver) {
spriteLoader->setObserver(this);

View File

@@ -1,5 +1,6 @@
#pragma once
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/style/transition_options.hpp>
#include <mbgl/style/observer.hpp>
@@ -36,7 +37,7 @@ class Style::Impl : public SpriteLoaderObserver,
public LightObserver,
public util::noncopyable {
public:
Impl(std::shared_ptr<FileSource>, float pixelRatio);
Impl(std::shared_ptr<FileSource>, float pixelRatio, const TaggedScheduler& threadPool_);
~Impl() override;
void loadJSON(const std::string&);

View File

@@ -169,10 +169,11 @@ GeometryTile::GeometryTile(const OverscaledTileID& id_, std::string sourceID_, c
: Tile(Kind::Geometry, id_),
ImageRequestor(parameters.imageManager),
sourceID(std::move(sourceID_)),
threadPool(Scheduler::GetBackground()),
threadPool(parameters.threadPool),
mailbox(std::make_shared<Mailbox>(*Scheduler::GetCurrent())),
worker(threadPool,
worker(parameters.threadPool, // Scheduler reference for the Actor retainer
ActorRef<GeometryTile>(*this, mailbox),
parameters.threadPool,
id_,
sourceID,
obsolete,
@@ -194,7 +195,7 @@ GeometryTile::~GeometryTile() {
imageManager->removeRequestor(*this);
if (layoutResult) {
threadPool->runOnRenderThread(
threadPool.runOnRenderThread(
[layoutResult_{std::move(layoutResult)}, atlasTextures_{std::move(atlasTextures)}]() {});
}
}

View File

@@ -106,7 +106,7 @@ protected:
private:
void markObsolete();
const std::shared_ptr<Scheduler> threadPool;
TaggedScheduler threadPool;
const std::shared_ptr<Mailbox> mailbox;
Actor<GeometryTileWorker> worker;

View File

@@ -31,6 +31,7 @@ using namespace style;
GeometryTileWorker::GeometryTileWorker(ActorRef<GeometryTileWorker> self_,
ActorRef<GeometryTile> parent_,
const TaggedScheduler& scheduler_,
OverscaledTileID id_,
std::string sourceID_,
const std::atomic<bool>& obsolete_,
@@ -39,6 +40,7 @@ GeometryTileWorker::GeometryTileWorker(ActorRef<GeometryTileWorker> self_,
const bool showCollisionBoxes_)
: self(std::move(self_)),
parent(std::move(parent_)),
scheduler(scheduler_),
id(id_),
sourceID(std::move(sourceID_)),
obsolete(obsolete_),
@@ -49,7 +51,7 @@ GeometryTileWorker::GeometryTileWorker(ActorRef<GeometryTileWorker> self_,
GeometryTileWorker::~GeometryTileWorker() {
MLN_TRACE_FUNC();
Scheduler::GetBackground()->runOnRenderThread([renderData_{std::move(renderData)}]() {});
scheduler.runOnRenderThread([renderData_{std::move(renderData)}]() {});
}
/*

View File

@@ -30,6 +30,7 @@ class GeometryTileWorker {
public:
GeometryTileWorker(ActorRef<GeometryTileWorker> self,
ActorRef<GeometryTile> parent,
const TaggedScheduler& scheduler_,
OverscaledTileID,
std::string,
const std::atomic<bool>&,
@@ -71,6 +72,7 @@ private:
ActorRef<GeometryTileWorker> self;
ActorRef<GeometryTile> parent;
TaggedScheduler scheduler;
const OverscaledTileID id;
const std::string sourceID;

View File

@@ -17,9 +17,9 @@ namespace mbgl {
RasterDEMTile::RasterDEMTile(const OverscaledTileID& id_, const TileParameters& parameters, const Tileset& tileset)
: Tile(Kind::RasterDEM, id_),
loader(*this, id_, parameters, tileset),
threadPool(Scheduler::GetBackground()),
threadPool(parameters.threadPool),
mailbox(std::make_shared<Mailbox>(*Scheduler::GetCurrent())),
worker(Scheduler::GetBackground(), ActorRef<RasterDEMTile>(*this, mailbox)) {
worker(parameters.threadPool, ActorRef<RasterDEMTile>(*this, mailbox)) {
encoding = tileset.encoding;
if (id.canonical.y == 0) {
// this tile doesn't have upper neighboring tiles so marked those as backfilled
@@ -37,7 +37,7 @@ RasterDEMTile::~RasterDEMTile() {
// The bucket has resources that need to be released on the render thread.
if (bucket) {
threadPool->runOnRenderThread([bucket_{std::move(bucket)}]() {});
threadPool.runOnRenderThread([bucket_{std::move(bucket)}]() {});
}
}

View File

@@ -93,7 +93,7 @@ private:
TileLoader<RasterDEMTile> loader;
const std::shared_ptr<Scheduler> threadPool;
TaggedScheduler threadPool;
std::shared_ptr<Mailbox> mailbox;
Actor<RasterDEMTileWorker> worker;

View File

@@ -17,16 +17,16 @@ namespace mbgl {
RasterTile::RasterTile(const OverscaledTileID& id_, const TileParameters& parameters, const Tileset& tileset)
: Tile(Kind::Raster, id_),
loader(*this, id_, parameters, tileset),
threadPool(Scheduler::GetBackground()),
threadPool(parameters.threadPool),
mailbox(std::make_shared<Mailbox>(*Scheduler::GetCurrent())),
worker(Scheduler::GetBackground(), ActorRef<RasterTile>(*this, mailbox)) {}
worker(parameters.threadPool, ActorRef<RasterTile>(*this, mailbox)) {}
RasterTile::~RasterTile() {
markObsolete();
// The bucket has resources that need to be released on the render thread.
if (bucket) {
threadPool->runOnRenderThread([bucket_{std::move(bucket)}]() {});
threadPool.runOnRenderThread([bucket_{std::move(bucket)}]() {});
}
}

View File

@@ -42,7 +42,7 @@ private:
TileLoader<RasterTile> loader;
const std::shared_ptr<Scheduler> threadPool;
TaggedScheduler threadPool;
std::shared_ptr<Mailbox> mailbox;
Actor<RasterTileWorker> worker;

View File

@@ -51,10 +51,16 @@ void TileCache::deferredRelease(std::unique_ptr<Tile>&& tile) {
// If this temporary outlives the `schedule` call, and the function is executed immediately
// by a waiting thread and is already complete, that temporary reference ends up being the
// last one and the destruction actually occurs here on this thread.
std::function<void()> func{[tile_{CaptureWrapper<Tile>{std::move(tile)}}]() {
std::function<void()> func{[tile_{CaptureWrapper<Tile>{std::move(tile)}}, this]() mutable {
tile_.item = {};
deferredDeletionsPending--;
deferredSignal.notify_all();
}};
threadPool->schedule(std::move(func));
std::unique_lock<std::mutex> counterLock(deferredSignalLock);
deferredDeletionsPending++;
threadPool.schedule(std::move(func));
}
void TileCache::add(const OverscaledTileID& key, std::unique_ptr<Tile>&& tile) {

View File

@@ -1,22 +1,33 @@
#pragma once
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/tile/tile_id.hpp>
#include <mbgl/tile/tile.hpp>
#include <list>
#include <memory>
#include <map>
#include <atomic>
#include <mutex>
#include <condition_variable>
namespace mbgl {
class Scheduler;
class TileCache {
public:
TileCache(std::shared_ptr<Scheduler> threadPool_, size_t size_ = 0)
: threadPool(std::move(threadPool_)),
TileCache(const TaggedScheduler& threadPool_, size_t size_ = 0)
: threadPool(threadPool_),
size(size_) {}
~TileCache() {
clear();
std::unique_lock<std::mutex> counterLock(deferredSignalLock);
while (deferredDeletionsPending != 0) {
deferredSignal.wait(counterLock);
}
}
/// Change the maximum size of the cache.
void setSize(size_t);
@@ -38,7 +49,10 @@ public:
private:
std::map<OverscaledTileID, std::unique_ptr<Tile>> tiles;
std::list<OverscaledTileID> orderedKeys;
std::shared_ptr<Scheduler> threadPool;
TaggedScheduler threadPool;
std::atomic<size_t> deferredDeletionsPending{0};
std::mutex deferredSignalLock;
std::condition_variable deferredSignal;
size_t size;
};

View File

@@ -11,11 +11,8 @@ namespace mbgl {
ThreadedSchedulerBase::~ThreadedSchedulerBase() = default;
void ThreadedSchedulerBase::terminate() {
// Run any leftover render jobs
runRenderJobs();
{
std::lock_guard<std::mutex> lock(mutex);
std::lock_guard<std::mutex> lock(workerMutex);
terminated = true;
}
@@ -37,48 +34,64 @@ std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) {
owningThreadPool.set(this);
while (true) {
std::unique_lock<std::mutex> lock(mutex);
if (queue.empty() && !pendingItems) {
cvEmpty.notify_all();
std::unique_lock<std::mutex> conditionLock(workerMutex);
if (!terminated && taskCount == 0) {
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++;
std::vector<std::shared_ptr<Queue>> pending;
{
// 1. Gather buckets for us to visit this iteration
std::lock_guard<std::mutex> 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<void()> tasklet;
{
std::lock_guard<std::mutex> lock(q->lock);
if (q->queue.size()) {
q->runningCount++;
tasklet = std::move(q->queue.front());
q->queue.pop();
}
if (!tasklet) continue;
}
assert(taskCount > 0);
taskCount--;
if (function) {
try {
function();
tasklet();
tasklet = {}; // destroy the function and release its captures before unblocking `waitForEmpty`
// destroy the function and release its captures before unblocking `waitForEmpty`
function = {};
if (!--pendingItems) {
std::unique_lock<std::mutex> inner_lock(mutex);
if (queue.empty()) {
cvEmpty.notify_all();
if (!--q->runningCount) {
std::lock_guard<std::mutex> lock(q->lock);
if (q->queue.empty()) {
q->cv.notify_all();
}
}
} catch (...) {
std::unique_lock<std::mutex> inner_lock(mutex);
std::lock_guard<std::mutex> 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 +105,61 @@ std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) {
}
void ThreadedSchedulerBase::schedule(std::function<void()>&& 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<std::mutex> addLock(addMutex, std::defer_lock);
if (!thisThreadIsOwned()) {
addLock.lock();
}
std::lock_guard<std::mutex> lock(mutex);
queue.push(std::move(fn));
}
cvAvailable.notify_one();
}
schedule(uniqueID, std::move(fn));
}
std::size_t ThreadedSchedulerBase::waitForEmpty(Milliseconds timeout) {
void ThreadedSchedulerBase::schedule(const util::SimpleIdentity tag, std::function<void()>&& fn) {
assert(fn);
if (!fn) return;
std::shared_ptr<Queue> q;
{
std::lock_guard<std::mutex> lock(taggedQueueLock);
auto it = taggedQueue.find(tag);
if (it == taggedQueue.end()) {
q = std::make_shared<Queue>();
taggedQueue.insert({tag, q});
} else {
q = it->second;
}
}
{
std::lock_guard<std::mutex> lock(q->lock);
q->queue.push(std::move(fn));
taskCount++;
}
cvAvailable.notify_one();
}
void ThreadedSchedulerBase::waitForEmpty(const util::SimpleIdentity 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<std::mutex> addLock(addMutex);
std::unique_lock<std::mutex> 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);
const auto tagToFind = tag.isEmpty() ? uniqueID : tag;
std::shared_ptr<Queue> q;
{
std::lock_guard<std::mutex> lock(taggedQueueLock);
auto it = taggedQueue.find(tagToFind);
if (it == taggedQueue.end()) {
return;
}
q = it->second;
}
std::unique_lock<std::mutex> 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<std::mutex> lock(taggedQueueLock);
taggedQueue.erase(tagToFind);
}
return queue.size() + pendingItems;
}
return 0;
}
} // namespace mbgl

View File

@@ -3,9 +3,12 @@
#include <mbgl/actor/mailbox.hpp>
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/util/thread_local.hpp>
#include <mbgl/util/containers.hpp>
#include <mbgl/util/identity.hpp>
#include <algorithm>
#include <condition_variable>
#include <map>
#include <mutex>
#include <queue>
#include <thread>
@@ -15,7 +18,16 @@ namespace mbgl {
class ThreadedSchedulerBase : public Scheduler {
public:
void schedule(std::function<void()>&&) 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<void()>&& fn) override;
/// @brief Schedule a task assigned to the given owner `tag`.
/// @param tag Identifier object to indicate ownership of `fn`
/// @param fn Task to run
void schedule(const util::SimpleIdentity tag, std::function<void()>&& fn) override;
const util::SimpleIdentity uniqueID;
protected:
ThreadedSchedulerBase() = default;
@@ -24,28 +36,31 @@ protected:
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 Tag of the owner to identify the collection of tasks to
// wait for. Not providing a tag waits on tasks owned by the scheduler.
void waitForEmpty(const util::SimpleIdentity = util::SimpleIdentity::Empty) override;
/// Returns true if called from a thread managed by the scheduler
bool thisThreadIsOwned() const { return owningThreadPool.get() == this; }
std::queue<std::function<void()>> 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<std::size_t> pendingItems{0};
// Points to the owning pool in owned threads
std::mutex workerMutex;
std::mutex taggedQueueLock;
util::ThreadLocal<ThreadedSchedulerBase> owningThreadPool;
std::atomic<size_t> taskCount{0};
bool terminated{false};
// Task queues bucketed by tag address
struct Queue {
std::atomic<std::size_t> runningCount; /* running tasks */
std::condition_variable cv; /* queue empty condition */
std::mutex lock; /* lock */
std::queue<std::function<void()>> queue; /* pending task queue */
};
mbgl::unordered_map<util::SimpleIdentity, std::shared_ptr<Queue>> taggedQueue;
};
/**
@@ -74,20 +89,55 @@ public:
}
}
void runOnRenderThread(std::function<void()>&& fn) override {
std::lock_guard<std::mutex> lock(renderMutex);
renderThreadQueue.push(std::move(fn));
void runOnRenderThread(const util::SimpleIdentity tag, std::function<void()>&& fn) override {
std::shared_ptr<RenderQueue> queue;
{
std::lock_guard<std::mutex> lock(taggedRenderQueueLock);
auto it = taggedRenderQueue.find(tag);
if (it != taggedRenderQueue.end()) {
queue = it->second;
} else {
queue = std::make_shared<RenderQueue>();
taggedRenderQueue.insert({tag, queue});
}
}
std::lock_guard<std::mutex> lock(queue->mutex);
queue->queue.push(std::move(fn));
}
void runRenderJobs() override {
std::lock_guard<std::mutex> lock(renderMutex);
while (renderThreadQueue.size()) {
auto fn = std::move(renderThreadQueue.front());
renderThreadQueue.pop();
void runRenderJobs(const util::SimpleIdentity tag, bool closeQueue = false) override {
std::shared_ptr<RenderQueue> queue;
std::unique_lock<std::mutex> lock(taggedRenderQueueLock);
{
auto it = taggedRenderQueue.find(tag);
if (it != taggedRenderQueue.end()) {
queue = it->second;
}
if (!closeQueue) {
lock.unlock();
}
}
if (!queue) {
return;
}
std::lock_guard<std::mutex> taskLock(queue->mutex);
while (queue->queue.size()) {
auto fn = std::move(queue->queue.front());
queue->queue.pop();
if (fn) {
fn();
}
}
if (closeQueue) {
// We hold both locks and can safely remove the queue entry
taggedRenderQueue.erase(tag);
}
}
mapbox::base::WeakPtr<Scheduler> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
@@ -95,8 +145,12 @@ public:
private:
std::vector<std::thread> threads;
std::queue<std::function<void()>> renderThreadQueue;
std::mutex renderMutex;
struct RenderQueue {
std::queue<std::function<void()>> queue;
std::mutex mutex;
};
mbgl::unordered_map<util::SimpleIdentity, std::shared_ptr<RenderQueue>> taggedRenderQueue;
std::mutex taggedRenderQueueLock;
mapbox::base::WeakPtrFactory<Scheduler> weakFactory{this};
// Do not add members here, see `WeakPtrFactory`

View File

@@ -90,10 +90,7 @@ TEST(Actor, DestructionBlocksOnSend) {
~TestScheduler() override { EXPECT_TRUE(waited.load()); }
std::size_t waitForEmpty(Milliseconds) override {
assert(false);
return 0;
}
void waitForEmpty(const util::SimpleIdentity) override { assert(false); }
void schedule(std::function<void()>&&) final {
promise.set_value();
@@ -101,6 +98,11 @@ TEST(Actor, DestructionBlocksOnSend) {
std::this_thread::sleep_for(1ms);
waited = true;
}
void schedule(const util::SimpleIdentity, std::function<void()>&& fn) override final {
schedule(std::move(fn));
}
mapbox::base::WeakPtr<Scheduler> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
mapbox::base::WeakPtrFactory<Scheduler> weakFactory{this};

View File

@@ -108,8 +108,9 @@ public:
constexpr auto numPoints = 100;
GeometryCoordinates polyline;
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
polyline.emplace_back(ipoint * util::EXTENT / numPoints,
std::sin(ipoint * 2 * M_PI / numPoints) * util::EXTENT / numLines / 2.f);
polyline.emplace_back(
ipoint * util::EXTENT / numPoints,
static_cast<int16_t>(std::sin(ipoint * 2 * M_PI / numPoints) * util::EXTENT / numLines / 2.f));
}
for (auto index{0}; index < numLines; ++index) {

View File

@@ -16,10 +16,10 @@ namespace mbgl {
class VectorTileTest {
public:
util::SimpleIdentity uniqueID;
std::shared_ptr<FileSource> fileSource = std::make_shared<FakeFileSource>();
TransformState transformState;
util::RunLoop loop;
style::Style style{fileSource, 1};
AnnotationManager annotationManager{style};
const std::shared_ptr<ImageManager> imageManager = std::make_shared<ImageManager>();
@@ -27,22 +27,30 @@ public:
Tileset tileset{{"https://example.com"}, {0, 22}, "none"};
const std::shared_ptr<Scheduler> threadPool = Scheduler::GetBackground();
TaggedScheduler threadPool;
TileParameters tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0};
TileParameters tileParameters;
style::Style style;
VectorTileTest()
: threadPool(Scheduler::GetBackground(), uniqueID),
tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0,
threadPool},
style{fileSource, 1, threadPool} {}
~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();
threadPool.runRenderJobs(true);
}
};

View File

@@ -31,12 +31,21 @@ public:
class SpriteLoaderTest {
public:
SpriteLoaderTest() = default;
SpriteLoaderTest()
: threadPool(Scheduler::GetBackground(), uniqueID),
spriteLoader(1, threadPool) {}
~SpriteLoaderTest() {
threadPool.waitForEmpty();
threadPool.runRenderJobs(true);
}
util::SimpleIdentity uniqueID;
util::RunLoop loop;
StubFileSource fileSource;
StubSpriteLoaderObserver observer;
SpriteLoader spriteLoader{1};
TaggedScheduler threadPool;
SpriteLoader spriteLoader;
void run() {
// Squelch logging.

View File

@@ -54,17 +54,18 @@ using SourceType = mbgl::style::SourceType;
class SourceTest {
public:
util::SimpleIdentity uniqueID;
util::RunLoop loop;
std::shared_ptr<StubFileSource> fileSource = std::make_shared<StubFileSource>();
StubStyleObserver styleObserver;
StubRenderSourceObserver renderSourceObserver;
Transform transform;
TransformState transformState;
Style style{fileSource, 1};
AnnotationManager annotationManager{style};
std::shared_ptr<ImageManager> imageManager = std::make_shared<ImageManager>();
std::shared_ptr<GlyphManager> glyphManager = std::make_shared<GlyphManager>();
std::shared_ptr<Scheduler> threadPool = Scheduler::GetBackground();
TaggedScheduler threadPool;
Style style;
TileParameters tileParameters(MapMode mapMode = MapMode::Continuous) {
return {1.0,
@@ -75,10 +76,13 @@ public:
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0};
0,
threadPool};
};
SourceTest() {
SourceTest()
: threadPool(Scheduler::GetBackground(), uniqueID),
style{fileSource, 1, threadPool} {
// Squelch logging.
Log::setObserver(std::make_unique<Log::NullObserver>());
@@ -88,7 +92,7 @@ public:
transformState = transform.getState();
}
~SourceTest() { threadPool->waitForEmpty(); }
~SourceTest() { threadPool.waitForEmpty(); }
void run() { loop.run(); }
@@ -783,8 +787,8 @@ public:
MOCK_METHOD1(tileSetNecessity, void(TileNecessity));
MOCK_METHOD1(tileSetMinimumUpdateInterval, void(Duration));
explicit FakeTileSource(Immutable<style::Source::Impl> impl_, std::shared_ptr<Scheduler> threadPool_)
: RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {}
explicit FakeTileSource(Immutable<style::Source::Impl> impl_, const TaggedScheduler& threadPool_)
: RenderTileSetSource(std::move(impl_), threadPool_) {}
void updateInternal(const Tileset& tileset,
const std::vector<Immutable<style::LayerProperties>>& layers,
const bool needsRendering,
@@ -887,8 +891,8 @@ TEST(Source, RenderTileSetSourceUpdate) {
class FakeRenderTileSetSource : public RenderTileSetSource {
public:
explicit FakeRenderTileSetSource(Immutable<style::Source::Impl> impl_, std::shared_ptr<Scheduler> threadPool_)
: RenderTileSetSource(std::move(impl_), std::move(threadPool_)) {}
explicit FakeRenderTileSetSource(Immutable<style::Source::Impl> impl_, TaggedScheduler threadPool_)
: RenderTileSetSource(std::move(impl_), threadPool_) {}
MOCK_METHOD0(mockedUpdateInternal, void());

View File

@@ -23,7 +23,7 @@ TEST(Style, Properties) {
util::RunLoop loop;
auto fileSource = std::make_shared<StubFileSource>();
Style::Impl style{fileSource, 1.0};
Style::Impl style{fileSource, 1.0, {Scheduler::GetBackground(), {}}};
style.loadJSON(R"STYLE({"name": "Test"})STYLE");
ASSERT_EQ("Test", style.getName());
@@ -65,7 +65,7 @@ TEST(Style, DuplicateSource) {
util::RunLoop loop;
auto fileSource = std::make_shared<StubFileSource>();
Style::Impl style{fileSource, 1.0};
Style::Impl style{fileSource, 1.0, {Scheduler::GetBackground(), {}}};
style.loadJSON(util::read_file("test/fixtures/resources/style-unused-sources.json"));
@@ -85,7 +85,7 @@ TEST(Style, RemoveSourceInUse) {
FixtureLog log;
auto fileSource = std::make_shared<StubFileSource>();
Style::Impl style{fileSource, 1.0};
Style::Impl style{fileSource, 1.0, {Scheduler::GetBackground(), {}}};
style.loadJSON(util::read_file("test/fixtures/resources/style-unused-sources.json"));
style.addSource(std::make_unique<VectorSource>("sourceId", "mptiler://tiles/contours"));
@@ -113,7 +113,7 @@ TEST(Style, RemoveSourceInUse) {
TEST(Style, SourceImplsOrder) {
util::RunLoop loop;
auto fileSource = std::make_shared<StubFileSource>();
Style::Impl style{fileSource, 1.0};
Style::Impl style{fileSource, 1.0, {Scheduler::GetBackground(), {}}};
style.addSource(std::make_unique<VectorSource>("c", "mptiler://tiles/contours"));
style.addSource(std::make_unique<VectorSource>("b", "mptiler://tiles/contours"));
@@ -135,7 +135,7 @@ TEST(Style, SourceImplsOrder) {
TEST(Style, AddRemoveImage) {
util::RunLoop loop;
auto fileSource = std::make_shared<StubFileSource>();
Style::Impl style{fileSource, 1.0};
Style::Impl style{fileSource, 1.0, {Scheduler::GetBackground(), {}}};
style.addImage(std::make_unique<style::Image>("one", PremultipliedImage({16, 16}), 2.0f));
style.addImage(std::make_unique<style::Image>("two", PremultipliedImage({16, 16}), 2.0f));
style.addImage(std::make_unique<style::Image>("three", PremultipliedImage({16, 16}), 2.0f));
@@ -152,7 +152,7 @@ TEST(Style, AddRemoveRemoveImage) {
// regression test for https://github.com/mapbox/mapbox-gl-native/pull/16391
util::RunLoop loop;
auto fileSource = std::make_shared<StubFileSource>();
Style::Impl style{fileSource, 1.0};
Style::Impl style{fileSource, 1.0, {Scheduler::GetBackground(), {}}};
style.addImage(std::make_unique<style::Image>("one", PremultipliedImage({16, 16}), 2.0f));
style.addImage(std::make_unique<style::Image>("two", PremultipliedImage({16, 16}), 2.0f));
style.addImage(std::make_unique<style::Image>("three", PremultipliedImage({16, 16}), 2.0f));

View File

@@ -292,7 +292,7 @@ TEST(Layer, DuplicateLayer) {
// Setup style
auto fileSource = std::make_shared<StubFileSource>();
Style::Impl style{fileSource, 1.0};
Style::Impl style{fileSource, 1.0, {Scheduler::GetBackground(), {}}};
style.loadJSON(util::read_file("test/fixtures/resources/style-unused-sources.json"));
// Add initial layer
@@ -313,7 +313,7 @@ TEST(Layer, IncompatibleLayer) {
// Setup style
auto fileSource = std::make_shared<StubFileSource>();
Style::Impl style{fileSource, 1.0};
Style::Impl style{fileSource, 1.0, {Scheduler::GetBackground(), {}}};
style.loadJSON(util::read_file("test/fixtures/resources/style-unused-sources.json"));
// Try to add duplicate

View File

@@ -22,23 +22,28 @@ using namespace mbgl::style;
class CustomTileTest {
public:
util::SimpleIdentity uniqueID;
std::shared_ptr<FileSource> fileSource = std::make_shared<FakeFileSource>();
TransformState transformState;
util::RunLoop loop;
style::Style style{fileSource, 1};
AnnotationManager annotationManager{style};
std::shared_ptr<ImageManager> imageManager = std::make_shared<ImageManager>();
std::shared_ptr<GlyphManager> glyphManager = std::make_shared<GlyphManager>();
TileParameters tileParameters;
style::Style style;
TileParameters tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0};
CustomTileTest()
: tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0,
{Scheduler::GetBackground(), uniqueID}},
style{fileSource, 1, tileParameters.threadPool} {}
};
TEST(CustomGeometryTile, InvokeFetchTile) {

View File

@@ -22,24 +22,29 @@ using namespace mbgl::style;
class GeoJSONTileTest {
public:
util::SimpleIdentity uniqueID;
std::shared_ptr<FileSource> fileSource = std::make_shared<FakeFileSource>();
TransformState transformState;
util::RunLoop loop;
style::Style style{fileSource, 1};
AnnotationManager annotationManager{style};
std::shared_ptr<ImageManager> imageManager = std::make_shared<ImageManager>();
std::shared_ptr<GlyphManager> glyphManager = std::make_shared<GlyphManager>();
Tileset tileset{{"https://example.com"}, {0, 22}, "none"};
TileParameters tileParameters;
style::Style style;
TileParameters tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0};
GeoJSONTileTest()
: tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0,
{Scheduler::GetBackground(), uniqueID}},
style{fileSource, 1, tileParameters.threadPool} {}
};
namespace {

View File

@@ -16,24 +16,29 @@ using namespace mbgl;
class RasterDEMTileTest {
public:
util::SimpleIdentity uniqueID;
std::shared_ptr<FileSource> fileSource = std::make_shared<FakeFileSource>();
TransformState transformState;
util::RunLoop loop;
style::Style style{fileSource, 1};
AnnotationManager annotationManager{style};
std::shared_ptr<ImageManager> imageManager = std::make_shared<ImageManager>();
std::shared_ptr<GlyphManager> glyphManager = std::make_shared<GlyphManager>();
Tileset tileset{{"https://example.com"}, {0, 22}, "none"};
TileParameters tileParameters;
style::Style style;
TileParameters tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0};
RasterDEMTileTest()
: tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0,
{Scheduler::GetBackground(), uniqueID}},
style{fileSource, 1, tileParameters.threadPool} {}
};
TEST(RasterDEMTile, setError) {

View File

@@ -16,24 +16,29 @@ using namespace mbgl;
class RasterTileTest {
public:
util::SimpleIdentity uniqueID;
std::shared_ptr<FileSource> fileSource = std::make_shared<FakeFileSource>();
TransformState transformState;
util::RunLoop loop;
style::Style style{fileSource, 1};
AnnotationManager annotationManager{style};
std::shared_ptr<ImageManager> imageManager = std::make_shared<ImageManager>();
std::shared_ptr<GlyphManager> glyphManager = std::make_shared<GlyphManager>();
Tileset tileset{{"https://example.com"}, {0, 22}, "none"};
TileParameters tileParameters;
style::Style style;
TileParameters tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0};
RasterTileTest()
: tileParameters{1.0,
MapDebugOptions(),
transformState,
fileSource,
MapMode::Continuous,
annotationManager.makeWeakPtr(),
imageManager,
glyphManager,
0,
{Scheduler::GetBackground(), uniqueID}},
style{fileSource, 1, tileParameters.threadPool} {}
};
TEST(RasterTile, setError) {

View File

@@ -45,48 +45,52 @@ public:
TEST(TileCache, Smoke) {
VectorTileTest test;
TileCache cache(Scheduler::GetBackground(), 1);
const OverscaledTileID id(0, 0, 0);
auto tile = std::make_unique<VectorTileMock>(id, "source", test.tileParameters, test.tileset);
{
TileCache cache(test.threadPool, 1);
const OverscaledTileID id(0, 0, 0);
auto tile = std::make_unique<VectorTileMock>(id, "source", test.tileParameters, test.tileset);
cache.add(id, std::move(tile));
EXPECT_TRUE(cache.has(id));
cache.clear();
EXPECT_FALSE(cache.has(id));
cache.add(id, std::move(tile));
EXPECT_TRUE(cache.has(id));
cache.clear();
EXPECT_FALSE(cache.has(id));
}
}
TEST(TileCache, Issue15926) {
VectorTileTest test;
TileCache cache(test.threadPool, 2);
const OverscaledTileID id0(0, 0, 0);
const OverscaledTileID id1(1, 0, 0);
auto tile1 = std::make_unique<VectorTileMock>(id0, "source", test.tileParameters, test.tileset);
auto tile2 = std::make_unique<VectorTileMock>(id0, "source", test.tileParameters, test.tileset);
auto tile3 = std::make_unique<VectorTileMock>(id1, "source", test.tileParameters, test.tileset);
auto tile4 = std::make_unique<VectorTileMock>(id0, "source", test.tileParameters, test.tileset);
const auto tile1Id = tile1->uniqueId;
{
TileCache cache(test.threadPool, 2);
const OverscaledTileID id0(0, 0, 0);
const OverscaledTileID id1(1, 0, 0);
auto tile1 = std::make_unique<VectorTileMock>(id0, "source", test.tileParameters, test.tileset);
auto tile2 = std::make_unique<VectorTileMock>(id0, "source", test.tileParameters, test.tileset);
auto tile3 = std::make_unique<VectorTileMock>(id1, "source", test.tileParameters, test.tileset);
auto tile4 = std::make_unique<VectorTileMock>(id0, "source", test.tileParameters, test.tileset);
const auto tile1Id = tile1->uniqueId;
// add
cache.add(id0, std::move(tile1));
EXPECT_TRUE(cache.has(id0));
// add
cache.add(id0, std::move(tile1));
EXPECT_TRUE(cache.has(id0));
// adding a key already present doesn't replace the existing item
cache.add(id0, std::move(tile2));
EXPECT_EQ(tile1Id, static_cast<VectorTileMock*>(cache.get(id0))->uniqueId);
// adding a key already present doesn't replace the existing item
cache.add(id0, std::move(tile2));
EXPECT_EQ(tile1Id, static_cast<VectorTileMock*>(cache.get(id0))->uniqueId);
// Evict on add
cache.setSize(1);
cache.add(id1, std::move(tile3));
EXPECT_FALSE(cache.has(id0));
EXPECT_TRUE(cache.has(id1));
// Evict on add
cache.setSize(1);
cache.add(id1, std::move(tile3));
EXPECT_FALSE(cache.has(id0));
EXPECT_TRUE(cache.has(id1));
// Evict due to size limit change
cache.setSize(2);
cache.add(id0, std::move(tile4));
EXPECT_TRUE(cache.has(id0));
EXPECT_TRUE(cache.has(id1));
cache.setSize(1);
// older item should be evicted
EXPECT_TRUE(cache.has(id0));
EXPECT_FALSE(cache.has(id1));
// Evict due to size limit change
cache.setSize(2);
cache.add(id0, std::move(tile4));
EXPECT_TRUE(cache.has(id0));
EXPECT_TRUE(cache.has(id1));
cache.setSize(1);
// older item should be evicted
EXPECT_TRUE(cache.has(id0));
EXPECT_FALSE(cache.has(id1));
}
}

View File

@@ -100,12 +100,13 @@ TEST(AsyncTask, DestroyAfterSignaling) {
TEST(AsyncTask, RequestCoalescingMultithreaded) {
RunLoop loop;
util::SimpleIdentity id;
unsigned count = 0, numThreads = 25;
AsyncTask async([&count] { ++count; });
std::shared_ptr<Scheduler> retainer = Scheduler::GetBackground();
auto mailbox = std::make_shared<Mailbox>(*retainer);
TaggedScheduler retainer = {Scheduler::GetBackground(), id};
auto mailbox = std::make_shared<Mailbox>(retainer);
TestWorker worker(&async);
ActorRef<TestWorker> workerRef(worker, mailbox);
@@ -127,14 +128,15 @@ TEST(AsyncTask, RequestCoalescingMultithreaded) {
TEST(AsyncTask, ThreadSafety) {
RunLoop loop;
mbgl::util::SimpleIdentity id;
unsigned count = 0, numThreads = 25;
std::atomic_uint completed(numThreads);
AsyncTask async([&count] { ++count; });
std::shared_ptr<Scheduler> retainer = Scheduler::GetBackground();
auto mailbox = std::make_shared<Mailbox>(*retainer);
TaggedScheduler retainer = {Scheduler::GetBackground(), id};
auto mailbox = std::make_shared<Mailbox>(retainer);
TestWorker worker(&async);
ActorRef<TestWorker> workerRef(worker, mailbox);
@@ -168,7 +170,7 @@ TEST(AsyncTask, scheduleAndReplyValue) {
};
std::shared_ptr<Scheduler> sheduler = Scheduler::GetBackground();
sheduler->scheduleAndReplyValue(runInBackground, onResult);
sheduler->scheduleAndReplyValue(util::SimpleIdentity::Empty, runInBackground, onResult);
loop.run();
}

View File

@@ -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,28 +385,14 @@ 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<std::mutex> outerLock(mutex);
pool->schedule([&] { std::lock_guard<std::mutex> innerLock(mutex); });
// should always time out
EXPECT_EQ(1, pool->waitForEmpty(Milliseconds(100)));
}
EXPECT_EQ(0, pool->waitForEmpty());
pool->waitForEmpty();
}
TEST(Thread, PoolWaitException) {
const auto id = util::SimpleIdentity{};
auto pool = Scheduler::GetBackground();
std::atomic<int> caught{0};
@@ -414,7 +400,7 @@ TEST(Thread, PoolWaitException) {
constexpr int threadCount = 3;
for (int i = 0; i < threadCount; ++i) {
pool->schedule([=] {
pool->schedule(id, [=] {
std::this_thread::sleep_for(Milliseconds(i));
if (i & 1) {
throw std::runtime_error("test");
@@ -425,7 +411,7 @@ TEST(Thread, PoolWaitException) {
}
// Exceptions shouldn't cause deadlocks by, e.g., abandoning locks.
EXPECT_EQ(0, pool->waitForEmpty());
pool->waitForEmpty(id);
EXPECT_EQ(threadCount, caught);
}
@@ -434,8 +420,55 @@ 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
std::function<void()> makeCounterThread(TaggedScheduler& pool,
std::atomic<bool>* stopFlag,
std::atomic<size_t>* counter) {
return [=]() mutable {
std::this_thread::sleep_for(std::chrono::microseconds(10));
(*counter)++;
if (!*stopFlag) {
pool.schedule(makeCounterThread(pool, stopFlag, counter));
}
};
}
TEST(Thread, TaggedPools) {
TaggedScheduler poolTag1{Scheduler::GetBackground(), {}};
TaggedScheduler poolTag2{Scheduler::GetBackground(), {}};
std::atomic<bool> stopTasks1{false};
std::atomic<bool> stopTasks2{false};
std::atomic<size_t> runCount1{0};
std::atomic<size_t> runCount2{0};
// Run a bunch of tasks on two different pool tags (that share the same thread pool)
for (auto i = 0; i < 50; i++) {
poolTag1.schedule(makeCounterThread(poolTag1, &stopTasks1, &runCount1));
poolTag2.schedule(makeCounterThread(poolTag2, &stopTasks2, &runCount2));
}
std::this_thread::sleep_for(std::chrono::milliseconds(150));
// Stop tasks on queue 1 and wait for it to empty
stopTasks1 = true;
poolTag1.waitForEmpty();
const auto totalRuns1 = runCount1.load();
// Do the same for queue 2
stopTasks2 = true;
poolTag2.waitForEmpty();
const auto totalRuns2 = runCount2.load();
// No other tasks in queue 1 ran while waiting on queue 2.
ASSERT_TRUE(totalRuns1 == runCount1);
std::this_thread::sleep_for(std::chrono::milliseconds(150));
// Same for queue 2
ASSERT_TRUE(totalRuns2 == runCount2);
}