Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[core] Use an actor model for tile worker concurrency
Browse files Browse the repository at this point in the history
  • Loading branch information
jfirebaugh committed Sep 16, 2016
1 parent 0bd66d4 commit 41bbd4e
Show file tree
Hide file tree
Showing 41 changed files with 1,191 additions and 818 deletions.
18 changes: 14 additions & 4 deletions cmake/core-files.cmake
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
# Do not edit. Regenerate this with ./scripts/generate-core-files.sh

set(MBGL_CORE_FILES
# actor
src/mbgl/actor/actor.hpp
src/mbgl/actor/actor_ref.hpp
src/mbgl/actor/mailbox.cpp
src/mbgl/actor/mailbox.hpp
src/mbgl/actor/message.hpp
src/mbgl/actor/scheduler.hpp
src/mbgl/actor/thread_pool.cpp
src/mbgl/actor/thread_pool.hpp

# algorithm
src/mbgl/algorithm/covered_by_children.hpp
src/mbgl/algorithm/generate_clip_ids.cpp
Expand Down Expand Up @@ -365,8 +375,12 @@ set(MBGL_CORE_FILES
src/mbgl/tile/geometry_tile.hpp
src/mbgl/tile/geometry_tile_data.cpp
src/mbgl/tile/geometry_tile_data.hpp
src/mbgl/tile/geometry_tile_worker.cpp
src/mbgl/tile/geometry_tile_worker.hpp
src/mbgl/tile/raster_tile.cpp
src/mbgl/tile/raster_tile.hpp
src/mbgl/tile/raster_tile_worker.cpp
src/mbgl/tile/raster_tile_worker.hpp
src/mbgl/tile/tile.cpp
src/mbgl/tile/tile.hpp
src/mbgl/tile/tile_cache.cpp
Expand All @@ -376,8 +390,6 @@ set(MBGL_CORE_FILES
src/mbgl/tile/tile_loader.hpp
src/mbgl/tile/tile_loader_impl.hpp
src/mbgl/tile/tile_observer.hpp
src/mbgl/tile/tile_worker.cpp
src/mbgl/tile/tile_worker.hpp
src/mbgl/tile/vector_tile.cpp
src/mbgl/tile/vector_tile.hpp

Expand Down Expand Up @@ -472,6 +484,4 @@ set(MBGL_CORE_FILES
src/mbgl/util/work_queue.cpp
src/mbgl/util/work_queue.hpp
src/mbgl/util/work_request.cpp
src/mbgl/util/worker.cpp
src/mbgl/util/worker.hpp
)
12 changes: 9 additions & 3 deletions cmake/test-files.cmake
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Do not edit. Regenerate this with ./scripts/generate-test-files.sh

set(MBGL_TEST_FILES
# actor
test/actor/actor.cpp
test/actor/actor_ref.cpp

# algorithm
test/algorithm/covered_by_children.cpp
test/algorithm/generate_clip_ids.cpp
Expand Down Expand Up @@ -43,10 +47,12 @@ set(MBGL_TEST_FILES

# src/mbgl/test
test/src/mbgl/test/conversion_stubs.hpp
test/src/mbgl/test/fake_file_source.hpp
test/src/mbgl/test/fixture_log_observer.cpp
test/src/mbgl/test/fixture_log_observer.hpp
test/src/mbgl/test/stub_file_source.cpp
test/src/mbgl/test/stub_file_source.hpp
test/src/mbgl/test/stub_layer_observer.hpp
test/src/mbgl/test/stub_style_observer.hpp
test/src/mbgl/test/test.cpp
test/src/mbgl/test/util.cpp
Expand All @@ -63,6 +69,9 @@ set(MBGL_TEST_FILES
test/storage/online_file_source.cpp
test/storage/resource.cpp

# style/conversion
test/style/conversion/geojson_options.cpp

# style
test/style/filter.cpp
test/style/functions.cpp
Expand All @@ -73,9 +82,6 @@ set(MBGL_TEST_FILES
test/style/style_parser.cpp
test/style/tile_source.cpp
test/style/variant.cpp

# style conversion
test/style/conversion/geojson_options.cpp

# text
test/text/quads.cpp
Expand Down
15 changes: 13 additions & 2 deletions include/mbgl/util/run_loop.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <mbgl/actor/scheduler.hpp>
#include <mbgl/actor/mailbox.hpp>
#include <mbgl/util/noncopyable.hpp>
#include <mbgl/util/util.hpp>
#include <mbgl/util/work_task.hpp>
Expand All @@ -16,7 +18,8 @@ namespace util {

typedef void * LOOP_HANDLE;

class RunLoop : private util::noncopyable {
class RunLoop : public Scheduler,
private util::noncopyable {
public:
enum class Type : uint8_t {
Default,
Expand All @@ -31,7 +34,7 @@ class RunLoop : private util::noncopyable {
};

RunLoop(Type type = Type::Default);
~RunLoop();
~RunLoop() override;

static RunLoop* Get();
static LOOP_HANDLE getLoopHandle();
Expand Down Expand Up @@ -78,6 +81,14 @@ class RunLoop : private util::noncopyable {

void push(std::shared_ptr<WorkTask>);

void schedule(std::weak_ptr<Mailbox> mailbox) override {
invoke([mailbox] () {
if (auto locked = mailbox.lock()) {
locked->receive();
}
});
}

void withMutex(std::function<void()>&& fn) {
std::lock_guard<std::mutex> lock(mutex);
fn();
Expand Down
76 changes: 76 additions & 0 deletions src/mbgl/actor/actor.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#pragma once

#include <mbgl/actor/mailbox.hpp>
#include <mbgl/actor/message.hpp>
#include <mbgl/actor/actor_ref.hpp>
#include <mbgl/util/noncopyable.hpp>

#include <memory>

namespace mbgl {

/*
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 a message to the object
(using `invoke`), passing a pointer to the member function to call and arguments which
are then forwarded to the actor.
The actor receives messages sent to it asynchronously, in a manner defined its `Scheduler`.
To store incoming messages before their receipt, each actor has a `Mailbox`, which acts as
a FIFO queue. Messages sent from actor S to actor R are guaranteed to be processed in the
order sent. However, relative order of messages sent by two *different* actors S1 and S2
to R is *not* guaranteed (and can't be: S1 and S2 may be acting asynchronously with respect
to each other).
Construction and destruction of an actor is currently synchronous: the corresponding `O`
object is constructed synchronously by the `Actor` constructor, and destructed synchronously
by the `~Actor` destructor, after ensuring that the `O` is not currently receiving an
asynchronous message. (Construction and destruction may change to be asynchronous in the
future.)
An `Actor<O>` can be converted to an `ActorRef<O>`, a non-owning value object representing
a (weak) reference to the actor. Messages can be sent via the `Ref` as well.
It's safe -- and encouraged -- to pass `Ref`s between actors via messages. This is how two-way
communication and other forms of collaboration between multiple actors is accomplished.
It's safe for a `Ref` to outlive its `Actor` -- the reference is "weak", and does not extend
the lifetime of the owning Actor, and sending a message to a `Ref` whose `Actor` has died is
a no-op. (In the future, a dead-letters queue or log may be implemented.)
Please don't send messages that contain shared pointers or references. That subverts the
purpose of the actor model: prohibiting direct concurrent access to shared state.
*/

template <class Object>
class Actor : public util::noncopyable {
public:
template <class... Args>
Actor(Scheduler& scheduler, Args&&... args_)
: mailbox(std::make_shared<Mailbox>(scheduler)),
object(self(), std::forward<Args>(args_)...) {
}

~Actor() {
mailbox->close();
}

template <typename Fn, class... Args>
void invoke(Fn fn, Args&&... args) {
mailbox->push(actor::makeMessage(object, fn, std::forward<Args>(args)...));
}

ActorRef<std::decay_t<Object>> self() {
return ActorRef<std::decay_t<Object>>(object, mailbox);
}

operator ActorRef<std::decay_t<Object>>() {
return self();
}

private:
std::shared_ptr<Mailbox> mailbox;
Object object;
};

} // namespace mbgl
43 changes: 43 additions & 0 deletions src/mbgl/actor/actor_ref.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#pragma once

#include <mbgl/actor/mailbox.hpp>
#include <mbgl/actor/message.hpp>

#include <memory>

namespace mbgl {

/*
An `ActorRef<O>` is a *non*-owning, weak reference to an actor of type `O`. You can send it
messages just like an `Actor<O>`. It's a value object: safe to copy and pass between actors
via messages.
An `ActorRef<O>` does not extend the lifetime of the corresponding `Actor<O>`. That's determined
entirely by whichever object owns the `Actor<O>` -- the actor's "supervisor".
It's safe for a `Ref` to outlive its `Actor` -- the reference is "weak", and does not extend
the lifetime of the owning Actor, and sending a message to a `Ref` whose `Actor` has died is
a no-op. (In the future, a dead-letters queue or log may be implemented.)
*/

template <class Object>
class ActorRef {
public:
ActorRef(Object& object_, std::weak_ptr<Mailbox> weakMailbox_)
: object(object_),
weakMailbox(std::move(weakMailbox_)) {
}

template <typename Fn, class... Args>
void invoke(Fn fn, Args&&... args) {
if (auto mailbox = weakMailbox.lock()) {
mailbox->push(actor::makeMessage(object, fn, std::forward<Args>(args)...));
}
}

private:
Object& object;
std::weak_ptr<Mailbox> weakMailbox;
};

} // namespace mbgl
55 changes: 55 additions & 0 deletions src/mbgl/actor/mailbox.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include <mbgl/actor/mailbox.hpp>
#include <mbgl/actor/message.hpp>
#include <mbgl/actor/scheduler.hpp>

#include <cassert>

namespace mbgl {

Mailbox::Mailbox(Scheduler& scheduler_)
: scheduler(scheduler_) {
}

void Mailbox::push(std::unique_ptr<Message> message) {
assert(!closing);

std::lock_guard<std::mutex> queueLock(queueMutex);
bool wasEmpty = queue.empty();
queue.push(std::move(message));
if (wasEmpty) {
scheduler.schedule(shared_from_this());
}
}

void Mailbox::close() {
// Block until the scheduler is guaranteed not to be executing receive().
std::lock_guard<std::mutex> closingLock(closingMutex);
closing = true;
}

void Mailbox::receive() {
std::lock_guard<std::mutex> closingLock(closingMutex);

if (closing) {
return;
}

std::unique_ptr<Message> message;
bool wasEmpty;

{
std::lock_guard<std::mutex> queueLock(queueMutex);
assert(!queue.empty());
message = std::move(queue.front());
queue.pop();
wasEmpty = queue.empty();
}

(*message)();

if (!wasEmpty) {
scheduler.schedule(shared_from_this());
}
}

} // namespace mbgl
31 changes: 31 additions & 0 deletions src/mbgl/actor/mailbox.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include <memory>
#include <mutex>
#include <queue>

namespace mbgl {

class Scheduler;
class Message;

class Mailbox : public std::enable_shared_from_this<Mailbox> {
public:
Mailbox(Scheduler&);

void push(std::unique_ptr<Message>);

void close();
void receive();

private:
Scheduler& scheduler;

std::mutex closingMutex;
bool closing { false };

std::mutex queueMutex;
std::queue<std::unique_ptr<Message>> queue;
};

} // namespace mbgl
48 changes: 48 additions & 0 deletions src/mbgl/actor/message.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#pragma once

#include <utility>

namespace mbgl {

// A movable type-erasing function wrapper. This allows to store arbitrary invokable
// things (like std::function<>, or the result of a movable-only std::bind()) in the queue.
// Source: http://stackoverflow.com/a/29642072/331379
class Message {
public:
virtual ~Message() = default;
virtual void operator()() = 0;
};

template <class Object, class MemberFn, class ArgsTuple>
class MessageImpl : public Message {
public:
MessageImpl(Object& object_, MemberFn memberFn_, ArgsTuple argsTuple_)
: object(object_),
memberFn(memberFn_),
argsTuple(std::move(argsTuple_)) {
}

void operator()() override {
invoke(std::make_index_sequence<std::tuple_size<ArgsTuple>::value>());
}

template <std::size_t... I>
void invoke(std::index_sequence<I...>) {
(object.*memberFn)(std::move(std::get<I>(argsTuple))...);
}

Object& object;
MemberFn memberFn;
ArgsTuple argsTuple;
};

namespace actor {

template <class Object, class MemberFn, class... Args>
std::unique_ptr<Message> makeMessage(Object& object, MemberFn memberFn, Args&&... args) {
auto tuple = std::make_tuple(std::forward<Args>(args)...);
return std::make_unique<MessageImpl<Object, MemberFn, decltype(tuple)>>(object, memberFn, std::move(tuple));
}

} // namespace actor
} // namespace mbgl
Loading

0 comments on commit 41bbd4e

Please sign in to comment.