| // 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 "src/ui/scenic/lib/shutdown/shutdown_manager.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <chrono> |
| #include <thread> |
| |
| namespace scenic_impl { |
| |
| ShutdownManager::ShutdownManager(async_dispatcher_t* dispatcher, QuitCallback quit_callback, |
| TimeoutCallback timeout_callback) |
| : executor_(dispatcher), |
| quit_callback_(std::move(quit_callback)), |
| timeout_callback_(std::move(timeout_callback)), |
| shared_bool_(std::make_shared<std::atomic_bool>()) { |
| shared_bool_->store(false); |
| } |
| |
| std::shared_ptr<ShutdownManager> ShutdownManager::New(async_dispatcher_t* dispatcher, |
| QuitCallback quit_callback, |
| TimeoutCallback timeout_callback) { |
| return std::shared_ptr<ShutdownManager>( |
| new ShutdownManager(dispatcher, std::move(quit_callback), std::move(timeout_callback))); |
| } |
| |
| ShutdownManager::~ShutdownManager() { |
| // Prevent |timeout_callback_| from running, if it hasn't already. No need to prevent |
| // |quit_callback_| from running, since destroying |executor_| will guarantee that. |
| bool was_set = shared_bool_->exchange(true); |
| if (was_set) { |
| // One of the two callbacks already ran. No need to log any warnings. |
| return; |
| } |
| |
| if (state_ == State::kInit) { |
| FX_LOGS(WARNING) << "ShutdownManager destroyed without Shutdown() being called; quit callback " |
| "was not invoked."; |
| } else { |
| FX_DCHECK(state_ == State::kShuttingDown); |
| FX_LOGS(WARNING) << "ShutdownManager destroyed before shutdown was completed; quit callback " |
| "was not invoked."; |
| } |
| } |
| |
| void ShutdownManager::RegisterClient(ClientCallback client) { |
| FX_DCHECK(state_ == State::kInit); |
| if (state_ != State::kInit) { |
| FX_LOGS(WARNING) << "ShutdownManager::RegisterClient(): already shutting down; ignoring."; |
| return; |
| } |
| clients_.push_back(std::move(client)); |
| } |
| |
| void ShutdownManager::Shutdown(zx::duration timeout) { |
| FX_DCHECK(state_ == State::kInit); |
| if (state_ != State::kInit) { |
| return; |
| } |
| state_ = State::kShuttingDown; |
| |
| if (clients_.empty()) { |
| state_ = State::kFinishedShuttingDown; |
| bool was_set = shared_bool_->exchange(true); |
| FX_DCHECK(!was_set); |
| quit_callback_(); |
| timeout_callback_(false); |
| return; |
| } |
| |
| std::vector<fpromise::promise<>> promises; |
| for (auto& callback : clients_) { |
| promises.push_back(callback()); |
| } |
| clients_.clear(); |
| |
| // NOTE: It's OK to capture |this| because the closure won't run if |exectutor_| is destroyed. |
| auto joined_promise = fpromise::join_promise_vector(std::move(promises)) |
| .and_then([this](std::vector<fpromise::result<>>& results) { |
| bool was_set = shared_bool_->exchange(true); |
| if (!was_set) { |
| FX_DCHECK(state_ == State::kShuttingDown); |
| state_ = State::kFinishedShuttingDown; |
| quit_callback_(); |
| } |
| }); |
| executor_.schedule_task(std::move(joined_promise)); |
| |
| std::thread timeout_thread([shared_bool = shared_bool_, |
| // We'll keep waking up to see if the deadline has been reached. |
| deadline = clock_callback_() + timeout, |
| timeout_cb = std::move(timeout_callback_), |
| clock_cb = std::move(clock_callback_)] { |
| while (true) { |
| if (shared_bool->load()) { |
| // Exit as early as possible if we notice that the shutdown promises have been completed. |
| timeout_cb(false); |
| return; |
| } |
| zx::time now = clock_cb(); |
| if (deadline <= now) { |
| // Deadline has been reached. Avoid race condition by trying to atomically "claim" the |
| // right to run the timeout callback. |
| bool was_set = shared_bool->exchange(true); |
| timeout_cb(!was_set); |
| return; |
| } |
| |
| // Go back to sleep; try again later. |
| std::this_thread::sleep_for(std::chrono::milliseconds(1)); |
| } |
| }); |
| timeout_thread.detach(); |
| } |
| |
| void ShutdownManager::set_clock_callback(fit::function<zx::time()> cb) { |
| FX_DCHECK(state_ == State::kInit); |
| clock_callback_ = std::move(cb); |
| } |
| |
| } // namespace scenic_impl |