Run RealmCoordinator::on_change() on a different thread when using tsan

This commit is contained in:
Thomas Goyne 2016-03-10 11:02:59 -08:00
parent 059f907a4a
commit 7f5277a97b
4 changed files with 86 additions and 36 deletions

View File

@ -61,8 +61,7 @@ TEST_CASE("list") {
f();
r->commit_transaction();
coordinator.on_change();
r->notify();
advance_and_notify(*r);
};
auto require_change = [&] {
@ -199,8 +198,7 @@ TEST_CASE("list") {
// Each of the Lists now has a different source version and state at
// that version, so they should all see different changes despite
// being for the same LinkList
coordinator.on_change();
r->notify();
advance_and_notify(*r);
REQUIRE_INDICES(changes[0].insertions, 0, 1, 2);
REQUIRE(changes[0].modifications.empty());
@ -213,8 +211,7 @@ TEST_CASE("list") {
// After making another change, they should all get the same notification
change_list();
coordinator.on_change();
r->notify();
advance_and_notify(*r);
for (int i = 0; i < 3; ++i) {
REQUIRE_INDICES(changes[i].insertions, 3);

View File

@ -58,15 +58,13 @@ TEST_CASE("Results") {
++notification_calls;
});
coordinator->on_change();
r->notify();
advance_and_notify(*r);
auto write = [&](auto&& f) {
r->begin_transaction();
f();
r->commit_transaction();
coordinator->on_change();
r->notify();
advance_and_notify(*r);
};
SECTION("initial results are delivered") {
@ -79,8 +77,7 @@ TEST_CASE("Results") {
r->commit_transaction();
REQUIRE(notification_calls == 1);
coordinator->on_change();
r->notify();
advance_and_notify(*r);
REQUIRE(notification_calls == 2);
}
@ -91,8 +88,7 @@ TEST_CASE("Results") {
REQUIRE(notification_calls == 1);
token = {};
coordinator->on_change();
r->notify();
advance_and_notify(*r);
REQUIRE(notification_calls == 1);
}
@ -117,9 +113,7 @@ TEST_CASE("Results") {
});
});
coordinator->on_change();
r->notify();
advance_and_notify(*r);
REQUIRE(called);
}
@ -132,8 +126,7 @@ TEST_CASE("Results") {
REQUIRE(false);
});
coordinator->on_change();
r->notify();
advance_and_notify(*r);
}
SECTION("removing the current callback does not stop later ones from being called") {
@ -146,8 +139,7 @@ TEST_CASE("Results") {
called = true;
});
coordinator->on_change();
r->notify();
advance_and_notify(*r);
REQUIRE(called);
}
@ -307,15 +299,13 @@ TEST_CASE("Results") {
++notification_calls;
});
coordinator->on_change();
r->notify();
advance_and_notify(*r);
auto write = [&](auto&& f) {
r->begin_transaction();
f();
r->commit_transaction();
coordinator->on_change();
r->notify();
advance_and_notify(*r);
};
SECTION("modifications that leave a non-matching row non-matching do not send notifications") {
@ -392,8 +382,7 @@ TEST_CASE("Results") {
r->commit_transaction();
REQUIRE(notification_calls == 1);
coordinator->on_change();
r->notify();
advance_and_notify(*r);
REQUIRE(notification_calls == 2);
}
}
@ -458,8 +447,7 @@ TEST_CASE("Async Results error handling") {
called = true;
});
coordinator->on_change();
r->notify();
advance_and_notify(*r);
bool called2 = false;
auto token2 = results.add_notification_callback([&](CollectionChangeIndices, std::exception_ptr err) {
@ -468,9 +456,7 @@ TEST_CASE("Async Results error handling") {
called2 = true;
});
coordinator->on_change();
r->notify();
advance_and_notify(*r);
REQUIRE(called2);
}
}
@ -500,8 +486,7 @@ TEST_CASE("Async Results error handling") {
});
OpenFileLimiter limiter;
coordinator->on_change();
r->notify();
advance_and_notify(*r);
bool called2 = false;
auto token2 = results.add_notification_callback([&](CollectionChangeIndices, std::exception_ptr err) {
@ -510,8 +495,7 @@ TEST_CASE("Async Results error handling") {
called2 = true;
});
coordinator->on_change();
r->notify();
advance_and_notify(*r);
REQUIRE(called2);
}

View File

@ -1,10 +1,18 @@
#include "util/test_file.hpp"
#include "impl/realm_coordinator.hpp"
#include <realm/disable_sync_to_disk.hpp>
#include <cstdlib>
#include <unistd.h>
#if defined(__has_feature) && __has_feature(thread_sanitizer)
#include <condition_variable>
#include <functional>
#include <thread>
#endif
TestFile::TestFile()
{
static std::string tmpdir = [] {
@ -29,3 +37,62 @@ InMemoryTestFile::InMemoryTestFile()
{
in_memory = true;
}
#if defined(__has_feature) && __has_feature(thread_sanitizer)
// A helper which synchronously runs on_change() on a fixed background thread
// so that ThreadSanitizer can potentially detect issues
// This deliberately uses an unsafe spinlock for synchronization to ensure that
// the code being tested has to supply all required safety
static class TsanNotifyWorker {
public:
TsanNotifyWorker()
{
m_thread = std::thread([&] { work(); });
}
void work()
{
while (true) {
auto value = m_signal.load(std::memory_order_relaxed);
if (value == 0 || value == 1)
continue;
if (value == 2)
return;
auto c = reinterpret_cast<realm::_impl::RealmCoordinator *>(value);
c->on_change();
m_signal.store(1, std::memory_order_relaxed);
}
}
~TsanNotifyWorker()
{
m_signal = 2;
m_thread.join();
}
void on_change(realm::_impl::RealmCoordinator* c)
{
m_signal.store(reinterpret_cast<uintptr_t>(c), std::memory_order_relaxed);
while (m_signal.load(std::memory_order_relaxed) != 1) ;
}
private:
std::atomic<uintptr_t> m_signal{0};
std::thread m_thread;
} s_worker;
void advance_and_notify(realm::Realm& realm)
{
s_worker.on_change(realm::_impl::RealmCoordinator::get_existing_coordinator(realm.config().path).get());
realm.notify();
}
#else // __has_feature(thread_sanitizer)
void advance_and_notify(realm::Realm& realm)
{
realm::_impl::RealmCoordinator::get_existing_coordinator(realm.config().path)->on_change();
realm.notify();
}
#endif

View File

@ -12,4 +12,6 @@ struct InMemoryTestFile : TestFile {
InMemoryTestFile();
};
void advance_and_notify(realm::Realm& realm);
#endif