blob: cfd532274957e3b74f1e3bf1ae3949ae1ff4193f [file] [log] [blame]
// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef _LIBCPP_TEST_EXPERIMENTAL_TASK_MANUAL_RESET_EVENT
#define _LIBCPP_TEST_EXPERIMENTAL_TASK_MANUAL_RESET_EVENT
#include <experimental/coroutine>
#include <atomic>
// manual_reset_event is a coroutine synchronisation tool that allows one
// coroutine to await the event object and if the event was not crrently
// in the 'set' state then will suspend the awaiting coroutine until some
// thread calls .set() on the event.
class manual_reset_event
{
friend class _Awaiter;
class _Awaiter
{
public:
_Awaiter(const manual_reset_event* __event) noexcept
: __event_(__event)
{}
bool await_ready() const noexcept
{
return __event_->is_set();
}
bool await_suspend(std::experimental::coroutine_handle<> __coro) noexcept
{
_LIBCPP_ASSERT(
__event_->__state_.load(std::memory_order_relaxed) !=
__State::__not_set_waiting_coroutine,
"This manual_reset_event already has another coroutine awaiting it. "
"Only one awaiting coroutine is supported."
);
__event_->__awaitingCoroutine_ = __coro;
// If the compare-exchange fails then this means that the event was
// already 'set' and so we should not suspend - this code path requires
// 'acquire' semantics so we have visibility of writes prior to the
// .set() operation that transitioned the event to the 'set' state.
// If the compare-exchange succeeds then this needs 'release' semantics
// so that a subsequent call to .set() has visibility of our writes
// to the coroutine frame and to __event_->__awaitingCoroutine_ after
// reading our write to __event_->__state_.
_State oldState = _State::__not_set;
return __event_->__state_.compare_exchange_strong(
oldState,
_State::__not_set_waiting_coroutine,
std::memory_order_release,
std::memory_order_acquire);
}
void await_resume() const noexcept {}
private:
const manual_reset_event* __event_;
};
public:
manual_reset_event(bool __initiallySet = false) noexcept
: __state_(__initiallySet ? _State::__set : _State::__not_set)
{}
bool is_set() const noexcept
{
return __state_.load(std::memory_order_acquire) == _State::__set;
}
void set() noexcept
{
// Needs to be 'acquire' in case the old value was a waiting coroutine
// so that we have visibility of the writes to the coroutine frame in
// the current thrad before we resume it.
// Also needs to be 'release' in case the old value was 'not-set' so that
// another thread that subsequently awaits the
_State oldState = __state_.exchange(_State::__set, std::memory_order_acq_rel);
if (oldState == _State::__not_set_waiting_coroutine)
{
_VSTD::exchange(__awaitingCoroutine_, {}).resume();
}
}
void reset() noexcept
{
_LIBCPP_ASSERT(
__state_.load(std::memory_order_relaxed) != _State::__not_set_waiting_coroutine,
"Illegal to call reset() if a coroutine is currently awaiting the event.");
// Note, we use 'relaxed' memory order here since it considered a
// data-race to call reset() concurrently either with operator co_await()
// or with set().
__state_.store(_State::__not_set, std::memory_order_relaxed);
}
auto operator co_await() const noexcept
{
return _Awaiter{ this };
}
private:
enum class _State {
__not_set,
__not_set_waiting_coroutine,
__set
};
// TODO: Can we combine these two members into a single std::atomic<void*>?
mutable std::atomic<_State> __state_;
mutable std::experimental::coroutine_handle<> __awaitingCoroutine_;
};
#endif