blob: 81b162ab2e687936b7c26c3dbfd37c8b7df05543 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/ui/scenic/lib/gfx/engine/object_linker.h"
#include <lib/async/default.h>
#include <functional>
#include "src/lib/fsl/handles/object_info.h"
#include "src/lib/fxl/logging.h"
namespace scenic_impl {
namespace gfx {
void ObjectLinkerBase::Link::LinkInvalidated(bool on_destruction) {
if (link_invalidated_) {
link_invalidated_(on_destruction);
link_invalidated_ = nullptr;
}
}
void ObjectLinkerBase::Link::LinkUnresolved() {
if (link_invalidated_) {
link_invalidated_(false);
}
}
size_t ObjectLinkerBase::UnresolvedExportCount() {
return std::count_if(exports_.begin(), exports_.end(),
[](const auto& iter) { return iter.second.IsUnresolved(); });
}
size_t ObjectLinkerBase::UnresolvedImportCount() {
return std::count_if(imports_.begin(), imports_.end(),
[](const auto& iter) { return iter.second.IsUnresolved(); });
}
zx_koid_t ObjectLinkerBase::CreateEndpoint(zx::handle token, ErrorReporter* error_reporter,
bool is_import) {
// Select imports or exports to operate on based on the flag.
auto& endpoints = is_import ? imports_ : exports_;
if (!token) {
error_reporter->ERROR() << "Token is invalid";
return ZX_KOID_INVALID;
}
zx_koid_t endpoint_id;
zx_koid_t peer_endpoint_id;
std::tie(endpoint_id, peer_endpoint_id) = fsl::GetKoids(token.get());
if (endpoint_id == ZX_KOID_INVALID || peer_endpoint_id == ZX_KOID_INVALID) {
error_reporter->ERROR() << "Token with ID " << token.get() << " refers to invalid objects";
return ZX_KOID_INVALID;
}
auto endpoint_iter = endpoints.find(endpoint_id);
if (endpoint_iter != endpoints.end()) {
error_reporter->ERROR() << "Endpoint with id " << endpoint_id
<< " is already in use by this ObjectLinker";
return ZX_KOID_INVALID;
}
// Create a new endpoint in an unresolved state. Full linking cannot occur
// until Initialize() is called on the endpoint to provide a link object and
// handler callbacks.
Endpoint new_endpoint;
new_endpoint.peer_endpoint_id = peer_endpoint_id;
new_endpoint.peer_death_waiter = WaitForPeerDeath(token.get(), endpoint_id, is_import);
new_endpoint.token = std::move(token);
auto emplaced_endpoint = endpoints.emplace(endpoint_id, std::move(new_endpoint));
FXL_DCHECK(emplaced_endpoint.second);
return endpoint_id;
}
void ObjectLinkerBase::DestroyEndpoint(zx_koid_t endpoint_id, bool is_import, bool destroy_peer) {
auto& endpoints = is_import ? imports_ : exports_;
auto& peer_endpoints = is_import ? exports_ : imports_;
auto endpoint_iter = endpoints.find(endpoint_id);
if (endpoint_iter == endpoints.end()) {
FXL_LOG(ERROR) << "Attempted to remove an unknown endpoint " << endpoint_id
<< " from ObjectLinker";
return;
}
// If the object has a peer linked tell it about the object being removed,
// which will immediately invalidate the peer.
if (destroy_peer) {
zx_koid_t peer_endpoint_id = endpoint_iter->second.peer_endpoint_id;
auto peer_endpoint_iter = peer_endpoints.find(peer_endpoint_id);
if (peer_endpoint_iter != peer_endpoints.end()) {
Endpoint& peer_endpoint = peer_endpoint_iter->second;
// Invalidate the peer endpoint. If Initialize() has already been called on
// the peer endpoint, then close its connection which will destroy it.
// Otherwise, any future connection attempts will fail immediately with a
// link_failed callback, due to peer_endpoint_id being marked as
// invalid.
//
// This handles the case where the peer exists but Initialize() has not been
// called on it yet (so no callbacks exist).
peer_endpoint.peer_endpoint_id = ZX_KOID_INVALID;
FXL_DCHECK(peer_endpoint.link);
peer_endpoint.link->Invalidate(/*on_destruction=*/false, /*invalidate_peer=*/true);
}
}
// At this point it is safe to completely erase the endpoint for the object.
endpoints.erase(endpoint_iter);
}
void ObjectLinkerBase::InitializeEndpoint(ObjectLinkerBase::Link* link, zx_koid_t endpoint_id,
bool is_import) {
FXL_DCHECK(link);
auto& endpoints = is_import ? imports_ : exports_;
// Update the endpoint with the connection information.
auto endpoint_iter = endpoints.find(endpoint_id);
FXL_DCHECK(endpoint_iter != endpoints.end());
Endpoint& endpoint = endpoint_iter->second;
// If the endpoint is no longer valid (i.e. its peer no longer exists), then
// immediately signal a disconnection (which will destroy the endpoint)
// instead of linking.
//
// This edge-case happens if the endpoint's peer is destroyed after the
// endpoint is created, but before Initialize() is called on it.
zx_koid_t peer_endpoint_id = endpoint.peer_endpoint_id;
if (peer_endpoint_id == ZX_KOID_INVALID) {
link->Invalidate(/*on_destruction=*/false, /*invalidate_peer=*/true);
return;
}
if (!endpoint.link) {
endpoint.link = link;
// Attempt to locate and link with the endpoint's peer.
AttemptLinking(endpoint_id, peer_endpoint_id, is_import);
} else {
endpoint.link = link;
}
}
void ObjectLinkerBase::AttemptLinking(zx_koid_t endpoint_id, zx_koid_t peer_endpoint_id,
bool is_import) {
auto& endpoints = is_import ? imports_ : exports_;
auto& peer_endpoints = is_import ? exports_ : imports_;
auto endpoint_iter = endpoints.find(endpoint_id);
FXL_DCHECK(endpoint_iter != endpoints.end());
auto peer_endpoint_iter = peer_endpoints.find(peer_endpoint_id);
if (peer_endpoint_iter == peer_endpoints.end()) {
return; // Peer endpoint hasn't even been created yet, bail.
}
Endpoint& endpoint = endpoint_iter->second;
Endpoint& peer_endpoint = peer_endpoint_iter->second;
if (!peer_endpoint.link) {
return; // Peer endpoint isn't connected yet, bail.
}
// Delete the peer waiters now that the endpoints are resolved.
endpoint.peer_death_waiter = nullptr;
peer_endpoint.peer_death_waiter = nullptr;
// Do linking last, so clients see a consistent view of the Linker.
// Always fire the callback for the Export first, so clients can rely on
// callbacks firing in a certain order.
if (is_import) {
peer_endpoint.link->LinkResolved(endpoint.link);
endpoint.link->LinkResolved(peer_endpoint.link);
} else {
endpoint.link->LinkResolved(peer_endpoint.link);
peer_endpoint.link->LinkResolved(endpoint.link);
}
}
std::unique_ptr<async::Wait> ObjectLinkerBase::WaitForPeerDeath(zx_handle_t endpoint_handle,
zx_koid_t endpoint_id,
bool is_import) {
// Each endpoint must be removed from being considered for linking if its
// peer's handle is closed before the two entries are successfully linked.
// This communication happens via the link_failed callback.
//
// Once linking has occurred, this communication happens via UnregisterExport
// or UnregisterImport and the peer_destroyed callback.
// TODO(SCN-982): Follow up on __ZX_OBJECT_PEER_CLOSED with Zircon.
static_assert(ZX_CHANNEL_PEER_CLOSED == __ZX_OBJECT_PEER_CLOSED, "enum mismatch");
static_assert(ZX_EVENTPAIR_PEER_CLOSED == __ZX_OBJECT_PEER_CLOSED, "enum mismatch");
static_assert(ZX_FIFO_PEER_CLOSED == __ZX_OBJECT_PEER_CLOSED, "enum mismatch");
static_assert(ZX_SOCKET_PEER_CLOSED == __ZX_OBJECT_PEER_CLOSED, "enum mismatch");
auto waiter = std::make_unique<async::Wait>(
endpoint_handle, __ZX_OBJECT_PEER_CLOSED, 0, std::bind([this, endpoint_id, is_import]() {
auto& endpoints = is_import ? imports_ : exports_;
auto endpoint_iter = endpoints.find(endpoint_id);
FXL_DCHECK(endpoint_iter != endpoints.end());
Endpoint& endpoint = endpoint_iter->second;
// Invalidate the endpoint. If Initialize() has
// already been called on the endpoint, then close
// its connection (which will cause it to be
// destroyed). Any future connection attempts will
// fail immediately with a link_failed call, due to
// peer_endpoint_id being marked as invalid.
endpoint.peer_endpoint_id = ZX_KOID_INVALID;
if (endpoint.link) {
endpoint.link->Invalidate(/*on_destruction=*/false, /*invalidate_peer=*/true);
}
}));
zx_status_t status = waiter->Begin(async_get_default_dispatcher());
FXL_DCHECK(status == ZX_OK);
return waiter;
}
zx::handle ObjectLinkerBase::ReleaseToken(zx_koid_t endpoint_id, bool is_import) {
auto& endpoints = is_import ? imports_ : exports_;
auto& peer_endpoints = is_import ? exports_ : imports_;
// If the endpoint was resolved, it will still be invalidated, but the peer endpoint must be
// unresolved first if it exists.
auto endpoint_iter = endpoints.find(endpoint_id);
FXL_DCHECK(endpoint_iter != endpoints.end());
zx_koid_t peer_endpoint_id = endpoint_iter->second.peer_endpoint_id;
auto peer_endpoint_iter = peer_endpoints.find(peer_endpoint_id);
if (peer_endpoint_iter == peer_endpoints.end()) {
return std::move(endpoint_iter->second.token);
}
// Signal that the link is now unresolved, then re-create the peer death waiter to flag the
// endpoint as unresolved.
if (peer_endpoint_iter->second.link) {
peer_endpoint_iter->second.link->LinkUnresolved();
}
peer_endpoint_iter->second.peer_death_waiter =
WaitForPeerDeath(peer_endpoint_iter->second.token.get(), peer_endpoint_id, !is_import);
return std::move(endpoint_iter->second.token);
}
} // namespace gfx
} // namespace scenic_impl