| // Copyright 2022 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_MEDIA_AUDIO_SERVICES_COMMON_BASE_FIDL_SERVER_H_ |
| #define SRC_MEDIA_AUDIO_SERVICES_COMMON_BASE_FIDL_SERVER_H_ |
| |
| #include <lib/fidl/llcpp/server.h> |
| #include <lib/sync/cpp/completion.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/clock.h> |
| #include <zircon/types.h> |
| |
| #include <map> |
| |
| namespace media_audio { |
| |
| namespace internal { |
| // Base class for BaseFidlServer. This is an implementation detail. Don't use directly. |
| class BaseFidlServerUntyped { |
| public: |
| ~BaseFidlServerUntyped() = default; |
| |
| // These are public so that BaseFidlServer<A> can wait for children of type BaseFidlServer<B>. |
| // BaseFidlServer will hide them from subclasses of BaseFidlServer. |
| bool WaitForShutdownOfThisServer(zx::duration timeout) { |
| return shutdown_complete_.Wait(timeout) == ZX_OK; |
| } |
| void SetShutdownComplete() { shutdown_complete_.Signal(); } |
| |
| private: |
| ::libsync::Completion shutdown_complete_; |
| }; |
| } // namespace internal |
| |
| // Base class for FIDL servers. Example of use: |
| // |
| // ```cpp |
| // class FidlServer : public BaseFidlServer<FidlServer, Protocol> { |
| // public: |
| // static std::shared_ptr<FidlServer> Create(async_dispatcher_t* dispatcher, |
| // fidl::ServerEnd<Protocol> server_end, |
| // int arg) { |
| // return BaseFidlServer::Create(dispatcher, std::move(server_end), arg); |
| // } |
| // |
| // // Override all methods from fidl::WireServer<Protocol> |
| // // ... |
| // |
| // private: |
| // static inline const std::string_view Name = "FidlServer"; |
| // template<class ServerT, class ProtocolT> |
| // friend class BaseFidlServer; |
| // |
| // FidlServer(int arg); |
| // }; |
| // ``` |
| // |
| // As shown above, subclasses should be created via a `Create` static method that calls |
| // into `BaseFidlServer::Create`. |
| template <typename ServerT, typename ProtocolT> |
| class BaseFidlServer : public fidl::WireServer<ProtocolT>, public internal::BaseFidlServerUntyped { |
| public: |
| using Protocol = ProtocolT; |
| |
| // Returns the dispatcher used by this server. Never null. |
| async_dispatcher_t* dispatcher() const { return dispatcher_; } |
| |
| // Triggers a shutdown of this server using the given epitaph. The actual shutdown process happens |
| // asynchronously. This may be called from any thread. After the first call, subsequent calls are |
| // no-ops. |
| void Shutdown(zx_status_t epitaph = ZX_ERR_CANCELED) { binding_->Close(epitaph); } |
| |
| // Waits until the server and all its children have shut down. This does not actually shut down |
| // any servers -- shutdown must be triggered separately. A server can be shutdown either via an |
| // explicit call to `Shutdown` or by closing the client channel, both of which trigger shutdown |
| // asynchronously. This is a blocking call that can be invoked from any thread. This is primarily |
| // intended for tests. |
| // |
| // Returns false if the server(s) do not shutdown before the given timeout has expired. |
| bool WaitForShutdown(zx::duration timeout = zx::duration::infinite()) { |
| auto deadline = zx::clock::get_monotonic() + timeout; |
| for (auto [p, weak_child] : children_) { |
| if (auto child = weak_child.lock(); child) { |
| auto timeout = deadline - zx::clock::get_monotonic(); |
| if (!child->WaitForShutdownOfThisServer(timeout)) { |
| return false; |
| } |
| } |
| } |
| timeout = deadline - zx::clock::get_monotonic(); |
| return WaitForShutdownOfThisServer(timeout); |
| } |
| |
| protected: |
| BaseFidlServer() = default; |
| |
| // Helper to create a server. The ServerT object is constructed via `ServerT(args...)`. |
| // Methods received on `server_end` will be dispatched on `dispatcher`. |
| template <typename... Args> |
| static std::shared_ptr<ServerT> Create(async_dispatcher_t* dispatcher, |
| fidl::ServerEnd<ProtocolT> server_end, Args... args) { |
| // std::make_shared requires a public ctor, but we hide our ctor to force callers to use Create. |
| struct WithPublicCtor : public ServerT { |
| public: |
| explicit WithPublicCtor(Args... args) : ServerT(args...) {} |
| }; |
| |
| auto server = std::make_shared<WithPublicCtor>(args...); |
| server->dispatcher_ = dispatcher; |
| |
| // Callback invoked when the server shuts down. |
| auto on_unbound = [](ServerT* server, fidl::UnbindInfo info, |
| fidl::ServerEnd<ProtocolT> server_end) { |
| server->OnShutdown(info); |
| server->SetShutdownComplete(); |
| }; |
| |
| // Passing `server` (a shared_ptr) to BindServer ensures that the `server` object |
| // lives until on_unbound is called. |
| server->binding_ = |
| fidl::BindServer(dispatcher, std::move(server_end), server, std::move(on_unbound)); |
| |
| return server; |
| } |
| |
| // Invoked from `dispatcher()` as the last step before the server shuts down. |
| // Can be overridden by subclasses. |
| virtual void OnShutdown(fidl::UnbindInfo info) { |
| if (!info.is_user_initiated() && !info.is_peer_closed()) { |
| FX_LOGS(ERROR) << ServerT::Name << " shutdown with unexpected status: " << info; |
| } else { |
| FX_LOGS(DEBUG) << ServerT::Name << " shutdown with status: " << info; |
| } |
| } |
| |
| // Adds a child server. The child is held as a weak_ptr so it will be automatically |
| // garbage collected after it is destroyed. |
| void AddChildServer(const std::shared_ptr<internal::BaseFidlServerUntyped>& server) { |
| GarbageCollectChildren(); // avoid unbounded growth |
| children_[server.get()] = server; |
| } |
| |
| private: |
| using BaseFidlServerUntyped::SetShutdownComplete; |
| using BaseFidlServerUntyped::WaitForShutdownOfThisServer; |
| |
| void GarbageCollectChildren() { |
| for (auto it = children_.begin(); it != children_.end();) { |
| if (it->second.expired()) { |
| it = children_.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| } |
| |
| async_dispatcher_t* dispatcher_; |
| std::optional<fidl::ServerBindingRef<ProtocolT>> binding_; |
| std::map<internal::BaseFidlServerUntyped*, std::weak_ptr<internal::BaseFidlServerUntyped>> |
| children_; |
| }; |
| |
| } // namespace media_audio |
| |
| #endif // SRC_MEDIA_AUDIO_SERVICES_COMMON_BASE_FIDL_SERVER_H_ |