Skip to content

Commit

Permalink
Merge pull request #481 from leapmotion/bug-barrierabort
Browse files Browse the repository at this point in the history
Fix behavior of Barrier when DispatchQueue::Abort is called
  • Loading branch information
gtremper committed Apr 2, 2015
2 parents 719b779 + 9b2e5c4 commit 2e311b4
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 1 deletion.
3 changes: 3 additions & 0 deletions autowiring/DispatchQueue.h
Expand Up @@ -194,6 +194,9 @@ class DispatchQueue {
/// This method does not cause any dispatchers to run. If the underlying dispatch queue does not have an event loop
/// operating on it, this method will deadlock. It is an error for the party responsible for driving the dispatch queue
/// via WaitForEvent or DispatchAllEvents unless that party first delegates the responsibility elsewhere.
///
/// If DispatchQueue::Abort() is called before the dispatcher has been completed, this method will throw an exception.
/// If a dispatcher on the underlying DispatchQueue throws an exception, this method will also throw an exception.
/// </remarks>
bool Barrier(std::chrono::nanoseconds timeout);

Expand Down
6 changes: 5 additions & 1 deletion src/autowiring/DispatchQueue.cpp
Expand Up @@ -2,6 +2,7 @@
#include "stdafx.h"
#include "DispatchQueue.h"
#include "at_exit.h"
#include "autowiring_error.h"
#include <assert.h>

dispatch_aborted_exception::dispatch_aborted_exception(void){}
Expand Down Expand Up @@ -186,7 +187,10 @@ bool DispatchQueue::Barrier(std::chrono::nanoseconds timeout) {

// Obtain the lock, wait until our variable is satisfied, which might be right away:
std::unique_lock<std::mutex> lk(m_dispatchLock);
return m_queueUpdated.wait_for(lk, timeout, [&] {return *complete; });
bool rv = m_queueUpdated.wait_for(lk, timeout, [&] { return m_aborted || *complete; });
if (m_aborted)
throw autowiring_error("Dispatch queue was aborted while a barrier was invoked");
return rv;
}

std::chrono::steady_clock::time_point
Expand Down
43 changes: 43 additions & 0 deletions src/autowiring/test/DispatchQueueTest.cpp
Expand Up @@ -87,3 +87,46 @@ TEST_F(DispatchQueueTest, Barrier) {
// Now we should be able to complete:
ASSERT_TRUE(ct->Barrier(std::chrono::seconds(5))) << "Barrier did not return even though a dispatcher should have completed";
}

struct BarrierMonitor {
// Standard continuation behavior:
std::mutex lock;
std::condition_variable cv;
bool done;
};

TEST_F(DispatchQueueTest, BarrierWithAbort) {
AutoCurrentContext()->Initiate();
AutoRequired<CoreThread> ct;

// Hold our initial lockdown:
auto b = std::make_shared<BarrierMonitor>();
std::unique_lock<std::mutex> lk(b->lock);

// This dispatch entry will delay until we're ready for it to continue:
*ct += [b] {
std::lock_guard<std::mutex> lk(b->lock);
};

// Launch something that will barrier:
auto exception = std::make_shared<bool>(false);
auto f = std::async(
std::launch::async,
[=] {
try {
ct->Barrier(std::chrono::seconds(5));
}
catch (autowiring_error&) {
*exception = true;
}
}
);

// Delay for long enough for the barrier to be reached:
std::this_thread::sleep_for(std::chrono::milliseconds(1));

// Now abandon the queue, this should cause the async thread to quit:
ct->Abort();
ASSERT_EQ(std::future_status::ready, f.wait_for(std::chrono::seconds(5))) << "Barrier did not abort fast enough";
ASSERT_TRUE(*exception) << "Exception should have been thrown inside the Barrier call";
}

0 comments on commit 2e311b4

Please sign in to comment.