blob: 3e9744cb132c5b226ed8f5626119d86486ec64b8 [file] [log] [blame]
// Copyright 2021 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 <fuchsia/component/runner/cpp/fidl.h>
#include <fuchsia/component/test/cpp/fidl.h>
#include <fuchsia/data/cpp/fidl.h>
#include <fuchsia/io/cpp/fidl.h>
#include <lib/async/dispatcher.h>
#include <lib/fdio/namespace.h>
#include <lib/fidl/cpp/interface_request.h>
#include <lib/sys/component/cpp/testing/internal/errors.h>
#include <lib/sys/component/cpp/testing/internal/local_component_runner.h>
#include <lib/sys/component/cpp/testing/realm_builder_types.h>
#include <zircon/assert.h>
#include <zircon/availability.h>
#include <zircon/status.h>
#include <cstddef>
#include <iterator>
#include <memory>
#include <optional>
#include <variant>
namespace component_testing {
namespace internal {
namespace {
std::string ExtractLocalComponentName(const fuchsia::data::Dictionary& program) {
ZX_ASSERT_MSG(program.has_entries(), "Received empty program from Component Manager");
for (const auto& entry : program.entries()) {
if (entry.key == fuchsia::component::test::LOCAL_COMPONENT_NAME_KEY) {
ZX_ASSERT_MSG(entry.value->is_str(), "Received local component key of wrong type");
return entry.value->str();
}
}
ZX_PANIC("Received program without local component key");
}
std::unique_ptr<LocalComponentHandles> CreateHandlesFromStartInfo(
fuchsia::component::runner::ComponentStartInfo start_info, async_dispatcher_t* dispatcher) {
fdio_ns_t* ns;
ZX_COMPONENT_ASSERT_STATUS_OK("CreateHandlesFromStartInfo", fdio_ns_create(&ns));
for (auto& entry : *start_info.mutable_ns()) {
ZX_COMPONENT_ASSERT_STATUS_OK(
"CreateHandlesFromStartInfo",
fdio_ns_bind(ns, entry.path().c_str(), entry.mutable_directory()->TakeChannel().release()));
}
sys::OutgoingDirectory outgoing_dir;
outgoing_dir.Serve(std::move(*start_info.mutable_outgoing_dir()), dispatcher);
return std::make_unique<LocalComponentHandles>(ns, std::move(outgoing_dir));
}
} // namespace
LocalComponentInstance::LocalComponentInstance(
fidl::InterfaceRequest<fuchsia::component::runner::ComponentController> controller,
async_dispatcher_t* dispatcher,
fit::function<void(LocalComponentInstance*, std::unique_ptr<LocalComponentHandles>)> on_start,
fit::closure on_exit)
: binding_(this),
starting_(false),
started_(false),
on_start_(std::move(on_start)),
on_exit_(std::move(on_exit)) {
ZX_COMPONENT_ASSERT_STATUS_OK("Bind ComponentController",
binding_.Bind(std::move(controller), dispatcher));
}
void LocalComponentInstance::SetOnStop(fit::closure on_stop) { on_stop_ = std::move(on_stop); }
void LocalComponentInstance::Start(std::unique_ptr<LocalComponentHandles> handles) {
starting_ = true;
handles->on_exit_ = [this](zx_status_t status) {
if (started_) {
Exit(status);
// `this` may now be invalid
} else {
// Delay `Exit` (delay calling `on_exit_`, if set) until after `on_start_`
// completes.
pending_exit_status_ = status;
}
};
// The `on_start_` callback calls LocalComponent->Start() or
// LocalComponentImpl->OnStart() (among other things).
on_start_(this, std::move(handles));
if (pending_exit_status_) {
// `ComponentInstance::Exit()` (which calls the
// `ComponentInstance::on_exit_` callback) must not be called before the
// `on_start_` callback completes.
//
// A LocalComponentImpl may call `Exit()` during the
// `LocalComponentImpl::OnStart()` method. This is a legitimate use case, if
// the component can complete its work via synchronous calls. See the
// `RoutesProtocolToLocalComponentSync` test in `realm_builder_test.cc`, for
// example. This test's component uses an `EchoSyncPtr` to invoke a client
// request and get a response, before the `OnStart()` method completes.
// Since the work is done, the client component is safe to terminate, by
// calling `Exit()`.
//
// Note that calling `Exit()` before `on_start_` saves the provided status
// (see above), but delays the call to `LocalComponentInstance::Exit()`
// until here, after `on_start_` has completed.
Exit(*pending_exit_status_);
// `this` may now be invalid
} else {
started_ = true;
starting_ = false;
}
}
bool LocalComponentInstance::IsRunning() {
return (starting_ || started_) && !pending_exit_status_ && binding_.is_bound();
}
void LocalComponentInstance::Stop() {
if (on_stop_) {
on_stop_();
}
Exit(ZX_OK);
// The component should exit the loop, if any, or should have already
// terminated. When it terminates, it should close the ComponentController.
// If it doesn't, component manager will call Kill(), which can force close
// the ComponentController.
}
void LocalComponentInstance::Kill() {
// Close the ComponentController immediately.
Exit(ZX_ERR_CANCELED);
}
void LocalComponentInstance::Exit(zx_status_t epitaph_value) {
if (binding_.is_bound()) {
binding_.Close(epitaph_value);
}
if (on_exit_) {
// If on_exit is not set, this is a LocalComponent* type, which does not
// support exiting. Don't close the binding while the component is still
// running.
auto on_exit = std::move(on_exit_);
on_exit_ = nullptr;
on_exit();
}
}
LocalComponentRunner::LocalComponentRunner(LocalComponents components,
async_dispatcher_t* dispatcher)
: ready_components_(std::move(components)), binding_(this), dispatcher_(dispatcher) {}
fidl::InterfaceHandle<fuchsia::component::runner::ComponentRunner>
LocalComponentRunner::NewBinding() {
return binding_.NewBinding(dispatcher_);
}
void LocalComponentRunner::Start(
fuchsia::component::runner::ComponentStartInfo start_info,
fidl::InterfaceRequest<fuchsia::component::runner::ComponentController> controller) {
ZX_ASSERT_MSG(start_info.has_program(), "Component manager sent start_info without program");
std::string const name = ExtractLocalComponentName(start_info.program());
ZX_ASSERT_MSG(ContainsReadyComponent(name),
"Component manager requested a named LocalComponent that is unregistered, already "
"running, or not restartable. Component name: %s",
name.c_str());
auto handles = CreateHandlesFromStartInfo(std::move(start_info), dispatcher_);
// take the component from the ready_components_ list
auto component = std::move(ready_components_[name]);
ZX_ASSERT_MSG(ready_components_.erase(name) == 1, "ready component not erased");
// TODO(https://fxbug.dev/296292544): Remove when build support for API level 16 is removed.
#if __Fuchsia_API_level__ < 17
// Ignore warnings caused by the use of the deprecated `LocalComponent` type as it is part of the
// implementation that supports the deprecated type.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (cpp17::holds_alternative<LocalComponent*>(component)) {
auto local_component_ptr = cpp17::get<LocalComponent*>(component);
#pragma clang diagnostic pop
auto on_start = [local_component_ptr](LocalComponentInstance*,
std::unique_ptr<LocalComponentHandles> handles) {
local_component_ptr->Start(std::move(handles));
// Do not call instance->SetOnStop() for components added by
// LocalComponent* (raw pointer). RealmBuilder does not manage the
// lifecycle of these components, so the LocalComponent pointer may not
// be valid, once started.
};
auto on_exit = [this, name]() mutable {
// Drop the ComponentInstance. This also causes the ComponentController to
// be dropped.
ZX_ASSERT_MSG(running_components_.erase(name) == 1, "running component not erased");
// Components added by LocalComponent* are not restartable, so they will
// not be added back to the ready_components_.
};
running_components_[name] = std::make_unique<LocalComponentInstance>(
std::move(controller), dispatcher_, std::move(on_start), std::move(on_exit));
} else {
#else
if (true) {
#endif
auto local_component_factory = std::move(cpp17::get<LocalComponentFactory>(component));
auto local_component = local_component_factory();
auto on_start = [name, local_component = std::move(local_component)](
LocalComponentInstance* instance,
std::unique_ptr<LocalComponentHandles> handles) mutable {
local_component->handles_ = std::move(handles);
local_component->OnStart();
// Drop the `LocalComponentImpl` on `ComponentController::Stop()`.
instance->SetOnStop(
[local_component = std::move(local_component)]() { local_component->OnStop(); });
};
auto on_exit = [this, local_component_factory = std::move(local_component_factory),
name]() mutable {
// Drop the ComponentInstance. This also causes the ComponentController
// and LocalComponentImpl to be dropped. Dropping the LocalComponentImpl
// drops the handles.
ZX_ASSERT_MSG(running_components_.erase(name) == 1, "running component not erased");
// return the factory back to the list of components that can be restarted
ready_components_[name] = std::move(local_component_factory);
};
running_components_[name] = std::make_unique<LocalComponentInstance>(
std::move(controller), dispatcher_, std::move(on_start), std::move(on_exit));
}
// Start the component instance.
running_components_[name]->Start(std::move(handles));
}
bool LocalComponentRunner::ContainsReadyComponent(std::string name) const {
return ready_components_.find(name) != ready_components_.cend();
}
std::unique_ptr<LocalComponentRunner> LocalComponentRunner::Builder::Build(
async_dispatcher_t* dispatcher) {
return std::make_unique<LocalComponentRunner>(std::move(components_), dispatcher);
}
void LocalComponentRunner::Builder::Register(std::string name, LocalComponentKind mock) {
ZX_ASSERT_MSG(!Contains(name), "Local component with same name being added: %s", name.c_str());
components_[name] = std::move(mock);
}
bool LocalComponentRunner::Builder::Contains(std::string name) const {
return components_.find(name) != components_.cend();
}
} // namespace internal
} // namespace component_testing