| // Copyright 2017 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 "garnet/lib/ui/gfx/engine/resource_linker.h" |
| |
| #include <lib/async/default.h> |
| |
| #include "garnet/lib/ui/gfx/resources/import.h" |
| |
| namespace scenic_impl { |
| namespace gfx { |
| |
| static zx_signals_t kEventPairDeathSignals = ZX_EVENTPAIR_PEER_CLOSED; |
| |
| #define ASSERT_INTERNAL_EXPORTS_CONSISTENCY \ |
| FXL_DCHECK(exported_resources_to_import_koids_.size() == \ |
| export_entries_.size()); |
| |
| ResourceLinker::ResourceLinker() |
| : unresolved_imports_(this), weak_factory_(this){}; |
| |
| ResourceLinker::~ResourceLinker() { |
| for (const auto& item : export_entries_) { |
| // Resource could be null if ExportResource() were called, but the |
| // import tokens weren't all released yet. |
| if (item.second.resource != nullptr) { |
| item.second.resource->SetExported(false, fxl::WeakPtr<ResourceLinker>()); |
| } |
| } |
| } |
| |
| bool ResourceLinker::ExportResource(Resource* resource, |
| zx::eventpair export_token) { |
| // Basic sanity checks for resource validity. |
| FXL_DCHECK(resource); |
| |
| zx_koid_t import_koid = fsl::GetRelatedKoid(export_token.get()); |
| if (import_koid == ZX_KOID_INVALID) { |
| // We were passed a bad export handle. |
| return false; |
| } |
| |
| // Ensure that the peer koid has not already been registered with the |
| // linker. |
| auto found = export_entries_.find(import_koid); |
| if (found != export_entries_.end()) { |
| // Cannot export more than once with the same |export_token|. |
| return false; |
| } |
| |
| // The resource must be removed from being considered for import if its |
| // peer is closed. |
| auto wait = std::make_unique<async::Wait>(export_token.get(), // handle |
| kEventPairDeathSignals // trigger |
| ); |
| wait->set_handler(std::bind(&ResourceLinker::OnTokenPeerDeath, this, |
| import_koid, std::placeholders::_3, |
| std::placeholders::_4)); |
| zx_status_t status = wait->Begin(async_get_default_dispatcher()); |
| FXL_CHECK(status == ZX_OK); |
| |
| // Add the resource and export token to our data structures. |
| export_entries_[import_koid] = ExportEntry{ |
| .export_token = std::move(export_token), // own the export token |
| .token_peer_death_waiter = std::move(wait), // |
| .resource = resource, // |
| }; |
| exported_resources_to_import_koids_.insert({resource, import_koid}); |
| exported_resources_.insert(resource); |
| resource->SetExported(true, GetWeakPtr()); |
| |
| ASSERT_INTERNAL_EXPORTS_CONSISTENCY; |
| |
| // Always perform linking last because it involves firing resolution |
| // callbacks which may access the linker. We need that view to be |
| // consistent. |
| PerformLinkingNow(import_koid); |
| |
| return true; |
| } |
| |
| void ResourceLinker::OnImportDestroyed(Import* import) { |
| // The exported resource is called the "imported resource" from Import's |
| // perspective. |
| Resource* exported_resource = import->imported_resource(); |
| if (exported_resource) { |
| RemoveExportedResourceIfUnbound(exported_resource); |
| } else { |
| unresolved_imports_.OnImportDestroyed(import); |
| } |
| } |
| |
| void ResourceLinker::OnImportResolvedForResource( |
| Import* import, Resource* exported_resource, |
| ImportResolutionResult resolution_result) { |
| switch (resolution_result) { |
| case ImportResolutionResult::kSuccess: |
| exported_resource->AddImport(import); |
| break; |
| case ImportResolutionResult::kExportHandleDiedBeforeBind: |
| import->UnbindImportedResource(); |
| break; |
| case ImportResolutionResult::kImportDestroyedBeforeBind: |
| break; |
| } |
| if (import_resolved_callback_) { |
| import_resolved_callback_(import, exported_resource, resolution_result); |
| } |
| } |
| |
| void ResourceLinker::RemoveExportedResourceIfUnbound( |
| Resource* exported_resource) { |
| FXL_DCHECK(exported_resource); |
| |
| if (!exported_resource->imports().empty()) { |
| // |exported_resource| still has imports bound to it. |
| return; |
| } |
| |
| auto range = |
| exported_resources_to_import_koids_.equal_range(exported_resource); |
| if (range.first != range.second) { |
| // There are outstanding import tokens that could be used to import the |
| // device. |
| return; |
| } |
| |
| // |exported_resource| is unbound. Remove it. |
| exported_resources_.erase(exported_resource); |
| |
| // Mark the resource as not exported, so it doesn't have to |
| // call back to us when it dies. |
| exported_resource->SetExported(false, fxl::WeakPtr<ResourceLinker>()); |
| |
| InvokeExpirationCallback(exported_resource, ExpirationCause::kNoImportsBound); |
| } |
| |
| bool ResourceLinker::ImportResource(Import* import, |
| ::fuchsia::ui::gfx::ImportSpec import_spec, |
| zx::eventpair import_token) { |
| // Make sure the import handle is valid. |
| zx_koid_t import_koid = fsl::GetKoid(import_token.get()); |
| if (import_koid == ZX_KOID_INVALID) { |
| // We were passed a bad import handle. |
| return false; |
| } |
| |
| // Register the import entry. |
| unresolved_imports_.AddUnresolvedImport(import, std::move(import_token), |
| import_koid); |
| |
| // Always perform linking last because it involves firing resolution callbacks |
| // which may access the linker. We need that view to be consistent. |
| |
| if (!PerformLinkingNow(import_koid)) { |
| // If |import| was not bound, it should listen for its peer token dying |
| // to know if it should be removed. |
| unresolved_imports_.ListenForTokenPeerDeath(import); |
| } |
| return true; |
| } |
| |
| void ResourceLinker::OnTokenPeerDeath(zx_koid_t import_koid, zx_status_t status, |
| const zx_packet_signal* signal) { |
| // This is invoked when all the peers for the registered export |
| // handle are closed, or if there is a loop death or other error. |
| RemoveExportEntryForExpiredKoid(import_koid); |
| } |
| |
| void ResourceLinker::InvokeExpirationCallback(Resource* resource, |
| ExpirationCause cause) { |
| if (expiration_callback_) { |
| expiration_callback_(resource, cause); |
| } |
| } |
| |
| Resource* ResourceLinker::RemoveExportEntryForExpiredKoid( |
| zx_koid_t import_koid) { |
| auto export_entry_iter = export_entries_.find(import_koid); |
| FXL_DCHECK(export_entry_iter != export_entries_.end()); |
| Resource* resource = export_entry_iter->second.resource; |
| |
| // Remove from |export_entries_|. |
| export_entries_.erase(export_entry_iter); |
| |
| // Remove from |exported_resources_to_import_koids_|. |
| RemoveFromExportedResourceToImportKoidsMap(resource, import_koid); |
| |
| // Remove from |resources_| if appropriate. |
| RemoveExportedResourceIfUnbound(resource); |
| |
| ASSERT_INTERNAL_EXPORTS_CONSISTENCY; |
| |
| return resource; |
| } |
| |
| void ResourceLinker::OnExportedResourceDestroyed(Resource* resource) { |
| FXL_DCHECK(resource); |
| |
| // Find all the exports for the Resource (it could have been |
| // exported more than once). |
| auto range = exported_resources_to_import_koids_.equal_range(resource); |
| for (auto import_koid_iter = range.first; import_koid_iter != range.second;) { |
| zx_koid_t import_koid = import_koid_iter->second; |
| |
| auto export_entry_iter = export_entries_.find(import_koid); |
| FXL_DCHECK(export_entry_iter != export_entries_.end()); |
| |
| // Remove from |export_entries_|. |
| export_entries_.erase(export_entry_iter); |
| |
| // Remove from |resources_to_import_koids_|, and advance iterator. |
| import_koid_iter = |
| exported_resources_to_import_koids_.erase(import_koid_iter); |
| } |
| |
| // Mark the resource as not exported, so it doesn't have to |
| // call back to us when it dies. |
| resource->SetExported(false, fxl::WeakPtr<ResourceLinker>()); |
| |
| // Remove from |resources_|. |
| size_t num_removed = exported_resources_.erase(resource); |
| |
| FXL_DCHECK(num_removed == 1); |
| |
| InvokeExpirationCallback(resource, ExpirationCause::kResourceDestroyed); |
| |
| // When the resource dies, it will unbind all its imported resources. |
| } |
| |
| size_t ResourceLinker::NumExports() const { |
| ASSERT_INTERNAL_EXPORTS_CONSISTENCY; |
| return exported_resources_.size(); |
| } |
| |
| size_t ResourceLinker::NumUnresolvedImports() const { |
| return unresolved_imports_.size(); |
| } |
| |
| void ResourceLinker::SetOnExpiredCallback(OnExpiredCallback callback) { |
| expiration_callback_ = std::move(callback); |
| } |
| |
| void ResourceLinker::SetOnImportResolvedCallback( |
| OnImportResolvedCallback callback) { |
| import_resolved_callback_ = std::move(callback); |
| } |
| |
| size_t ResourceLinker::NumExportsForSession(Session* session) { |
| size_t count = 0; |
| for (auto& pair : export_entries_) { |
| if (session == pair.second.resource->session()) { |
| ++count; |
| } |
| } |
| return count; |
| } |
| |
| bool ResourceLinker::PerformLinkingNow(zx_koid_t import_koid) { |
| // Find the unresolved import entries if present. |
| size_t num_imports = |
| unresolved_imports_.NumUnresolvedImportsForKoid(import_koid); |
| |
| if (num_imports == 0) { |
| // Nothing to resolve yet. |
| return false; |
| } |
| |
| // Find the corresponding entry in the exported resource registrations. |
| auto export_entry_iter = export_entries_.find(import_koid); |
| if (export_entry_iter == export_entries_.end()) { |
| // Export is not present yet. |
| return false; |
| } |
| |
| // We have import and export entries that match for the same import_koid. |
| // Collect all the resolution callbacks that need to be invoked. |
| auto imports = |
| unresolved_imports_.GetAndRemoveUnresolvedImportsForKoid(import_koid); |
| |
| // Finally, invoke the resolution callbacks last. This is |
| // important because we want to ensure that any code that runs |
| // within the callbacks sees a consistent view of the linker. |
| auto matched_resource = export_entry_iter->second.resource; |
| for (const auto& import : imports) { |
| OnImportResolvedForResource(import, matched_resource, |
| ImportResolutionResult::kSuccess); |
| } |
| |
| return true; |
| } |
| |
| void ResourceLinker::RemoveFromExportedResourceToImportKoidsMap( |
| Resource* resource, zx_koid_t import_koid) { |
| // Remove this specific export from |export_entries_by_resource_|. (The |
| // same resource can be exported multiple times). |
| auto range = exported_resources_to_import_koids_.equal_range(resource); |
| size_t range_size = 0; |
| |
| for (auto it = range.first; it != range.second;) { |
| range_size++; |
| if (it->second == import_koid) { |
| it = exported_resources_to_import_koids_.erase(it); |
| } else { |
| it++; |
| } |
| } |
| } |
| |
| } // namespace gfx |
| } // namespace scenic_impl |