| // Copyright 2020 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. | |
| #ifndef SRC_DEVELOPER_FORENSICS_UTILS_FIDL_CACHING_PTR_H_ | |
| #define SRC_DEVELOPER_FORENSICS_UTILS_FIDL_CACHING_PTR_H_ | |
| #include <lib/async/cpp/task.h> | |
| #include <lib/async/dispatcher.h> | |
| #include <lib/fidl/cpp/interface_ptr.h> | |
| #include <lib/fit/function.h> | |
| #include <lib/fit/promise.h> | |
| #include <lib/sys/cpp/service_directory.h> | |
| #include <lib/syslog/cpp/macros.h> | |
| #include <functional> | |
| #include "src/developer/forensics/utils/errors.h" | |
| #include "src/developer/forensics/utils/fit/bridge_map.h" | |
| #include "src/developer/forensics/utils/fit/timeout.h" | |
| #include "src/lib/backoff/exponential_backoff.h" | |
| namespace forensics { | |
| namespace fidl { | |
| // Wrapper around InterfacePtr<Interface> that can cache the results of calls made to the interface. | |
| // | |
| // For example, if we wished to fetch a device's update channel from | |
| // fuchsia::update::channel::Provider then we would use CachingPtr as follows: | |
| // | |
| // class CachingChannelPtr { | |
| // public: | |
| // CachingChannelPtr(async_dispatcher_t* dispatcher, std::shared_ptr<sys::ServiceDirectory> | |
| // services) | |
| // : connection_(dispatcher, services, [this] { GetChannel(); }) {} | |
| // | |
| // ::fit::promise<std::string> GetChannel(zx::duration timeout) { | |
| // return connection_.GetValue(fit::Timeout(timeout)); | |
| // } | |
| // | |
| // private: | |
| // void GetChannel() { | |
| // connection_->GetCurrent([this](std::string channel) { | |
| // std::optional<std::string> value; | |
| // if (!channel.empty()) { | |
| // connection_.SetValue(channel); | |
| // } else { | |
| // connection_.SetError(Error::kMissingValue); | |
| // } | |
| // }); | |
| // } | |
| // | |
| // CachingPtr<fuchsia::update::channel::Provider, std::string> connection_; | |
| // }; | |
| // | |
| // This class is not thread safe. | |
| template <typename Interface, typename V> | |
| class CachingPtr { | |
| public: | |
| CachingPtr(async_dispatcher_t* dispatcher, std::shared_ptr<sys::ServiceDirectory> services, | |
| std::function<void(void)> make_call) | |
| : dispatcher_(dispatcher), | |
| services_(services), | |
| pending_calls_(dispatcher_), | |
| make_call_(make_call), | |
| make_call_task_([this] { | |
| Connect(); | |
| make_call_(); | |
| }), | |
| make_call_backoff_(/*initial_delay=*/zx::msec(100), /*retry_factor=*/2u, | |
| /*max_delay=*/zx::hour(1)) { | |
| // Post |make_call_| on the async loop with an immediate deadline in an attempt to | |
| // pre-cache |value_|. Because the class that owns the CachingPtr and supplies |make_call_| may | |
| // capture itself in the lambda, we need to ensure that the owning class is fully initialized | |
| // before |make_call_| is executed. Thus, we post |make_call_| on the async loop and are | |
| // guarenteed that any data initialized along side the CachingPtr is initialized before | |
| // |make_call_| is executed. | |
| // | |
| // This isn't safe if |dispatcher_| is on a different thread than |this|. | |
| make_call_task_.Post(dispatcher_); | |
| } | |
| void SetValue(V value) { SetAsDone(std::move(value)); } | |
| void SetError(Error error) { SetAsDone(error); } | |
| ::fit::promise<V, Error> GetValue(fit::Timeout timeout) { | |
| if (value_) { | |
| return ::fit::make_result_promise(ValueToResult()); | |
| } | |
| const uint64_t id = pending_calls_.NewBridgeForTask(Interface::Name_); | |
| // A call to GetValue() is only ever completed with an error due to circumstances that affect | |
| // only that call, i.e. the call times out or there is an issue posting the timeout task, so we | |
| // don't set an error for all pending GetValue() calls, i.e. we don't set |value_| with the | |
| // Error and instead only propagate the Error to the caller. | |
| return pending_calls_.WaitForDone(id, std::move(timeout)) | |
| .then([id, this](const ::fit::result<void, Error>& result) { | |
| pending_calls_.Delete(id); | |
| return result; | |
| }) | |
| .and_then([this]() { return ValueToResult(); }) | |
| .or_else([](const Error& error) { return ::fit::error(error); }); | |
| } | |
| Interface* operator->() { return connection_.get(); } | |
| private: | |
| ::fit::result<V, Error> ValueToResult() const { | |
| if (!value_) { | |
| FX_LOGS(FATAL) << "Attempting to return a result when none has been cached"; | |
| } | |
| if (value_->HasValue()) { | |
| return ::fit::ok(value_->Value()); | |
| } else { | |
| return ::fit::error(value_->Error()); | |
| } | |
| } | |
| void Connect() { | |
| connection_ = services_->Connect<Interface>(); | |
| connection_.set_error_handler([this](zx_status_t status) { | |
| FX_PLOGS(ERROR, status) << "Lost connection with " << Interface::Name_; | |
| if (const auto post_status = | |
| make_call_task_.PostDelayed(dispatcher_, make_call_backoff_.GetNext()); | |
| post_status != ZX_OK) { | |
| FX_PLOGS(ERROR, post_status) << "Failed to post task to make call on async loop"; | |
| SetError(Error::kAsyncTaskPostFailure); | |
| } | |
| }); | |
| } | |
| void SetAsDone(ErrorOr<V> value) { | |
| value_ = std::make_unique<ErrorOr<V>>(std::move(value)); | |
| pending_calls_.CompleteAllOk(); | |
| connection_.Unbind(); | |
| // We never need to make another call nor re-connect. | |
| make_call_task_.Cancel(); | |
| make_call_backoff_.Reset(); | |
| } | |
| async_dispatcher_t* dispatcher_; | |
| const std::shared_ptr<sys::ServiceDirectory> services_; | |
| ::fidl::InterfacePtr<Interface> connection_; | |
| fit::BridgeMap<> pending_calls_; | |
| // The std::unique_ptr<> indicates whether the value is cached, the ErrorOr<> indicates | |
| // whether the cached value is a payload or an error. | |
| std::unique_ptr<ErrorOr<V>> value_; | |
| std::function<void(void)> make_call_; | |
| async::TaskClosure make_call_task_; | |
| backoff::ExponentialBackoff make_call_backoff_; | |
| }; | |
| } // namespace fidl | |
| } // namespace forensics | |
| #endif // SRC_DEVELOPER_FORENSICS_UTILS_FIDL_CACHING_PTR_H_ |