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

Commit

Permalink
Join worker tasks before destroying TileData
Browse files Browse the repository at this point in the history
Fixes #1309
  • Loading branch information
jfirebaugh committed May 4, 2015
1 parent e476fcb commit 5c7867f
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 40 deletions.
6 changes: 5 additions & 1 deletion src/mbgl/map/live_tile_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ LiveTileData::LiveTileData(const TileID& id_,
state = State::loaded;
}

LiveTileData::~LiveTileData() {}
LiveTileData::~LiveTileData() {
// Cancel in most derived class destructor so that worker tasks are joined before
// any member data goes away.
cancel();
}

void LiveTileData::parse() {
if (state != State::loaded) {
Expand Down
3 changes: 3 additions & 0 deletions src/mbgl/map/raster_tile_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ RasterTileData::RasterTileData(const TileID& id_, TexturePool &texturePool,
}

RasterTileData::~RasterTileData() {
// Cancel in most derived class destructor so that worker tasks are joined before
// any member data goes away.
cancel();
}

void RasterTileData::parse() {
Expand Down
18 changes: 4 additions & 14 deletions src/mbgl/map/tile_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,11 @@ void TileData::cancel() {
env.cancelRequest(req);
req = nullptr;
}
if (workRequest) {
workRequest.join();
}
}

void TileData::reparse(Worker& worker, std::function<void()> callback) {
util::ptr<TileData> tile = shared_from_this();
worker.send(
[this]() {
EnvironmentScope scope(env, ThreadType::TileWorker, "TileWorker_" + name);
parse();
},
[tile, callback]() {
// `tile` is bound in this lambda to ensure that if it's the last owning pointer,
// destruction happens on the map thread, not the worker thread. It is _not_ bound
// in the above lambda, because we do not want the possibility to arise that the
// after callback could execute and release the penultimate reference before the
// work callback has been destructed.
callback();
});
workRequest = worker.send([this] { parse(); }, callback);
}
6 changes: 4 additions & 2 deletions src/mbgl/map/tile_data.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <mbgl/util/noncopyable.hpp>
#include <mbgl/util/ptr.hpp>
#include <mbgl/util/work_request.hpp>

#include <atomic>
#include <string>
Expand All @@ -21,8 +22,7 @@ class StyleLayer;
class Request;
class Worker;

class TileData : public std::enable_shared_from_this<TileData>,
private util::noncopyable {
class TileData : private util::noncopyable {
public:
enum class State {
invalid,
Expand Down Expand Up @@ -60,6 +60,8 @@ class TileData : public std::enable_shared_from_this<TileData>,
Request *req = nullptr;
std::string data;

WorkRequest workRequest;

// Contains the tile ID string for painting debug information.
DebugFontBuffer debugFontBuffer;

Expand Down
3 changes: 3 additions & 0 deletions src/mbgl/map/vector_tile_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ VectorTileData::VectorTileData(const TileID& id_,
}

VectorTileData::~VectorTileData() {
// Cancel in most derived class destructor so that worker tasks are joined before
// any member data goes away.
cancel();
glyphAtlas.removeGlyphs(reinterpret_cast<uintptr_t>(this));
}

Expand Down
21 changes: 5 additions & 16 deletions src/mbgl/util/run_loop.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,10 @@ class RunLoop : private util::noncopyable {
RunLoop* outer = current.get();
assert(outer);

invoke([fn, callback, outer] {
/*
With C++14, we could write:
outer->invoke([cb = std::move(callback), result = std::move(fn())] () mutable {
cb(std::move(result));
});
Instead we're using a workaround with std::bind
to obtain move-capturing semantics with C++11:
http://stackoverflow.com/a/12744730/52207
*/
outer->invoke(std::bind([] (std::function<void (R)>& cb, R& result) {
cb(std::move(result));
}, std::move(callback), std::move(fn())));
invoke([fn = std::move(fn), callback = std::move(callback), outer] () mutable {
outer->invoke([callback = std::move(callback), result = std::move(fn())] () mutable {
callback(std::move(result));
});
});
}

Expand All @@ -57,7 +46,7 @@ class RunLoop : private util::noncopyable {
RunLoop* outer = current.get();
assert(outer);

invoke([fn, callback, outer] {
invoke([fn = std::move(fn), callback = std::move(callback), outer] () mutable {
fn();
outer->invoke(std::move(callback));
});
Expand Down
4 changes: 2 additions & 2 deletions src/mbgl/util/thread.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ class Thread {
// Invoke object->fn(args...) in the runloop thread, then invoke callback(result) in the current thread.
template <typename Fn, class R, class... Args>
void invokeWithResult(Fn fn, std::function<void (R)>&& callback, Args&&... args) {
loop->invokeWithResult(std::bind(fn, object, args...), std::move(callback));
loop->invokeWithResult(std::bind(fn, object, std::move(args)...), std::move(callback));
}

// Invoke object->fn(args...) in the runloop thread, then invoke callback() in the current thread.
template <typename Fn, class... Args>
void invokeWithResult(Fn fn, std::function<void ()>&& callback, Args&&... args) {
loop->invokeWithResult(std::bind(fn, object, args...), std::move(callback));
loop->invokeWithResult(std::bind(fn, object, std::move(args)...), std::move(callback));
}

// Invoke object->fn(args...) in the runloop thread, and wait for the result.
Expand Down
34 changes: 34 additions & 0 deletions src/mbgl/util/work_request.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include <mbgl/util/work_request.hpp>

namespace mbgl {

WorkRequest::WorkRequest() = default;

WorkRequest::WorkRequest(Future&& future)
: complete(std::move(future)) {
}

WorkRequest::WorkRequest(WorkRequest&& o)
: complete(std::move(o.complete)) {
}

WorkRequest::~WorkRequest() {
if (complete.valid()) {
complete.get();
}
}

WorkRequest& WorkRequest::operator=(WorkRequest&& o) {
complete = std::move(o.complete);
return *this;
}

WorkRequest::operator bool() const {
return complete.valid();
}

void WorkRequest::join() {
complete.get();
}

}
31 changes: 31 additions & 0 deletions src/mbgl/util/work_request.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#ifndef MBGL_UTIL_WORK_REQUEST
#define MBGL_UTIL_WORK_REQUEST

#include <mbgl/util/noncopyable.hpp>

#include <future>

namespace mbgl {

class WorkRequest : public util::noncopyable {
public:
using Future = std::future<void>;

WorkRequest();
WorkRequest(Future&&);
WorkRequest(WorkRequest&&);
~WorkRequest();

WorkRequest& operator=(WorkRequest&&);
operator bool() const;

// Wait for the worker task to complete.
void join();

private:
Future complete;
};

}

#endif
14 changes: 10 additions & 4 deletions src/mbgl/util/worker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
#include <mbgl/platform/platform.hpp>

#include <cassert>
#include <future>

namespace mbgl {

class Worker::Impl {
public:
Impl(uv_loop_t*) {}

void doWork(Fn work) {
work();
void doWork(std::packaged_task<void ()>& task) {
task();
}
};

Expand All @@ -22,9 +23,14 @@ Worker::Worker(std::size_t count) {

Worker::~Worker() = default;

void Worker::send(Fn&& work, Fn&& after) {
threads[current]->invokeWithResult(&Worker::Impl::doWork, std::move(after), std::move(work));
WorkRequest Worker::send(Fn work, Fn after) {
std::packaged_task<void ()> task(work);
std::future<void> future = task.get_future();

threads[current]->invokeWithResult(&Worker::Impl::doWork, std::move(after), task);

current = (current + 1) % threads.size();
return WorkRequest(std::move(future));
}

}
11 changes: 10 additions & 1 deletion src/mbgl/util/worker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define MBGL_UTIL_WORKER

#include <mbgl/util/noncopyable.hpp>
#include <mbgl/util/work_request.hpp>
#include <mbgl/util/thread.hpp>

#include <functional>
Expand All @@ -15,7 +16,15 @@ class Worker : public mbgl::util::noncopyable {
Worker(std::size_t count);
~Worker();

void send(Fn&& work, Fn&& after);
// Request work be done on a thread pool. The optional after callback is
// executed on the invoking thread, which must have a run loop, after the
// work is complete.
//
// The return value represents the request to perform the work asynchronously.
// Its destructor guarantees that the work is either cancelled and will never
// execute, or has finished executing. In other words, the WorkRequest is
// guaranteed to outlive any references held by the work function.
WorkRequest send(Fn work, Fn after);

private:
class Impl;
Expand Down

3 comments on commit 5c7867f

@kkaefer
Copy link
Contributor

@kkaefer kkaefer commented on 5c7867f May 5, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This causes severe render stalls, but will likely be fixed by #1326

@kkaefer
Copy link
Contributor

@kkaefer kkaefer commented on 5c7867f May 5, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, this makes the library pretty much unusable in areas that require many different glyph PBFs: When zooming into the area, you'll likely trigger a cancellation, which blocks the Map thread until it is complete. However, the Worker threads wait for the Glyph PBF, which never happens because the Map thread can't process the return value and we end up in a deadlock that we'll never recover from.

@jfirebaugh
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Damn, that's bad. If #1326 will definitely fix this I agree we should consider merging it now rather than later. The alternatives are to back this out and keep trying to patch up the shared pointer lifetime issues (failed in several tries so far), or address #1364/#926 head-on.

Please sign in to comment.