blob: e250a1e071acd0fc2cca021b4ed5a11b8f1b3db6 [file] [log] [blame]
// Copyright 2017 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 "peridot/bin/ledger/coroutine/coroutine_impl.h"
#include <lib/fit/function.h>
#if __has_feature(address_sanitizer)
#include <sanitizer/common_interface_defs.h>
#endif
#include <lib/fxl/logging.h>
#include "peridot/bin/ledger/coroutine/context/context.h"
#include "peridot/bin/ledger/coroutine/context/stack.h"
namespace coroutine {
constexpr size_t kMaxAvailableStacks = 25;
class CoroutineServiceImpl::CoroutineHandlerImpl : public CoroutineHandler {
public:
CoroutineHandlerImpl(std::unique_ptr<context::Stack> stack,
fit::function<void(CoroutineHandler*)> runnable);
~CoroutineHandlerImpl() override;
// CoroutineHandler.
ContinuationStatus Yield() override;
void Resume(ContinuationStatus status) override;
void Start();
void set_cleanup(
fit::function<void(std::unique_ptr<context::Stack>)> cleanup) {
cleanup_ = std::move(cleanup);
}
private:
static void StaticRun(void* data);
void Run();
ContinuationStatus DoYield();
std::unique_ptr<context::Stack> stack_;
fit::function<void(CoroutineHandler*)> runnable_;
fit::function<void(std::unique_ptr<context::Stack>)> cleanup_;
context::Context main_context_;
context::Context routine_context_;
bool interrupted_ = false;
bool finished_ = false;
#if __has_feature(address_sanitizer)
const void* origin_stack_ = nullptr;
size_t origin_stacksize_ = 0;
#endif
FXL_DISALLOW_COPY_AND_ASSIGN(CoroutineHandlerImpl);
};
CoroutineServiceImpl::CoroutineHandlerImpl::CoroutineHandlerImpl(
std::unique_ptr<context::Stack> stack,
fit::function<void(CoroutineHandler*)> runnable)
: stack_(std::move(stack)), runnable_(std::move(runnable)) {
FXL_DCHECK(stack_);
FXL_DCHECK(runnable_);
}
CoroutineServiceImpl::CoroutineHandlerImpl::~CoroutineHandlerImpl() {
FXL_DCHECK(!stack_);
}
ContinuationStatus CoroutineServiceImpl::CoroutineHandlerImpl::Yield() {
FXL_DCHECK(!interrupted_);
if (interrupted_) {
return ContinuationStatus::INTERRUPTED;
}
return DoYield();
}
void CoroutineServiceImpl::CoroutineHandlerImpl::Resume(
ContinuationStatus status) {
FXL_DCHECK(!finished_);
interrupted_ = interrupted_ || (status == ContinuationStatus::INTERRUPTED);
#if __has_feature(address_sanitizer)
void* fake_stack_save;
__sanitizer_start_switch_fiber(
&fake_stack_save, reinterpret_cast<const void*>(stack_->safe_stack()),
stack_->stack_size());
#endif
context::SwapContext(&main_context_, &routine_context_);
#if __has_feature(address_sanitizer)
__sanitizer_finish_switch_fiber(fake_stack_save, nullptr, nullptr);
#endif
if (finished_) {
cleanup_(std::move(stack_));
// this object has been deleted by |cleanup_|, return.
return;
}
}
void CoroutineServiceImpl::CoroutineHandlerImpl::Start() {
context::MakeContext(&routine_context_, stack_.get(),
&CoroutineServiceImpl::CoroutineHandlerImpl::StaticRun,
this);
Resume(ContinuationStatus::OK);
}
void CoroutineServiceImpl::CoroutineHandlerImpl::StaticRun(void* data) {
reinterpret_cast<CoroutineHandlerImpl*>(data)->Run();
}
void CoroutineServiceImpl::CoroutineHandlerImpl::Run() {
#if __has_feature(address_sanitizer)
__sanitizer_finish_switch_fiber(nullptr, &origin_stack_, &origin_stacksize_);
#endif
runnable_(this);
// Delete |runnable_|, as it can have side effects that should be run inside
// the co-routine.
runnable_ = [](CoroutineHandler*) {};
finished_ = true;
DoYield();
FXL_NOTREACHED() << "Last yield should never return.";
}
ContinuationStatus CoroutineServiceImpl::CoroutineHandlerImpl::DoYield() {
#if __has_feature(address_sanitizer)
FXL_DCHECK(origin_stack_);
FXL_DCHECK(origin_stacksize_);
void* fake_stack_save = nullptr;
__sanitizer_start_switch_fiber(finished_ ? nullptr : &fake_stack_save,
origin_stack_, origin_stacksize_);
#endif
context::SwapContext(&routine_context_, &main_context_);
#if __has_feature(address_sanitizer)
__sanitizer_finish_switch_fiber(fake_stack_save, &origin_stack_,
&origin_stacksize_);
#endif
return interrupted_ ? ContinuationStatus::INTERRUPTED
: ContinuationStatus::OK;
}
CoroutineServiceImpl::CoroutineServiceImpl() {}
CoroutineServiceImpl::~CoroutineServiceImpl() {
while (!handlers_.empty()) {
handlers_.back()->Resume(ContinuationStatus::INTERRUPTED);
}
}
void CoroutineServiceImpl::StartCoroutine(
fit::function<void(CoroutineHandler* handler)> runnable) {
std::unique_ptr<context::Stack> stack;
if (available_stack_.empty()) {
stack = std::make_unique<context::Stack>();
} else {
stack = std::move(available_stack_.back());
available_stack_.pop_back();
}
auto handler = std::make_unique<CoroutineHandlerImpl>(std::move(stack),
std::move(runnable));
auto handler_ptr = handler.get();
handler->set_cleanup([this,
handler_ptr](std::unique_ptr<context::Stack> stack) {
if (available_stack_.size() < kMaxAvailableStacks) {
stack->Release();
available_stack_.push_back(std::move(stack));
}
handlers_.erase(std::find_if(
handlers_.begin(), handlers_.end(),
[handler_ptr](const std::unique_ptr<CoroutineHandlerImpl>& handler) {
return handler.get() == handler_ptr;
}));
});
handlers_.push_back(std::move(handler));
handler_ptr->Start();
}
} // namespace coroutine