blob: a0a134f3515ab74cfc6e38850aff1a8409f7f027 [file] [log] [blame]
// 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/cpp/wire/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>
#include <thread>
#include "src/media/audio/services/common/fidl_thread.h"
namespace media_audio {
namespace internal {
// Base class for BaseFidlServer. This is an implementation detail. Don't use directly.
class BaseFidlServerUntyped {
public:
~BaseFidlServerUntyped() = default;
virtual bool WaitForShutdown(zx::duration timeout) = 0;
protected:
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 ProtocolServer : public BaseFidlServer<ProtocolServer, fidl::WireServer<Protocol>> {
// public:
// static std::shared_ptr<ProtocolServer> Create(std::shared_ptr<const FidlThread> thread,
// fidl::ServerEnd<Protocol> server_end,
// int arg) {
// return BaseFidlServer::Create(std::move(thread), std::move(server_end), arg);
// }
//
// // Override all methods from fidl::WireServer<Protocol>
// // ...
//
// private:
// static inline const std::string_view kClassName = "ProtocolServer";
// template <typename ServerT, template <typename T> typename FidlServerT, typename ProtocolT>
// friend class BaseFidlServer;
//
// ProtocolServer(int arg);
// };
// ```
//
// As shown above, subclasses should be created via a `Create` static method that calls
// into `BaseFidlServer::Create`. The `FidlServerT` template argument can be `fidl::Server<>` or
// `fidl::WireServer`.
template <typename ServerT, template <typename ProtocolT> typename FidlServerT, typename ProtocolT>
class BaseFidlServer : public FidlServerT<ProtocolT>, public internal::BaseFidlServerUntyped {
public:
using Protocol = ProtocolT;
// Returns the thread used by this server.
const FidlThread& thread() const { return *thread_; }
// Like thread, but returns a `std::shared_ptr` which can be copied.
std::shared_ptr<const FidlThread> thread_ptr() const { return thread_; }
// 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()) {
// Wait for this server to shutdown first.
if (!WaitForShutdownOfThisServer(timeout)) {
return false;
}
// Wait for all children. Since this server has shutdown, it should not call `AddChildServer`
// concurrently.
std::lock_guard<std::mutex> lock(mutex_);
auto deadline = zx::clock::get_monotonic() + timeout;
for (auto [p, weak_child] : children_) {
if (auto child = weak_child.lock(); child) {
timeout = deadline - zx::clock::get_monotonic();
if (!child->WaitForShutdown(timeout)) {
return false;
}
}
}
return true;
}
protected:
BaseFidlServer() = default;
const fidl::ServerBindingRef<ProtocolT>& binding() const { return *binding_; }
// Helper to create a server. The ServerT object is constructed via `ServerT(args...)`.
// Methods received on `server_end` will be dispatched on `thread->dispatcher()`.
template <typename... Args>
static std::shared_ptr<ServerT> Create(std::shared_ptr<const FidlThread> thread,
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(std::forward<Args>(args)...) {}
};
auto server = std::make_shared<WithPublicCtor>(std::forward<Args>(args)...);
server->thread_ = thread;
// 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(thread->dispatcher(), std::move(server_end), server,
std::move(on_unbound));
return server;
}
// Invoked on `thread()` 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::kClassName << " shutdown with unexpected status: " << info;
} else {
FX_LOGS(DEBUG) << ServerT::kClassName << " 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) {
std::lock_guard<std::mutex> lock(mutex_);
GarbageCollectChildren(); // avoid unbounded growth
children_[server.get()] = server;
}
private:
using BaseFidlServerUntyped::SetShutdownComplete;
using BaseFidlServerUntyped::WaitForShutdownOfThisServer;
void GarbageCollectChildren() TA_REQ(mutex_) {
for (auto it = children_.begin(); it != children_.end();) {
if (it->second.expired()) {
it = children_.erase(it);
} else {
++it;
}
}
}
std::shared_ptr<const FidlThread> thread_;
std::optional<fidl::ServerBindingRef<ProtocolT>> binding_;
// This lock is required so that WaitForShutdown can be called from any thread, while
// AddChildServer is always called from `thread_`. In practice those should never be called
// concurrently, but to be safe we require a lock anyway.
std::mutex mutex_;
std::map<internal::BaseFidlServerUntyped*, std::weak_ptr<internal::BaseFidlServerUntyped>>
children_ TA_GUARDED(mutex_);
};
} // namespace media_audio
#endif // SRC_MEDIA_AUDIO_SERVICES_COMMON_BASE_FIDL_SERVER_H_