| // 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 |