blob: cbe4e5deb6c14afb42600bf894893a45c6021c46 [file] [log] [blame]
// Copyright 2023 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 <fidl/fuchsia.scheduler/cpp/fidl.h>
#include <lib/scheduler/role.h>
#include <lib/zx/result.h>
#include <zircon/availability.h>
#include <shared_mutex>
#include <variant>
#include <sdk/lib/component/incoming/cpp/protocol.h>
namespace {
using fuchsia_scheduler::wire::Parameter;
using fuchsia_scheduler::wire::ParameterValue;
using fuchsia_scheduler::wire::RoleName;
using fuchsia_scheduler::wire::RoleTarget;
class RoleClient {
public:
RoleClient() = default;
zx::result<std::shared_ptr<fidl::WireSyncClient<fuchsia_scheduler::RoleManager>>> Connect();
void Disconnect();
private:
// Keeps track of a synchronous connection to the RoleManager service.
// This shared pointer is set to nullptr if the connection has been terminated.
std::shared_ptr<fidl::WireSyncClient<fuchsia_scheduler::RoleManager>> role_manager_;
std::shared_mutex mutex_;
};
zx::result<std::shared_ptr<fidl::WireSyncClient<fuchsia_scheduler::RoleManager>>>
RoleClient::Connect() {
// Check if we already have a connection to the role manager, and return it if we do.
// This requires acquiring the read lock.
{
std::shared_lock lock(mutex_);
if (role_manager_ != nullptr) {
return zx::ok(role_manager_);
}
}
// At this point, we don't have an existing connection to the role manager, so we need to
// create one. This requires the following set of operations to happen in order.
// 1. Acquire the write lock. This will prevent concurrent connection attempts.
// 2. Make sure that another thread didn't already connect the client by the time we got the write
// lock.
// 3. Establish the connection.
std::unique_lock lock(mutex_);
if (role_manager_ != nullptr) {
return zx::ok(role_manager_);
}
auto client_end_result = component::Connect<fuchsia_scheduler::RoleManager>();
if (!client_end_result.is_ok()) {
return client_end_result.take_error();
}
role_manager_ = std::make_shared<fidl::WireSyncClient<fuchsia_scheduler::RoleManager>>(
fidl::WireSyncClient(std::move(*client_end_result)));
return zx::ok(role_manager_);
}
void RoleClient::Disconnect() {
std::unique_lock lock(mutex_);
role_manager_ = nullptr;
}
// Stores a persistent connection to the RoleManager that reconnects on disconnect.
static RoleClient role_client{};
zx::result<std::vector<fuchsia_scheduler::RoleParameter>> SetRoleCommon(
RoleTarget target, std::string_view role, const std::vector<Parameter>& input_parameters) {
// TODO(https://fxbug.dev/323262398): Remove this check once the necessary API is in the SDK.
#if FUCHSIA_API_LEVEL_LESS_THAN(HEAD)
return zx::error(ZX_ERR_NOT_SUPPORTED);
#endif // #if FUCHSIA_API_LEVEL_LESS_THAN(HEAD)
zx::result client = role_client.Connect();
if (!client.is_ok()) {
return client.take_error();
}
fidl::Arena arena;
auto builder = fuchsia_scheduler::wire::RoleManagerSetRoleRequest::Builder(arena);
builder.target(std::move(target))
.role(RoleName{fidl::StringView::FromExternal(role)})
.input_parameters(input_parameters);
fidl::WireResult result = (*(*client))->SetRole(builder.Build());
if (!result.ok()) {
// If the service closed the connection, disconnect the client. This will ensure that future
// callers of SetRole reconnect to the RoleManager service.
if (result.status() == ZX_ERR_PEER_CLOSED) {
role_client.Disconnect();
}
return zx::error(result.status());
}
if (!result.value().is_ok()) {
return result.value().take_error();
}
std::vector<fuchsia_scheduler::RoleParameter> out_params;
if (!result->value()->has_output_parameters()) {
return zx::ok(out_params);
}
out_params.reserve(result->value()->output_parameters().size());
for (Parameter& param : result->value()->output_parameters()) {
std::variant<double, int64_t, std::string> value;
switch (param.value.Which()) {
case ParameterValue::Tag::kIntValue:
value = param.value.int_value();
break;
case ParameterValue::Tag::kFloatValue:
value = param.value.float_value();
break;
case ParameterValue::Tag::kStringValue:
value = std::string{param.value.string_value().get()};
break;
default:
continue;
}
out_params.emplace_back(std::string{param.key.get()}, std::move(value));
}
return zx::ok(out_params);
}
} // anonymous namespace
namespace fuchsia_scheduler {
zx::result<std::vector<RoleParameter>> SetRoleForVmarWithParams(
zx::unowned_vmar borrowed_vmar, std::string_view role,
const std::vector<wire::Parameter>& input_parameters) {
zx::vmar vmar;
zx_status_t status = borrowed_vmar->duplicate(ZX_RIGHT_SAME_RIGHTS, &vmar);
if (status != ZX_OK) {
return zx::error(status);
}
return SetRoleCommon(wire::RoleTarget::WithVmar(std::move(vmar)), role, input_parameters);
}
zx_status_t SetRoleForVmar(zx::unowned_vmar vmar, std::string_view role) {
return SetRoleForVmarWithParams(vmar->borrow(), role, std::vector<wire::Parameter>())
.status_value();
}
zx_status_t SetRoleForRootVmar(std::string_view role) {
return SetRoleForVmar(zx::vmar::root_self(), role);
}
zx::result<std::vector<RoleParameter>> SetRoleForThreadWithParams(
zx::unowned_thread borrowed_thread, std::string_view role,
const std::vector<wire::Parameter>& input_parameters) {
zx::thread thread;
zx_status_t status = borrowed_thread->duplicate(ZX_RIGHT_SAME_RIGHTS, &thread);
if (status != ZX_OK) {
return zx::error(status);
}
return SetRoleCommon(wire::RoleTarget::WithThread(std::move(thread)), role, input_parameters);
}
zx_status_t SetRoleForThread(zx::unowned_thread thread, std::string_view role) {
return SetRoleForThreadWithParams(thread->borrow(), role, std::vector<wire::Parameter>())
.status_value();
}
zx::result<std::vector<RoleParameter>> SetRoleForThread(
zx::unowned_thread borrowed_thread, std::string_view role,
std::vector<RoleParameter>& input_parameters) {
zx::thread thread;
zx_status_t status = borrowed_thread->duplicate(ZX_RIGHT_SAME_RIGHTS, &thread);
if (status != ZX_OK) {
return zx::error(status);
}
// FIDL ObjectViews hold a pointer to the data they view. This is fine for ints and floats, which
// it can directly reference the basic types. These live as long as "input_parameters" do.
// However, strings have nested views (fidl::ObjectView<fidl::StringView>). Since the object keeps
// a pointer the the StringView, which in turn keeps a pointer to the string data, we need a way
// to ensure that the string data lives as long as the wire::Parameter does.
std::vector<fidl::StringView> string_views;
std::vector<wire::Parameter> fidl_params;
fidl_params.reserve(input_parameters.size());
auto to_fidl_param = [&string_views](auto&& arg) mutable -> wire::ParameterValue {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int64_t>) {
return wire::ParameterValue::WithIntValue(fidl::ObjectView<int64_t>::FromExternal(&arg));
} else if constexpr (std::is_same_v<T, double>) {
return wire::ParameterValue::WithFloatValue(fidl::ObjectView<double>::FromExternal(&arg));
} else if constexpr (std::is_same_v<T, std::string>) {
fidl::StringView& view = string_views.emplace_back(fidl::StringView::FromExternal(arg));
return wire::ParameterValue::WithStringValue(
fidl::ObjectView<fidl::StringView>::FromExternal(&view));
}
};
for (RoleParameter& param : input_parameters) {
wire::ParameterValue val = std::visit(to_fidl_param, param.value);
fidl_params.emplace_back(fidl::StringView::FromExternal(param.name), val);
}
return SetRoleCommon(wire::RoleTarget::WithThread(std::move(thread)), role, fidl_params);
}
zx_status_t SetRoleForThisThread(std::string_view role) {
return SetRoleForThread(zx::thread::self()->borrow(), role);
}
zx::result<std::vector<RoleParameter>> SetRoleForThisThread(
std::string_view role, std::vector<RoleParameter>& input_parameters) {
return SetRoleForThread(zx::thread::self()->borrow(), role, input_parameters);
}
} // namespace fuchsia_scheduler