blob: 55b25753940c0a3e4c3f6f1c2fb6d4af01d14d20 [file] [edit]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/task.h>
#include <lib/closure-queue/closure_queue.h>
#include <lib/fit/defer.h>
#include <lib/fit/function.h>
#include <lib/sync/cpp/completion.h>
#include <lib/thread-safe-deleter/thread_safe_deleter.h>
#include <zxtest/zxtest.h>
namespace {
class ThreadSafeDeleterTest : public zxtest::Test {
protected:
ThreadSafeDeleterTest();
void WaitForDeferHolderDestruction();
async::Loop main_loop_;
ClosureQueue main_queue_;
async::Loop other_loop_;
ClosureQueue other_queue_;
std::mutex lock_;
thrd_t destruction_thread_ __TA_GUARDED(lock_) = {};
using DeferHolder = ThreadSafeDeleter<fit::deferred_action<fit::closure>>;
std::optional<DeferHolder> defer_holder_;
};
void ThreadSafeDeleterTest::WaitForDeferHolderDestruction() {
ZX_DEBUG_ASSERT(main_queue_.IsSynchronized());
while (true) {
// This must happen outside lock_.
main_loop_.RunUntilIdle();
{ // scope lock
std::lock_guard<std::mutex> lock(lock_);
if (destruction_thread_) {
return;
}
} // ~lock
// TODO(dustingreen): Make OneShotEvent usable here, and switch to that.
zx::nanosleep(zx::deadline_after(zx::msec(5)));
}
}
ThreadSafeDeleterTest::ThreadSafeDeleterTest()
: main_loop_(&kAsyncLoopConfigAttachToCurrentThread),
main_queue_(main_loop_.dispatcher()),
other_loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {
zx_status_t status = other_loop_.StartThread("other_loop");
ZX_ASSERT(status == ZX_OK);
libsync::Completion set_dispatcher_done;
zx_status_t post_status = async::PostTask(other_loop_.dispatcher(), [this, &set_dispatcher_done] {
// must be called on other_loop_
other_queue_.SetDispatcher(other_loop_.dispatcher());
set_dispatcher_done.Signal();
});
ZX_ASSERT(post_status == ZX_OK);
set_dispatcher_done.Wait();
defer_holder_.emplace(&main_queue_, fit::defer<fit::closure>([this] {
thrd_t current_thread = thrd_current();
std::lock_guard<std::mutex> lock(lock_);
destruction_thread_ = current_thread;
}));
}
TEST_F(ThreadSafeDeleterTest, DeleteHolderOnMainThread) {
{
std::lock_guard<std::mutex> lock(lock_);
ZX_ASSERT(!destruction_thread_);
}
other_queue_.Enqueue([this, defer_holder = std::move(*defer_holder_)]() mutable {
ZX_DEBUG_ASSERT(other_queue_.IsSynchronized());
// Must be stopped on its own dispatcher thread, so go ahead and take care of that now.
other_queue_.StopAndClear();
main_queue_.Enqueue([this, defer_holder = std::move(defer_holder)]() mutable {
ZX_DEBUG_ASSERT(main_queue_.IsSynchronized());
// ~defer_holder
});
});
WaitForDeferHolderDestruction();
{
std::lock_guard<std::mutex> lock(lock_);
ASSERT_EQ(destruction_thread_, thrd_current());
}
}
TEST_F(ThreadSafeDeleterTest, DeleteHolderOnOtherThread) {
{
std::lock_guard<std::mutex> lock(lock_);
ZX_ASSERT(!destruction_thread_);
}
other_queue_.Enqueue([this, defer_holder = std::move(*defer_holder_)]() mutable {
ZX_DEBUG_ASSERT(!main_queue_.IsSynchronized());
ZX_DEBUG_ASSERT(other_queue_.IsSynchronized());
// Must be stopped on its own dispatcher thread, so go ahead and take care of that now.
other_queue_.StopAndClear();
// ~defer_holder should Enqueue destruction of held fit::defer to
// main_queue_.
});
WaitForDeferHolderDestruction();
{
std::lock_guard<std::mutex> lock(lock_);
ASSERT_EQ(destruction_thread_, thrd_current());
}
}
} // namespace