diff options
Diffstat (limited to 'chromium/base/fuchsia')
12 files changed, 294 insertions, 82 deletions
diff --git a/chromium/base/fuchsia/default_context.h b/chromium/base/fuchsia/default_context.h index 4484f10f910..d45b68cc82b 100644 --- a/chromium/base/fuchsia/default_context.h +++ b/chromium/base/fuchsia/default_context.h @@ -16,8 +16,12 @@ class ComponentContext; namespace base { namespace fuchsia { + +// TODO(https://crbug.com/1090364): Remove this file when external dependencies +// have been migrated to process_context. // Returns default sys::ComponentContext for the current process. BASE_EXPORT sys::ComponentContext* ComponentContextForCurrentProcess(); + } // namespace fuchsia // Replaces the default sys::ComponentContext for the current process, and diff --git a/chromium/base/fuchsia/intl_profile_watcher.cc b/chromium/base/fuchsia/intl_profile_watcher.cc index 7b16517f339..27247f87a2a 100644 --- a/chromium/base/fuchsia/intl_profile_watcher.cc +++ b/chromium/base/fuchsia/intl_profile_watcher.cc @@ -9,8 +9,8 @@ #include <string> #include <vector> -#include "base/fuchsia/default_context.h" #include "base/fuchsia/fuchsia_logging.h" +#include "base/fuchsia/process_context.h" #include "base/strings/string_piece.h" using ::fuchsia::intl::Profile; @@ -19,7 +19,7 @@ namespace base { namespace fuchsia { IntlProfileWatcher::IntlProfileWatcher(ProfileChangeCallback on_profile_changed) - : IntlProfileWatcher(ComponentContextForCurrentProcess() + : IntlProfileWatcher(ComponentContextForProcess() ->svc() ->Connect<::fuchsia::intl::PropertyProvider>(), on_profile_changed) {} @@ -66,7 +66,7 @@ std::string IntlProfileWatcher::GetPrimaryTimeZoneIdFromProfile( // static std::string IntlProfileWatcher::GetPrimaryTimeZoneIdForIcuInitialization() { ::fuchsia::intl::PropertyProviderSyncPtr provider; - ComponentContextForCurrentProcess()->svc()->Connect(provider.NewRequest()); + ComponentContextForProcess()->svc()->Connect(provider.NewRequest()); return GetPrimaryTimeZoneIdFromPropertyProvider(std::move(provider)); } diff --git a/chromium/base/fuchsia/process_context.cc b/chromium/base/fuchsia/process_context.cc new file mode 100644 index 00000000000..02dfa43be75 --- /dev/null +++ b/chromium/base/fuchsia/process_context.cc @@ -0,0 +1,41 @@ +// Copyright 2020 The Chromium 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 "base/fuchsia/process_context.h" + +#include <lib/sys/cpp/component_context.h> +#include <lib/sys/inspect/cpp/component.h> +#include <utility> + +#include "base/fuchsia/process_context.h" +#include "base/no_destructor.h" + +namespace base { + +namespace { +std::unique_ptr<sys::ComponentContext>* ProcessComponentContextPtr() { + static base::NoDestructor<std::unique_ptr<sys::ComponentContext>> value( + std::make_unique<sys::ComponentContext>( + sys::ServiceDirectory::CreateFromNamespace())); + return value.get(); +} +} // namespace + +sys::ComponentInspector* ComponentInspectorForProcess() { + static base::NoDestructor<sys::ComponentInspector> value( + ComponentContextForProcess()); + return value.get(); +} + +sys::ComponentContext* ComponentContextForProcess() { + return ProcessComponentContextPtr()->get(); +} + +std::unique_ptr<sys::ComponentContext> ReplaceComponentContextForProcessForTest( + std::unique_ptr<sys::ComponentContext> context) { + std::swap(*ProcessComponentContextPtr(), context); + return context; +} + +} // namespace base diff --git a/chromium/base/fuchsia/process_context.h b/chromium/base/fuchsia/process_context.h new file mode 100644 index 00000000000..7808eacd08d --- /dev/null +++ b/chromium/base/fuchsia/process_context.h @@ -0,0 +1,35 @@ +// Copyright 2020 The Chromium 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 BASE_FUCHSIA_PROCESS_CONTEXT_H_ +#define BASE_FUCHSIA_PROCESS_CONTEXT_H_ + +#include <memory> + +#include "base/base_export.h" + +namespace sys { +class ComponentContext; +class ComponentInspector; +} // namespace sys + +namespace base { + +// Returns sys::ComponentInspector for the current process. +BASE_EXPORT sys::ComponentInspector* ComponentInspectorForProcess(); + +// Returns default sys::ComponentContext for the current process. +BASE_EXPORT sys::ComponentContext* ComponentContextForProcess(); + +// Replaces the default sys::ComponentContext for the current process, and +// returns the previously-active one. +// Use the base::TestComponentContextForProcess rather than calling this +// directly. +BASE_EXPORT std::unique_ptr<sys::ComponentContext> +ReplaceComponentContextForProcessForTest( + std::unique_ptr<sys::ComponentContext> context); + +} // namespace base + +#endif // BASE_FUCHSIA_PROCESS_CONTEXT_H_ diff --git a/chromium/base/fuchsia/scoped_service_binding.h b/chromium/base/fuchsia/scoped_service_binding.h index 568a1ffe56b..9da1218510b 100644 --- a/chromium/base/fuchsia/scoped_service_binding.h +++ b/chromium/base/fuchsia/scoped_service_binding.h @@ -7,10 +7,13 @@ #include <lib/fidl/cpp/binding.h> #include <lib/fidl/cpp/binding_set.h> +#include <lib/fidl/cpp/interface_request.h> #include <lib/zx/channel.h> #include "base/base_export.h" #include "base/callback.h" +#include "base/fuchsia/scoped_service_publisher.h" +#include "base/optional.h" namespace sys { class OutgoingDirectory; @@ -23,128 +26,95 @@ class PseudoDir; namespace base { namespace fuchsia { -namespace internal { - -class BASE_EXPORT ScopedServiceBindingBase { - public: - explicit ScopedServiceBindingBase(sys::OutgoingDirectory* outgoing_directory); - explicit ScopedServiceBindingBase(vfs::PseudoDir* pseudo_dir); - - ~ScopedServiceBindingBase(); - - protected: - // Same type as vfs::Service::Connector, so the value can be passed directly - // to vfs::Service. - using Connector = - fit::function<void(zx::channel channel, async_dispatcher_t* dispatcher)>; - - void RegisterService(const char* service_name, Connector connector); - void UnregisterService(const char* service_name); - - private: - vfs::PseudoDir* const pseudo_dir_ = nullptr; -}; - -} // namespace internal - template <typename Interface> -class ScopedServiceBinding : public internal::ScopedServiceBindingBase { +class BASE_EXPORT ScopedServiceBinding { public: // Published a public service in the specified |outgoing_directory|. // |outgoing_directory| and |impl| must outlive the binding. ScopedServiceBinding(sys::OutgoingDirectory* outgoing_directory, Interface* impl) - : ScopedServiceBindingBase(outgoing_directory), impl_(impl) { - RegisterService(Interface::Name_, - fit::bind_member(this, &ScopedServiceBinding::BindClient)); - } + : publisher_(outgoing_directory, bindings_.GetHandler(impl)) {} // Publishes a service in the specified |pseudo_dir|. |pseudo_dir| and |impl| // must outlive the binding. ScopedServiceBinding(vfs::PseudoDir* pseudo_dir, Interface* impl) - : ScopedServiceBindingBase(pseudo_dir), impl_(impl) { - RegisterService(Interface::Name_, - fit::bind_member(this, &ScopedServiceBinding::BindClient)); - } + : publisher_(pseudo_dir, bindings_.GetHandler(impl)) {} - ~ScopedServiceBinding() { UnregisterService(Interface::Name_); } + ~ScopedServiceBinding() = default; - void SetOnLastClientCallback(base::OnceClosure on_last_client_callback) { - on_last_client_callback_ = std::move(on_last_client_callback); + // |on_last_client_callback| will be called every time the number of connected + // clients drops to 0. + void SetOnLastClientCallback(base::RepeatingClosure on_last_client_callback) { bindings_.set_empty_set_handler( - fit::bind_member(this, &ScopedServiceBinding::OnBindingSetEmpty)); + [callback = std::move(on_last_client_callback)] { callback.Run(); }); } bool has_clients() const { return bindings_.size() != 0; } private: - void BindClient(zx::channel channel, async_dispatcher_t* dispatcher) { - bindings_.AddBinding(impl_, - fidl::InterfaceRequest<Interface>(std::move(channel)), - dispatcher); - } - - void OnBindingSetEmpty() { - bindings_.set_empty_set_handler(nullptr); - std::move(on_last_client_callback_).Run(); - } - - sys::OutgoingDirectory* const directory_ = nullptr; - vfs::PseudoDir* const pseudo_dir_ = nullptr; - Interface* const impl_; fidl::BindingSet<Interface> bindings_; - base::OnceClosure on_last_client_callback_; + ScopedServicePublisher<Interface> publisher_; DISALLOW_COPY_AND_ASSIGN(ScopedServiceBinding); }; // Scoped service binding which allows only a single client to be connected // at any time. By default a new connection will disconnect an existing client. -enum class ScopedServiceBindingPolicy { kPreferNew, kPreferExisting }; +enum class ScopedServiceBindingPolicy { + kPreferNew, + kPreferExisting, + kConnectOnce +}; template <typename Interface, ScopedServiceBindingPolicy Policy = ScopedServiceBindingPolicy::kPreferNew> -class ScopedSingleClientServiceBinding - : public internal::ScopedServiceBindingBase { +class BASE_EXPORT ScopedSingleClientServiceBinding { public: // |outgoing_directory| and |impl| must outlive the binding. ScopedSingleClientServiceBinding(sys::OutgoingDirectory* outgoing_directory, Interface* impl) - : ScopedServiceBindingBase(outgoing_directory), binding_(impl) { - RegisterService( - Interface::Name_, + : binding_(impl) { + publisher_.emplace( + outgoing_directory, fit::bind_member(this, &ScopedSingleClientServiceBinding::BindClient)); + binding_.set_error_handler(fit::bind_member( + this, &ScopedSingleClientServiceBinding::OnBindingEmpty)); } - ~ScopedSingleClientServiceBinding() { UnregisterService(Interface::Name_); } + ~ScopedSingleClientServiceBinding() = default; typename Interface::EventSender_& events() { return binding_.events(); } + // |on_last_client_callback| will be called the first time a client + // disconnects. It is still possible for a client to connect after that point + // if Policy is kPreferNew of kPreferExisting. void SetOnLastClientCallback(base::OnceClosure on_last_client_callback) { on_last_client_callback_ = std::move(on_last_client_callback); - binding_.set_error_handler(fit::bind_member( - this, &ScopedSingleClientServiceBinding::OnBindingEmpty)); } bool has_clients() const { return binding_.is_bound(); } private: - void BindClient(zx::channel channel, async_dispatcher_t* dispatcher) { + void BindClient(fidl::InterfaceRequest<Interface> request) { if (Policy == ScopedServiceBindingPolicy::kPreferExisting && binding_.is_bound()) { return; } - binding_.Bind(fidl::InterfaceRequest<Interface>(std::move(channel)), - dispatcher); + binding_.Bind(std::move(request)); + if (Policy == ScopedServiceBindingPolicy::kConnectOnce) { + publisher_.reset(); + } } void OnBindingEmpty(zx_status_t status) { - binding_.set_error_handler(nullptr); - std::move(on_last_client_callback_).Run(); + if (on_last_client_callback_) { + std::move(on_last_client_callback_).Run(); + } } fidl::Binding<Interface> binding_; + base::Optional<ScopedServicePublisher<Interface>> publisher_; base::OnceClosure on_last_client_callback_; DISALLOW_COPY_AND_ASSIGN(ScopedSingleClientServiceBinding); diff --git a/chromium/base/fuchsia/scoped_service_binding_unittest.cc b/chromium/base/fuchsia/scoped_service_binding_unittest.cc index 4368055cad4..a01bae3ea60 100644 --- a/chromium/base/fuchsia/scoped_service_binding_unittest.cc +++ b/chromium/base/fuchsia/scoped_service_binding_unittest.cc @@ -122,5 +122,73 @@ TEST_F(ScopedServiceBindingTest, SingleBindingSetOnLastClientCallback) { run_loop.Run(); } +// Test the kConnectOnce option for ScopedSingleClientServiceBinding properly +// stops publishing the service after a first disconnect. +TEST_F(ScopedServiceBindingTest, ConnectOnce_OnlyFirstConnectionSucceeds) { + // Teardown the default multi-client binding and create a connect-once one. + service_binding_ = nullptr; + ScopedSingleClientServiceBinding<testfidl::TestInterface, + ScopedServiceBindingPolicy::kConnectOnce> + binding(outgoing_directory_.get(), &test_service_); + + // Connect the first client, and verify that it is functional. + auto existing_client = + public_service_directory_->Connect<testfidl::TestInterface>(); + VerifyTestInterface(&existing_client, ZX_OK); + + // Connect the second client, then verify that it gets closed and the existing + // one remains functional. + auto new_client = + public_service_directory_->Connect<testfidl::TestInterface>(); + RunLoop().RunUntilIdle(); + EXPECT_FALSE(new_client); + VerifyTestInterface(&existing_client, ZX_OK); + + // Disconnect the first client. + existing_client.Unbind().TakeChannel().reset(); + RunLoop().RunUntilIdle(); + + // Re-connect the second client, then verify that it gets closed. + new_client = public_service_directory_->Connect<testfidl::TestInterface>(); + RunLoop().RunUntilIdle(); + EXPECT_FALSE(new_client); +} + +class MultiUseBindingTest : public ScopedServiceBindingTest { + public: + MultiUseBindingTest() { + service_binding_->SetOnLastClientCallback( + BindRepeating(&MultiUseBindingTest::OnLastClient, Unretained(this))); + } + ~MultiUseBindingTest() override = default; + + protected: + void OnLastClient() { disconnect_count_++; } + + int disconnect_count_ = 0; +}; + +// Test the last client callback is called every time the number of active +// clients reaches 0. +TEST_F(MultiUseBindingTest, MultipleLastClientCallback) { + // Connect a client, verify it is functional. + auto stub = public_service_directory_->Connect<testfidl::TestInterface>(); + VerifyTestInterface(&stub, ZX_OK); + + // Disconnect the client, the callback should have been called once. + stub.Unbind().TakeChannel().reset(); + RunLoop().RunUntilIdle(); + EXPECT_EQ(disconnect_count_, 1); + + // Re-connect the client, verify it is functional. + stub = public_service_directory_->Connect<testfidl::TestInterface>(); + VerifyTestInterface(&stub, ZX_OK); + + // Disconnect the client, the callback should have been called a second time. + stub.Unbind().TakeChannel().reset(); + RunLoop().RunUntilIdle(); + EXPECT_EQ(disconnect_count_, 2); +} + } // namespace fuchsia } // namespace base diff --git a/chromium/base/fuchsia/scoped_service_publisher.h b/chromium/base/fuchsia/scoped_service_publisher.h new file mode 100644 index 00000000000..60eb1cf404f --- /dev/null +++ b/chromium/base/fuchsia/scoped_service_publisher.h @@ -0,0 +1,51 @@ +// Copyright 2020 The Chromium 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 BASE_FUCHSIA_SCOPED_SERVICE_PUBLISHER_H_ +#define BASE_FUCHSIA_SCOPED_SERVICE_PUBLISHER_H_ + +#include <lib/async/dispatcher.h> +#include <lib/fidl/cpp/interface_request.h> +#include <lib/sys/cpp/outgoing_directory.h> +#include <lib/vfs/cpp/pseudo_dir.h> +#include <lib/vfs/cpp/service.h> +#include <lib/zx/channel.h> + +#include "base/base_export.h" +#include "base/macros.h" + +namespace base { +namespace fuchsia { + +template <typename Interface> +class BASE_EXPORT ScopedServicePublisher { + public: + // Publishes a public service in the specified |outgoing_directory|. + // |outgoing_directory| and |handler| must outlive the binding. + ScopedServicePublisher(sys::OutgoingDirectory* outgoing_directory, + fidl::InterfaceRequestHandler<Interface> handler) + : ScopedServicePublisher(outgoing_directory->GetOrCreateDirectory("svc"), + std::move(handler)) {} + + // Publishes a service in the specified |pseudo_dir|. |pseudo_dir| and + // |handler| must outlive the binding. + ScopedServicePublisher(vfs::PseudoDir* pseudo_dir, + fidl::InterfaceRequestHandler<Interface> handler) + : pseudo_dir_(pseudo_dir) { + pseudo_dir_->AddEntry(Interface::Name_, + std::make_unique<vfs::Service>(std::move(handler))); + } + + ~ScopedServicePublisher() { pseudo_dir_->RemoveEntry(Interface::Name_); } + + private: + vfs::PseudoDir* const pseudo_dir_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(ScopedServicePublisher); +}; + +} // namespace fuchsia +} // namespace base + +#endif // BASE_FUCHSIA_SCOPED_SERVICE_PUBLISHER_H_ diff --git a/chromium/base/fuchsia/scoped_service_publisher_unittest.cc b/chromium/base/fuchsia/scoped_service_publisher_unittest.cc new file mode 100644 index 00000000000..2d9f5e41e86 --- /dev/null +++ b/chromium/base/fuchsia/scoped_service_publisher_unittest.cc @@ -0,0 +1,46 @@ +// Copyright 2020 The Chromium 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 "base/fuchsia/scoped_service_publisher.h" + +#include <lib/fidl/cpp/binding_set.h> + +#include "base/fuchsia/service_directory_test_base.h" +#include "base/run_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace fuchsia { + +class ScopedServicePublisherTest : public ServiceDirectoryTestBase {}; + +TEST_F(ScopedServicePublisherTest, ConstructorPublishesService) { + // Remove the default service binding. + service_binding_.reset(); + + // Create bindings and register using a publisher instance. + fidl::BindingSet<testfidl::TestInterface> bindings; + ScopedServicePublisher<testfidl::TestInterface> publisher( + outgoing_directory_.get(), bindings.GetHandler(&test_service_)); + auto client = public_service_directory_->Connect<testfidl::TestInterface>(); + VerifyTestInterface(&client, ZX_OK); +} + +TEST_F(ScopedServicePublisherTest, DestructorRemovesService) { + // Remove the default service binding. + service_binding_.reset(); + + fidl::BindingSet<testfidl::TestInterface> bindings; + { + ScopedServicePublisher<testfidl::TestInterface> publisher( + outgoing_directory_.get(), bindings.GetHandler(&test_service_)); + } + // Once the publisher leaves scope, the service shouldn't be available. + auto new_client = + public_service_directory_->Connect<testfidl::TestInterface>(); + VerifyTestInterface(&new_client, ZX_ERR_PEER_CLOSED); +} + +} // namespace fuchsia +} // namespace base diff --git a/chromium/base/fuchsia/service_provider_impl.h b/chromium/base/fuchsia/service_provider_impl.h index f701684982c..a997a56fa65 100644 --- a/chromium/base/fuchsia/service_provider_impl.h +++ b/chromium/base/fuchsia/service_provider_impl.h @@ -15,7 +15,6 @@ #include "base/base_export.h" #include "base/callback.h" -#include "base/fuchsia/default_context.h" #include "base/macros.h" namespace sys { diff --git a/chromium/base/fuchsia/test_component_context_for_process.cc b/chromium/base/fuchsia/test_component_context_for_process.cc index 3f2f9ea2a39..f4ae62d12bc 100644 --- a/chromium/base/fuchsia/test_component_context_for_process.cc +++ b/chromium/base/fuchsia/test_component_context_for_process.cc @@ -10,9 +10,9 @@ #include <lib/sys/cpp/component_context.h> #include "base/files/file_enumerator.h" -#include "base/fuchsia/default_context.h" #include "base/fuchsia/filtered_service_directory.h" #include "base/fuchsia/fuchsia_logging.h" +#include "base/fuchsia/process_context.h" #include "base/run_loop.h" namespace base { @@ -26,7 +26,7 @@ TestComponentContextForProcess::TestComponentContextForProcess( // Set up |incoming_services_| to use the ServiceDirectory from the current // default ComponentContext to fetch services from. context_services_ = std::make_unique<fuchsia::FilteredServiceDirectory>( - base::fuchsia::ComponentContextForCurrentProcess()->svc().get()); + base::ComponentContextForProcess()->svc().get()); // Push all services from /svc to the test context if requested. if (initial_state == InitialState::kCloneAll) { @@ -47,7 +47,7 @@ TestComponentContextForProcess::TestComponentContextForProcess( // directory of |context_services_| published by the test, and with a request // for the process' root outgoing directory. fidl::InterfaceHandle<::fuchsia::io::Directory> published_root_directory; - old_context_ = ReplaceComponentContextForCurrentProcessForTest( + old_context_ = ReplaceComponentContextForProcessForTest( std::make_unique<sys::ComponentContext>( std::move(incoming_services), published_root_directory.NewRequest().TakeChannel())); @@ -64,7 +64,7 @@ TestComponentContextForProcess::TestComponentContextForProcess( } TestComponentContextForProcess::~TestComponentContextForProcess() { - ReplaceComponentContextForCurrentProcessForTest(std::move(old_context_)); + ReplaceComponentContextForProcessForTest(std::move(old_context_)); } sys::OutgoingDirectory* TestComponentContextForProcess::additional_services() { diff --git a/chromium/base/fuchsia/test_component_context_for_process.h b/chromium/base/fuchsia/test_component_context_for_process.h index 0c3544780ba..28cd7107243 100644 --- a/chromium/base/fuchsia/test_component_context_for_process.h +++ b/chromium/base/fuchsia/test_component_context_for_process.h @@ -24,9 +24,8 @@ class FilteredServiceDirectory; } // namespace fuchsia // Replaces the process-global sys::ComponentContext (as returned by the -// base::fuchsia::ComponentContextForCurrentProcess() function) with an empty -// instance which the calling test can configure, and restores the original -// when deleted. +// base::ComponentContextForProcess() function) with an empty instance which the +// calling test can configure, and restores the original when deleted. // // The test ComponentContext runs on the test main thread, which means that: // - Tests using TestComponentContextForProcess must instantiate a diff --git a/chromium/base/fuchsia/test_component_context_for_process_unittest.cc b/chromium/base/fuchsia/test_component_context_for_process_unittest.cc index 9531a1cc637..e83dea9ee55 100644 --- a/chromium/base/fuchsia/test_component_context_for_process_unittest.cc +++ b/chromium/base/fuchsia/test_component_context_for_process_unittest.cc @@ -7,8 +7,8 @@ #include <fuchsia/intl/cpp/fidl.h> #include <lib/sys/cpp/component_context.h> -#include "base/fuchsia/default_context.h" #include "base/fuchsia/fuchsia_logging.h" +#include "base/fuchsia/process_context.h" #include "base/fuchsia/scoped_service_binding.h" #include "base/fuchsia/testfidl/cpp/fidl.h" #include "base/run_loop.h" @@ -26,7 +26,7 @@ class TestComponentContextForProcessTest bool HasTestInterface() { return VerifyTestInterface( - fuchsia::ComponentContextForCurrentProcess() + ComponentContextForProcess() ->svc() ->Connect<fuchsia::testfidl::TestInterface>()); } @@ -85,8 +85,7 @@ TEST_F(TestComponentContextForProcessTest, InjectTestInterface) { TEST_F(TestComponentContextForProcessTest, PublishTestInterface) { // Publish TestInterface to the process' outgoing-directory. base::fuchsia::ScopedServiceBinding<fuchsia::testfidl::TestInterface> - service_binding( - fuchsia::ComponentContextForCurrentProcess()->outgoing().get(), this); + service_binding(ComponentContextForProcess()->outgoing().get(), this); // Attempt to use the TestInterface from the outgoing-directory. EXPECT_TRUE(HasPublishedTestInterface()); @@ -100,7 +99,7 @@ TEST_F(TestComponentContextForProcessTest, ProvideSystemService) { // Attempt to use the PropertyProvider via the process ComponentContext. RunLoop wait_loop; - auto property_provider = fuchsia::ComponentContextForCurrentProcess() + auto property_provider = ComponentContextForProcess() ->svc() ->Connect<::fuchsia::intl::PropertyProvider>(); property_provider.set_error_handler( |