diff --git a/CMakePresets.json b/CMakePresets.json index 2b9e5f2..2ad7428 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -16,9 +16,7 @@ "CMAKE_INSTALL_PREFIX": "/home/edward/projects/rpc2/submodules/c-ares", "CMAKE_RULE_MESSAGES": "OFF", "CMAKE_VERBOSE_MAKEFILE": "OFF", - "CANOPY_DEBUG_THREAD": "OFF", "CANOPY_DEBUG_GEN": "OFF", - "CANOPY_ENABLE_CLANG_TIDY": "OFF", "LIBCORO_BUILD_EXAMPLES": "OFF", "LIBCORO_BUILD_TESTS": "OFF", "LIBCORO_EXTERNAL_DEPENDENCIES": "OFF", @@ -34,7 +32,15 @@ "LIBCORO_FEATURE_NETWORKING": "OFF", "CANOPY_HANG_ON_FAILED_ASSERT": "OFF", "CANOPY_USE_TELEMETRY_RAII_LOGGING": "OFF", - "CANOPY_BUILD_DEMOS": "OFF" + "CANOPY_BUILD_DEMOS": "OFF", + "CANOPY_DEBUG_LEAK": "OFF", + "CANOPY_DEBUG_ADDRESS": "OFF", + "CANOPY_DEBUG_THREAD": "OFF", + "CANOPY_DEBUG_UNDEFINED": "OFF", + "CANOPY_DEBUG_ALL": "OFF", + "CANOPY_ENABLE_COVERAGE": "OFF", + "CANOPY_ENABLE_CLANG_TIDY": "OFF", + "CANOPY_ENABLE_CLANG_TIDY_FIX": "OFF" } }, { @@ -48,7 +54,8 @@ "CANOPY_USE_LOGGING": "ON", "CANOPY_USE_TELEMETRY": "ON", "CANOPY_USE_CONSOLE_TELEMETRY": "ON", - "CANOPY_USE_TELEMETRY_RAII_LOGGING": "ON" + "CANOPY_USE_TELEMETRY_RAII_LOGGING": "ON", + "CANOPY_DEBUG_DEFAULT_DESTRUCTOR": "ON" } }, { @@ -277,9 +284,9 @@ "CANOPY_BUILD_COROUTINE": "ON", "CMAKE_BUILD_TYPE": "Release", "CANOPY_USE_LOGGING": "OFF", - "CANOPY_USE_TELEMETRY": "ON", - "CANOPY_USE_CONSOLE_TELEMETRY": "ON", - "CANOPY_USE_TELEMETRY_RAII_LOGGING": "ON", + "CANOPY_USE_TELEMETRY": "OFF", + "CANOPY_USE_CONSOLE_TELEMETRY": "OFF", + "CANOPY_USE_TELEMETRY_RAII_LOGGING": "OFF", "CMAKE_C_COMPILER": "gcc", "CMAKE_CXX_COMPILER": "g++", "CANOPY_BUILD_DEMOS": "ON" diff --git a/cmake/Canopy.cmake b/cmake/Canopy.cmake index 45777f2..ce06619 100644 --- a/cmake/Canopy.cmake +++ b/cmake/Canopy.cmake @@ -30,6 +30,8 @@ if(NOT DEPENDENCIES_LOADED) option(CANOPY_BUILD_DEMOS "Build demo code" OFF) option(CANOPY_BUILD_COROUTINE "Include coroutine support" OFF) option(CANOPY_STANDALONE "Build Canopy stand alone" OFF) + option(CANOPY_DEBUG_GEN "Get the generator produce verbose messages" OFF) + option(CANOPY_DEBUG_DEFAULT_DESTRUCTOR "Get the generator produce verbose messages" OFF) # SGX Enclave support (disabled by default - most users don't need this) option(CANOPY_BUILD_ENCLAVE "Build SGX enclave code" OFF) @@ -244,6 +246,12 @@ if(NOT DEPENDENCIES_LOADED) set(CANOPY_BUILD_COROUTINE_FLAG) endif() + if(${CANOPY_DEBUG_DEFAULT_DESTRUCTOR}) + set(CANOPY_DEBUG_DEFAULT_DESTRUCTOR_FLAG CANOPY_DEBUG_DEFAULT_DESTRUCTOR) + else() + set(CANOPY_DEBUG_DEFAULT_DESTRUCTOR_FLAG) + endif() + set(CANOPY_FMT_LIB fmt::fmt-header-only) # #################################################################################################################### @@ -261,6 +269,7 @@ if(NOT DEPENDENCIES_LOADED) ${CANOPY_USE_CONSOLE_TELEMETRY_FLAG} ${CANOPY_USE_TELEMETRY_RAII_LOGGING_FLAG} ${CANOPY_BUILD_TEST_FLAG} + ${CANOPY_DEBUG_DEFAULT_DESTRUCTOR_FLAG} CANOPY_OUT_BUFFER_SIZE=${CANOPY_OUT_BUFFER_SIZE} CANOPY_DEFAULT_ENCODING=${CANOPY_DEFAULT_ENCODING_VALUE}) diff --git a/demos/comprehensive/README.md b/demos/comprehensive/README.md index 07e452f..a170c58 100644 --- a/demos/comprehensive/README.md +++ b/demos/comprehensive/README.md @@ -249,13 +249,8 @@ auto service = std::make_shared("name", rpc::zone{id}, scheduler); ### Interface Implementation ```cpp -class my_impl : public i_my_interface +class my_impl : public rpc::base { - void* get_address() const override { return const_cast(this); } - const rpc::casting_interface* query_interface(rpc::interface_ordinal id) const override - { - return match(id) ? this : nullptr; - } CORO_TASK(error_code) my_method(...) override { ... } }; ``` diff --git a/demos/comprehensive/include/demo_impl.h b/demos/comprehensive/include/demo_impl.h index 7660099..f9540b7 100644 --- a/demos/comprehensive/include/demo_impl.h +++ b/demos/comprehensive/include/demo_impl.h @@ -115,22 +115,13 @@ namespace comprehensive // ============================================================================ // Callback Receiver Implementation // ============================================================================ - class callback_receiver_impl : public i_callback_receiver + class callback_receiver_impl : public rpc::base { std::vector received_progress_; std::vector received_data_; mutable std::mutex mutex_; public: - void* get_address() const override { return const_cast(this); } - - const rpc::casting_interface* query_interface(rpc::interface_ordinal interface_id) const override - { - if (rpc::match(interface_id)) - return static_cast(this); - return nullptr; - } - CORO_TASK(int) on_progress(int percentage) override { std::lock_guard lock(mutex_); @@ -149,22 +140,13 @@ namespace comprehensive // ============================================================================ // Worker Implementation (Callbacks) // ============================================================================ - class worker_impl : public i_worker + class worker_impl : public rpc::base { rpc::shared_ptr parent_callback_; mutable std::mutex mutex_; bool running_{false}; public: - void* get_address() const override { return const_cast(this); } - - const rpc::casting_interface* query_interface(rpc::interface_ordinal interface_id) const override - { - if (rpc::match(interface_id)) - return static_cast(this); - return nullptr; - } - CORO_TASK(int) set_callback_receiver(rpc::shared_ptr receiver) override { std::lock_guard lock(mutex_); @@ -216,7 +198,7 @@ namespace comprehensive // ============================================================================ // Managed Object Implementation (Shared Pointer Demo) // ============================================================================ - class managed_object_impl : public i_managed_object + class managed_object_impl : public rpc::base { uint64_t object_id_; int ref_count_{1}; @@ -234,15 +216,6 @@ namespace comprehensive ~managed_object_impl() { --live_count_; } - void* get_address() const override { return const_cast(this); } - - const rpc::casting_interface* query_interface(rpc::interface_ordinal interface_id) const override - { - if (rpc::match(interface_id)) - return static_cast(this); - return nullptr; - } - CORO_TASK(int) get_object_id(uint64_t& id) override { id = object_id_; @@ -270,21 +243,12 @@ namespace comprehensive // ============================================================================ // Object Factory Implementation // ============================================================================ - class object_factory_impl : public i_object_factory + class object_factory_impl : public rpc::base { std::map> objects_; mutable std::mutex mutex_; public: - void* get_address() const override { return const_cast(this); } - - const rpc::casting_interface* query_interface(rpc::interface_ordinal interface_id) const override - { - if (rpc::match(interface_id)) - return static_cast(this); - return nullptr; - } - CORO_TASK(int) create_object(rpc::shared_ptr& obj) override { auto new_obj = rpc::shared_ptr(new managed_object_impl()); @@ -328,7 +292,8 @@ namespace comprehensive // ============================================================================ // Demo Service Implementation (For transport demos) // ============================================================================ - class demo_service_impl : public i_demo_service, public rpc::enable_shared_from_this + class demo_service_impl : public rpc::base, + public rpc::enable_shared_from_this { std::string name_; rpc::shared_ptr child_service_; @@ -347,15 +312,6 @@ namespace comprehensive { } - void* get_address() const override { return const_cast(this); } - - const rpc::casting_interface* query_interface(rpc::interface_ordinal interface_id) const override - { - if (rpc::match(interface_id)) - return static_cast(this); - return nullptr; - } - CORO_TASK(int) get_zone_id(uint64_t& zone_id) override { auto service = this_service_.lock(); diff --git a/demos/comprehensive/src/transport/benchmark.cpp b/demos/comprehensive/src/transport/benchmark.cpp index 6ec476d..e58b19d 100644 --- a/demos/comprehensive/src/transport/benchmark.cpp +++ b/demos/comprehensive/src/transport/benchmark.cpp @@ -220,7 +220,6 @@ namespace comprehensive std::atomic zone_gen{0}; auto root_service = std::make_shared("benchmark_root", rpc::zone{++zone_gen}, scheduler); root_service->set_default_encoding(enc); - comprehensive_idl_register_stubs(root_service); rpc::zone child_zone_id{++zone_gen}; auto child_transport @@ -280,7 +279,6 @@ namespace comprehensive { auto service_1 = std::make_shared("spsc_client", zone_1, scheduler); service_1->set_default_encoding(enc); - comprehensive_idl_register_stubs(service_1); auto on_shutdown_event = std::make_shared(); service_1->set_shutdown_event(on_shutdown_event); @@ -332,10 +330,9 @@ namespace comprehensive auto service_2 = std::make_shared("spsc_server", zone_2, scheduler); service_2->set_shutdown_event(on_shutdown_event); service_2->set_default_encoding(enc); - comprehensive_idl_register_stubs(service_2); rpc::event on_connected; - auto handler = [&, enc](const rpc::interface_descriptor& input_interface, + auto handler = [&, enc](const rpc::connection_settings& input_interface, rpc::interface_descriptor& output_interface, std::shared_ptr service, std::shared_ptr transport) -> CORO_TASK(int) @@ -414,10 +411,9 @@ namespace comprehensive auto service = std::make_shared("tcp_server", rpc::zone{++zone_gen}, scheduler); service->set_default_encoding(enc); service->set_shutdown_event(on_shutdown_event); - comprehensive_idl_register_stubs(service); auto listener = std::make_shared( - [enc](const rpc::interface_descriptor& input_descr, + [enc](const rpc::connection_settings& input_descr, rpc::interface_descriptor& output_interface, std::shared_ptr child_service_ptr, std::shared_ptr transport) -> CORO_TASK(int) @@ -475,7 +471,6 @@ namespace comprehensive auto peer_zone_id = rpc::zone{1}; auto client_service = std::make_shared("tcp_client", rpc::zone{++zone_gen}, scheduler); client_service->set_default_encoding(enc); - comprehensive_idl_register_stubs(client_service); coro::net::tcp::client client(scheduler, coro::net::tcp::client::options{.address = coro::net::ip_address::from_string(host), .port = port}); diff --git a/demos/comprehensive/src/transport/spsc_transport_demo.cpp b/demos/comprehensive/src/transport/spsc_transport_demo.cpp index 91f6fb2..38681ad 100644 --- a/demos/comprehensive/src/transport/spsc_transport_demo.cpp +++ b/demos/comprehensive/src/transport/spsc_transport_demo.cpp @@ -51,7 +51,6 @@ namespace comprehensive rpc::event& client_finished) { auto service_1 = std::make_shared("process_1", zone_1, scheduler); - comprehensive_idl_register_stubs(service_1); auto on_shutdown_event = std::make_shared(); service_1->set_shutdown_event(on_shutdown_event); @@ -120,10 +119,9 @@ namespace comprehensive auto on_shutdown_event = std::make_shared(); auto service_2 = std::make_shared("process_2", zone_2, scheduler); service_2->set_shutdown_event(on_shutdown_event); - comprehensive_idl_register_stubs(service_2); rpc::event on_connected; - auto handler = [&, zone_1](const rpc::interface_descriptor& input_interface, + auto handler = [&, zone_1](const rpc::connection_settings& input_interface, rpc::interface_descriptor& output_interface, std::shared_ptr service, std::shared_ptr transport) -> CORO_TASK(int) diff --git a/demos/comprehensive/src/transport/tcp_transport_demo.cpp b/demos/comprehensive/src/transport/tcp_transport_demo.cpp index b1ed04d..2b9d2f5 100644 --- a/demos/comprehensive/src/transport/tcp_transport_demo.cpp +++ b/demos/comprehensive/src/transport/tcp_transport_demo.cpp @@ -66,19 +66,17 @@ namespace comprehensive // Create server service auto service = std::make_shared("tcp_server", rpc::zone{++zone_gen}, scheduler); service->set_shutdown_event(on_shutdown_event); - comprehensive_idl_register_stubs(service); std::cout << "Server zone ID: " << service->get_zone_id().get_val() << "\n"; // Create TCP listener with connection handler auto listener = std::make_shared( - [](const rpc::interface_descriptor& input_descr, + [](const rpc::connection_settings& input_descr, rpc::interface_descriptor& output_interface, std::shared_ptr child_service_ptr, std::shared_ptr transport) -> CORO_TASK(int) { - std::cout << "Server: Accepting connection from zone " << input_descr.destination_zone_id.get_val() - << "\n"; + std::cout << "Server: Accepting connection from zone " << input_descr.input_zone_id.get_val() << "\n"; // Use attach_remote_zone to handle the connection auto ret @@ -159,7 +157,6 @@ namespace comprehensive // Create client service auto client_service = std::make_shared("tcp_client", rpc::zone{++zone_gen}, scheduler); - comprehensive_idl_register_stubs(client_service); std::cout << "Client zone ID: " << client_service->get_zone_id().get_val() << "\n"; std::cout << "Client: Connecting to " << host << ":" << port << "...\n"; diff --git a/demos/websocket/server/transport.h b/demos/websocket/server/transport.h index 7759337..dcfa01e 100644 --- a/demos/websocket/server/transport.h +++ b/demos/websocket/server/transport.h @@ -32,10 +32,10 @@ namespace websocket_demo const std::shared_ptr>>& pending_messages, const std::shared_ptr& pending_messages_mutex); - virtual ~transport() DEFAULT_DESTRUCTOR; + virtual ~transport() CANOPY_DEFAULT_DESTRUCTOR; CORO_TASK(int) - inner_connect(rpc::interface_descriptor input_descr, rpc::interface_descriptor& output_descr) override + inner_connect(rpc::connection_settings& input_descr, rpc::interface_descriptor& output_descr) override { std::ignore = input_descr; std::ignore = output_descr; diff --git a/demos/websocket/server/websocket_service.h b/demos/websocket/server/websocket_service.h index dda4638..bc801b3 100644 --- a/demos/websocket/server/websocket_service.h +++ b/demos/websocket/server/websocket_service.h @@ -18,7 +18,7 @@ namespace websocket_demo public: websocket_service(std::string name, rpc::zone zone_id, std::shared_ptr scheduler); - virtual ~websocket_service() DEFAULT_DESTRUCTOR; + virtual ~websocket_service() CANOPY_DEFAULT_DESTRUCTOR; rpc::shared_ptr get_demo_instance(); }; diff --git a/demos/websocket/server/ws_client_connection.cpp b/demos/websocket/server/ws_client_connection.cpp index f5987ef..9313577 100644 --- a/demos/websocket/server/ws_client_connection.cpp +++ b/demos/websocket/server/ws_client_connection.cpp @@ -67,14 +67,15 @@ namespace websocket_demo = CO_AWAIT service_->attach_remote_zone( "websocket", transport_, - rpc::interface_descriptor{1, 2}, // this magically makes sink + rpc::connection_settings{1, + websocket_demo::v1::i_context_event::get_id(rpc::get_version()), + 2}, // this magically makes sink output_descr, [](const rpc::shared_ptr& sink, rpc::shared_ptr& local, const std::shared_ptr& svc) -> coro::task { std::cout << "[WS] Inside attach_remote_zone lambda" << std::endl; - secret_llama_idl_register_stubs(svc); auto wsrvc = std::static_pointer_cast(svc); diff --git a/documents/02-getting-started.md b/documents/02-getting-started.md index 77900bf..4163ad7 100644 --- a/documents/02-getting-started.md +++ b/documents/02-getting-started.md @@ -127,28 +127,11 @@ Create `include/calculator_impl.h`: namespace calculator { - -class calculator_impl : public v1::i_calculator +class calculator_impl : public rpc::base { public: calculator_impl() = default; - // Required overrides - void* get_address() const override - { - return const_cast(this); - } - - const rpc::casting_interface* query_interface( - rpc::interface_ordinal interface_id) const override - { - if (rpc::match(interface_id)) - { - return static_cast(this); - } - return nullptr; - } - // Interface methods CORO_TASK(error_code) add(int a, int b, int& result) override { diff --git a/documents/09-api-reference.md b/documents/09-api-reference.md index 9ff18c8..f58e4f0 100644 --- a/documents/09-api-reference.md +++ b/documents/09-api-reference.md @@ -51,18 +51,6 @@ error_code attach_remote_zone( SetupCallback setup); ``` -### Stub Registration - -```cpp -void add_interface_stub_factory( - rpc::interface_ordinal interface_id, - i_interface_stub_factory* factory); - -void register_stub( - rpc::object object_id, - const rpc::shared_ptr& stub); -``` - ## 2. rpc::shared_ptr ### Creation diff --git a/documents/10-examples.md b/documents/10-examples.md index b52c31b..8fb021f 100644 --- a/documents/10-examples.md +++ b/documents/10-examples.md @@ -168,20 +168,11 @@ namespace service ### Data Object Implementation ```cpp -class data_impl : public i_data +class data_impl : public rpc::base { int value_ = 0; public: - void* get_address() const override { return const_cast(this); } - - const rpc::casting_interface* query_interface( - rpc::interface_ordinal interface_id) const override - { - if (rpc::match(interface_id)) - return static_cast(this); - return nullptr; - } CORO_TASK(error_code) get_value(int& value) override { @@ -200,19 +191,10 @@ public: ### Factory Implementation ```cpp -class factory_impl : public i_factory + +class factory_impl : public rpc::base { public: - void* get_address() const override { return const_cast(this); } - - const rpc::casting_interface* query_interface( - rpc::interface_ordinal interface_id) const override - { - if (rpc::match(interface_id)) - return static_cast(this); - return nullptr; - } - CORO_TASK(error_code) create_object(rpc::shared_ptr& obj) override { obj = rpc::make_shared(); diff --git a/documents/11-best-practices.md b/documents/11-best-practices.md index ad0ce7c..03990c8 100644 --- a/documents/11-best-practices.md +++ b/documents/11-best-practices.md @@ -397,30 +397,6 @@ class node ## 13. Common Implementation Mistakes -### Mistake: query_interface Using Incorrect API - -**Problem**: Using `Interface::get_id(rpc::CURRENT_VERSION)` fails because `CURRENT_VERSION` doesn't exist. - -**Incorrect**: -```cpp -const rpc::casting_interface* query_interface(rpc::interface_ordinal interface_id) const override -{ - if (v1::i_calculator::get_id(rpc::CURRENT_VERSION) == interface_id) - return static_cast(this); - return nullptr; -} -``` - -**Correct**: -```cpp -const rpc::casting_interface* query_interface(rpc::interface_ordinal interface_id) const override -{ - if (rpc::match(interface_id)) - return static_cast(this); - return nullptr; -} -``` - ### Mistake: Missing [in]/[out] Attributes Understanding **Problem**: Not understanding what parameter attributes mean. diff --git a/documents/12-migration-guide.md b/documents/12-migration-guide.md index 367a5bd..b63284a 100644 --- a/documents/12-migration-guide.md +++ b/documents/12-migration-guide.md @@ -243,18 +243,8 @@ class Calculator : public ICalculator ```cpp // Canopy -class calculator : public i_calculator +class calculator : public rpc::base { - void* get_address() const override { return this; } - - const rpc::casting_interface* query_interface( - rpc::interface_ordinal interface_id) const override - { - if (match(interface_id)) - return this; - return nullptr; - } - CORO_TASK(error_code) add(int a, int b, [out] int& result) override { result = a + b; diff --git a/documents/architecture/02-zones.md b/documents/architecture/02-zones.md index 8c5ccdb..6026ce7 100644 --- a/documents/architecture/02-zones.md +++ b/documents/architecture/02-zones.md @@ -111,11 +111,6 @@ child_transport->set_child_entry_point( rpc::shared_ptr& new_example, const std::shared_ptr& child_service_ptr) -> CORO_TASK(error_code) { - // Register IDL stubs in child zone so that incoming calls can be handled. - example_import_idl_register_stubs(child_service_ptr); - example_shared_idl_register_stubs(child_service_ptr); - example_idl_register_stubs(child_service_ptr); - // Create the object in the child zone, to be transferred to the parent zone. new_example = rpc::shared_ptr(new marshalled_tests::example(child_service_ptr, host)); @@ -132,9 +127,6 @@ Peer zones connect via TCP or other network transports, this example uses corout ```cpp // Server side auto server_service = std::make_shared("server", get_next_zone_id(), io_scheduler_); -example_import_idl_register_stubs(server_service); -example_shared_idl_register_stubs(server_service); -example_idl_register_stubs(server_service); // Create the listener for the server side // The connection handler will be called when a client connects @@ -144,9 +136,6 @@ auto listener = std::make_unique( std::shared_ptr child_service_ptr, std::shared_ptr transport) -> CORO_TASK(int) { - // Add the transport to the service first, BEFORE calling attach_remote_zone - // attach_remote_zone expects the transport to already be registered - child_service_ptr->add_transport(input_descr.destination_zone_id, transport); // Use attach_remote_zone to properly manage object lifetime, like SPSC does auto ret = CO_AWAIT child_service_ptr->attach_remote_zone("service_proxy", @@ -178,9 +167,6 @@ if (!listener->start_listening(peer_service_, server_options)) // Create the client service auto client_service = std::make_shared("client", get_next_zone_id(), io_scheduler_); -example_import_idl_register_stubs(client_service); -example_shared_idl_register_stubs(client_service); -example_idl_register_stubs(client_service); coro::net::tcp::client tcp_client(scheduler, coro::net::tcp::client::options{ diff --git a/documents/architecture/03-services.md b/documents/architecture/03-services.md index d0dd804..46637af 100644 --- a/documents/architecture/03-services.md +++ b/documents/architecture/03-services.md @@ -244,16 +244,6 @@ CORO_TASK(error_code) attach_remote_zone( Attaches to a remote zone with interface negotiation. -### Stub Registration - -```cpp -void add_interface_stub_factory( - rpc::interface_ordinal interface_id, - i_interface_stub_factory* factory); -``` - -Registers factory for creating interface stubs (called during code generation). - ## Service Lifecycle ### Service Birth diff --git a/documents/architecture/05-proxies-and-stubs.md b/documents/architecture/05-proxies-and-stubs.md index 4292357..87bede1 100644 --- a/documents/architecture/05-proxies-and-stubs.md +++ b/documents/architecture/05-proxies-and-stubs.md @@ -167,19 +167,6 @@ class i_calculator_proxy : public comprehensive::i_calculator - Each method marshals parameters, sends RPC, unmarshals result - Returns same error codes as defined in IDL -### Interface Stub (e.g., i_calculator_stub) - -Type-safe server-side stub generated from IDL interface definition. Responsible for: -- **Parameter deserialization** - Converts wire format to C++ types -- **Method dispatch** - Routes to correct implementation method -- **Result serialization** - Converts C++ return values to wire format - - -**Key Characteristics**: -- Inherits from `rpc::i_interface_stub` -- Holds `rpc::shared_ptr` to actual implementation -- `call()` method dispatches based on method ID -- Each case unmarshals parameters, calls implementation, marshals result ## Serialization Formats @@ -384,18 +371,13 @@ namespace comprehensive template class interface : public rpc::casting_interface { ... }; - class i_calculator : public interface + class calculator : public rpc::base { public: static constexpr uint64_t get_id(uint64_t rpc_version); virtual CORO_TASK(int) add(int a, int b, int& sum) = 0; virtual CORO_TASK(int) subtract(int a, int b, int& difference) = 0; - - // Required interface methods - void* get_address() const override; - const rpc::casting_interface* query_interface( - rpc::interface_ordinal interface_id) const override; }; } ``` @@ -418,17 +400,7 @@ static constexpr uint64_t get_id(uint64_t rpc_version) } ``` -**Version Negotiation**: -```cpp -// Client checks if server supports interface -rpc::shared_ptr calc; -int error = CO_AWAIT proxy->query_interface(calc); -if (error == rpc::error::INTERFACE_NOT_SUPPORTED()) -{ - // Server doesn't support this interface version -} -``` ## Working Together: Proxies, Stubs, and Memory Management diff --git a/documents/architecture/08-core-concepts.md b/documents/architecture/08-core-concepts.md index db633f7..56dcfba 100644 --- a/documents/architecture/08-core-concepts.md +++ b/documents/architecture/08-core-concepts.md @@ -183,9 +183,6 @@ error_code attach_remote_zone(const char* name, const rpc::interface_descriptor& output_descr, SetupCallback setup); -// Stub Registration -void add_interface_stub_factory(rpc::interface_ordinal interface_id, - i_interface_stub_factory* factory); ``` ## 3. Smart Pointers @@ -482,26 +479,6 @@ class object_stub }; ``` -### Interface Stub (`i_interface_stub`) - -Abstract interface for interface-specific stub behavior. - -```cpp -class i_interface_stub -{ -public: - virtual CORO_TASK(int) call( - uint64_t protocol_version, - rpc::encoding enc, - rpc::caller_zone caller_zone_id, - rpc::method method_id, - const rpc::span& in_data, - std::vector& out_data) = 0; - - virtual void* cast(rpc::interface_ordinal interface_id) = 0; - virtual rpc::casting_interface* get_pointer() = 0; -}; -``` ## 5. Interface Pattern @@ -510,24 +487,11 @@ All IDL interfaces must inherit from `casting_interface` and implement required ### Base Interface Requirements ```cpp -class my_interface : public rpc::interface +class app : public rpc::base { public: virtual error_code do_something(int value) = 0; - // Required overrides - void* get_address() const override - { - return const_cast(this); - } - - const rpc::casting_interface* query_interface( - rpc::interface_ordinal interface_id) const override - { - if (rpc::match(interface_id)) - return static_cast(this); - return nullptr; - } }; ``` @@ -544,19 +508,6 @@ public: { // SHA3-based fingerprint } - - void* get_address() const override - { - return const_cast(static_cast(this)); - } - - const rpc::casting_interface* query_interface( - rpc::interface_ordinal interface_id) const override - { - if (get_id(rpc::CURRENT_VERSION) == interface_id) - return static_cast(this); - return nullptr; - } }; ``` @@ -564,14 +515,6 @@ public: Each interface has a version-independent ID based on its definition: -```cpp -// Get interface ID for current version -auto interface_id = xxx::i_foo::get_id(rpc::CURRENT_VERSION); - -// Check interface support -if (proxy->query_interface(interface_id)) { - // Interface is supported -} ``` ## 6. Lifecycle Management diff --git a/documents/transports/local.md b/documents/transports/local.md index 46f202e..1c91989 100644 --- a/documents/transports/local.md +++ b/documents/transports/local.md @@ -46,7 +46,6 @@ child_transport->set_child_entry_point( const std::shared_ptr& child_service_ptr) -> CORO_TASK(int) { // Initialize child zone - example_idl_register_stubs(child_service_ptr); new_example = rpc::make_shared(child_service_ptr, host); CO_RETURN rpc::error::OK(); }); diff --git a/generator/include/helpers.h b/generator/include/helpers.h index f7fa1f5..4476375 100644 --- a/generator/include/helpers.h +++ b/generator/include/helpers.h @@ -22,10 +22,10 @@ bool is_pointer(std::string type_name); bool is_pointer_reference(std::string type_name); bool is_pointer_to_pointer(std::string type_name); -std::string get_smart_ptr_type(const std::string& type_name); +std::string get_smart_ptr_type(const std::string& type_name, bool& is_optimistic); bool is_interface_param( - const class_entity& lib, const std::string& type, bool is_optimistic, std::shared_ptr& obj); + const class_entity& lib, const std::string& type, bool& is_optimistic, std::shared_ptr& obj); bool is_type_and_parameter_the_same(std::string type, std::string name); diff --git a/generator/include/synchronous_generator.h b/generator/include/synchronous_generator.h index a9f4ca5..c14aa9a 100644 --- a/generator/include/synchronous_generator.h +++ b/generator/include/synchronous_generator.h @@ -6,8 +6,7 @@ namespace synchronous_generator { // entry point - void write_files(std::string module_name, - bool from_host, + void write_files(bool from_host, const class_entity& lib, std::ostream& hos, std::ostream& pos, diff --git a/generator/src/helpers.cpp b/generator/src/helpers.cpp index 78e442f..80adf12 100644 --- a/generator/src/helpers.cpp +++ b/generator/src/helpers.cpp @@ -11,9 +11,8 @@ #include "coreclasses.h" #include "attributes.h" -std::string get_smart_ptr_type(const std::string& type_name, bool is_optimistic) +std::string get_smart_ptr_type(const std::string& type_name, bool& is_optimistic) { - is_optimistic = false; auto data = type_name.data(); while (*data == ' ' || *data == '\t') { @@ -26,7 +25,7 @@ std::string get_smart_ptr_type(const std::string& type_name, bool is_optimistic) data += sizeof("rpc::shared_ptr"); found = true; } - else if (is_optimistic && begins_with(data, "rpc::optimistic_ptr<")) + else if (begins_with(data, "rpc::optimistic_ptr<")) { found = true; data += sizeof("rpc::optimistic_ptr"); @@ -61,7 +60,7 @@ std::string get_smart_ptr_type(const std::string& type_name, bool is_optimistic) } bool is_interface_param( - const class_entity& lib, const std::string& type, bool is_optimistic, std::shared_ptr& obj) + const class_entity& lib, const std::string& type, bool& is_optimistic, std::shared_ptr& obj) { std::string reference_modifiers; std::string type_name = type; diff --git a/generator/src/interface_declaration_generator.cpp b/generator/src/interface_declaration_generator.cpp index 430f93d..35cea95 100644 --- a/generator/src/interface_declaration_generator.cpp +++ b/generator/src/interface_declaration_generator.cpp @@ -658,7 +658,6 @@ namespace interface_declaration_generator i++; } } - header("class {}_stub;", interface_name); header("class {}{} : public rpc::casting_interface", interface_name, base_class_declaration); header("{{"); header("public:"); @@ -680,7 +679,7 @@ namespace interface_declaration_generator header("static std::shared_ptr> create_local_proxy(const rpc::weak_ptr<{0}>& ptr);", interface_name); header(""); - header("virtual ~{}() = default;", interface_name); + header("virtual ~{}() CANOPY_DEFAULT_DESTRUCTOR", interface_name); header(""); header("// ********************* interface methods *********************"); @@ -776,6 +775,25 @@ namespace interface_declaration_generator } } header("}};"); + + header(""); + + header("// the caller to stubs"); + header("struct stub_caller"); + header("{{"); + header("static CORO_TASK(int) call({}* __rpc_target_,", interface_name); + header("uint64_t protocol_version,"); + header("rpc::encoding encoding,"); + header("uint64_t tag,"); + header("rpc::caller_zone caller_zone_id,"); + header("rpc::destination_zone destination_zone_id,"); + header("rpc::object object_id,"); + header("rpc::method method_id,"); + header("const rpc::span& in_data,"); + header("std::vector& out_buf_,"); + header("const std::vector& in_back_channel,"); + header("std::vector& out_back_channel);"); + header("}};"); header(""); header("// template pure static class for serialising reply data from a stub"); header("template"); @@ -909,7 +927,6 @@ namespace interface_declaration_generator else header("// no usable functions for a buffered_proxy_serialiser class"); } - header("friend {}_stub;", interface_name); header("}};"); header(""); }; diff --git a/generator/src/main.cpp b/generator/src/main.cpp index 850b20a..5c76420 100644 --- a/generator/src/main.cpp +++ b/generator/src/main.cpp @@ -309,8 +309,7 @@ int main(const int argc, char* argv[]) std::stringstream stub_header_stream; std::stringstream mock_stream; - synchronous_generator::write_files(module_name, - true, + synchronous_generator::write_files(true, *objects, header_stream, proxy_stream, diff --git a/generator/src/synchronous_generator.cpp b/generator/src/synchronous_generator.cpp index efcc8cb..d9acc9f 100644 --- a/generator/src/synchronous_generator.cpp +++ b/generator/src/synchronous_generator.cpp @@ -426,18 +426,33 @@ namespace synchronous_generator throw std::runtime_error("INTERFACE does not support out vals"); } + bool is_optimistic = object_type.find("rpc::optimistic_ptr") != std::string::npos; + print_type pt = static_cast(option); switch (pt) { case PROXY_PREPARE_IN: return fmt::format("std::shared_ptr {}_stub_;", name); case PROXY_PREPARE_IN_INTERFACE_ID: + if (is_optimistic) + { + return fmt::format("rpc::interface_descriptor {0}_stub_id_;\n" + "\t\t\tif(!rpc::error::is_error(__rpc_ret))\n" + "\t\t\t{{{{\n" + "\t\t\t\t__rpc_ret = CO_AWAIT rpc::proxy_bind_in_param(__rpc_get_object_proxy(), " + "__rpc_sp->get_remote_rpc_version(), " + "{0}, {0}_stub_, {0}_stub_id_);\n" + "\t\t\t}}}}", + name); + } return fmt::format("RPC_ASSERT(rpc::are_in_same_zone(this, {0}.get()));\n" "\t\t\trpc::interface_descriptor {0}_stub_id_;\n" - "\t\t\tif(__rpc_ret == rpc::error::OK())\n" - "\t\t\t\t__rpc_ret = CO_AWAIT rpc::proxy_bind_in_param(get_object_proxy(), " + "\t\t\tif(!rpc::error::is_error(__rpc_ret))\n" + "\t\t\t{{{{\n" + "\t\t\t\t__rpc_ret = CO_AWAIT rpc::proxy_bind_in_param(__rpc_get_object_proxy(), " "__rpc_sp->get_remote_rpc_version(), " - "{0}, {0}_stub_, {0}_stub_id_);", + "{0}, {0}_stub_, {0}_stub_id_);\n" + "\t\t\t}}}}", name); case PROXY_MARSHALL_IN: { @@ -463,12 +478,12 @@ namespace synchronous_generator case STUB_PARAM_WRAP: return fmt::format(R"__( {0} {1}; - if(__rpc_ret == rpc::error::OK() && {1}_object_.destination_zone_id.is_set() && {1}_object_.object_id.is_set()) + if(!rpc::error::is_error(__rpc_ret) && {1}_object_.destination_zone_id.is_set() && {1}_object_.object_id.is_set()) {{ - auto target_stub_strong = target_stub_.lock(); - if (target_stub_strong) + auto stub = __rpc_target_->__rpc_get_stub(); + auto zone_ = stub ? stub->get_zone() : nullptr; + if (zone_) {{ - auto zone_ = target_stub_strong->get_zone(); __rpc_ret = CO_AWAIT rpc::stub_bind_in_param(protocol_version, zone_, caller_zone_id, {1}_object_, {1}); }} else @@ -512,17 +527,32 @@ namespace synchronous_generator std::ignore = is_const; std::ignore = count; + bool is_optimistic = object_type.find("rpc::optimistic_ptr") != std::string::npos; + switch (option) { case PROXY_PREPARE_IN: return fmt::format("std::shared_ptr {}_stub_;", name); case PROXY_PREPARE_IN_INTERFACE_ID: + if (is_optimistic) + { + return fmt::format("rpc::interface_descriptor {0}_stub_id_;\n" + "\t\t\tif(!rpc::error::is_error(__rpc_ret))\n" + "\t\t\t{{{{\n" + "\t\t\t\t__rpc_ret = CO_AWAIT rpc::proxy_bind_in_param(__rpc_get_object_proxy(), " + "__rpc_sp->get_remote_rpc_version(), " + "{0}, {0}_stub_, {0}_stub_id_);\n" + "\t\t\t}}}}", + name); + } return fmt::format("RPC_ASSERT(rpc::are_in_same_zone(this, {0}.get()));\n" "\t\t\trpc::interface_descriptor {0}_stub_id_;\n" - "\t\t\tif(__rpc_ret == rpc::error::OK())\n" - "\t\t\t\t__rpc_ret = CO_AWAIT rpc::proxy_bind_in_param(get_object_proxy(), " + "\t\t\tif(!rpc::error::is_error(__rpc_ret))\n" + "\t\t\t{{{{\n" + "\t\t\t\t__rpc_ret = CO_AWAIT rpc::proxy_bind_in_param(__rpc_get_object_proxy(), " "__rpc_sp->get_remote_rpc_version(), " - "{0}, {0}_stub_);", + "{0}, {0}_stub_, {0}_stub_id_);\n" + "\t\t\t}}}}", name); case PROXY_MARSHALL_IN: { @@ -593,6 +623,24 @@ namespace synchronous_generator r, static_cast(option), from_host, lib, name, type, attribs, count, output); } + // Lambda to emit PROXY_CLEAN_IN cleanup code - used at early return points and at end of function + void emit_proxy_clean_in( + bool from_host, const class_entity& m_ob, writer& proxy, const std::shared_ptr& function) + { + proxy("//PROXY_CLEAN_IN"); + uint64_t clean_count = 1; + for (auto& clean_param : function->get_parameters()) + { + std::string clean_output; + if (do_in_param( + PROXY_CLEAN_IN, from_host, m_ob, clean_param.get_name(), clean_param.get_type(), clean_param, clean_count, clean_output)) + { + proxy(clean_output); + } + clean_count++; + } + }; + void write_method(bool from_host, const class_entity& m_ob, writer& proxy, @@ -618,7 +666,8 @@ namespace synchronous_generator if (has_out) { - throw std::runtime_error(std::string("Error in ") + m_ob.get_name() + "::" + function->get_name() + throw std::runtime_error( + std::string("Error in ") + m_ob.get_name() + "::" + function->get_name() + ": [post] methods cannot have [out] or [in,out] parameters. Parameter '" + parameter.get_name() + "' has [out] attribute."); } @@ -629,8 +678,9 @@ namespace synchronous_generator bool is_interface = is_interface_param(library, parameter.get_type(), optimistic, obj); if (is_interface) { - throw std::runtime_error(std::string("Error in ") + m_ob.get_name() + "::" + function->get_name() - + ": [post] methods cannot have interface parameters (rpc::shared_ptr or rpc::optimistic_ptr). " + throw std::runtime_error( + std::string("Error in ") + m_ob.get_name() + "::" + + function->get_name() + ": [post] methods cannot have interface parameters (rpc::shared_ptr or rpc::optimistic_ptr). " + "Parameter '" + parameter.get_name() + "' of type '" + parameter.get_type() + "' is not supported. Posting interfaces is not currently supported."); } @@ -773,7 +823,7 @@ namespace synchronous_generator count++; } - proxy("while (__rpc_ret == rpc::error::OK() && __rpc_version >= __rpc_min_version)"); + proxy("while (!rpc::error::is_error(__rpc_ret) && __rpc_version >= __rpc_min_version)"); proxy("{{"); proxy("std::vector __rpc_in_buf;"); @@ -852,8 +902,11 @@ namespace synchronous_generator proxy("__rpc_ret = rpc::error::INCOMPATIBLE_SERIALISATION();"); proxy("break;"); proxy("}}"); - proxy("if(__rpc_ret != rpc::error::OK())"); - proxy(" CO_RETURN __rpc_ret;"); + proxy("if(rpc::error::is_error(__rpc_ret))"); + proxy("{{"); + emit_proxy_clean_in(from_host, m_ob, proxy, function); + proxy("CO_RETURN __rpc_ret;"); + proxy("}}"); // Generate stub deserializer stub("int __rpc_ret = rpc::error::OK();"); @@ -931,8 +984,10 @@ namespace synchronous_generator stub("CO_RETURN rpc::error::INCOMPATIBLE_SERIALISATION();"); stub("}}"); - stub("if(__rpc_ret != rpc::error::OK())"); - stub(" CO_RETURN __rpc_ret;"); + stub("if(rpc::error::is_error(__rpc_ret))"); + stub("{{"); + stub("CO_RETURN __rpc_ret;"); + stub("}}"); std::string tag = function->get_value("tag"); if (tag.empty()) @@ -963,9 +1018,9 @@ namespace synchronous_generator if (!function->has_value("post")) { proxy("__rpc_out_buf.clear();"); - } - if (!function->has_value("post")) + emit_proxy_clean_in(from_host, m_ob, proxy, function); proxy("CO_RETURN __rpc_ret;"); + } proxy("}}"); proxy("--__rpc_version;"); proxy("__rpc_sp->update_remote_rpc_version(__rpc_version);"); @@ -994,17 +1049,18 @@ namespace synchronous_generator proxy("else"); proxy("{{"); proxy("// Already using yas_json, no more fallback options"); + emit_proxy_clean_in(from_host, m_ob, proxy, function); proxy("CO_RETURN __rpc_ret;"); proxy("}}"); } else { + emit_proxy_clean_in(from_host, m_ob, proxy, function); proxy("CO_RETURN __rpc_ret;"); } proxy("}}"); - proxy("if(__rpc_ret >= rpc::error::MIN() && __rpc_ret <= rpc::error::MAX() && __rpc_ret != " - "rpc::error::OBJECT_GONE())"); + proxy("if(rpc::error::is_critical(__rpc_ret))"); proxy("{{"); proxy("//if you fall into this rabbit hole ensure that you have added any error offsets compatible with " "your error code system to the rpc library"); @@ -1015,6 +1071,7 @@ namespace synchronous_generator { proxy("__rpc_out_buf.clear();"); } + emit_proxy_clean_in(from_host, m_ob, proxy, function); proxy("CO_RETURN __rpc_ret;"); proxy("}}"); @@ -1037,7 +1094,7 @@ namespace synchronous_generator } stub("//STUB_PARAM_CAST"); - stub("if(__rpc_ret == rpc::error::OK())"); + stub("if(!rpc::error::is_error(__rpc_ret))"); stub("{{"); if (catch_stub_exceptions) { @@ -1151,12 +1208,12 @@ namespace synchronous_generator if (!has_preamble && !output.empty()) { stub("//STUB_ADD_REF_OUT"); - stub("if(__rpc_ret < rpc::error::MIN() || __rpc_ret > rpc::error::MAX())"); + stub("if(!rpc::error::is_error(__rpc_ret))"); stub("{{"); - stub("auto target_stub_strong = target_stub_.lock();"); - stub("if (target_stub_strong)"); + stub("auto stub = __rpc_target_->__rpc_get_stub();"); + stub("auto zone_ = stub ? stub->get_zone() : nullptr;"); + stub("if (zone_)"); stub("{{"); - stub("auto zone_ = target_stub_strong->get_zone();"); has_preamble = true; } stub(output); @@ -1188,8 +1245,7 @@ namespace synchronous_generator proxy("{{"); { proxy.print_tabs(); - proxy.raw( - "auto __receiver_result = {}proxy_deserialiser::{}(", + proxy.raw("__rpc_ret = {}proxy_deserialiser::{}(", scoped_namespace, function->get_name()); @@ -1209,8 +1265,6 @@ namespace synchronous_generator proxy.raw(output); } proxy.raw("__rpc_out_buf, __rpc_sp->get_encoding());\n"); - proxy("if(__receiver_result != rpc::error::OK())"); - proxy(" __rpc_ret = __receiver_result;"); } proxy("break;"); proxy("}}"); @@ -1221,8 +1275,7 @@ namespace synchronous_generator proxy("{{"); { proxy.print_tabs(); - proxy.raw( - "auto __receiver_result = {}proxy_deserialiser::{}(", + proxy.raw("__rpc_ret = {}proxy_deserialiser::{}(", scoped_namespace, function->get_name()); count = 1; @@ -1242,8 +1295,6 @@ namespace synchronous_generator proxy.raw(output); } proxy.raw("__rpc_out_buf);\n"); - proxy("if(__receiver_result != rpc::error::OK())"); - proxy(" __rpc_ret = __receiver_result;"); } proxy("break;"); proxy("}}"); @@ -1349,22 +1400,7 @@ namespace synchronous_generator proxy(output); } } - proxy("//PROXY_CLEAN_IN"); - { - uint64_t count = 1; - for (auto& parameter : function->get_parameters()) - { - std::string output; - { - if (!do_in_param( - PROXY_CLEAN_IN, from_host, m_ob, parameter.get_name(), parameter.get_type(), parameter, count, output)) - continue; - - proxy(output); - } - count++; - } - } + emit_proxy_clean_in(from_host, m_ob, proxy, function); proxy("CO_RETURN __rpc_ret;"); proxy("}}"); @@ -1436,7 +1472,7 @@ namespace synchronous_generator bool optimistic = false; std::shared_ptr obj; - marshalls_interfaces = is_interface_param(library, parameter.get_type(), optimistic, obj); + marshalls_interfaces |= is_interface_param(library, parameter.get_type(), optimistic, obj); } // Get description attribute @@ -1480,19 +1516,9 @@ namespace synchronous_generator proxy("__{0}_local_proxy(const rpc::weak_ptr<{0}>& ptr)", interface_name); proxy(": rpc::local_proxy<{0}>(ptr)", interface_name); proxy("{{}}"); - proxy("virtual ~__{0}_local_proxy() = default;", interface_name); - - proxy("void* get_address() const override"); - proxy("{{"); - proxy("auto ptr = ptr_.lock();"); - proxy("if(!ptr)"); - proxy("{{"); - proxy("return (void*)this;"); - proxy("}}"); - proxy("return ptr->get_address();"); - proxy("}}"); - - proxy("const rpc::casting_interface* query_interface(rpc::interface_ordinal interface_id) const override"); + proxy("virtual ~__{0}_local_proxy() CANOPY_DEFAULT_DESTRUCTOR", interface_name); + proxy("const rpc::casting_interface* __rpc_query_interface(rpc::interface_ordinal interface_id) const " + "override"); proxy("{{"); proxy("std::ignore = interface_id;"); proxy("return nullptr;"); @@ -1589,6 +1615,8 @@ namespace synchronous_generator proxy("class {0}_proxy : public rpc::interface_proxy<{0}>", interface_name); proxy("{{"); + proxy("mutable rpc::weak_ptr<{}_proxy> weak_this_;", interface_name); + proxy(""); proxy("{}_proxy(std::shared_ptr&& object_proxy) : ", interface_name); proxy(" rpc::interface_proxy<{}>(std::move(object_proxy))", interface_name); proxy("{{"); @@ -1605,7 +1633,7 @@ namespace synchronous_generator proxy("}}"); proxy("#endif"); proxy("}}"); - proxy("mutable rpc::weak_ptr<{}_proxy> weak_this_;", interface_name); + proxy(""); proxy("public:"); proxy(""); proxy("virtual ~{}_proxy()", interface_name); @@ -1636,12 +1664,18 @@ namespace synchronous_generator interface_name); proxy(""); - stub("CORO_TASK(int) {0}_stub::call([[maybe_unused]] uint64_t protocol_version, rpc::encoding enc, " - "[[maybe_unused]] rpc::caller_zone caller_zone_id, rpc::method method_id, const rpc::span& in_data, " - "std::vector& " - "__rpc_out_buf)", + stub("CORO_TASK(int) {0}::stub_caller::call({0}* __rpc_target_, [[maybe_unused]] uint64_t protocol_version, " + "rpc::encoding enc, [[maybe_unused]] uint64_t tag, [[maybe_unused]] rpc::caller_zone caller_zone_id, " + "[[maybe_unused]] rpc::destination_zone destination_zone_id, [[maybe_unused]] rpc::object object_id, " + "rpc::method method_id, const rpc::span& in_data, std::vector& __rpc_out_buf, " + "[[maybe_unused]] const std::vector& in_back_channel, " + "[[maybe_unused]] std::vector& out_back_channel)", interface_name); stub("{{"); + stub("if(!__rpc_target_)"); + stub("{{"); + stub("CO_RETURN rpc::error::OBJECT_NOT_FOUND();"); + stub("}}"); bool has_methods = false; for (auto& function : m_ob.get_functions()) @@ -1687,108 +1721,10 @@ namespace synchronous_generator stub(""); }; - void write_stub_factory(const class_entity& m_ob, writer& stub, std::set& done) - { - auto interface_name = m_ob.get_name(); - auto owner = m_ob.get_owner(); - std::string ns = interface_name; - while (!owner->get_name().empty()) - { - ns = owner->get_name() + "::" + ns; - owner = owner->get_owner(); - } - if (done.find(ns) != done.end()) - return; - done.insert(ns); - - stub("srv->add_interface_stub_factory(::{0}::get_id, " - "std::make_shared(const " - "std::shared_ptr&)>>([](const std::shared_ptr& " - "original) -> std::shared_ptr", - ns); - stub("{{"); - stub("auto ci = original->get_castable_interface();"); - stub("{{"); - stub("auto* tmp = const_cast<::{0}*>(static_cast(ci->query_interface(::{0}::get_id(rpc::get_version()))));", - ns); - stub("if(tmp != nullptr)"); - stub("{{"); - stub("rpc::shared_ptr<::{0}> tmp_ptr(ci, tmp);", ns); - stub("return std::static_pointer_cast(::{}_stub::create(tmp_ptr, " - "original->get_object_stub()));", - ns); - stub("}}"); - stub("}}"); - stub("return nullptr;"); - stub("}}));"); - } - - void write_stub_cast_factory(const class_entity& m_ob, writer& stub) - { - auto interface_name = m_ob.get_name(); - stub("int {}_stub::cast(rpc::interface_ordinal interface_id, std::shared_ptr& " - "new_stub)", - interface_name); - stub("{{"); - stub("auto service = get_object_stub().lock()->get_zone();"); - stub("int __rpc_ret = service->create_interface_stub(interface_id, {}::get_id, shared_from_this(), " - "new_stub);", - interface_name); - stub("return __rpc_ret;"); - stub("}}"); - } - - void write_interface_forward_declaration(const class_entity& m_ob, writer& header, writer& proxy, writer& stub) + void write_interface_forward_declaration(const class_entity& m_ob, writer& header, writer& proxy) { header("class {};", m_ob.get_name()); proxy("class {}_proxy;", m_ob.get_name()); - - auto interface_name = m_ob.get_name(); - - stub("class {0}_stub : public rpc::i_interface_stub", interface_name); - stub("{{"); - stub("rpc::shared_ptr<{}> __rpc_target_;", interface_name); - stub("std::weak_ptr target_stub_;", interface_name); - stub(""); - stub("{0}_stub(const rpc::shared_ptr<{0}>& __rpc_target, std::weak_ptr " - "__rpc_target_stub) : ", - interface_name); - stub(" __rpc_target_(__rpc_target),", interface_name); - stub(" target_stub_(__rpc_target_stub)"); - stub(" {{}}"); - stub("mutable std::weak_ptr<{}_stub> weak_this_;", interface_name); - stub(""); - stub("public:"); - stub("virtual ~{0}_stub() = default;", interface_name); - stub("static std::shared_ptr<{0}_stub> create(const rpc::shared_ptr<{0}>& __rpc_target, " - "std::weak_ptr __rpc_target_stub)", - interface_name); - stub("{{"); - stub("auto __rpc_ret = std::shared_ptr<{0}_stub>(new {0}_stub(__rpc_target, __rpc_target_stub));", interface_name); - stub("__rpc_ret->weak_this_ = __rpc_ret;", interface_name); - stub("return __rpc_ret;", interface_name); - stub("}}"); - stub("std::shared_ptr<{0}_stub> shared_from_this(){{return weak_this_.lock();}}", interface_name); - stub(""); - stub("rpc::interface_ordinal get_interface_id(uint64_t rpc_version) const override"); - stub("{{"); - stub("return {{{}::get_id(rpc_version)}};", interface_name); - stub("}}"); - stub("virtual rpc::shared_ptr get_castable_interface() const override {{ return " - "rpc::static_pointer_cast(__rpc_target_); }}", - interface_name); - - stub("std::weak_ptr get_object_stub() const override {{ return target_stub_;}}"); - stub("void* get_pointer() const override {{ return __rpc_target_.get();}}"); - stub("CORO_TASK(int) call(uint64_t protocol_version, rpc::encoding enc, " - "rpc::caller_zone caller_zone_id, rpc::method method_id, const rpc::span& in_data, " - "std::vector& " - "__rpc_out_buf) override;"); - stub("int cast(rpc::interface_ordinal interface_id, std::shared_ptr& new_stub) " - "override;"); - stub("}};"); - stub(""); } void write_enum_forward_declaration(const entity& ent, writer& header) @@ -2211,6 +2147,12 @@ namespace synchronous_generator "descriptor);", ns, interface_name); + header("template<> CORO_TASK(int) " + "rpc::service::bind_in_proxy(uint64_t protocol_version, const rpc::optimistic_ptr<::{}{}>& " + "iface, std::shared_ptr& stub, caller_zone caller_zone_id, rpc::interface_descriptor& " + "descriptor);", + ns, + interface_name); } void write_library_proxy_factory( @@ -2238,21 +2180,26 @@ namespace synchronous_generator proxy("}}"); proxy(""); - stub("template<> std::function(const std::shared_ptr& " - "stub)> " - "service::get_interface_stub_factory(const rpc::shared_ptr<::{}{}>& iface)", + stub("template<> CORO_TASK(int) service::bind_in_proxy(uint64_t protocol_version, " + "const " + "rpc::shared_ptr<::{}{}>& iface, std::shared_ptr& stub, caller_zone caller_zone_id, " + "rpc::interface_descriptor& " + "descriptor)", ns, interface_name); stub("{{"); - stub("return [&](const std::shared_ptr& stub) -> " - "std::shared_ptr{{"); - stub("return std::static_pointer_cast(::{}{}_stub::create(iface, stub));", ns, interface_name); - stub("}};"); + stub("if(!iface)"); + stub("{{"); + stub("CO_RETURN rpc::error::INVALID_DATA();"); + stub("}}"); + stub("auto iface_cast = rpc::static_pointer_cast(iface);"); + stub("CO_RETURN CO_AWAIT get_descriptor_from_interface_stub(caller_zone_id, iface_cast, stub, descriptor, " + "false);"); stub("}}"); stub("template<> CORO_TASK(int) service::bind_in_proxy(uint64_t protocol_version, " "const " - "rpc::shared_ptr<::{}{}>& iface, std::shared_ptr& stub, caller_zone caller_zone_id, " + "rpc::optimistic_ptr<::{}{}>& iface, std::shared_ptr& stub, caller_zone caller_zone_id, " "rpc::interface_descriptor& " "descriptor)", ns, @@ -2262,31 +2209,16 @@ namespace synchronous_generator stub("{{"); stub("CO_RETURN rpc::error::INVALID_DATA();"); stub("}}"); - - stub("auto factory = get_interface_stub_factory(iface);"); - stub("CO_RETURN CO_AWAIT get_descriptor_from_interface_stub(caller_zone_id, iface.get(), " - "factory, " - "stub, descriptor);"); + stub("rpc::shared_ptr<::{}{}> iface_shared;", ns, interface_name); + stub("auto __rpc_ret = CO_AWAIT rpc::make_shared(iface, iface_shared);"); + stub("if(rpc::error::is_error(__rpc_ret))"); + stub("{{"); + stub("CO_RETURN __rpc_ret;"); + stub("}}"); + stub("auto iface_cast = rpc::static_pointer_cast(iface_shared);"); + stub("CO_RETURN CO_AWAIT get_descriptor_from_interface_stub(caller_zone_id, iface_cast, stub, descriptor, " + "true);"); stub("}}"); - } - - void write_marshalling_logic(const class_entity& lib, writer& stub) - { - { - for (auto& cls : lib.get_classes()) - { - if (!cls->get_import_lib().empty()) - continue; - if (cls->get_entity_type() == entity_type::INTERFACE) - write_stub_cast_factory(*cls, stub); - } - - for (auto& cls : lib.get_classes()) - { - if (!cls->get_import_lib().empty()) - continue; - } - } } // entry point @@ -2297,7 +2229,7 @@ namespace synchronous_generator if (!cls->get_import_lib().empty()) continue; if (cls->get_entity_type() == entity_type::INTERFACE) - write_interface_forward_declaration(*cls, header, proxy, stub); + write_interface_forward_declaration(*cls, header, proxy); } for (auto cls : lib.get_classes()) @@ -2413,7 +2345,6 @@ namespace synchronous_generator } } } - write_marshalling_logic(lib, stub); } void write_epilog(bool from_host, @@ -2448,53 +2379,8 @@ namespace synchronous_generator } } - void write_stub_factory_lookup_items( - const class_entity& lib, std::string prefix, writer& stub, std::set& done) - { - for (auto cls : lib.get_classes()) - { - if (!cls->get_import_lib().empty()) - continue; - if (cls->get_entity_type() == entity_type::NAMESPACE) - { - write_stub_factory_lookup_items(*cls, prefix + cls->get_name() + "::", stub, done); - } - else - { - for (auto& cls : lib.get_classes()) - { - if (!cls->get_import_lib().empty()) - continue; - if (cls->get_entity_type() == entity_type::INTERFACE) - write_stub_factory(*cls, stub, done); - } - - for (auto& cls : lib.get_classes()) - { - if (!cls->get_import_lib().empty()) - continue; - } - } - } - } - - void write_stub_factory_lookup( - const std::string module_name, const class_entity& lib, std::string prefix, writer& stub_header, writer& stub) - { - stub_header("void {}_register_stubs(const std::shared_ptr& srv);", module_name); - stub("void {}_register_stubs([[maybe_unused]]const std::shared_ptr& srv)", module_name); - stub("{{"); - - std::set done; - - write_stub_factory_lookup_items(lib, prefix, stub, done); - - stub("}}"); - } - // entry point - void write_files(std::string module_name, - bool from_host, + void write_files(bool from_host, const class_entity& lib, std::ostream& hos, std::ostream& pos, @@ -2624,7 +2510,5 @@ namespace synchronous_generator header("}}"); proxy("}}"); stub("}}"); - - write_stub_factory_lookup(module_name, lib, prefix, stub_header, stub); } } diff --git a/generator/src/synchronous_mock_generator.cpp b/generator/src/synchronous_mock_generator.cpp index f5e83de..08c82d5 100644 --- a/generator/src/synchronous_mock_generator.cpp +++ b/generator/src/synchronous_mock_generator.cpp @@ -52,9 +52,9 @@ namespace synchronous_mock_generator } header("class {0}_mock : public {0}", interface_name); header("{{"); - header("void* get_address() const override {{ return (void*)this; }}"); header("public:"); - header("const rpc::casting_interface* query_interface(rpc::interface_ordinal interface_id) const override "); + header( + "const rpc::casting_interface* __rpc_query_interface(rpc::interface_ordinal interface_id) const override "); header("{{"); for (const auto& version : protocol_versions) { diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..a2fee1e --- /dev/null +++ b/notes.txt @@ -0,0 +1,53 @@ + +• Your redesign is viable and aligns with where the code already wants to go, but there are two immediate gaps: + + 1. stub_caller is declared but never generated/defined (build_release/generated/include/example_shared/example_shared.h:288). + 2. rpc::base::call currently returns OK even if no interface matched (rpc/include/rpc/internal/base.h:48 and rpc/include/rpc/internal/base.h:71). + + Refactor Map + + 1. generator/src/interface_declaration_generator.cpp:661 and generator/src/interface_declaration_generator.cpp:930 + Remove *_stub forward-decl/friend coupling. + Change stub_caller::call declaration (generator/src/interface_declaration_generator.cpp:785) to include the target interface pointer/reference (or an equivalent way to access the local object), otherwise static dispatch cannot invoke methods directly. + 2. generator/src/synchronous_generator.cpp:1677 and generator/src/synchronous_generator.cpp:1787 + Stop generating concrete class _stub : i_interface_stub. + Generate CORO_TASK(int) ::stub_caller::call(...) implementations from the current switch body. + Replace target_stub_.lock() usage in generated method glue (generator/src/synchronous_generator.cpp:483 and generator/src/synchronous_generator.cpp:1213) with __rpc_target_->get_stub(). + 3. generator/src/synchronous_generator.cpp:1728, generator/src/synchronous_generator.cpp:1765, generator/src/synchronous_generator.cpp:2285 + Delete stub-factory/cast codegen paths: + + - write_stub_factory + - write_stub_cast_factory + - service::get_interface_stub_factory specializations + Bind helpers should call service descriptor creation directly, without interface-stub factories. + + 4. rpc/include/rpc/internal/base.h:34 + Pass the concrete interface pointer into Interfaces::stub_caller::call(...). + Return INVALID_INTERFACE_ID when no fold-branch matched. + 5. rpc/include/rpc/internal/stub.h:104 and rpc/src/stub.cpp:27 + Redesign object_stub to hold one strong rpc::shared_ptr target instead of stub_map_. + Then: + + - object_stub::call forwards to target casting_interface::call(...) (rpc/src/stub.cpp:51 path replacement). + - object_stub::try_cast uses query_interface(...) instead of factory/cached stub creation (rpc/src/stub.cpp:77). + - get_castable_interface(interface_id) should query and alias from the stored root target. + + 6. rpc/include/rpc/internal/service.h:138, rpc/include/rpc/internal/service.h:593, rpc/src/service.cpp:389, rpc/src/service.cpp:1135 + Remove stub_factories_, add_interface_stub_factory, and create_interface_stub. + Refactor get_descriptor_from_interface_stub(...)/add_ref_local_or_remote_return_descriptor(...) to no longer accept stub-factory callbacks; they should create object_stub directly from the local casting_interface. + 7. rpc/include/rpc/internal/bindings.h:58, rpc/include/rpc/internal/bindings.h:155, rpc/include/rpc/internal/bindings.h:360 + Remove factory usage and any ob->get_interface(...) dependence. + Use service::get_castable_interface(...)/new object_stub query-based access for local return binding. + + Important Compatibility Decision + + If you remove i_interface_stub, local implementations that do not derive from rpc::base<...> (or otherwise override casting_interface::call) will stop working. + If that compatibility matters, keep a temporary fallback path in object_stub::call during migration. + + Suggested Order + + 1. Generate and use real stub_caller::call first. + 2. Switch runtime dispatch (object_stub::call) to casting_interface::call. + 3. Remove i_interface_stub/stub_map_/factory plumbing. + + I only analyzed and mapped refactors; no files were modified yet. \ No newline at end of file diff --git a/rpc/include/rpc/internal/base.h b/rpc/include/rpc/internal/base.h index a5fdf2c..7311eac 100644 --- a/rpc/include/rpc/internal/base.h +++ b/rpc/include/rpc/internal/base.h @@ -11,11 +11,13 @@ namespace rpc // derive your class from this class and you will get more features for free when they arrive template class base : public Interfaces... { + std::weak_ptr stub_; + public: virtual ~base() = default; // Query to see if this class supports an an interface - const rpc::casting_interface* query_interface(rpc::interface_ordinal interface_id) const override + const rpc::casting_interface* __rpc_query_interface(rpc::interface_ordinal interface_id) const override { const rpc::casting_interface* out = nullptr; ( @@ -29,8 +31,42 @@ namespace rpc return out; } - // Get the address of the implementation, needed to do reverse lookups in the stub table and for - // proper dynamic casting in clang and gcc, msvc is much more forgiving - void* get_address() const override { return (void*)static_cast(this); } + CORO_TASK(int) + __rpc_call(uint64_t protocol_version, + encoding encoding, + uint64_t tag, + caller_zone caller_zone_id, + destination_zone destination_zone_id, + object object_id, + interface_ordinal interface_id, + method method_id, + const rpc::span& in_data, + std::vector& out_buf_, + const std::vector& in_back_channel, + std::vector& out_back_channel) override + { + int ret = rpc::error::INVALID_INTERFACE_ID(); + [[maybe_unused]] bool found = ((rpc::match(interface_id) + ? ((ret = CO_AWAIT Interfaces::stub_caller::call( + static_cast(static_cast(this)), + protocol_version, + encoding, + tag, + caller_zone_id, + destination_zone_id, + object_id, + method_id, + in_data, + out_buf_, + in_back_channel, + out_back_channel)), + true) + : false) + || ...); + CO_RETURN ret; + } + + std::shared_ptr __rpc_get_stub() const override { return stub_.lock(); } + void __rpc_set_stub(const std::shared_ptr& stub) override { stub_ = stub; } }; } diff --git a/rpc/include/rpc/internal/bindings.h b/rpc/include/rpc/internal/bindings.h index 02820ec..bf8fd3f 100644 --- a/rpc/include/rpc/internal/bindings.h +++ b/rpc/include/rpc/internal/bindings.h @@ -30,7 +30,7 @@ namespace rpc auto operating_service = sp->get_operating_zone_service(); // this is to check that an interface is belonging to another zone and not the operating zone - if (!iface->is_local() + if (!iface->__rpc_is_local() && casting_interface::get_destination_zone(*iface) != operating_service->get_zone_id().as_destination()) { descriptor = {casting_interface::get_object_id(*iface), casting_interface::get_destination_zone(*iface)}; @@ -55,11 +55,11 @@ namespace rpc descriptor = {0, 0}; CO_RETURN rpc::error::OK(); } - auto factory = zone->get_interface_stub_factory(iface); + auto iface_cast = rpc::static_pointer_cast(iface); - std::shared_ptr stub; - CO_RETURN CO_AWAIT zone->add_ref_local_or_remote_return_descriptor( - protocol_version, caller_zone_id, iface.get(), factory, stub, descriptor); + auto ret = CO_AWAIT zone->add_ref_local_or_remote_return_descriptor( + protocol_version, caller_zone_id, iface_cast, descriptor, false); + CO_RETURN ret; } // do not use directly it is for the interface generator use rpc::create_interface_proxy if you want to get a proxied pointer to a remote implementation @@ -79,12 +79,19 @@ namespace rpc // if it is local to this service then just get the relevant stub else if (serv->get_zone_id().as_destination() == encap.destination_zone_id) { - iface = rpc::static_pointer_cast(serv->get_castable_interface(encap.object_id, T::get_id(protocol_version))); - if (!iface) + auto os = serv->get_object(encap.object_id).lock(); + if (!os) { - RPC_ERROR("Object not found in local interface lookup"); + RPC_ERROR("Object not found in zone {}", serv->get_zone_id().get_val()); CO_RETURN rpc::error::OBJECT_NOT_FOUND(); } + + iface = rpc::static_pointer_cast(os->get_castable_interface(T::get_id(protocol_version))); + if (!iface) + { + RPC_ERROR("interface not implemented by this object"); + CO_RETURN rpc::error::INVALID_INTERFACE_ID(); + } CO_RETURN rpc::error::OK(); } else @@ -152,17 +159,14 @@ namespace rpc CO_RETURN rpc::error::REFERENCE_COUNT_ERROR(); } - auto interface_stub = ob->get_interface(T::get_id(rpc::VERSION_2)); - if (!interface_stub) + auto castable = ob->get_castable_interface(T::get_id(rpc::get_version())); + if (!castable) { - if (!interface_stub) - { - RPC_ERROR("Invalid interface ID in proxy release"); - CO_RETURN rpc::error::INVALID_INTERFACE_ID(); - } + RPC_ERROR("Invalid interface ID in proxy release"); + CO_RETURN rpc::error::INVALID_INTERFACE_ID(); } - val = rpc::static_pointer_cast(interface_stub->get_castable_interface()); + val = rpc::static_pointer_cast(castable); CO_RETURN rpc::error::OK(); } @@ -288,7 +292,159 @@ namespace rpc CO_RETURN error::INVALID_DATA(); } std::shared_ptr stub; - auto factory = serv.get_interface_stub_factory(iface); - CO_RETURN CO_AWAIT serv.get_descriptor_from_interface_stub(caller_zone_id, iface.get(), factory, stub, descriptor); + auto iface_cast = rpc::static_pointer_cast(iface); + CO_RETURN CO_AWAIT serv.get_descriptor_from_interface_stub(caller_zone_id, iface_cast, stub, descriptor, false); + } + + // optimistic_ptr overload: convert to shared_ptr and delegate + template + CORO_TASK(int) + proxy_bind_in_param(std::shared_ptr object_p, + uint64_t protocol_version, + const rpc::optimistic_ptr& iface, + std::shared_ptr& stub, + interface_descriptor& descriptor) + { + if (!iface) + { + descriptor = {0, 0}; + CO_RETURN error::OK(); + } + + RPC_ASSERT(object_p); + if (!object_p) + CO_RETURN error::INVALID_DATA(); + auto sp = object_p->get_service_proxy(); + auto operating_service = sp->get_operating_zone_service(); + + // this is to check that an interface is belonging to another zone and not the operating zone + if (!iface->__rpc_is_local() + && casting_interface::get_destination_zone(*iface) != operating_service->get_zone_id().as_destination()) + { + descriptor = {casting_interface::get_object_id(*iface), casting_interface::get_destination_zone(*iface)}; + CO_RETURN error::OK(); + } + + // else encapsulate away + CO_RETURN CO_AWAIT operating_service->bind_in_proxy( + protocol_version, iface, stub, sp->get_destination_zone_id().as_caller(), descriptor); + } + + // optimistic_ptr overload: get shared_ptr result then convert to optimistic_ptr + template + CORO_TASK(int) + proxy_bind_out_param( + const std::shared_ptr& sp, const rpc::interface_descriptor& encap, rpc::optimistic_ptr& val) + { + // if we have a null object id then return a null ptr + if (!encap.object_id.is_set() || !encap.destination_zone_id.is_set()) + CO_RETURN rpc::error::OK(); + + auto serv = sp->get_operating_zone_service(); + + // if it is local to this service then just get the relevant stub + if (encap.destination_zone_id == serv->get_zone_id().as_destination()) + { + auto ob = serv->get_object(encap.object_id).lock(); + if (!ob) + { + RPC_ERROR("Object not found - object is null in release"); + CO_RETURN rpc::error::OBJECT_NOT_FOUND(); + } + + auto count = serv->release_local_stub(ob, true, encap.destination_zone_id.as_caller()); + RPC_ASSERT(count); + if (!count || count == std::numeric_limits::max()) + { + RPC_ERROR("Reference count error in release"); + CO_RETURN rpc::error::REFERENCE_COUNT_ERROR(); + } + + auto castable = ob->get_castable_interface(T::get_id(rpc::get_version())); + if (!castable) + { + RPC_ERROR("Invalid interface ID in proxy release"); + CO_RETURN rpc::error::INVALID_INTERFACE_ID(); + } + + CO_RETURN CO_AWAIT rpc::make_optimistic(rpc::static_pointer_cast(castable), val); + } + + // get the right service proxy + bool new_proxy_added = false; + auto service_proxy = sp; + + if (sp->get_destination_zone_id() != encap.destination_zone_id) + { + // if the zone is different lookup or clone the right proxy + // the service proxy is where the object came from so it should be used as the new caller channel for this returned object + service_proxy = serv->get_zone_proxy({0}, {encap.destination_zone_id}, new_proxy_added); + if (!service_proxy) + { + RPC_ERROR("Object not found - service proxy is null in proxy_bind_out_param"); + CO_RETURN rpc::error::ZONE_NOT_FOUND(); + } + } + + std::shared_ptr op; + auto err = CO_AWAIT service_proxy->get_or_create_object_proxy( + encap.object_id, service_proxy::object_proxy_creation_rule::RELEASE_IF_NOT_NEW, new_proxy_added, {}, true, op); + if (err != error::OK()) + { + RPC_ERROR("get_or_create_object_proxy failed"); + CO_RETURN err; + } + if (!op) + { + RPC_ERROR("Object not found in proxy_bind_out_param"); + CO_RETURN rpc::error::OBJECT_NOT_FOUND(); + } + RPC_ASSERT(op != nullptr); + CO_RETURN CO_AWAIT op->query_interface(val, false); + } + + // optimistic_ptr overload: get shared_ptr result then convert to optimistic_ptr + template + CORO_TASK(int) + stub_bind_in_param(uint64_t protocol_version, + const std::shared_ptr& serv, + caller_zone caller_zone_id, + const rpc::interface_descriptor& encap, + rpc::optimistic_ptr& iface) + { + rpc::shared_ptr shared_iface; + auto err = CO_AWAIT stub_bind_in_param(protocol_version, serv, caller_zone_id, encap, shared_iface); + if (err != error::OK()) + CO_RETURN err; + if (!shared_iface) + CO_RETURN error::OK(); + + CO_RETURN CO_AWAIT rpc::make_optimistic(shared_iface, iface); + } + + // optimistic_ptr overload: convert to shared_ptr and delegate + template + CORO_TASK(int) + stub_bind_out_param(const std::shared_ptr& zone, + uint64_t protocol_version, + caller_zone caller_zone_id, + const optimistic_ptr& iface, + interface_descriptor& descriptor) + { + if (!iface) + { + descriptor = {0, 0}; + CO_RETURN rpc::error::OK(); + } + + rpc::shared_ptr shared_iface; + auto to_shared = CO_AWAIT rpc::make_shared(iface, shared_iface); + if (rpc::error::is_error(to_shared)) + CO_RETURN to_shared; + auto iface_cast = rpc::static_pointer_cast(shared_iface); + + auto ret = CO_AWAIT zone->add_ref_local_or_remote_return_descriptor( + protocol_version, caller_zone_id, iface_cast, descriptor, true); + CO_RETURN ret; } } diff --git a/rpc/include/rpc/internal/bindings_fwd.h b/rpc/include/rpc/internal/bindings_fwd.h index 4e187a4..4485add 100644 --- a/rpc/include/rpc/internal/bindings_fwd.h +++ b/rpc/include/rpc/internal/bindings_fwd.h @@ -61,10 +61,41 @@ namespace rpc proxy_bind_out_param( const std::shared_ptr& sp, const rpc::interface_descriptor& encap, rpc::shared_ptr& val); + // optimistic_ptr overloads for marshalling + template + CORO_TASK(int) + proxy_bind_in_param(std::shared_ptr object_p, + uint64_t protocol_version, + const rpc::optimistic_ptr& iface, + std::shared_ptr& stub, + rpc::interface_descriptor& descriptor); + + template + CORO_TASK(int) + proxy_bind_out_param(const std::shared_ptr& sp, + const rpc::interface_descriptor& encap, + rpc::optimistic_ptr& val); + + template + CORO_TASK(int) + stub_bind_in_param(uint64_t protocol_version, + const std::shared_ptr& serv, + caller_zone caller_zone_id, + const rpc::interface_descriptor& encap, + rpc::optimistic_ptr& iface); + + template + CORO_TASK(int) + stub_bind_out_param(const std::shared_ptr& zone, + uint64_t protocol_version, + caller_zone caller_zone_id, + const optimistic_ptr& iface, + interface_descriptor& descriptor); + template CORO_TASK(int) demarshall_interface_proxy(uint64_t protocol_version, const std::shared_ptr& sp, const rpc::interface_descriptor& encap, - rpc::shared_ptr& val); + rpc::optimistic_ptr& val); } diff --git a/rpc/include/rpc/internal/build_modifiers.h b/rpc/include/rpc/internal/build_modifiers.h index db080d6..9ad5bde 100644 --- a/rpc/include/rpc/internal/build_modifiers.h +++ b/rpc/include/rpc/internal/build_modifiers.h @@ -3,12 +3,12 @@ * All rights reserved. */ -#ifdef DEBUG_DEFAULT_DESTRUCTOR -#define DEFAULT_DESTRUCTOR \ +#ifdef CANOPY_DEBUG_DEFAULT_DESTRUCTOR +#define CANOPY_DEFAULT_DESTRUCTOR \ { \ } #else -#define DEFAULT_DESTRUCTOR = default; +#define CANOPY_DEFAULT_DESTRUCTOR = default; #endif // Macros to disable/enable warnings around YAS usage diff --git a/rpc/include/rpc/internal/casting_interface.h b/rpc/include/rpc/internal/casting_interface.h index eb93ac2..965605f 100644 --- a/rpc/include/rpc/internal/casting_interface.h +++ b/rpc/include/rpc/internal/casting_interface.h @@ -23,7 +23,7 @@ namespace rpc // this is a nice helper function to match an interface id to a interface in a version independent way template bool match(rpc::interface_ordinal interface_id) { - return T::get_id(rpc::VERSION_2) == interface_id; + return T::get_id(rpc::get_version()) == interface_id; } // this is the base class to all interfaces @@ -31,11 +31,35 @@ namespace rpc { public: virtual ~casting_interface() = default; - virtual void* get_address() const = 0; - virtual const rpc::casting_interface* query_interface(rpc::interface_ordinal interface_id) const = 0; + virtual const rpc::casting_interface* __rpc_query_interface(rpc::interface_ordinal interface_id) const = 0; - virtual bool is_local() const { return true; } - virtual std::shared_ptr get_object_proxy() const { return nullptr; } + virtual bool __rpc_is_local() const { return true; } + virtual std::shared_ptr __rpc_get_object_proxy() const { return nullptr; } + + // only for local objects + virtual std::shared_ptr __rpc_get_stub() const + { + RPC_ASSERT(false); + return nullptr; + } + virtual void __rpc_set_stub(const std::shared_ptr&) { RPC_ASSERT(false); } + + virtual CORO_TASK(int) __rpc_call([[maybe_unused]] uint64_t protocol_version, + [[maybe_unused]] encoding encoding, + [[maybe_unused]] uint64_t tag, + [[maybe_unused]] caller_zone caller_zone_id, + [[maybe_unused]] destination_zone destination_zone_id, + [[maybe_unused]] object object_id, + [[maybe_unused]] interface_ordinal interface_id, + [[maybe_unused]] method method_id, + [[maybe_unused]] const rpc::span& in_data, + [[maybe_unused]] std::vector& out_buf_, + [[maybe_unused]] const std::vector& in_back_channel, + [[maybe_unused]] std::vector& out_back_channel) + { + RPC_ASSERT(false); + CO_RETURN rpc::error::INVALID_CAST(); + } static object get_object_id(const casting_interface& iface); static std::shared_ptr get_service_proxy(const casting_interface& iface); @@ -59,16 +83,18 @@ namespace rpc } virtual ~interface_proxy() = default; - void* get_address() const override { return (void*)this; } - const rpc::casting_interface* query_interface(rpc::interface_ordinal interface_id) const override + const rpc::casting_interface* __rpc_query_interface(rpc::interface_ordinal interface_id) const override { if (rpc::match(interface_id)) return static_cast(this); return nullptr; } - bool is_local() const override { return false; } - std::shared_ptr get_object_proxy() const override { return object_proxy_.get_nullable(); } + bool __rpc_is_local() const override { return false; } + std::shared_ptr __rpc_get_object_proxy() const override + { + return object_proxy_.get_nullable(); + } }; constexpr uint64_t STD_VECTOR_UINT_8_ID = 0x71FC1FAC5CD5E6FA; @@ -157,5 +183,4 @@ namespace rpc public: static constexpr uint64_t get(uint64_t) { return FLOAT_64_ID; } }; - } diff --git a/rpc/include/rpc/internal/error_codes.h b/rpc/include/rpc/internal/error_codes.h index 05cc4f5..70fceaa 100644 --- a/rpc/include/rpc/internal/error_codes.h +++ b/rpc/include/rpc/internal/error_codes.h @@ -36,31 +36,34 @@ namespace rpc { int OK(); int MIN(); - int OUT_OF_MEMORY(); // service has no more memory - int NEED_MORE_MEMORY(); // a call needs more memory for its out parameters - int SECURITY_ERROR(); // a security specific issue - int INVALID_DATA(); // - int TRANSPORT_ERROR(); // an error with the custom transport has occurred - int INVALID_METHOD_ID(); // a method call is invalid based on the wrong ordinal - int INVALID_INTERFACE_ID(); // an interface is not implemented by an object - int INVALID_CAST(); // unable to cast an interface to another one - int ZONE_NOT_SUPPORTED(); // zone is not consistent with the proxy - int ZONE_NOT_INITIALISED(); // zone is not ready - int ZONE_NOT_FOUND(); // zone not found - int OBJECT_NOT_FOUND(); // object id is invalid - int INVALID_VERSION(); // a service proxy does not supports a version of rpc - int EXCEPTION(); // an uncaught exception has occurred - int PROXY_DESERIALISATION_ERROR(); // a proxy is unable to deserialise data from a service - int STUB_DESERIALISATION_ERROR(); // a stub is unable to deserialise data from a caller - int INCOMPATIBLE_SERVICE(); // a service proxy is incompatible with the client - int INCOMPATIBLE_SERIALISATION(); // service proxy does not support this serialisation format try JSON - int REFERENCE_COUNT_ERROR(); // reference count error - int UNABLE_TO_CREATE_SERVICE_PROXY(); // unable to create service proxy - int SERVICE_PROXY_LOST_CONNECTION(); // channel is no longer available - int CALL_CANCELLED(); // Service proxy remote call is cancelled + int OUT_OF_MEMORY(); // service has no more memory + int NEED_MORE_MEMORY(); // a call needs more memory for its out parameters, specifically for synchronous out parameters from enclave calls + int SECURITY_ERROR(); // a security specific issue + int INVALID_DATA(); // invalid data + int TRANSPORT_ERROR(); // an error with the custom transport has occurred + int INVALID_METHOD_ID(); // a method call is invalid based on the wrong ordinal + int INVALID_INTERFACE_ID(); // an interface is not implemented by an object + int INVALID_CAST(); // unable to cast an interface to another one + int ZONE_NOT_SUPPORTED(); // zone is not consistent with the proxy + int ZONE_NOT_INITIALISED(); // zone is not ready + int ZONE_NOT_FOUND(); // zone not found + int OBJECT_NOT_FOUND(); // object id is invalid + int INVALID_VERSION(); // a service proxy does not supports a version of rpc + int EXCEPTION(); // an uncaught exception has occurred + int PROXY_DESERIALISATION_ERROR(); // a proxy is unable to deserialise data from a service + int STUB_DESERIALISATION_ERROR(); // a stub is unable to deserialise data from a caller + int INCOMPATIBLE_SERVICE(); // a service proxy is incompatible with the client + int INCOMPATIBLE_SERIALISATION(); // service proxy does not support this serialisation format try JSON + int REFERENCE_COUNT_ERROR(); // reference count error + // int UNABLE_TO_CREATE_SERVICE_PROXY(); // unable to create service proxy + int SERVICE_PROXY_LOST_CONNECTION(); // channel is no longer available + int CALL_CANCELLED(); // Service proxy remote call is cancelled int OBJECT_GONE(); // The service no longer has an object of that id, perhaps an optimistic pointer call attempt is happining int MAX(); // the biggest value + bool is_error(int err); // any error listed above other than OK + bool is_critical(int err); // any error listed above other than OK and OBJECT_GONE + void set_OK_val(int val); void set_offset_val(int val); void set_offset_val_is_negative(bool val); diff --git a/rpc/include/rpc/internal/object_proxy.h b/rpc/include/rpc/internal/object_proxy.h index 83b5e3b..f0b9982 100644 --- a/rpc/include/rpc/internal/object_proxy.h +++ b/rpc/include/rpc/internal/object_proxy.h @@ -113,12 +113,12 @@ namespace rpc { // scope for the lock std::lock_guard guard(insert_control_); - if (T::get_id(rpc::VERSION_2) == 0) + if (T::get_id(rpc::get_version()) == 0) { CO_RETURN rpc::error::OK(); } { - auto item = proxy_map.find(T::get_id(rpc::VERSION_2)); + auto item = proxy_map.find(T::get_id(rpc::get_version())); if (item != proxy_map.end()) { CO_RETURN create(item); @@ -127,7 +127,7 @@ namespace rpc if (!do_remote_check) { create_interface_proxy(iface); - proxy_map[T::get_id(rpc::VERSION_2)] = rpc::reinterpret_pointer_cast(iface); + proxy_map[T::get_id(rpc::get_version())] = rpc::reinterpret_pointer_cast(iface); CO_RETURN rpc::error::OK(); } } @@ -147,17 +147,89 @@ namespace rpc // check again... { - auto item = proxy_map.find(T::get_id(rpc::VERSION_2)); + auto item = proxy_map.find(T::get_id(rpc::get_version())); if (item != proxy_map.end()) { CO_RETURN create(item); } } create_interface_proxy(iface); - proxy_map[T::get_id(rpc::VERSION_2)] = rpc::reinterpret_pointer_cast(iface); + proxy_map[T::get_id(rpc::get_version())] = rpc::reinterpret_pointer_cast(iface); CO_RETURN rpc::error::OK(); } } + + template CORO_TASK(int) query_interface(rpc::optimistic_ptr& iface, bool do_remote_check = true) + { + { // scope for the lock + if (T::get_id(rpc::get_version()) == 0) + { + CO_RETURN rpc::error::OK(); + } + auto guard = std::make_unique>(insert_control_); + { + auto item = proxy_map.find(T::get_id(rpc::get_version())); + if (item != proxy_map.end()) + { + guard.reset(); + auto proxy = rpc::reinterpret_pointer_cast(item->second.lock()); + if (!proxy) + { + // weak pointer needs refreshing + create_interface_proxy(proxy); + item->second = rpc::reinterpret_pointer_cast(proxy); + } + CO_RETURN CO_AWAIT rpc::make_optimistic(proxy, iface); + } + } + if (!do_remote_check) + { + rpc::shared_ptr tmp; + create_interface_proxy(tmp); + proxy_map[T::get_id(rpc::get_version())] = rpc::reinterpret_pointer_cast(tmp); + + guard.reset(); + CO_RETURN CO_AWAIT rpc::make_optimistic(tmp, iface); + } + } + + // release the lock and then check for casting + if (do_remote_check) + { + // see if object_id can implement interface + int ret = CO_AWAIT try_cast(T::get_id); + if (ret != rpc::error::OK()) + { + CO_RETURN ret; + } + } + { // another scope for the lock + auto guard = std::make_unique>(insert_control_); + + // check again... + { + auto item = proxy_map.find(T::get_id(rpc::get_version())); + if (item != proxy_map.end()) + { + auto proxy = rpc::reinterpret_pointer_cast(item->second.lock()); + if (!proxy) + { + // weak pointer needs refreshing + create_interface_proxy(proxy); + item->second = rpc::reinterpret_pointer_cast(proxy); + } + guard.reset(); + CO_RETURN CO_AWAIT rpc::make_optimistic(proxy, iface); + } + } + rpc::shared_ptr tmp; + create_interface_proxy(tmp); + proxy_map[T::get_id(rpc::get_version())] = rpc::reinterpret_pointer_cast(tmp); + + guard.reset(); + CO_RETURN CO_AWAIT rpc::make_optimistic(tmp, iface); + } + } }; } diff --git a/rpc/include/rpc/internal/remote_pointer.h b/rpc/include/rpc/internal/remote_pointer.h index 3f167ce..4449b89 100644 --- a/rpc/include/rpc/internal/remote_pointer.h +++ b/rpc/include/rpc/internal/remote_pointer.h @@ -67,7 +67,7 @@ #ifdef TEST_STL_COMPLIANCE -#define DEFAULT_DESTRUCTOR = default; +#define CANOPY_DEFAULT_DESTRUCTOR = default; #define RPC_MEMORY std @@ -359,7 +359,7 @@ namespace rpc rpc::casting_interface* ptr = reinterpret_cast(managed_object_ptr_); if (ptr) { - is_local_ = ptr->is_local(); + is_local_ = ptr->__rpc_is_local(); // CRITICAL: Initialize object_proxy counters to match control block's initial state. // The control block starts with shared_count_=0, then immediately increments to 1. // We need object_proxy to track this so that when control block decrements 1→0, @@ -367,7 +367,7 @@ namespace rpc // when the total count reaches 0. if (!is_local_) { - auto obj_proxy = ptr->get_object_proxy(); + auto obj_proxy = ptr->__rpc_get_object_proxy(); if (obj_proxy) { object_proxy_add_ref_shared(obj_proxy); @@ -568,7 +568,7 @@ namespace rpc if (managed_object_ptr_ && !is_local_) { auto ci = reinterpret_cast<::rpc::casting_interface*>(managed_object_ptr_); - if (auto obj_proxy = ci->get_object_proxy()) + if (auto obj_proxy = ci->__rpc_get_object_proxy()) { CO_RETURN CO_AWAIT object_proxy_add_ref(obj_proxy, options); } @@ -581,7 +581,7 @@ namespace rpc if (managed_object_ptr_ && !is_local_) { auto ci = reinterpret_cast<::rpc::casting_interface*>(managed_object_ptr_); - if (auto obj_proxy = ci->get_object_proxy()) + if (auto obj_proxy = ci->__rpc_get_object_proxy()) { object_proxy_release(obj_proxy, is_optimistic); } @@ -1882,12 +1882,12 @@ namespace rpc T* ptr = nullptr; // First try local interface casting - ptr = const_cast(static_cast(from->query_interface(T::get_id(VERSION_2)))); + ptr = const_cast(static_cast(from->__rpc_query_interface(T::get_id(get_version())))); if (ptr) CO_RETURN shared_ptr(from, ptr); // Then try remote interface casting through object_proxy - auto ob = from->get_object_proxy(); + auto ob = from->__rpc_get_object_proxy(); if (!ob) { CO_RETURN shared_ptr(); @@ -2142,6 +2142,15 @@ namespace rpc // local_proxy_holder_ destructor runs automatically } + element_type_impl* get() const noexcept + { + // For local objects: ptr_ points to __i_xxx_local_proxy (safe - returns OBJECT_GONE) + // For remote objects: ptr_ points to interface_proxy (safe - RPC handles errors) + if (local_proxy_holder_) + return local_proxy_holder_->__get_weak().lock().get(); + return ptr_; + } + // Copy assignment optimistic_ptr& operator=(const optimistic_ptr& r) noexcept { @@ -2178,6 +2187,15 @@ namespace rpc return *this; } + template + std::enable_if_t && !std::is_array_v, std::remove_extent_t&> operator*() const noexcept + { + using result_type = std::remove_extent_t; + if (local_proxy_holder_) + return *static_cast(local_proxy_holder_.get()); + return *static_cast(ptr_); + } + // Access operators - operator-> is safe for making calls through the proxy element_type_impl* operator->() const noexcept { @@ -2208,7 +2226,7 @@ namespace rpc return ptr_; } - explicit operator bool() const noexcept { return local_proxy_holder_ != nullptr || ptr_ != nullptr; } + explicit operator bool() const noexcept { return get() != nullptr; } void reset() noexcept { optimistic_ptr().swap(*this); } @@ -2262,7 +2280,8 @@ namespace rpc CO_RETURN error::OK(); } - auto ptr = const_cast(static_cast(local_shared->query_interface(T::get_id(VERSION_2)))); + auto ptr + = const_cast(static_cast(local_shared->__rpc_query_interface(T::get_id(get_version())))); if (ptr) { to = optimistic_ptr(from, ptr); @@ -2276,7 +2295,7 @@ namespace rpc } // Then try remote interface casting through object_proxy - auto ob = from->get_object_proxy(); + auto ob = from->__rpc_get_object_proxy(); if (!ob) { to = optimistic_ptr(); @@ -2313,8 +2332,7 @@ namespace rpc if (cb->is_local_) { // Local object: create local_proxy using generated static method - weak_ptr wp(in); - out.local_proxy_holder_ = T::create_local_proxy(wp); + out.local_proxy_holder_ = T::create_local_proxy(in); // out.ptr_ and out.cb_ remain nullptr for local objects CO_RETURN error::OK(); } @@ -2328,7 +2346,7 @@ namespace rpc // Get object_proxy to check inherited counts (service-level stub counts) auto casting_iface = reinterpret_cast(in.internal_get_ptr()); - auto obj_proxy = casting_iface->get_object_proxy(); + auto obj_proxy = casting_iface->__rpc_get_object_proxy(); int inherited_shared_before = 0; int inherited_optimistic_before = 0; if (obj_proxy) @@ -2456,7 +2474,7 @@ namespace rpc // Get object_proxy to check inherited counts (service-level stub counts) auto casting_iface = reinterpret_cast(in.ptr_); - auto obj_proxy = casting_iface ? casting_iface->get_object_proxy() : nullptr; + auto obj_proxy = casting_iface ? casting_iface->__rpc_get_object_proxy() : nullptr; int inherited_shared_before = 0; int inherited_optimistic_before = 0; if (obj_proxy) @@ -2559,11 +2577,7 @@ namespace rpc { // Remote object: create shared_ptr from interface_proxy // The control block already exists, just increment shared count - auto err = CO_AWAIT in.cb_->increment_shared(); - if (err) - { - CO_RETURN err; - } + in.cb_->increment_shared(); out.cb_ = in.cb_; out.ptr_ = in.ptr_; CO_RETURN error::OK(); diff --git a/rpc/include/rpc/internal/service.h b/rpc/include/rpc/internal/service.h index 771a9de..6b62173 100644 --- a/rpc/include/rpc/internal/service.h +++ b/rpc/include/rpc/internal/service.h @@ -42,6 +42,7 @@ #include #include #include +#include #ifdef CANOPY_USE_TELEMETRY #include @@ -53,7 +54,6 @@ namespace rpc { - class i_interface_stub; class object_stub; class service; class child_service; @@ -70,7 +70,7 @@ namespace rpc * Services can register event listeners to receive notifications when * objects are released or zone events occur. */ - class service_event + class service_event : public std::enable_shared_from_this { public: virtual ~service_event() = default; @@ -133,14 +133,6 @@ namespace rpc mutable std::mutex stub_control_; std::unordered_map> stubs_; - // factory - std::unordered_map(const std::shared_ptr&)>>> - stub_factories_; - - // map wrapped objects pointers to stubs_ - std::unordered_map> wrapped_object_to_stub_; - mutable std::mutex service_events_control_; std::set, std::owner_less>> service_events_; @@ -175,6 +167,9 @@ namespace rpc template friend CORO_TASK(int) stub_bind_out_param( const std::shared_ptr&, uint64_t, caller_zone, const shared_ptr&, interface_descriptor&); + template + friend CORO_TASK(int) stub_bind_out_param( + const std::shared_ptr&, uint64_t, caller_zone, const optimistic_ptr&, interface_descriptor&); template friend CORO_TASK(int) stub_bind_in_param( @@ -395,7 +390,7 @@ namespace rpc CORO_TASK(int) attach_remote_zone(const char* name, std::shared_ptr peer_transport, - rpc::interface_descriptor input_descr, + rpc::connection_settings input_descr, rpc::interface_descriptor& output_descr, std::function&, rpc::shared_ptr&, @@ -574,34 +569,6 @@ namespace rpc const std::shared_ptr& transport); public: - ///////////////////////////////// - // STUB REGISTRATION AND FACTORY LOGIC - ///////////////////////////////// - - /** - * @brief Register a factory for creating interface stubs - * @param id_getter Function to get interface ordinal for a protocol version - * @param factory Function to create stub from another stub (for casting) - * - * This registers a factory that can create stubs for a specific interface type. - * Used during stub creation and interface casting operations. - * - * IMPORTANT: This function is NOT thread-safe. Call it during service initialization - * before the service is used for normal RPC operations. - */ - void add_interface_stub_factory(std::function id_getter, - std::shared_ptr(const std::shared_ptr&)>> - factory); - - template - std::function(const std::shared_ptr& stub)> - get_interface_stub_factory(const shared_ptr& iface); - - int create_interface_stub(rpc::interface_ordinal interface_id, - std::function original_interface_id, - const std::shared_ptr& original, - std::shared_ptr& new_stub); - uint64_t release_local_stub( const std::shared_ptr& stub, bool is_optimistic, caller_zone caller_zone_id); @@ -663,28 +630,32 @@ namespace rpc caller_zone caller_zone_id, interface_descriptor& descriptor); + template + CORO_TASK(int) + bind_in_proxy(uint64_t protocol_version, + const optimistic_ptr& iface, + std::shared_ptr& stub, + caller_zone caller_zone_id, + interface_descriptor& descriptor); + CORO_TASK(int) get_descriptor_from_interface_stub(caller_zone caller_zone_id, - rpc::casting_interface* pointer, - std::function(std::shared_ptr)> fn, + const rpc::shared_ptr& iface, std::shared_ptr& stub, - interface_descriptor& descriptor); + interface_descriptor& descriptor, + bool optimistic); // Specialized version for binding out parameters (used by stub_bind_out_param) CORO_TASK(int) add_ref_local_or_remote_return_descriptor(uint64_t protocol_version, caller_zone caller_zone_id, - rpc::casting_interface* pointer, - std::function(std::shared_ptr)> fn, - std::shared_ptr& stub, - interface_descriptor& descriptor); + const rpc::shared_ptr& iface, + interface_descriptor& descriptor, + bool optimistic); ///////////////////////////////// // PRIVATE FUNCTIONS ///////////////////////////////// - - rpc::shared_ptr get_castable_interface(object object_id, interface_ordinal interface_id); - void inner_add_zone_proxy(const std::shared_ptr& service_proxy); void cleanup_service_proxy(const std::shared_ptr& other_zone); @@ -703,6 +674,11 @@ namespace rpc const rpc::interface_descriptor& encap, rpc::shared_ptr& val); + template + friend CORO_TASK(int) rpc::proxy_bind_out_param(const std::shared_ptr& sp, + const rpc::interface_descriptor& encap, + rpc::optimistic_ptr& val); + template friend CORO_TASK(int) rpc::stub_bind_in_param(uint64_t protocol_version, const std::shared_ptr& serv, @@ -722,6 +698,12 @@ namespace rpc rpc::caller_zone caller_zone_id, const rpc::shared_ptr& iface, rpc::interface_descriptor& descriptor); + template + friend CORO_TASK(int) rpc::stub_bind_out_param(const std::shared_ptr& zone, + uint64_t protocol_version, + rpc::caller_zone caller_zone_id, + const rpc::optimistic_ptr& iface, + rpc::interface_descriptor& descriptor); template friend CORO_TASK(int) rpc::proxy_bind_in_param(std::shared_ptr object_p, @@ -729,6 +711,37 @@ namespace rpc const rpc::shared_ptr& iface, std::shared_ptr& stub, rpc::interface_descriptor& descriptor); + + template + friend CORO_TASK(int) rpc::proxy_bind_in_param(std::shared_ptr object_p, + uint64_t protocol_version, + const rpc::optimistic_ptr& iface, + std::shared_ptr& stub, + rpc::interface_descriptor& descriptor); + }; + + /** + * @brief Helper class to keep a transport alive even if it's not being immediately used, needed for components that + * expect the transport to be there + */ + struct transport_keep_alive + { + std::shared_ptr transport_; + destination_zone zone_id_; + + transport_keep_alive() { } + transport_keep_alive(const std::shared_ptr& transport, destination_zone zone_id) + : transport_(transport) + , zone_id_(zone_id) + { + transport_->increment_outbound_proxy_count(zone_id); + } + + ~transport_keep_alive() + { + if (transport_) + transport_->decrement_outbound_proxy_count(zone_id_); + } }; /** @@ -833,7 +846,7 @@ namespace rpc template static CORO_TASK(int) create_child_zone(const char* name, std::shared_ptr parent_transport, - rpc::interface_descriptor input_descr, + rpc::connection_settings input_descr, rpc::interface_descriptor& output_descr, std::function&, rpc::shared_ptr&, @@ -844,6 +857,16 @@ namespace rpc #endif ) { + if (input_descr.caller_interface_id != PARENT_INTERFACE::get_id(rpc::get_version())) + { + RPC_ERROR("caller_interface_id does not match"); + CO_RETURN rpc::error::INVALID_INTERFACE_ID(); + } + if (input_descr.destination_interface_id != CHILD_INTERFACE::get_id(rpc::get_version())) + { + RPC_ERROR("destination_interface_id does not match"); + CO_RETURN rpc::error::INVALID_INTERFACE_ID(); + } auto zone_id = parent_transport->get_zone_id(); auto adjacent_zone_id = parent_transport->get_adjacent_zone_id(); @@ -863,16 +886,50 @@ namespace rpc // This ensures parent zone remains reachable while child exists child_svc->set_parent_transport(parent_transport); - child_svc->add_transport(input_descr.destination_zone_id, parent_transport); + child_svc->add_transport(input_descr.input_zone_id, parent_transport); + transport_keep_alive ka(parent_transport, input_descr.input_zone_id); + transport_keep_alive adjacent_ka; + if (input_descr.input_zone_id != parent_transport->get_adjacent_zone_id().as_destination()) + { + child_svc->add_transport(parent_transport->get_adjacent_zone_id().as_destination(), parent_transport); + adjacent_ka.transport_ = parent_transport; + adjacent_ka.zone_id_ = parent_transport->get_adjacent_zone_id().as_destination(); + } rpc::shared_ptr parent_ptr; if (input_descr.object_id != 0) { auto parent_service_proxy - = rpc::service_proxy::create("parent", child_svc, parent_transport, input_descr.destination_zone_id); + = rpc::service_proxy::create("parent", child_svc, parent_transport, input_descr.input_zone_id); child_svc->add_zone_proxy(parent_service_proxy); + bool new_proxy_added = true; + + std::shared_ptr op; + auto err_code = CO_AWAIT parent_service_proxy->get_or_create_object_proxy(input_descr.object_id, + service_proxy::object_proxy_creation_rule::ADD_REF_IF_NEW, + new_proxy_added, + {parent_transport->get_adjacent_zone_id().get_val()}, + false, + op); + if (err_code != error::OK()) + { + RPC_ERROR("get_or_create_object_proxy failed"); + CO_RETURN err_code; + } + RPC_ASSERT(op != nullptr); + if (!op) + { + RPC_ERROR("Object not found - object proxy is null"); + CO_RETURN rpc::error::OBJECT_NOT_FOUND(); + } + err_code = CO_AWAIT op->query_interface(parent_ptr, false); + if (err_code != rpc::error::OK()) + { + CO_RETURN err_code; + } + #if defined(CANOPY_USE_TELEMETRY) && defined(CANOPY_USE_TELEMETRY_RAII_LOGGING) if (auto telemetry_service = rpc::get_telemetry_service(); telemetry_service) telemetry_service->on_transport_inbound_add_ref(zone_id, @@ -883,13 +940,6 @@ namespace rpc adjacent_zone_id, rpc::add_ref_options::normal); #endif - - auto err_code = CO_AWAIT rpc::demarshall_interface_proxy( - rpc::get_version(), parent_service_proxy, input_descr, parent_ptr); - if (err_code != rpc::error::OK()) - { - CO_RETURN err_code; - } } rpc::shared_ptr child_ptr; { @@ -901,11 +951,11 @@ namespace rpc } if (child_ptr) { - RPC_ASSERT( - child_ptr->is_local() - && "we cannot support remote pointers to subordinate zones as it has not been registered yet"); - auto err_code = CO_AWAIT rpc::create_interface_stub( - *child_svc, child_ptr, parent_transport->get_adjacent_zone_id().as_caller(), output_descr); + auto err_code = CO_AWAIT rpc::stub_bind_out_param(child_svc, + rpc::get_version(), + parent_transport->get_adjacent_zone_id().as_caller(), + child_ptr, + output_descr); if (err_code == rpc::error::OK()) { @@ -934,40 +984,53 @@ namespace rpc rpc::shared_ptr& output_interface) { // Marshal input interface if provided - rpc::interface_descriptor input_descr{{0}, {0}}; + rpc::connection_settings input_descr; // Connect via transport (calls remote zone's entry point) rpc::interface_descriptor output_descr{{0}, {0}}; int err_code = rpc::error::OK(); - bool transport_added = false; + std::shared_ptr input_stub; + + add_transport(child_transport->get_adjacent_zone_id().as_destination(), child_transport); + transport_keep_alive ka(child_transport, child_transport->get_adjacent_zone_id().as_destination()); if (input_interface) { - add_transport(child_transport->get_adjacent_zone_id().as_destination(), child_transport); - transport_added = true; - std::shared_ptr stub; - auto factory = get_interface_stub_factory(input_interface); - err_code = CO_AWAIT get_descriptor_from_interface_stub( - child_transport->get_adjacent_zone_id().as_caller(), input_interface.get(), factory, stub, input_descr); - - if (err_code != error::OK()) + // this is to check that an interface is belonging to another zone and not the operating zone + if (!input_interface->__rpc_is_local() + && casting_interface::get_destination_zone(*input_interface) != get_zone_id().as_destination()) { - remove_transport(child_transport->get_adjacent_zone_id().as_destination()); - CO_RETURN err_code; + input_descr.input_zone_id = casting_interface::get_destination_zone(*input_interface); + input_descr.object_id = casting_interface::get_object_id(*input_interface); + } + else + { + rpc::interface_descriptor tmp; + + err_code = CO_AWAIT bind_in_proxy( + rpc::get_version(), input_interface, input_stub, child_transport->get_adjacent_zone_id().as_caller(), tmp); + if (err_code != error::OK()) + { + CO_RETURN err_code; + } + input_descr.input_zone_id = tmp.destination_zone_id; + input_descr.object_id = tmp.object_id; } } else { - input_descr.destination_zone_id = child_transport->get_zone_id().get_val(); + input_descr.input_zone_id = child_transport->get_zone_id().get_val(); } + input_descr.caller_interface_id = in_param_type::get_id(rpc::get_version()); + input_descr.destination_interface_id = out_param_type::get_id(rpc::get_version()); + err_code = CO_AWAIT child_transport->connect(input_descr, output_descr); if (err_code != rpc::error::OK()) { - if (transport_added) + if (input_stub) { - // Clean up on failure - remove_transport(child_transport->get_adjacent_zone_id().as_destination()); + input_stub->release_from_service(child_transport->get_adjacent_zone_id().as_caller()); } CO_RETURN err_code; } @@ -975,11 +1038,6 @@ namespace rpc // Demarshal output interface if provided if (output_descr.object_id != 0 && output_descr.destination_zone_id != 0) { - if (!transport_added) - { - add_transport(child_transport->get_adjacent_zone_id().as_destination(), child_transport); - } - // Create service_proxy for this connection auto new_service_proxy = rpc::service_proxy::create( name, shared_from_this(), child_transport, child_transport->get_adjacent_zone_id().as_destination()); @@ -987,8 +1045,12 @@ namespace rpc // add the proxy to the service add_zone_proxy(new_service_proxy); - err_code = CO_AWAIT rpc::demarshall_interface_proxy( - rpc::get_version(), new_service_proxy, output_descr, output_interface); + err_code = CO_AWAIT rpc::proxy_bind_out_param(new_service_proxy, output_descr, output_interface); + } + + if (input_stub) + { + input_stub->release_from_service(child_transport->get_adjacent_zone_id().as_caller()); } CO_RETURN err_code; @@ -1000,71 +1062,109 @@ namespace rpc CORO_TASK(int) service::attach_remote_zone(const char* name, std::shared_ptr peer_transport, - rpc::interface_descriptor input_descr, + rpc::connection_settings input_descr, rpc::interface_descriptor& output_descr, std::function&, rpc::shared_ptr&, const std::shared_ptr&)> fn) { - // Demarshal parent interface if provided + if (input_descr.caller_interface_id != PARENT_INTERFACE::get_id(rpc::get_version())) + { + RPC_ERROR("caller_interface_id does not match"); + CO_RETURN rpc::error::INVALID_INTERFACE_ID(); + } + if (input_descr.destination_interface_id != CHILD_INTERFACE::get_id(rpc::get_version())) + { + RPC_ERROR("destination_interface_id does not match"); + CO_RETURN rpc::error::INVALID_INTERFACE_ID(); + } + + auto adjacent_zone_id = peer_transport->get_adjacent_zone_id(); + rpc::shared_ptr parent_ptr; + add_transport(input_descr.input_zone_id, peer_transport); + transport_keep_alive ka(peer_transport, input_descr.input_zone_id); + transport_keep_alive adjacent_ka; + if (input_descr.input_zone_id != adjacent_zone_id.as_destination()) + { + add_transport(adjacent_zone_id.as_destination(), peer_transport); + adjacent_ka.transport_ = peer_transport; + adjacent_ka.zone_id_ = adjacent_zone_id.as_destination(); + } + if (input_descr.object_id != 0) { - // Create service_proxy for peer connection - auto peer_service_proxy - = rpc::service_proxy::create(name, shared_from_this(), peer_transport, input_descr.destination_zone_id); - add_zone_proxy(peer_service_proxy); + auto parent_service_proxy + = rpc::service_proxy::create(name, shared_from_this(), peer_transport, input_descr.input_zone_id); - auto err_code = CO_AWAIT rpc::demarshall_interface_proxy( - rpc::get_version(), peer_service_proxy, input_descr, parent_ptr); - if (err_code != rpc::error::OK()) + add_zone_proxy(parent_service_proxy); + + bool new_proxy_added = true; + + std::shared_ptr op; + auto err_code = CO_AWAIT parent_service_proxy->get_or_create_object_proxy(input_descr.object_id, + service_proxy::object_proxy_creation_rule::ADD_REF_IF_NEW, + new_proxy_added, + {adjacent_zone_id.get_val()}, + false, + op); + if (err_code != error::OK()) { + RPC_ERROR("get_or_create_object_proxy failed"); CO_RETURN err_code; } - } - else - { - add_transport(peer_transport->get_adjacent_zone_id().as_destination(), peer_transport); - } - - auto err_code = CO_AWAIT peer_transport->accept(); - if (err_code != rpc::error::OK()) - { - if (input_descr != interface_descriptor()) + RPC_ASSERT(op != nullptr); + if (!op) { - // perhaps we should stop the transport first? - remove_transport(peer_transport->get_adjacent_zone_id().as_destination()); + RPC_ERROR("Object not found - object proxy is null"); + CO_RETURN rpc::error::OBJECT_NOT_FOUND(); } - CO_RETURN err_code; + err_code = CO_AWAIT op->query_interface(parent_ptr, false); + if (err_code != rpc::error::OK()) + { + CO_RETURN err_code; + } + +#if defined(CANOPY_USE_TELEMETRY) && defined(CANOPY_USE_TELEMETRY_RAII_LOGGING) + if (auto telemetry_service = rpc::get_telemetry_service(); telemetry_service) + telemetry_service->on_transport_inbound_add_ref(zone_id_, + adjacent_zone_id, + zone_id_.as_destination(), + adjacent_zone_id.as_caller(), + input_descr.object_id, + adjacent_zone_id, + rpc::add_ref_options::normal); +#endif } // Call local entry point to create child interface rpc::shared_ptr child_ptr; - err_code = CO_AWAIT fn(parent_ptr, child_ptr, shared_from_this()); - if (err_code != rpc::error::OK()) { - if (input_descr != interface_descriptor()) + auto err_code = CO_AWAIT fn(parent_ptr, child_ptr, shared_from_this()); + if (err_code != rpc::error::OK()) { - // perhaps we should stop the transport first? - remove_transport(peer_transport->get_adjacent_zone_id().as_destination()); + CO_RETURN err_code; } - CO_RETURN err_code; } - // Marshal child interface to return to peer if (child_ptr) { - RPC_ASSERT(child_ptr->is_local() && "Cannot support remote pointers from subordinate zones"); - err_code = CO_AWAIT rpc::create_interface_stub( - *this, child_ptr, peer_transport->get_adjacent_zone_id().as_caller(), output_descr); - if (err_code != rpc::error::OK()) + auto err_code = CO_AWAIT rpc::stub_bind_out_param( + shared_from_this(), rpc::get_version(), adjacent_zone_id.as_caller(), child_ptr, output_descr); + + if (err_code == rpc::error::OK()) { - if (input_descr != interface_descriptor()) - { - // perhaps we should stop the transport first? - remove_transport(peer_transport->get_adjacent_zone_id().as_destination()); - } - CO_RETURN err_code; +#if defined(CANOPY_USE_TELEMETRY) && defined(CANOPY_USE_TELEMETRY_RAII_LOGGING) + if (auto telemetry_service = rpc::get_telemetry_service(); telemetry_service) + telemetry_service->on_transport_outbound_add_ref(zone_id_, + adjacent_zone_id, + zone_id_.as_destination(), + adjacent_zone_id.as_caller(), + output_descr.object_id, + zone_id_, + rpc::add_ref_options::build_caller_route); +#endif } + CO_RETURN err_code; } CO_RETURN rpc::error::OK(); diff --git a/rpc/include/rpc/internal/stub.h b/rpc/include/rpc/internal/stub.h index ca15d31..4982424 100644 --- a/rpc/include/rpc/internal/stub.h +++ b/rpc/include/rpc/internal/stub.h @@ -50,8 +50,6 @@ namespace rpc { - - class i_interface_stub; class object_stub; class service; class service_proxy; @@ -90,21 +88,20 @@ namespace rpc * Lifetime Management: * - Holds strong reference to service (zone_) to keep service alive * - Service holds strong references to stubs (in stubs_ map) - * - Self-reference (p_this_) enables shared_from_this pattern + * - Self-reference (p_keep_self_alive_) enables shared_from_this pattern * * See documents/architecture/04-memory-management.md for reference counting details. */ - class object_stub + class object_stub : public std::enable_shared_from_this { // Unique object ID within the zone object id_ = {0}; - // Interface stub map (stubs have strong pointers to interface implementations) - mutable std::mutex map_control_; - std::unordered_map> stub_map_; + // Root interface target for this local object + rpc::shared_ptr target_; // Self-reference for shared_from_this pattern - std::shared_ptr p_this_; + std::shared_ptr p_keep_self_alive_; // Global reference counts (sum across all zones) std::atomic shared_count_ = 0; // RAII references (rpc::shared_ptr) @@ -118,16 +115,12 @@ namespace rpc // CRITICAL: Strong reference to service keeps service alive while stub exists std::shared_ptr zone_; - void add_interface(const std::shared_ptr& iface); - friend service; // Allows service to call add_interface - public: - object_stub(object id, const std::shared_ptr& zone, void* target); + object_stub(object id, const std::shared_ptr& zone, const rpc::shared_ptr& target); ~object_stub(); object get_id() const { return id_; } - rpc::shared_ptr get_castable_interface() const; - void reset() { p_this_.reset(); } + rpc::shared_ptr get_castable_interface(interface_ordinal interface_id = {0}) const; /** * @brief Activate lifetime management for this stub @@ -136,7 +129,8 @@ namespace rpc * Called after stub is added to service's stubs_ map. Enables the * shared_from_this pattern by storing self-reference. */ - void on_added_to_zone(std::shared_ptr stub) { p_this_ = stub; } + void keep_self_alive() { p_keep_self_alive_ = shared_from_this(); } + void dont_keep_alive() { p_keep_self_alive_.reset(); } std::shared_ptr get_zone() const { return zone_; } @@ -169,13 +163,6 @@ namespace rpc */ int try_cast(interface_ordinal interface_id); - /** - * @brief Get interface stub for a specific interface - * @param interface_id Interface ordinal - * @return Shared pointer to interface stub, or nullptr if not found - */ - std::shared_ptr get_interface(interface_ordinal interface_id); - /** * @brief Add reference to this stub * @param is_optimistic true for optimistic reference, false for shared reference @@ -234,87 +221,17 @@ namespace rpc * Thread-Safety: Uses atomic operations for counts, mutex for per-zone maps */ bool release_all_from_zone(caller_zone caller_zone_id); - }; - - /** - * @brief Interface stub base class for type-specific RPC dispatch - * - * Generated stub classes (from IDL) inherit from i_interface_stub and - * implement RPC call dispatching for a specific interface type. - * - * Each i_interface_stub wraps a specific C++ interface implementation - * and handles: - * - Unmarshalling parameters for incoming RPC calls - * - Dispatching to the actual C++ method - * - Marshalling return values - * - Interface casting to related interfaces - * - * Generated stubs are named _stub and are created by - * the IDL compiler from .idl interface definitions. - * - * Multiple i_interface_stub instances can exist for a single object_stub, - * one for each interface the object implements. The object_stub's stub_map_ - * holds all interface stubs for an object. - */ - class i_interface_stub - { - public: - virtual ~i_interface_stub() = default; - - /** - * @brief Get the interface ordinal for this stub - * @param rpc_version RPC protocol version - * @return Interface ordinal identifying this interface type - */ - virtual interface_ordinal get_interface_id(uint64_t rpc_version) const = 0; /** - * @brief Dispatch RPC call to the wrapped C++ object - * @param protocol_version RPC protocol version - * @param enc Encoding format for parameters - * @param caller_zone_id Zone making the call - * @param method_id Method to invoke - * @param in_data Serialized input parameters - * @param out_buf_[out] Buffer for serialized return value - * @return error::OK() on success, error code on failure + * @brief Snapshot of all zones with at least one active optimistic reference + * @return Vector of caller_zone values whose optimistic reference count is > 0 * - * Generated stub classes unmarshal parameters, call the C++ method, - * and marshal the return value. - */ - virtual CORO_TASK(int) call(uint64_t protocol_version, - rpc::encoding enc, - caller_zone caller_zone_id, - method method_id, - const rpc::span& in_data, - std::vector& out_buf_) - = 0; - - /** - * @brief Cast to a different interface stub - * @param interface_id Target interface ordinal - * @param new_stub[out] New interface stub if cast succeeds - * @return error::OK() if cast supported, error code otherwise + * Used by the service when the shared count reaches zero to discover which zones + * still hold optimistic references so that object_released notifications can be + * dispatched to them. * - * Allows QueryInterface-style casting between related interfaces. - */ - virtual int cast(interface_ordinal interface_id, std::shared_ptr& new_stub) = 0; - - /** - * @brief Get the owning object_stub - * @return Weak pointer to the object_stub that contains this interface stub - */ - virtual std::weak_ptr get_object_stub() const = 0; - - /** - * @brief Get raw pointer to the wrapped C++ object - * @return Pointer to the local C++ object implementation - */ - virtual void* get_pointer() const = 0; - - /** - * @brief Get castable interface pointer - * @return rpc::shared_ptr to the casting_interface + * Thread-Safety: Protected internally by references_mutex_ */ - virtual rpc::shared_ptr get_castable_interface() const = 0; + std::vector get_zones_with_optimistic_refs() const; }; } diff --git a/rpc/include/rpc/internal/transport.h b/rpc/include/rpc/internal/transport.h index b8a1631..1d406c6 100644 --- a/rpc/include/rpc/internal/transport.h +++ b/rpc/include/rpc/internal/transport.h @@ -315,7 +315,7 @@ namespace rpc * * Thread-Safety: Implementation-specific (varies by derived class) */ - CORO_TASK(int) connect(interface_descriptor input_descr, interface_descriptor& output_descr); + CORO_TASK(int) connect(connection_settings input_descr, interface_descriptor& output_descr); /** * @brief Initiate a transport about to receive a connection request @@ -529,7 +529,7 @@ namespace rpc * * Thread-Safety: Implementation-specific */ - virtual CORO_TASK(int) inner_connect(interface_descriptor input_descr, interface_descriptor& output_descr) = 0; + virtual CORO_TASK(int) inner_connect(connection_settings& input_descr, interface_descriptor& output_descr) = 0; virtual CORO_TASK(int) inner_accept() = 0; diff --git a/rpc/interfaces/rpc/rpc_types.idl b/rpc/interfaces/rpc/rpc_types.idl index 72487bb..f8e8a12 100644 --- a/rpc/interfaces/rpc/rpc_types.idl +++ b/rpc/interfaces/rpc/rpc_types.idl @@ -511,6 +511,14 @@ namespace rpc destination_zone destination_zone_id; }; + struct connection_settings + { + interface_ordinal caller_interface_id; + interface_ordinal destination_interface_id; + object object_id; + destination_zone input_zone_id; + }; + struct function_info { std::string full_name; diff --git a/rpc/src/casting_interface.cpp b/rpc/src/casting_interface.cpp index 78f0995..8a942fe 100644 --- a/rpc/src/casting_interface.cpp +++ b/rpc/src/casting_interface.cpp @@ -12,7 +12,7 @@ namespace rpc // Consolidated null and locality checks if (!first || !second) return true; - if (first->is_local() || second->is_local()) + if (first->__rpc_is_local() || second->__rpc_is_local()) return true; // Compare zones directly @@ -23,7 +23,7 @@ namespace rpc object casting_interface::get_object_id(const casting_interface& iface) { - auto obj = iface.get_object_proxy(); + auto obj = iface.__rpc_get_object_proxy(); if (!obj) return {0}; return obj->get_object_id(); @@ -31,7 +31,7 @@ namespace rpc std::shared_ptr casting_interface::get_service_proxy(const casting_interface& iface) { - auto obj = iface.get_object_proxy(); + auto obj = iface.__rpc_get_object_proxy(); if (!obj) return nullptr; return obj->get_service_proxy(); diff --git a/rpc/src/error_codes.cpp b/rpc/src/error_codes.cpp index 705b6a3..a8eaffb 100644 --- a/rpc/src/error_codes.cpp +++ b/rpc/src/error_codes.cpp @@ -92,10 +92,10 @@ namespace rpc { return offset_val + (offset_val_is_negative ? -19 : 19); } - [[nodiscard]] int UNABLE_TO_CREATE_SERVICE_PROXY() - { - return offset_val + (offset_val_is_negative ? -20 : 20); - } + // [[nodiscard]] int UNABLE_TO_CREATE_SERVICE_PROXY() + // { + // return offset_val + (offset_val_is_negative ? -20 : 20); + // } [[nodiscard]] int SERVICE_PROXY_LOST_CONNECTION() { return offset_val + (offset_val_is_negative ? -21 : 21); @@ -120,6 +120,24 @@ namespace rpc return offset_val + (offset_val_is_negative ? -1 : 23); } + bool is_error(int err) + { + if (err >= MIN() && err <= MAX()) + { + return true; + } + return false; + } + + bool is_critical(int err) + { + if (is_error(err) && err != OBJECT_GONE() && err != INVALID_CAST()) + { + return true; + } + return false; + } + void set_OK_val(int val) { OK_val = val; @@ -215,10 +233,10 @@ namespace rpc { return "reference count error"; } - if (err == UNABLE_TO_CREATE_SERVICE_PROXY()) - { - return "unable to create service proxy"; - } + // if (err == UNABLE_TO_CREATE_SERVICE_PROXY()) + // { + // return "unable to create service proxy"; + // } if (err == SERVICE_PROXY_LOST_CONNECTION()) { return "Service proxy has lost connection to the remote service"; diff --git a/rpc/src/service.cpp b/rpc/src/service.cpp index 5ada0ec..e9928d0 100644 --- a/rpc/src/service.cpp +++ b/rpc/src/service.cpp @@ -100,7 +100,6 @@ namespace rpc { std::lock_guard l(stub_control_); stubs_.clear(); - wrapped_object_to_stub_.clear(); } service_proxies_.clear(); @@ -114,23 +113,22 @@ namespace rpc { if (ptr == nullptr) return {}; - auto* addr = ptr->get_address(); - if (addr) + if (ptr->__rpc_is_local()) { - std::lock_guard g(stub_control_); - auto item = wrapped_object_to_stub_.find(addr); - if (item != wrapped_object_to_stub_.end()) + auto stub = ptr->__rpc_get_stub(); + if (stub) + { + return stub->get_id(); + } + else { - auto obj = item->second.lock(); - if (obj) - return obj->get_id(); + return {}; } } else { return casting_interface::get_object_id(*ptr); } - return {}; } bool service::check_is_empty() const @@ -156,24 +154,6 @@ namespace rpc } success = false; } - for (auto item : wrapped_object_to_stub_) - { - auto stub = item.second.lock(); - if (!stub) - { - RPC_WARNING("wrapped stub zone_id {}, wrapped_object has been released but not deregistered in the " - "service suspected unclean shutdown", - std::to_string(zone_id_)); - } - else - { - RPC_WARNING("wrapped stub zone_id {}, wrapped_object {} has not been deregistered in the service " - "suspected unclean shutdown", - std::to_string(zone_id_), - std::to_string(stub->get_id())); - } - success = false; - } for (auto item : service_proxies_) { @@ -407,36 +387,33 @@ namespace rpc // or if the interface is a proxy to add ref it CORO_TASK(int) service::get_descriptor_from_interface_stub(caller_zone caller_zone_id, - rpc::casting_interface* iface, - std::function(std::shared_ptr)> fn, + const rpc::shared_ptr& iface, std::shared_ptr& stub, - interface_descriptor& descriptor) + interface_descriptor& descriptor, + bool optimistic) { - - auto* pointer = iface->get_address(); - // find the stub by its address + if (!iface) + { + CO_RETURN error::INVALID_DATA(); + } + if (!iface->__rpc_is_local()) + { + // we should not be getting the interface from remote objects + CO_RETURN error::OBJECT_NOT_FOUND(); + } { std::lock_guard g(stub_control_); - auto item = wrapped_object_to_stub_.find(pointer); - if (item != wrapped_object_to_stub_.end()) - { - stub = item->second.lock(); - // Don't mask the race condition - if stub is null here, we have a serious problem - RPC_ASSERT(stub != nullptr); - } - else + stub = iface->__rpc_get_stub(); + if (!stub) { - // else create a stub auto id = generate_new_object_id(); - stub = std::make_shared(id, shared_from_this(), pointer); - std::shared_ptr interface_stub = fn(stub); - stub->add_interface(interface_stub); - wrapped_object_to_stub_[pointer] = stub; + stub = std::make_shared(id, shared_from_this(), iface); stubs_[id] = stub; - stub->on_added_to_zone(stub); + stub->keep_self_alive(); + iface->__rpc_set_stub(stub); } } - auto ret = CO_AWAIT stub->add_ref(false, false, caller_zone_id); + auto ret = CO_AWAIT stub->add_ref(optimistic, false, caller_zone_id); if (ret != rpc::error::OK()) { CO_RETURN ret; @@ -448,16 +425,19 @@ namespace rpc CORO_TASK(int) service::add_ref_local_or_remote_return_descriptor(uint64_t protocol_version, caller_zone caller_zone_id, - rpc::casting_interface* iface, - std::function(std::shared_ptr)> fn, - std::shared_ptr& stub, - interface_descriptor& descriptor) + const rpc::shared_ptr& iface, + interface_descriptor& descriptor, + bool optimistic) { + if (!iface) + { + CO_RETURN error::INVALID_DATA(); + } // This is ALWAYS an out parameter case - if (caller_zone_id.is_set() && !iface->is_local()) + if (caller_zone_id.is_set() && !iface->__rpc_is_local()) { // Inline prepare_out_param logic here for out parameter binding - auto object_proxy = iface->get_object_proxy(); + auto object_proxy = iface->__rpc_get_object_proxy(); RPC_ASSERT(object_proxy != nullptr); auto object_service_proxy = object_proxy->get_service_proxy(); RPC_ASSERT(object_service_proxy->zone_id_ == zone_id_); @@ -541,7 +521,8 @@ namespace rpc object_id, caller_zone_id, known_direction, - rpc::add_ref_options::build_destination_route | rpc::add_ref_options::build_caller_route, + rpc::add_ref_options::build_destination_route | rpc::add_ref_options::build_caller_route + | (optimistic ? add_ref_options::optimistic : add_ref_options::normal), empty_in, empty_out); if (err_code != rpc::error::OK()) @@ -557,7 +538,8 @@ namespace rpc caller_zone_id, object_id, known_direction, - rpc::add_ref_options::build_destination_route | rpc::add_ref_options::build_caller_route); + rpc::add_ref_options::build_destination_route | rpc::add_ref_options::build_caller_route + | (optimistic ? add_ref_options::optimistic : add_ref_options::normal)); } #endif @@ -566,32 +548,32 @@ namespace rpc } // For local interfaces or when caller_zone_id is not set, create a local stub - auto* pointer = iface->get_address(); + auto stub = iface->__rpc_get_stub(); + if (!stub) { + if (optimistic) + { + CO_RETURN error::OBJECT_GONE(); + } + else { std::lock_guard g(stub_control_); - auto item = wrapped_object_to_stub_.find(pointer); - if (item != wrapped_object_to_stub_.end()) - { - stub = item->second.lock(); - RPC_ASSERT(stub != nullptr); - } - else + stub = iface->__rpc_get_stub(); + if (!stub) { auto id = generate_new_object_id(); - stub = std::make_shared(id, shared_from_this(), pointer); - std::shared_ptr interface_stub = fn(stub); - stub->add_interface(interface_stub); - wrapped_object_to_stub_[pointer] = stub; + stub = std::make_shared(id, shared_from_this(), iface); stubs_[id] = stub; - stub->on_added_to_zone(stub); + stub->keep_self_alive(); + iface->__rpc_set_stub(stub); } } - auto ret = CO_AWAIT stub->add_ref(false, true, caller_zone_id); // outcall=true - if (ret != rpc::error::OK()) - { - CO_RETURN ret; - } + } + + auto ret = CO_AWAIT stub->add_ref(optimistic, true, caller_zone_id); // outcall=true + if (ret != rpc::error::OK()) + { + CO_RETURN ret; } descriptor = {stub->get_id(), zone_id_.as_destination()}; CO_RETURN error::OK(); @@ -654,6 +636,7 @@ namespace rpc const std::vector& in_back_channel, std::vector& out_back_channel) { + bool optimistic = !!(build_out_param_channel & add_ref_options::optimistic); bool build_caller_channel = !!(build_out_param_channel & add_ref_options::build_caller_route); bool build_dest_channel = !!(build_out_param_channel & add_ref_options::build_destination_route) || build_out_param_channel == add_ref_options::normal @@ -672,7 +655,8 @@ namespace rpc object_id, caller_zone_id, zone_id_.as_known_direction_zone(), - add_ref_options::build_caller_route, + add_ref_options::build_caller_route + | (optimistic ? add_ref_options::optimistic : add_ref_options::normal), in_back_channel, out_back_channel); if (error != rpc::error::OK()) @@ -707,7 +691,7 @@ namespace rpc destination_zone_id, object_id, caller_zone_id, - zone_id_.as_known_direction_zone(), + known_direction_zone_id, build_out_param_channel & (~add_ref_options::build_caller_route), in_back_channel, out_back_channel); @@ -767,23 +751,8 @@ namespace rpc uint64_t count = stub->release(is_optimistic, caller_zone_id); if (!is_optimistic && !count) { - { - stubs_.erase(stub->get_id()); - } - { - auto* pointer = stub->get_castable_interface()->get_address(); - auto it = wrapped_object_to_stub_.find(pointer); - if (it != wrapped_object_to_stub_.end()) - { - wrapped_object_to_stub_.erase(it); - } - else - { - // if you get here make sure that get_address is defined in the most derived class - RPC_ASSERT(false); - } - } - stub->reset(); + stubs_.erase(stub->get_id()); + stub->dont_keep_alive(); } return count; } @@ -840,44 +809,18 @@ namespace rpc count = stub->release(!!(release_options::optimistic & options), caller_zone_id); if (!count && !(release_options::optimistic & options)) { - // When shared count drops to zero, notify all transports that have optimistic references - // Get the optimistic reference map before releasing the stub - std::vector optimistic_refs; - { - std::lock_guard lock(stub->references_mutex_); - optimistic_refs.reserve(stub->optimistic_references_.size()); - for (const auto& [zone, count_atomic] : stub->optimistic_references_) - { - uint64_t count_val = count_atomic.load(std::memory_order_acquire); - if (count_val > 0) - { - optimistic_refs.push_back(zone); - } - } - } + // When shared count drops to zero, notify all transports that have optimistic references. + // Snapshot is taken before erasing from service maps so no zone is missed. + auto optimistic_refs = stub->get_zones_with_optimistic_refs(); { // a scoped lock - erase stub from maps std::lock_guard l(stub_control_); - { - stubs_.erase(object_id); - } - { - auto* pointer = stub->get_castable_interface()->get_address(); - auto it = wrapped_object_to_stub_.find(pointer); - if (it != wrapped_object_to_stub_.end()) - { - wrapped_object_to_stub_.erase(it); - } - else - { - RPC_ASSERT(false); - CO_RETURN rpc::error::OBJECT_NOT_FOUND(); - } - } + stubs_.erase(object_id); } // Release stub_control_ lock before calling object_released - stub->reset(); + stub->dont_keep_alive(); + stub.reset(); // Now notify all transports that had optimistic references // IMPORTANT: This must be done AFTER releasing stub_control_ mutex to avoid deadlock @@ -1001,13 +944,11 @@ namespace rpc // Remove from maps stubs_.erase(obj_id); - auto* pointer = stub->get_castable_interface()->get_address(); - wrapped_object_to_stub_.erase(pointer); // Track for notification objects_to_notify.push_back(obj_id); - stub->reset(); + stub->dont_keep_alive(); } } } // Release stub_control_ before calling notify @@ -1192,60 +1133,6 @@ namespace rpc } } - int service::create_interface_stub(rpc::interface_ordinal interface_id, - std::function interface_getter, - const std::shared_ptr& original, - std::shared_ptr& new_stub) - { - // an identity check, send back the same pointer - if (interface_getter(rpc::VERSION_2) == interface_id) - { - new_stub = std::static_pointer_cast(original); - return rpc::error::OK(); - } - - auto it = stub_factories_.find(interface_id); - if (it == stub_factories_.end()) - { - RPC_INFO("stub factory does not have a record of this interface this not an error in the rpc stack"); - return rpc::error::INVALID_CAST(); - } - - new_stub = (*it->second)(original); - if (!new_stub) - { - RPC_INFO("Object does not support the interface this not an error in the rpc stack"); - return rpc::error::INVALID_CAST(); - } - // note a nullptr return value is a valid value, it indicates that this object does not implement that interface - return rpc::error::OK(); - } - - // note this function is not thread safe! Use it before using the service class for normal operation - void service::add_interface_stub_factory(std::function id_getter, - std::shared_ptr(const std::shared_ptr&)>> factory) - { - auto interface_id = id_getter(rpc::VERSION_2); - auto it = stub_factories_.find({interface_id}); - if (it != stub_factories_.end()) - { - RPC_ERROR("Invalid data - add_interface_stub_factory failed"); - rpc::error::INVALID_DATA(); - } - stub_factories_[{interface_id}] = factory; - } - - rpc::shared_ptr service::get_castable_interface(object object_id, interface_ordinal interface_id) - { - auto ob = get_object(object_id).lock(); - if (!ob) - return nullptr; - auto interface_stub = ob->get_interface(interface_id); - if (!interface_stub) - return nullptr; - return interface_stub->get_castable_interface(); - } - void service::add_service_event(const std::weak_ptr& event) { std::lock_guard g(service_events_control_); @@ -1258,16 +1145,20 @@ namespace rpc } CORO_TASK(void) service::notify_object_gone_event(object object_id, destination_zone destination) { - if (!service_events_.empty()) + std::set, std::owner_less>> service_events_copy; { - auto service_events_copy = service_events_; - for (auto se : service_events_copy) + std::lock_guard g(service_events_control_); + if (!service_events_.empty()) { - auto se_handler = se.lock(); - if (se_handler) - CO_AWAIT se_handler->on_object_released(object_id, destination); + service_events_copy = service_events_; } } + for (auto se : service_events_copy) + { + auto se_handler = se.lock(); + if (se_handler) + CO_AWAIT se_handler->on_object_released(object_id, destination); + } CO_RETURN; } @@ -1433,23 +1324,18 @@ namespace rpc obj_id.get_val(), remote_zone.get_val()); - // Remove from maps - stubs_.erase(obj_id); - auto* pointer = stub->get_castable_interface()->get_address(); - wrapped_object_to_stub_.erase(pointer); - // Track for notification objects_to_notify.push_back({obj_id, remote_zone}); - - stub->reset(); } } // Notify service events about deleted objects (outside the lock) for (const auto& obj : objects_to_notify) { + // Remove from maps + stubs_.erase(obj.object_id); + CO_AWAIT notify_object_gone_event(obj.object_id, obj.destination_zone_id); } } - } diff --git a/rpc/src/service_proxy.cpp b/rpc/src/service_proxy.cpp index 1bcabe7..9ac589c 100644 --- a/rpc/src/service_proxy.cpp +++ b/rpc/src/service_proxy.cpp @@ -412,9 +412,6 @@ namespace rpc } #endif - // Notify that object is gone after all cleanup is complete - CO_AWAIT svc->notify_object_gone_event(object_id, destination_zone_id); - RPC_DEBUG( "send_object_release: release returned {} for object {}", rpc::error::to_string(ret), object_id.get_val()); diff --git a/rpc/src/stub.cpp b/rpc/src/stub.cpp index 49c59e3..9837f6c 100644 --- a/rpc/src/stub.cpp +++ b/rpc/src/stub.cpp @@ -6,13 +6,15 @@ namespace rpc { - object_stub::object_stub(object id, const std::shared_ptr& zone, [[maybe_unused]] void* target) + object_stub::object_stub( + object id, const std::shared_ptr& zone, const rpc::shared_ptr& target) : id_(id) + , target_(target) , zone_(zone) { #ifdef CANOPY_USE_TELEMETRY if (auto telemetry_service = rpc::get_telemetry_service(); telemetry_service) - telemetry_service->on_stub_creation(zone_->get_zone_id(), id_, (uint64_t)target); + telemetry_service->on_stub_creation(zone_->get_zone_id(), id_, (uint64_t)target.get()); #endif } object_stub::~object_stub() @@ -24,28 +26,17 @@ namespace rpc RPC_ASSERT(shared_count_ == 0); } - rpc::shared_ptr object_stub::get_castable_interface() const + rpc::shared_ptr object_stub::get_castable_interface(interface_ordinal interface_id) const { - std::lock_guard g(map_control_); - RPC_ASSERT(!stub_map_.empty()); - auto& iface = stub_map_.begin()->second; - return iface->get_castable_interface(); - } - - // this method is not thread safe as it is only used when the object is constructed by service - // or by an internal call by this class - void object_stub::add_interface(const std::shared_ptr& iface) - { - stub_map_[iface->get_interface_id(rpc::VERSION_2)] = iface; - } + if (!target_) + return nullptr; + if (!interface_id.is_set()) + return target_; - std::shared_ptr object_stub::get_interface(interface_ordinal interface_id) - { - std::lock_guard g(map_control_); - auto res = stub_map_.find(interface_id); - if (res == stub_map_.end()) + auto* iface = const_cast(target_->__rpc_query_interface(interface_id)); + if (!iface) return nullptr; - return res->second; + return rpc::shared_ptr(target_, iface); } CORO_TASK(int) @@ -57,18 +48,22 @@ namespace rpc const rpc::span& in_data, std::vector& out_buf_) { - std::shared_ptr stub; - { - std::lock_guard g(map_control_); - auto item = stub_map_.find(interface_id); - if (item != stub_map_.end()) - { - stub = item->second; - } - } - if (stub) + if (target_) { - CO_RETURN CO_AWAIT stub->call(protocol_version, enc, caller_zone_id, method_id, in_data, out_buf_); + std::vector empty_in_back_channel; + std::vector empty_out_back_channel; + CO_RETURN CO_AWAIT target_->__rpc_call(protocol_version, + enc, + 0, + caller_zone_id, + zone_->get_zone_id().as_destination(), + id_, + interface_id, + method_id, + in_data, + out_buf_, + empty_in_back_channel, + empty_out_back_channel); } RPC_ERROR("Invalid interface ID in stub call"); CO_RETURN rpc::error::INVALID_INTERFACE_ID(); @@ -76,20 +71,14 @@ namespace rpc int object_stub::try_cast(interface_ordinal interface_id) { - std::lock_guard g(map_control_); - int ret = rpc::error::OK(); - auto item = stub_map_.find(interface_id); - if (item == stub_map_.end()) - { - std::shared_ptr new_stub; - std::shared_ptr stub = stub_map_.begin()->second; - ret = stub->cast(interface_id, new_stub); - if (ret == rpc::error::OK() && new_stub) - { - add_interface(new_stub); - } - } - return ret; + if (!target_) + return rpc::error::OBJECT_NOT_FOUND(); + + auto* iface = const_cast(target_->__rpc_query_interface(interface_id)); + if (!iface) + return rpc::error::INVALID_CAST(); + + return rpc::error::OK(); } CORO_TASK(int) object_stub::add_ref(bool is_optimistic, bool outcall, caller_zone caller_zone_id) @@ -172,6 +161,10 @@ namespace rpc optimistic_references_.erase(it); } } + else + { + RPC_ERROR("negative optimistic reference count"); + } } else { @@ -198,6 +191,10 @@ namespace rpc shared_references_.erase(it); } } + else + { + RPC_ERROR("negative shared reference count"); + } } else { @@ -225,7 +222,7 @@ namespace rpc void object_stub::release_from_service(caller_zone caller_zone_id) { - zone_->release_local_stub(p_this_, false, caller_zone_id); + zone_->release_local_stub(shared_from_this(), false, caller_zone_id); } bool object_stub::has_references_from_zone(caller_zone caller_zone_id) const @@ -251,6 +248,21 @@ namespace rpc return false; } + std::vector object_stub::get_zones_with_optimistic_refs() const + { + std::lock_guard lock(references_mutex_); + std::vector result; + result.reserve(optimistic_references_.size()); + for (const auto& [zone, count_atomic] : optimistic_references_) + { + if (count_atomic.load(std::memory_order_acquire) > 0) + { + result.push_back(zone); + } + } + return result; + } + bool object_stub::release_all_from_zone(caller_zone caller_zone_id) { uint64_t shared_refs_to_release = 0; diff --git a/rpc/src/transport.cpp b/rpc/src/transport.cpp index efffb41..9c84366 100644 --- a/rpc/src/transport.cpp +++ b/rpc/src/transport.cpp @@ -52,7 +52,7 @@ namespace rpc service_ = service; } - CORO_TASK(int) transport::connect(interface_descriptor input_descr, interface_descriptor& output_descr) + CORO_TASK(int) transport::connect(connection_settings input_descr, interface_descriptor& output_descr) { #if defined(CANOPY_USE_TELEMETRY) && defined(CANOPY_USE_TELEMETRY_RAII_LOGGING) if (input_descr.object_id.is_set()) @@ -679,35 +679,56 @@ namespace rpc CORO_TASK(void) transport::notify_all_destinations_of_disconnect() { - std::shared_lock lock(destinations_mutex_); - auto service = service_.lock(); - if (!service) + std::shared_ptr service; + std::vector> handlers_to_notify; + std::vector zones_to_notify; + { - RPC_ERROR("notify_all_destinations_of_disconnect: Local service no longer exists on transport zone={} " - "adjacent_zone={}", - zone_id_.get_val(), - adjacent_zone_id_.get_val()); - CO_RETURN; + std::shared_lock lock(destinations_mutex_); + service = service_.lock(); + if (!service) + { + RPC_ERROR("notify_all_destinations_of_disconnect: Local service no longer exists on transport zone={} " + "adjacent_zone={}", + zone_id_.get_val(), + adjacent_zone_id_.get_val()); + CO_RETURN; + } + + handlers_to_notify.reserve(pass_thoughs_.size()); + for (const auto& [dest_zone, pt] : pass_thoughs_) + { + std::ignore = dest_zone; + if (auto handler = pt.lock()) + { + handlers_to_notify.push_back(std::move(handler)); + } + } + + zones_to_notify.reserve(zone_counts_.size()); + for (const auto& zone_item : zone_counts_) + { + zones_to_notify.push_back(zone_item.first.as_destination()); + } } + auto self = shared_from_this(); + // Iterate through passthrough handlers and notify them - for (const auto& [dest_zone, pt] : pass_thoughs_) + for (const auto& handler : handlers_to_notify) { - if (auto handler = pt.lock()) - { - // Send zone_terminating post - CO_AWAIT handler->local_transport_down(shared_from_this()); - } + // Send zone_terminating post + CO_AWAIT handler->local_transport_down(self); } // Notify service about transport down for each connected remote zone // Using zone_counts_ provides direct knowledge of which zones were connected, // enabling efficient forward cleanup - for (const auto& [remote_zone, counts] : zone_counts_) + for (const auto& remote_zone : zones_to_notify) { RPC_DEBUG("notify_all_destinations_of_disconnect: Notifying service about zone={} going down", remote_zone.get_val()); - CO_AWAIT service->notify_transport_down(shared_from_this(), remote_zone.as_destination()); + CO_AWAIT service->notify_transport_down(self, remote_zone); } } @@ -921,11 +942,7 @@ namespace rpc build_out_param_channel); } #endif - - if (error_code != error::OK()) - { - CO_RETURN error_code; - } + CO_RETURN error_code; } if (!dest_transport) { @@ -1374,6 +1391,7 @@ namespace rpc get_zone_id(), get_adjacent_zone_id(), destination_zone_id, caller_zone_id, object_id); } #endif + decrement_inbound_stub_count(caller_zone_id); } CORO_TASK(void) diff --git a/scripts/format-staged.sh b/scripts/format-staged.sh new file mode 100755 index 0000000..e990a33 --- /dev/null +++ b/scripts/format-staged.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env sh +# +# format-staged.sh - Run clang-format and cmake-format on staged files +# +# Formats all staged C/C++ and CMake files in-place, then re-stages them +# so the commit contains the formatted versions. +# +# Exit codes: +# 0 - success (files formatted and re-staged, or nothing to format) +# 1 - a formatting tool failed + +set -e + +REPO_ROOT=$(git rev-parse --show-toplevel) + +# Get staged files (Added, Copied, Modified) — exclude deleted files +STAGED=$(git diff --cached --name-only --diff-filter=ACM) + +if [ -z "$STAGED" ]; then + exit 0 +fi + +# ── C / C++ formatting ──────────────────────────────────────────────────────── + +if command -v clang-format >/dev/null 2>&1; then + CPP_FILES="" + for f in $STAGED; do + case "$f" in + # Skip generated output and submodules + build/*|build_*|submodules/*) continue ;; + esac + case "$f" in + *.c|*.cc|*.cpp|*.cxx|*.h|*.hh|*.hpp|*.hxx) + CPP_FILES="$CPP_FILES $REPO_ROOT/$f" + ;; + esac + done + + if [ -n "$CPP_FILES" ]; then + # shellcheck disable=SC2086 + clang-format -i $CPP_FILES + for f in $STAGED; do + case "$f" in + build/*|build_*|submodules/*) continue ;; + esac + case "$f" in + *.c|*.cc|*.cpp|*.cxx|*.h|*.hh|*.hpp|*.hxx) + git add "$REPO_ROOT/$f" + ;; + esac + done + fi +else + echo "Warning: clang-format not found, skipping C/C++ formatting" >&2 +fi + +# ── CMake formatting ────────────────────────────────────────────────────────── + +if command -v cmake-format >/dev/null 2>&1; then + CMAKE_FILES="" + for f in $STAGED; do + case "$f" in + build/*|build_*|submodules/*) continue ;; + esac + case "$f" in + CMakeLists.txt|*.cmake) + CMAKE_FILES="$CMAKE_FILES $REPO_ROOT/$f" + ;; + esac + done + + if [ -n "$CMAKE_FILES" ]; then + # shellcheck disable=SC2086 + cmake-format -i $CMAKE_FILES + for f in $STAGED; do + case "$f" in + build/*|build_*|submodules/*) continue ;; + esac + case "$f" in + CMakeLists.txt|*.cmake) + git add "$REPO_ROOT/$f" + ;; + esac + done + fi +else + echo "Warning: cmake-format not found, skipping CMake formatting" >&2 +fi + +exit 0 diff --git a/scripts/install-hooks.sh b/scripts/install-hooks.sh new file mode 100755 index 0000000..b837878 --- /dev/null +++ b/scripts/install-hooks.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env sh +# +# install-hooks.sh - Install the composite pre-commit hook +# +# Run this once after cloning, and again after 'bd hooks install' +# regenerates the standard shim. +# +# The composite hook runs clang-format + cmake-format on staged files +# before delegating to bd's pre-commit logic. + +set -e + +REPO_ROOT=$(git rev-parse --show-toplevel) +HOOK="$REPO_ROOT/.git/hooks/pre-commit" + +cat > "$HOOK" << 'EOF' +#!/usr/bin/env sh +# +# Composite pre-commit hook: formatting + bd shim +# + +# ── Format staged files ─────────────────────────────────────────────────────── +REPO_ROOT=$(git rev-parse --show-toplevel) +if [ -x "$REPO_ROOT/scripts/format-staged.sh" ]; then + "$REPO_ROOT/scripts/format-staged.sh" || exit 1 +fi + +# ── BD shim (beads issue tracking) ─────────────────────────────────────────── +if ! command -v bd >/dev/null 2>&1; then + echo "Warning: bd command not found in PATH, skipping bd pre-commit hook" >&2 + exit 0 +fi + +exec bd hooks run pre-commit "$@" +EOF + +chmod +x "$HOOK" +echo "Installed composite pre-commit hook at $HOOK" diff --git a/submodules/CMakeLists.txt b/submodules/CMakeLists.txt index 1a4ceca..d85591e 100644 --- a/submodules/CMakeLists.txt +++ b/submodules/CMakeLists.txt @@ -127,6 +127,8 @@ if(CANOPY_STANDALONE) if(CANOPY_BUILD_DEMOS) set(LLAMA_BUILD_COMMON ON) + # set(GGML_CUDA ON) set(CMAKE_CUDA_ARCHITECTURES 61) set(GGML_CUDA_F16 OFF) set(CMAKE_CUDA_COMPILER + # /usr/local/cuda/bin/nvcc) set(CMAKE_CUDA_FLAGS "-allow-unsupported-compiler") add_subdirectory(llama.cpp) endif() endif() diff --git a/telemetry/include/rpc/telemetry/multiplexing_telemetry_service.h b/telemetry/include/rpc/telemetry/multiplexing_telemetry_service.h index ed3e703..1d68b5d 100755 --- a/telemetry/include/rpc/telemetry/multiplexing_telemetry_service.h +++ b/telemetry/include/rpc/telemetry/multiplexing_telemetry_service.h @@ -73,7 +73,7 @@ namespace rpc */ explicit multiplexing_telemetry_service(std::vector>&& child_services); - virtual ~multiplexing_telemetry_service() DEFAULT_DESTRUCTOR; + virtual ~multiplexing_telemetry_service() CANOPY_DEFAULT_DESTRUCTOR; multiplexing_telemetry_service(const multiplexing_telemetry_service&) = delete; multiplexing_telemetry_service& operator=(const multiplexing_telemetry_service&) = delete; diff --git a/tests/common/include/common/foo_impl.h b/tests/common/include/common/foo_impl.h index 4dfd918..757c1a0 100644 --- a/tests/common/include/common/foo_impl.h +++ b/tests/common/include/common/foo_impl.h @@ -458,9 +458,6 @@ namespace marshalled_tests rpc::shared_ptr& new_example, const std::shared_ptr& child_service_ptr) -> CORO_TASK(error_code) { - example_import_idl_register_stubs(child_service_ptr); - example_shared_idl_register_stubs(child_service_ptr); - example_idl_register_stubs(child_service_ptr); new_example = rpc::shared_ptr(new marshalled_tests::example(child_service_ptr, host)); CO_RETURN rpc::error::OK(); }); @@ -842,9 +839,22 @@ namespace marshalled_tests CO_RETURN rpc::error::OK(); } + CORO_TASK(error_code) set_optimistic_ptr(const rpc::optimistic_ptr& val) override + { + cached_optimistic_foo_ = val; + CO_RETURN rpc::error::OK(); + } + + CORO_TASK(error_code) get_optimistic_ptr(rpc::optimistic_ptr& val) override + { + val = cached_optimistic_foo_; + CO_RETURN rpc::error::OK(); + } + private: // Cache for storing objects from autonomous zones rpc::shared_ptr cached_autonomous_object_; + rpc::optimistic_ptr cached_optimistic_foo_; public: CORO_TASK(error_code) cache_object_from_autonomous_zone(const std::vector& zone_ids) override diff --git a/tests/common/include/common/tests.h b/tests/common/include/common/tests.h index 7e39f92..bcbd797 100644 --- a/tests/common/include/common/tests.h +++ b/tests/common/include/common/tests.h @@ -116,7 +116,7 @@ namespace marshalled_tests foo f; CO_AWAIT standard_tests(f, lib.get_has_enclave()); - CO_RETURN !lib.error_has_occured(); + CO_RETURN !lib.error_has_occurred(); } CORO_TASK(bool) remote_tests(bool use_host_in_child, rpc::shared_ptr example_ptr); diff --git a/tests/fixtures/src/crash_handler.cpp b/tests/fixtures/src/crash_handler.cpp index b997754..44501a2 100644 --- a/tests/fixtures/src/crash_handler.cpp +++ b/tests/fixtures/src/crash_handler.cpp @@ -501,61 +501,93 @@ namespace crash_handler // Get the base address of the executable to calculate relative address // For PIE executables, we need to subtract the base address Dl_info info; - if (dladdr(address, &info) == 0 || info.dli_fbase == nullptr) + uintptr_t addr_to_resolve = (uintptr_t)address; + + if (dladdr(address, &info) != 0 && info.dli_fbase != nullptr) { - return ""; + // For shared libraries or PIE executables, calculate relative address + addr_to_resolve = (uintptr_t)address - (uintptr_t)info.dli_fbase; } - - // Calculate relative address - uintptr_t relative_addr = (uintptr_t)address - (uintptr_t)info.dli_fbase; + // If dladdr fails, try the absolute address directly // Use addr2line to get function name, source file and line number - std::stringstream cmd; - cmd << "addr2line -f -C -e " << exe_path << " 0x" << std::hex << relative_addr << " 2>/dev/null"; + // Try multiple strategies: relative address, absolute address, and with -p flag + std::vector commands; - FILE* pipe = popen(cmd.str().c_str(), "r"); - if (!pipe) - return ""; + // Strategy 1: Relative address (for PIE/shared libs) + std::stringstream cmd1; + cmd1 << "addr2line -f -C -p -i -e " << exe_path << " 0x" << std::hex << addr_to_resolve << " 2>/dev/null"; + commands.push_back(cmd1.str()); - char function_buffer[512]; - char location_buffer[512]; - std::string result; + // Strategy 2: Absolute address (for non-PIE) + if (addr_to_resolve != (uintptr_t)address) + { + std::stringstream cmd2; + cmd2 << "addr2line -f -C -p -i -e " << exe_path << " 0x" << std::hex << (uintptr_t)address << " 2>/dev/null"; + commands.push_back(cmd2.str()); + } - // First line: function name - if (fgets(function_buffer, sizeof(function_buffer), pipe) != nullptr) + // Try each strategy until one succeeds + for (const auto& cmd : commands) { - std::string function = function_buffer; - if (!function.empty() && function.back() == '\n') - { - function.pop_back(); - } + FILE* pipe = popen(cmd.c_str(), "r"); + if (!pipe) + continue; - // Second line: file:line - if (fgets(location_buffer, sizeof(location_buffer), pipe) != nullptr) + char buffer[1024]; + std::string result; + + // With -p flag, addr2line outputs everything on one line: "function at file:line" + if (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - std::string location = location_buffer; - if (!location.empty() && location.back() == '\n') + std::string line = buffer; + if (!line.empty() && line.back() == '\n') { - location.pop_back(); + line.pop_back(); } - // Skip if we get default "??" responses - if (function != "??" && location != "??:0" && location != "??:?") + // Skip if we get default "??" responses or "?? ??:0" + if (line != "?? ??:0" && line != "?? ??:?" && line.find("??") != 0) { // Extract just the filename from full path - size_t last_slash = location.find_last_of('/'); - if (last_slash != std::string::npos && last_slash + 1 < location.length()) + size_t at_pos = line.find(" at "); + if (at_pos != std::string::npos) + { + std::string function = line.substr(0, at_pos); + std::string location = line.substr(at_pos + 4); + + size_t last_slash = location.find_last_of('/'); + if (last_slash != std::string::npos && last_slash + 1 < location.length()) + { + location = location.substr(last_slash + 1); + } + + // Check for discriminator info and preserve it + size_t disc_pos = location.find(" (discriminator "); + std::string discriminator; + if (disc_pos != std::string::npos) + { + discriminator = location.substr(disc_pos); + location = location.substr(0, disc_pos); + } + + result = function + " at " + location + discriminator; + } + else { - location = location.substr(last_slash + 1); + result = line; } - result = function + " at " + location; + pclose(pipe); + if (!result.empty()) + return result; } } + + pclose(pipe); } - pclose(pipe); - return result; + return ""; } std::vector crash_handler::detect_crash_patterns(const crash_report& report) diff --git a/tests/fixtures/src/rpc_log.cpp b/tests/fixtures/src/rpc_log.cpp index 2fa43ed..78d6c6c 100644 --- a/tests/fixtures/src/rpc_log.cpp +++ b/tests/fixtures/src/rpc_log.cpp @@ -82,7 +82,7 @@ extern "C" out_data, in_back_channel, out_back_channel); - if (retry_buf.return_value >= rpc::error::MIN() && retry_buf.return_value <= rpc::error::MAX()) + if (rpc::error::is_error(retry_buf.return_value)) { CO_RETURN retry_buf.return_value; } diff --git a/tests/fuzz_test/fuzz_test_main.cpp b/tests/fuzz_test/fuzz_test_main.cpp index e1ce179..11168cd 100644 --- a/tests/fuzz_test/fuzz_test_main.cpp +++ b/tests/fuzz_test/fuzz_test_main.cpp @@ -448,9 +448,8 @@ class autonomous_node_impl : public rpc::baseset_child_entry_point( [](const rpc::shared_ptr&, rpc::shared_ptr& new_factory, - const std::shared_ptr& child_service_ptr) -> CORO_TASK(int) + const std::shared_ptr&) -> CORO_TASK(int) { - fuzz_test_idl_register_stubs(child_service_ptr); new_factory = rpc::make_shared(); CO_RETURN rpc::error::OK(); }); @@ -476,9 +475,8 @@ class autonomous_node_impl : public rpc::baseset_child_entry_point( [](const rpc::shared_ptr&, rpc::shared_ptr& new_cache, - const std::shared_ptr& child_service_ptr) -> CORO_TASK(int) + const std::shared_ptr&) -> CORO_TASK(int) { - fuzz_test_idl_register_stubs(child_service_ptr); new_cache = rpc::make_shared(); CO_RETURN rpc::error::OK(); }); @@ -505,9 +503,8 @@ class autonomous_node_impl : public rpc::baseset_child_entry_point( [](const rpc::shared_ptr&, rpc::shared_ptr& new_worker, - const std::shared_ptr& child_service_ptr) -> CORO_TASK(int) + const std::shared_ptr&) -> CORO_TASK(int) { - fuzz_test_idl_register_stubs(child_service_ptr); new_worker = rpc::make_shared(); CO_RETURN rpc::error::OK(); }); @@ -824,10 +821,9 @@ class autonomous_node_impl : public rpc::baseset_child_entry_point( [child_type, child_zone_id]([[maybe_unused]] const rpc::shared_ptr& parent, rpc::shared_ptr& new_child, - const std::shared_ptr& child_service_ptr) -> CORO_TASK(int) + const std::shared_ptr&) -> CORO_TASK(int) { RPC_INFO("===CALLBACK INVOKED=== for child_zone_id={}", child_zone_id); - fuzz_test_idl_register_stubs(child_service_ptr); new_child = rpc::make_shared(child_type, child_zone_id); CO_RETURN CO_AWAIT new_child->initialize_node(child_type, child_zone_id); }); @@ -1053,7 +1049,7 @@ class garbage_collector_impl : public rpc::baseget_object_proxy(); + auto op = obj->__rpc_get_object_proxy(); [[maybe_unused]] auto object_id = op->get_object_id(); [[maybe_unused]] auto zone_id = op->get_service_proxy()->get_zone_id(); RPC_INFO("[GARBAGE_COLLECTOR] Object zone id: {} object_id: {}", zone_id.get_val(), object_id.get_val()); @@ -1269,9 +1265,6 @@ CORO_TASK(void) run_autonomous_instruction_test(int test_cycle, int instruction_ auto root_service = std::make_shared("AUTONOMOUS_ROOT", rpc::zone{++g_zone_id_counter}); #endif - // IMPORTANT: Register stubs in root service BEFORE creating child zones - fuzz_test_idl_register_stubs(root_service); - // Initialize test scenario configuration for replay system test_scenario_config scenario_config; scenario_config.test_cycle = test_cycle; @@ -1333,9 +1326,8 @@ CORO_TASK(void) run_autonomous_instruction_test(int test_cycle, int instruction_ child_transport->set_child_entry_point( [&, new_zone_id]([[maybe_unused]] const rpc::shared_ptr& parent, rpc::shared_ptr& new_node, - const std::shared_ptr& child_service_ptr) -> CORO_TASK(int) + const std::shared_ptr&) -> CORO_TASK(int) { - fuzz_test_idl_register_stubs(child_service_ptr); new_node = rpc::make_shared(node_type::ROOT_NODE, new_zone_id.get_val()); CO_RETURN CO_AWAIT new_node->initialize_node(node_type::ROOT_NODE, new_zone_id.get_val()); }); diff --git a/tests/idls/example/example.idl b/tests/idls/example/example.idl index 00b231b..1fc8741 100644 --- a/tests/idls/example/example.idl +++ b/tests/idls/example/example.idl @@ -55,6 +55,9 @@ namespace yyy [description="Retrieves cached object from autonomous zone (triggers routing failure for unaware zones)"] error_code give_host_cached_object(); + + error_code set_optimistic_ptr(const rpc::optimistic_ptr& val); + error_code get_optimistic_ptr([out]rpc::optimistic_ptr& val); }; [status=production] diff --git a/tests/std_test/CMakeLists.txt b/tests/std_test/CMakeLists.txt index ac71d83..eb2f8da 100644 --- a/tests/std_test/CMakeLists.txt +++ b/tests/std_test/CMakeLists.txt @@ -83,11 +83,12 @@ foreach(test_name ${BUILDABLE_TESTS}) add_test(NAME ${test_name} COMMAND ${test_name}) - # ASan options: disable leak detection for all tests - # Dev10_445289_make_shared also needs alloc_dealloc_mismatch=0 because it overrides operator new/delete + # ASan options: disable leak detection for all tests Dev10_445289_make_shared also needs alloc_dealloc_mismatch=0 + # because it overrides operator new/delete if(test_name STREQUAL "Dev10_445289_make_shared") - set_tests_properties(${test_name} PROPERTIES ENVIRONMENT - "ASAN_OPTIONS=detect_leaks=0:alloc_dealloc_mismatch=0;LSAN_OPTIONS=detect_leaks=0") + set_tests_properties( + ${test_name} PROPERTIES ENVIRONMENT + "ASAN_OPTIONS=detect_leaks=0:alloc_dealloc_mismatch=0;LSAN_OPTIONS=detect_leaks=0") else() set_tests_properties(${test_name} PROPERTIES ENVIRONMENT "ASAN_OPTIONS=detect_leaks=0;LSAN_OPTIONS=detect_leaks=0") diff --git a/tests/std_test/tests/remote_pointer_stl_compliance.md b/tests/std_test/tests/remote_pointer_stl_compliance.md index 3af3784..e4b87a4 100644 --- a/tests/std_test/tests/remote_pointer_stl_compliance.md +++ b/tests/std_test/tests/remote_pointer_stl_compliance.md @@ -329,7 +329,7 @@ shared_ptr dynamic_pointer_cast(const shared_ptr& from) noexcept { // First try local interface casting ptr = const_cast(static_cast( - from->query_interface(T::get_id(VERSION_2))); + from->query_interface(T::get_id(get_version()))); if (ptr) CO_RETURN shared_ptr(from, ptr); @@ -348,7 +348,7 @@ shared_ptr dynamic_pointer_cast(const shared_ptr& from) noexcept { **Key Features**: - **Dual-Path Casting**: First attempts local interface casting using `query_interface()`, then falls back to remote casting via `object_proxy` - **Coroutine-Based**: Uses `CO_RETURN` and `CO_AWAIT` for asynchronous remote interface queries -- **Version-Aware**: Uses `T::get_id(VERSION_2)` for proper interface identification +- **Version-Aware**: Uses `T::get_id(get_version())` for proper interface identification - **Reference Count Semantics**: Remote casting creates new proxy with independent reference counting - **Compatibility Note**: `static_pointer_cast` will not work for remote interfaces, only `dynamic_pointer_cast` diff --git a/tests/test_enclave/marshal_test_enclave.cpp b/tests/test_enclave/marshal_test_enclave.cpp index 54aa421..587fc94 100644 --- a/tests/test_enclave/marshal_test_enclave.cpp +++ b/tests/test_enclave/marshal_test_enclave.cpp @@ -31,7 +31,7 @@ std::shared_ptr rpc_server; int marshal_test_init_enclave(uint64_t host_zone_id, uint64_t host_id, uint64_t child_zone_id, uint64_t* example_object_id) { - rpc::interface_descriptor input_descr{}; + rpc::connection_settings input_descr{}; rpc::interface_descriptor output_descr{}; if (host_id) @@ -53,9 +53,6 @@ int marshal_test_init_enclave(uint64_t host_zone_id, uint64_t host_id, uint64_t rpc::shared_ptr& new_example, const std::shared_ptr& child_service_ptr) -> int { - example_import_idl_register_stubs(child_service_ptr); - example_shared_idl_register_stubs(child_service_ptr); - example_idl_register_stubs(child_service_ptr); new_example = rpc::shared_ptr(new marshalled_tests::example(child_service_ptr, host)); return rpc::error::OK(); }, @@ -157,7 +154,7 @@ int call_enclave(uint64_t protocol_version, // version of the rpc call protocol tmp, in_back_channel, out_back_channel); - if (ret >= rpc::error::MIN() && ret <= rpc::error::MAX()) + if (rpc::error::is_error(ret)) return ret; // Combine output payload + back-channel into single buffer diff --git a/tests/test_host/CMakeLists.txt b/tests/test_host/CMakeLists.txt index 36f8364..0e58dc9 100644 --- a/tests/test_host/CMakeLists.txt +++ b/tests/test_host/CMakeLists.txt @@ -11,6 +11,7 @@ add_executable( remote_type_test_suite.cpp hierachical_transport_tests.cpp post_functionality_test_suite.cpp + optimistic_ptr_tests.cpp ${COROUTINE_TESTS}) target_compile_definitions(rpc_test PRIVATE ${CANOPY_DEFINES}) diff --git a/tests/test_host/main.cpp b/tests/test_host/main.cpp index a10dfe9..a8244d6 100644 --- a/tests/test_host/main.cpp +++ b/tests/test_host/main.cpp @@ -43,190 +43,189 @@ bool enable_multithreaded_tests = false; // This line tests that we can define tests in an unnamed namespace. namespace { +} - extern "C" int main(int argc, char* argv[]) +int main(int argc, char* argv[]) +{ + args::ArgumentParser parser("RPC++ Test Suite - Comprehensive RPC testing framework"); + + // Basic test control flags + args::Flag enable_multithreaded_flag( + parser, "multithreaded", "Enable multithreaded tests", {'m', "enable-multithreaded"}); + + // Telemetry service flags + args::Flag enable_console_telemetry( + parser, "console", "Add console telemetry service", {"telemetry-console", "console"}); + args::ValueFlag console_path( + parser, "console-path", "Console telemetry output path", {"console-path"}, "../../telemetry/reports/"); + args::Flag enable_sequence_diagram_telemetry( + parser, "sequence", "Add sequence diagram telemetry service", {"telemetry-sequence"}); + args::ValueFlag sequence_path( + parser, "sequence-path", "Sequence diagram output path", {"sequence-path"}, "../../telemetry/reports/"); + args::Flag enable_animation_telemetry(parser, "animation", "Add animation telemetry service", {"telemetry-animation"}); + args::ValueFlag animation_path( + parser, "animation-path", "Animation diagram output path", {"animation-path"}, "../../telemetry/reports/"); + + args::Flag help(parser, "help", "Display this help menu", {'h', "help"}); + + try { - args::ArgumentParser parser("RPC++ Test Suite - Comprehensive RPC testing framework"); - - // Basic test control flags - args::Flag enable_multithreaded_flag( - parser, "multithreaded", "Enable multithreaded tests", {'m', "enable-multithreaded"}); - - // Telemetry service flags - args::Flag enable_console_telemetry( - parser, "console", "Add console telemetry service", {"telemetry-console", "console"}); - args::ValueFlag console_path( - parser, "console-path", "Console telemetry output path", {"console-path"}, "../../telemetry/reports/"); - args::Flag enable_sequence_diagram_telemetry( - parser, "sequence", "Add sequence diagram telemetry service", {"telemetry-sequence"}); - args::ValueFlag sequence_path( - parser, "sequence-path", "Sequence diagram output path", {"sequence-path"}, "../../telemetry/reports/"); - args::Flag enable_animation_telemetry( - parser, "animation", "Add animation telemetry service", {"telemetry-animation"}); - args::ValueFlag animation_path( - parser, "animation-path", "Animation diagram output path", {"animation-path"}, "../../telemetry/reports/"); - - args::Flag help(parser, "help", "Display this help menu", {'h', "help"}); - - try - { - parser.ParseCLI(argc, argv); - } - catch (const args::ParseError& e) + parser.ParseCLI(argc, argv); + } + catch (const args::ParseError& e) + { + // std::cout << "Continuing to pass all arguments to Google Test: " << e.what() << std::endl; + } + catch (const std::exception& e) + { + std::cerr << "Error during argument parsing: " << e.what() << std::endl; + return 1; + } + + if (help.HasFlag()) + { + std::cout << parser; + std::cout << "\nGoogle Test Integration:\n"; + std::cout << " All Google Test flags are supported and will be passed through.\n"; + std::cout << " Use --gtest_help to see Google Test specific options.\n\n"; + std::cout << "Examples:\n"; + std::cout << " ./rpc_test --telemetry-console\n"; + std::cout << " ./rpc_test --telemetry-console --telemetry-sequence\n"; + std::cout << " ./rpc_test --telemetry-console --gtest_filter=\"*standard_tests*\"\n"; + } + + // Extract parsed values + enable_multithreaded_tests = args::get(enable_multithreaded_flag); + +#ifdef CANOPY_USE_TELEMETRY + // Ensure we have a multiplexing telemetry service when any telemetry flags are provided + if ((args::get(enable_console_telemetry) || args::get(enable_sequence_diagram_telemetry) + || args::get(enable_animation_telemetry))) + { + // Create empty multiplexing service + std::vector> empty_services; + if (rpc::multiplexing_telemetry_service::create(std::move(empty_services))) { - // std::cout << "Continuing to pass all arguments to Google Test: " << e.what() << std::endl; + std::cout << "Created multiplexing telemetry service" << std::endl; } - catch (const std::exception& e) + } + + // Assume telemetry_service_ is always a multiplexing service and register configurations + if (rpc::get_telemetry_service()) + { + auto multiplexing_service + = std::static_pointer_cast(rpc::get_telemetry_service()); + + if (args::get(enable_console_telemetry)) { - std::cerr << "Error during argument parsing: " << e.what() << std::endl; - return 1; + multiplexing_service->register_service_config("console", console_path.Get()); + // std::cout << "Registered console telemetry service configuration" << std::endl; } - if (help.HasFlag()) + if (args::get(enable_sequence_diagram_telemetry)) { - std::cout << parser; - std::cout << "\nGoogle Test Integration:\n"; - std::cout << " All Google Test flags are supported and will be passed through.\n"; - std::cout << " Use --gtest_help to see Google Test specific options.\n\n"; - std::cout << "Examples:\n"; - std::cout << " ./rpc_test --telemetry-console\n"; - std::cout << " ./rpc_test --telemetry-console --telemetry-sequence\n"; - std::cout << " ./rpc_test --telemetry-console --gtest_filter=\"*standard_tests*\"\n"; + multiplexing_service->register_service_config("sequence", sequence_path.Get()); + // std::cout << "Registered sequence diagram telemetry service configuration" << std::endl; } - // Extract parsed values - enable_multithreaded_tests = args::get(enable_multithreaded_flag); - -#ifdef CANOPY_USE_TELEMETRY - // Ensure we have a multiplexing telemetry service when any telemetry flags are provided - if ((args::get(enable_console_telemetry) || args::get(enable_sequence_diagram_telemetry) - || args::get(enable_animation_telemetry))) + if (args::get(enable_animation_telemetry)) { - // Create empty multiplexing service - std::vector> empty_services; - if (rpc::multiplexing_telemetry_service::create(std::move(empty_services))) - { - std::cout << "Created multiplexing telemetry service" << std::endl; - } + multiplexing_service->register_service_config("animation", animation_path.Get()); + std::cout << "Registered animation telemetry service configuration" << std::endl; } + } +#endif - // Assume telemetry_service_ is always a multiplexing service and register configurations - if (rpc::get_telemetry_service()) +#ifndef _WIN32 + // Initialize comprehensive crash handler with multi-threaded support + // (Only available on POSIX systems - Windows not supported) + crash_handler::crash_handler::Config crash_config; + crash_config.enable_multithreaded_traces = true; + crash_config.enable_symbol_resolution = true; + crash_config.enable_threading_debug_info = true; + crash_config.enable_pattern_detection = true; + crash_config.max_stack_frames = 64; + crash_config.max_threads = 50; + crash_config.save_crash_dump = true; + crash_config.crash_dump_path = "./build/crash"; + + // Set up custom crash analysis callback + crash_handler::crash_handler::set_analysis_callback( + [](const crash_handler::crash_handler::crash_report& report) { - auto multiplexing_service - = std::static_pointer_cast(rpc::get_telemetry_service()); - - if (args::get(enable_console_telemetry)) + std::cout << "\n=== CUSTOM CRASH ANALYSIS ===" << std::endl; + std::cout << "Crash occurred at: " + << std::chrono::duration_cast(report.crash_time.time_since_epoch()).count() + << std::endl; + + // Check for threading debug patterns + bool threading_bug_detected = false; + for (const auto& pattern : report.detected_patterns) { - multiplexing_service->register_service_config("console", console_path.Get()); - // std::cout << "Registered console telemetry service configuration" << std::endl; + if (pattern.find("THREADING") != std::string::npos || pattern.find("ZONE PROXY") != std::string::npos) + { + threading_bug_detected = true; + break; + } } - if (args::get(enable_sequence_diagram_telemetry)) + if (threading_bug_detected) { - multiplexing_service->register_service_config("sequence", sequence_path.Get()); - // std::cout << "Registered sequence diagram telemetry service configuration" << std::endl; + std::cout << "🐛 THREADING BUG CONFIRMED: This crash was caused by a race condition!" << std::endl; + std::cout << " The threading debug system successfully detected the issue." << std::endl; } - - if (args::get(enable_animation_telemetry)) + else { - multiplexing_service->register_service_config("animation", animation_path.Get()); - std::cout << "Registered animation telemetry service configuration" << std::endl; + std::cout << "ℹ️ Standard crash - not detected as threading-related." << std::endl; } - } -#endif -#ifndef _WIN32 - // Initialize comprehensive crash handler with multi-threaded support - // (Only available on POSIX systems - Windows not supported) - crash_handler::crash_handler::Config crash_config; - crash_config.enable_multithreaded_traces = true; - crash_config.enable_symbol_resolution = true; - crash_config.enable_threading_debug_info = true; - crash_config.enable_pattern_detection = true; - crash_config.max_stack_frames = 64; - crash_config.max_threads = 50; - crash_config.save_crash_dump = true; - crash_config.crash_dump_path = "./build/crash"; - - // Set up custom crash analysis callback - crash_handler::crash_handler::set_analysis_callback( - [](const crash_handler::crash_handler::crash_report& report) - { - std::cout << "\n=== CUSTOM CRASH ANALYSIS ===" << std::endl; - std::cout << "Crash occurred at: " - << std::chrono::duration_cast(report.crash_time.time_since_epoch()).count() - << std::endl; - - // Check for threading debug patterns - bool threading_bug_detected = false; - for (const auto& pattern : report.detected_patterns) - { - if (pattern.find("THREADING") != std::string::npos || pattern.find("ZONE PROXY") != std::string::npos) - { - threading_bug_detected = true; - break; - } - } + std::cout << "=== END CUSTOM ANALYSIS ===" << std::endl; + }); - if (threading_bug_detected) - { - std::cout << "🐛 THREADING BUG CONFIRMED: This crash was caused by a race condition!" << std::endl; - std::cout << " The threading debug system successfully detected the issue." << std::endl; - } - else - { - std::cout << "ℹ️ Standard crash - not detected as threading-related." << std::endl; - } - - std::cout << "=== END CUSTOM ANALYSIS ===" << std::endl; - }); - - if (!crash_handler::crash_handler::initialize(crash_config)) - { - std::cerr << "Failed to initialize crash handler" << std::endl; - return 1; - } + if (!crash_handler::crash_handler::initialize(crash_config)) + { + std::cerr << "Failed to initialize crash handler" << std::endl; + return 1; + } - std::cout << "[Main] Comprehensive crash handler initialized" << std::endl; - std::cout << "[Main] - Multi-threaded stack traces: ENABLED" << std::endl; - std::cout << "[Main] - Symbol resolution: ENABLED" << std::endl; - std::cout << "[Main] - Threading debug integration: ENABLED" << std::endl; - std::cout << "[Main] - Pattern detection: ENABLED" << std::endl; - std::cout << "[Main] - Crash dumps will be saved to: " << crash_config.crash_dump_path << std::endl; + std::cout << "[Main] Comprehensive crash handler initialized" << std::endl; + std::cout << "[Main] - Multi-threaded stack traces: ENABLED" << std::endl; + std::cout << "[Main] - Symbol resolution: ENABLED" << std::endl; + std::cout << "[Main] - Threading debug integration: ENABLED" << std::endl; + std::cout << "[Main] - Pattern detection: ENABLED" << std::endl; + std::cout << "[Main] - Crash dumps will be saved to: " << crash_config.crash_dump_path << std::endl; #else - std::cout << "[Main] Crash handler not available on Windows" << std::endl; + std::cout << "[Main] Crash handler not available on Windows" << std::endl; #endif - int ret = 0; - try - { - // Initialize global logger for consistent logging - rpc_global_logger::get_logger(); + int ret = 0; + try + { + // Initialize global logger for consistent logging + rpc_global_logger::get_logger(); - ::testing::InitGoogleTest(&argc, argv); - ret = RUN_ALL_TESTS(); - rpc_global_logger::reset_logger(); - } - catch (const std::exception& e) - { - std::cerr << "Exception caught in main: " << e.what() << std::endl; - ret = 1; - } - catch (...) - { - std::cerr << "Unknown exception caught in main" << std::endl; - ret = 1; - } + ::testing::InitGoogleTest(&argc, argv); + ret = RUN_ALL_TESTS(); + rpc_global_logger::reset_logger(); + } + catch (const std::exception& e) + { + std::cerr << "Exception caught in main: " << e.what() << std::endl; + ret = 1; + } + catch (...) + { + std::cerr << "Unknown exception caught in main" << std::endl; + ret = 1; + } - // Cleanup crash handler + // Cleanup crash handler #ifndef _WIN32 - crash_handler::crash_handler::shutdown(); - // std::cout << "[Main] test shutdown complete" << std::endl; + crash_handler::crash_handler::shutdown(); + // std::cout << "[Main] test shutdown complete" << std::endl; #endif - return ret; - } + return ret; } static_assert(rpc::id::get(rpc::VERSION_2) == rpc::STD_STRING_ID); diff --git a/tests/test_host/optimistic_ptr_tests.cpp b/tests/test_host/optimistic_ptr_tests.cpp new file mode 100644 index 0000000..9a51a64 --- /dev/null +++ b/tests/test_host/optimistic_ptr_tests.cpp @@ -0,0 +1,928 @@ +/* + * Copyright (c) 2026 Edward Boggis-Rolfe + * All rights reserved. + */ + +// Standard C++ headers +#include +#include +#include +#include +#include +#include + +// RPC headers +#include +#ifdef CANOPY_USE_TELEMETRY +#include +#include +#include +#include +#endif + +// Other headers +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsuggest-override" +#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-override" +#endif + +#include + +#ifdef __clang__ +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#ifdef CANOPY_BUILD_COROUTINE +#include +#endif +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +#include + +#include "rpc_global_logger.h" +#include "test_host.h" +#include +#include +#ifdef CANOPY_BUILD_ENCLAVE +#include +#endif +#ifdef CANOPY_BUILD_COROUTINE +#include +#include +#endif +#include "crash_handler.h" + +using namespace marshalled_tests; + +#include "test_globals.h" +#include "type_test_fixture.h" + +extern bool enable_multithreaded_tests; + +// Type list for optimistic_ptr test instantiations. +using optimistic_ptr_implementations = ::testing::Types, + in_memory_setup, + inproc_setup, + inproc_setup, + inproc_setup, + inproc_setup, + inproc_setup, + inproc_setup, + inproc_setup, + inproc_setup +#ifdef CANOPY_BUILD_COROUTINE + , + tcp_setup, + tcp_setup, + tcp_setup, + tcp_setup, + tcp_setup, + tcp_setup, + tcp_setup, + tcp_setup, + spsc_setup, + spsc_setup, + spsc_setup, + spsc_setup, + spsc_setup, + spsc_setup, + spsc_setup, + spsc_setup +#endif +#ifdef CANOPY_BUILD_ENCLAVE + , + sgx_setup, + sgx_setup, + sgx_setup, + sgx_setup, + sgx_setup, + sgx_setup, + sgx_setup, + sgx_setup +#endif + >; + +template class optimistic_ptr_test : public type_test +{ +}; + +TYPED_TEST_SUITE(optimistic_ptr_test, optimistic_ptr_implementations); + +// ============================================================================ +// Optimistic Pointer Tests +// ============================================================================ + +// Test 1: Basic optimistic_ptr construction and lifecycle +CORO_TASK(bool) optimistic_ptr_basic_lifecycle_test(std::shared_ptr root_service) +{ + // Create a shared_ptr to a local object + auto f = rpc::shared_ptr(new foo()); + CORO_ASSERT_NE(f, nullptr); + + // Create optimistic_ptr from shared_ptr + rpc::optimistic_ptr opt_f; + auto err = CO_AWAIT rpc::make_optimistic(f, opt_f); + CORO_ASSERT_EQ(err, rpc::error::OK()); + CORO_ASSERT_NE(opt_f.get_unsafe_only_for_testing(), nullptr); + CORO_ASSERT_EQ(opt_f.get_unsafe_only_for_testing(), f.get()); + + // Test copy constructor + rpc::optimistic_ptr opt_f_copy(opt_f); + CORO_ASSERT_NE(opt_f_copy.get_unsafe_only_for_testing(), nullptr); + CORO_ASSERT_EQ(opt_f_copy.get_unsafe_only_for_testing(), opt_f.get_unsafe_only_for_testing()); + + // Test move constructor + rpc::optimistic_ptr opt_f_move(std::move(opt_f_copy)); + CORO_ASSERT_NE(opt_f_move.get_unsafe_only_for_testing(), nullptr); + CORO_ASSERT_EQ(opt_f_move.get_unsafe_only_for_testing(), + opt_f.get_unsafe_only_for_testing()); // Should point to same local_proxy + CORO_ASSERT_EQ(opt_f_copy.get_unsafe_only_for_testing(), nullptr); // Moved-from should be null + + // Test assignment + rpc::optimistic_ptr opt_f_assigned; + CORO_ASSERT_EQ(opt_f_assigned.get_unsafe_only_for_testing(), nullptr); + opt_f_assigned = opt_f_move; + CORO_ASSERT_NE(opt_f_assigned.get_unsafe_only_for_testing(), nullptr); + CORO_ASSERT_EQ(opt_f_assigned.get_unsafe_only_for_testing(), f.get()); + + // Test reset + opt_f_move.reset(); + CORO_ASSERT_EQ(opt_f_move.get_unsafe_only_for_testing(), nullptr); + + CO_RETURN true; +} + +TYPED_TEST(optimistic_ptr_test, optimistic_ptr_basic_lifecycle_test) +{ + auto& lib = this->get_lib(); + auto root_service = lib.get_root_service(); + run_coro_test(*this, + [root_service](auto& lib) + { + (void)lib; + return optimistic_ptr_basic_lifecycle_test(root_service); + }); +} + +// Test 2: Optimistic pointer weak semantics for local objects +CORO_TASK(bool) optimistic_ptr_weak_semantics_local_test(std::shared_ptr root_service) +{ + rpc::optimistic_ptr opt_f; + + { + // Create a shared_ptr to a local object + auto f = rpc::shared_ptr(new foo()); + CORO_ASSERT_NE(f, nullptr); + + // Create optimistic_ptr from shared_ptr + auto err = CO_AWAIT rpc::make_optimistic(f, opt_f); + CORO_ASSERT_EQ(err, rpc::error::OK()); + CORO_ASSERT_NE(opt_f.get_unsafe_only_for_testing(), nullptr); + + // Verify object is accessible + CORO_ASSERT_EQ(opt_f.get_unsafe_only_for_testing(), f.get()); + // f goes out of scope - object should be deleted + // (optimistic_ptr has weak semantics for local objects) + } + + // opt_f still exists and points to local_proxy (which is still alive via shared_ptr) + // The local_proxy internally has a weak_ptr which will fail to lock + // This is valid behavior per spec - optimistic_ptr has weak semantics + CORO_ASSERT_NE(opt_f.get_unsafe_only_for_testing(), nullptr); // local_proxy still exists + + CO_RETURN true; +} + +TYPED_TEST(optimistic_ptr_test, optimistic_ptr_weak_semantics_local_test) +{ + auto& lib = this->get_lib(); + auto root_service = lib.get_root_service(); + run_coro_test(*this, + [root_service](auto& lib) + { + (void)lib; + return optimistic_ptr_weak_semantics_local_test(root_service); + }); +} + +// Test 3: optimistic_ptr local_proxy pattern test +CORO_TASK(bool) optimistic_ptr_local_proxy_test(std::shared_ptr root_service) +{ + rpc::shared_ptr f; + rpc::optimistic_ptr opt_f; + + // Create a shared_ptr to a local object + f = rpc::shared_ptr(new foo()); + CORO_ASSERT_NE(f, nullptr); + + // Create optimistic_ptr from shared_ptr + // optimistic_ptr should automatically create a local_proxy + auto err = CO_AWAIT rpc::make_optimistic(f, opt_f); + CORO_ASSERT_EQ(err, rpc::error::OK()); + CORO_ASSERT_NE(opt_f.get_unsafe_only_for_testing(), nullptr); + + // Test calling through the optimistic_ptr while object is alive + err = CO_AWAIT opt_f->do_something_in_val(42); + CORO_ASSERT_EQ(err, rpc::error::OK()); + + // Clear the shared_ptr - object will be deleted + f.reset(); + CORO_ASSERT_EQ(f, nullptr); + + // optimistic_ptr still points to the local_proxy + // but calling through it should return OBJECT_GONE + err = CO_AWAIT opt_f->do_something_in_val(42); + CORO_ASSERT_EQ(err, rpc::error::OBJECT_GONE()); + + // The local_proxy is still valid (weak_ptr failed to lock) + CORO_ASSERT_NE(opt_f.get_unsafe_only_for_testing(), nullptr); + + CO_RETURN true; +} + +TYPED_TEST(optimistic_ptr_test, optimistic_ptr_local_proxy_test) +{ + auto& lib = this->get_lib(); + auto root_service = lib.get_root_service(); + run_coro_test(*this, + [root_service](auto& lib) + { + (void)lib; + return optimistic_ptr_local_proxy_test(root_service); + }); +} + +// Test 4: Optimistic pointer semantics (weak for local, shared for remote) +template CORO_TASK(bool) optimistic_ptr_remote_shared_semantics_test(T& lib) +{ + // Get example object (local or remote depending on setup) + auto example = lib.get_example(); + CORO_ASSERT_NE(example, nullptr); + + // Create foo through example (will be local or marshalled depending on setup) + rpc::shared_ptr f; + CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f), 0); + CORO_ASSERT_NE(f, nullptr); + + // Create baz interface (local or marshalled depending on setup) + rpc::shared_ptr baz; + CORO_ASSERT_EQ(CO_AWAIT f->create_baz_interface(baz), 0); + CORO_ASSERT_NE(baz, nullptr); + + // Release f to ensure it's not holding any references to baz via cached_ member + f.reset(); + + // Store raw pointer for later comparison + auto* raw_ptr = baz.get(); + + // Get object_id directly from the interface (avoids service mutex) + auto baz_object_id = rpc::casting_interface::get_object_id(*baz); + + // Create optimistic_ptr + rpc::optimistic_ptr opt_baz; + auto err = CO_AWAIT rpc::make_optimistic(baz, opt_baz); + CORO_ASSERT_EQ(err, rpc::error::OK()); + CORO_ASSERT_NE(opt_baz.get_unsafe_only_for_testing(), nullptr); + + // Can call through optimistic_ptr directly (local_proxy handles weak semantics) + auto error2 = CO_AWAIT opt_baz->callback(42); + CORO_ASSERT_EQ(error2, 0); + + // Register for object deletion notification with continuation for verification + auto waiter = std::make_shared(baz_object_id); + + // Schedule verification - handles both local (immediate) and remote (async) cases + // CRITICAL: Pass opt_baz as argument to ensure it lives in the coroutine frame + waiter->schedule( + lib.get_root_service(), + baz, + [](auto opt_baz) -> CORO_TASK(void) + { + // This runs after the object is deleted + // The object is deleted when the last shared_ptr goes away + // local_proxy pointer is still valid but weak_ptr inside will fail to lock + EXPECT_NE(opt_baz.get_unsafe_only_for_testing(), nullptr); // local_proxy still exists + + // Calling through optimistic_ptr should return OBJECT_GONE (weak_ptr failed to lock) + auto error = CO_AWAIT opt_baz->callback(42); + EXPECT_EQ(error, rpc::error::OBJECT_GONE()); + + CO_RETURN; + }, + opt_baz); // Pass as argument instead of capturing + + // Clear the shared_ptr - for remote this triggers async cleanup, for local it's immediate + baz.reset(); + + // For local objects, run verification immediately; for remote, it runs via async callback + CO_AWAIT waiter->run_if_local(); + + CO_RETURN true; +} + +TYPED_TEST(optimistic_ptr_test, optimistic_ptr_remote_shared_semantics_test) +{ + GTEST_SKIP() << "skipped for now."; + run_coro_test(*this, [](auto& lib) { return optimistic_ptr_remote_shared_semantics_test(lib); }); +} + +// Test 5: Optimistic pointer transparent operator-> for both local and remote +template CORO_TASK(bool) optimistic_ptr_transparent_access_test(T& lib) +{ + auto example = lib.get_example(); + // Test 1: Local object access + { + // Create interface (local or remote depending on setup) + rpc::shared_ptr f_local; + CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f_local), 0); + CORO_ASSERT_NE(f_local, nullptr); + + rpc::optimistic_ptr opt_f_local; + auto err = CO_AWAIT rpc::make_optimistic(f_local, opt_f_local); + CORO_ASSERT_EQ(err, rpc::error::OK()); + + // operator-> works transparently for local object + CORO_ASSERT_NE(opt_f_local.operator->(), nullptr); + CORO_ASSERT_NE(opt_f_local.get_unsafe_only_for_testing(), nullptr); + CORO_ASSERT_EQ(opt_f_local.get_unsafe_only_for_testing(), f_local.get()); + } + + // Test 2: Remote object access + { + // Create interface (local or remote depending on setup) + rpc::shared_ptr baz; + CORO_ASSERT_EQ(CO_AWAIT example->create_baz(baz), 0); + CORO_ASSERT_NE(baz, nullptr); + + rpc::optimistic_ptr opt_baz; + auto err = CO_AWAIT rpc::make_optimistic(baz, opt_baz); + CORO_ASSERT_EQ(err, rpc::error::OK()); + + // operator-> works transparently for remote proxy + CORO_ASSERT_NE(opt_baz.operator->(), nullptr); + CORO_ASSERT_EQ(opt_baz.get_unsafe_only_for_testing(), baz.get()); + + // No bad_local_object exception - works transparently + auto error = CO_AWAIT opt_baz->callback(45); + CORO_ASSERT_EQ(error, 0); + } + + CO_RETURN true; +} + +TYPED_TEST(optimistic_ptr_test, optimistic_ptr_transparent_access_test) +{ + run_coro_test(*this, [](auto& lib) { return optimistic_ptr_transparent_access_test(lib); }); +} + +// Test 7: Circular dependency resolution use case +template CORO_TASK(bool) optimistic_ptr_circular_dependency_test(T& lib) +{ + // Simulate circular dependency scenario: + // - Host owns children (shared_ptr) + // - Children hold optimistic_ptr to host (no RAII ownership) + + // Get example object (local or remote depending on setup) + auto example = lib.get_example(); + CORO_ASSERT_NE(example, nullptr); + + // Create host (will be local or marshalled depending on setup) + rpc::shared_ptr host; + CORO_ASSERT_EQ(CO_AWAIT example->create_foo(host), 0); + CORO_ASSERT_NE(host, nullptr); + + // Create child + rpc::shared_ptr child_ref; + CORO_ASSERT_EQ(CO_AWAIT host->create_baz_interface(child_ref), 0); + + // Child could hold optimistic_ptr back to host (breaking circular RAII ownership) + rpc::optimistic_ptr opt_host; + auto err = CO_AWAIT rpc::make_optimistic(host, opt_host); + CORO_ASSERT_EQ(err, rpc::error::OK()); + CORO_ASSERT_NE(opt_host.get_unsafe_only_for_testing(), nullptr); + + auto host_object_id = rpc::casting_interface::get_object_id(*host); + auto waiter = std::make_shared(host_object_id); + + // Schedule verification - handles both local (immediate) and remote (async) cases + // CRITICAL: Pass opt_host as argument to ensure it lives in the coroutine frame + waiter->schedule( + lib.get_root_service(), + host, + [](auto opt_host) -> CORO_TASK(void) + { + // opt_host still exists but points to deleted object + // This is correct behavior - circular dependency is broken + EXPECT_NE(opt_host.get_unsafe_only_for_testing(), nullptr); // Control block remains + + CO_RETURN; + }, + opt_host); // Pass as argument instead of capturing + + // If we delete host (last shared_ptr), object is destroyed + // even though optimistic_ptr exists (weak semantics) + host.reset(); + + // For local objects, run verification immediately; for remote, it runs via async callback + CO_AWAIT waiter->run_if_local(); + + CO_RETURN true; +} + +TYPED_TEST(optimistic_ptr_test, optimistic_ptr_circular_dependency_test) +{ + GTEST_SKIP() << "skipped for now."; + run_coro_test(*this, [](auto& lib) { return optimistic_ptr_circular_dependency_test(lib); }); +} + +// Test 8: Comparison and nullptr operations +template CORO_TASK(bool) optimistic_ptr_comparison_test(T& lib) +{ + // Get example object (local or remote depending on setup) + auto example = lib.get_example(); + CORO_ASSERT_NE(example, nullptr); + + // Create two separate foo objects + rpc::shared_ptr f1; + CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f1), 0); + CORO_ASSERT_NE(f1, nullptr); + + rpc::shared_ptr f2; + CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f2), 0); + CORO_ASSERT_NE(f2, nullptr); + + rpc::optimistic_ptr opt_f1; + auto err = CO_AWAIT rpc::make_optimistic(f1, opt_f1); + CORO_ASSERT_EQ(err, rpc::error::OK()); + + rpc::optimistic_ptr opt_f2; + err = CO_AWAIT rpc::make_optimistic(f2, opt_f2); + CORO_ASSERT_EQ(err, rpc::error::OK()); + + rpc::optimistic_ptr opt_null; + + // Test equality using get_unsafe_only_for_testing() + CORO_ASSERT_NE(opt_f1.get_unsafe_only_for_testing(), opt_f2.get_unsafe_only_for_testing()); + CORO_ASSERT_EQ(opt_f1.get_unsafe_only_for_testing(), opt_f1.get_unsafe_only_for_testing()); + + // Test nullptr comparison + CORO_ASSERT_EQ(opt_null.get_unsafe_only_for_testing(), nullptr); + CORO_ASSERT_NE(opt_f1.get_unsafe_only_for_testing(), nullptr); + + // Test bool operator + CORO_ASSERT_EQ(static_cast(opt_null), false); + CORO_ASSERT_EQ(static_cast(opt_f1), true); + + // Test assignment to nullptr + opt_f1 = nullptr; + CORO_ASSERT_EQ(opt_f1.get_unsafe_only_for_testing(), nullptr); + + CO_RETURN true; +} + +TYPED_TEST(optimistic_ptr_test, optimistic_ptr_comparison_test) +{ + run_coro_test(*this, [](auto& lib) { return optimistic_ptr_comparison_test(lib); }); +} + +// Test 9: Heterogeneous upcast (statically verifiable) +template CORO_TASK(bool) optimistic_ptr_heterogeneous_upcast_test(T& lib) +{ + // Get example object (local or remote depending on setup) + auto example = lib.get_example(); + CORO_ASSERT_NE(example, nullptr); + + // Create foo through example (will be local or marshalled depending on setup) + rpc::shared_ptr f; + CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f), 0); + CORO_ASSERT_NE(f, nullptr); + + // Create a baz object (implements both i_baz and i_bar) + rpc::shared_ptr baz; + CORO_ASSERT_EQ(CO_AWAIT f->create_baz_interface(baz), 0); + CORO_ASSERT_NE(baz, nullptr); + + // Create optimistic_ptr + rpc::optimistic_ptr opt_baz; + auto err = CO_AWAIT rpc::make_optimistic(baz, opt_baz); + CORO_ASSERT_EQ(err, rpc::error::OK()); + CORO_ASSERT_NE(opt_baz.get_unsafe_only_for_testing(), nullptr); + + // Upcast to i_bar (statically verifiable - should compile) + // Note: This requires i_baz to properly derive from i_bar + // For now, test with same type conversion + rpc::optimistic_ptr opt_baz2(opt_baz); + CORO_ASSERT_NE(opt_baz2.get_unsafe_only_for_testing(), nullptr); + CORO_ASSERT_EQ(opt_baz2.get_unsafe_only_for_testing(), opt_baz.get_unsafe_only_for_testing()); + + CO_RETURN true; +} + +TYPED_TEST(optimistic_ptr_test, optimistic_ptr_heterogeneous_upcast_test) +{ + run_coro_test(*this, [](auto& lib) { return optimistic_ptr_heterogeneous_upcast_test(lib); }); +} + +// Test 10: Multiple optimistic_ptr instances to same object +template CORO_TASK(bool) optimistic_ptr_multiple_refs_test(T& lib) +{ + // Get example object (local or remote depending on setup) + auto example = lib.get_example(); + CORO_ASSERT_NE(example, nullptr); + + // Create foo through example (will be local or marshalled depending on setup) + rpc::shared_ptr f; + CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f), 0); + CORO_ASSERT_NE(f, nullptr); + + // Create multiple optimistic_ptr instances to same object + rpc::optimistic_ptr opt_f1; + auto err = CO_AWAIT rpc::make_optimistic(f, opt_f1); + CORO_ASSERT_EQ(err, rpc::error::OK()); + + rpc::optimistic_ptr opt_f2; + err = CO_AWAIT rpc::make_optimistic(f, opt_f2); + CORO_ASSERT_EQ(err, rpc::error::OK()); + + rpc::optimistic_ptr opt_f3(opt_f1); + rpc::optimistic_ptr opt_f4 = opt_f2; + + // All should point to same object + CORO_ASSERT_EQ(opt_f1.get_unsafe_only_for_testing(), f.get()); + CORO_ASSERT_EQ(opt_f2.get_unsafe_only_for_testing(), f.get()); + CORO_ASSERT_EQ(opt_f3.get_unsafe_only_for_testing(), f.get()); + CORO_ASSERT_EQ(opt_f4.get_unsafe_only_for_testing(), f.get()); + + // All should be equal + CORO_ASSERT_EQ(opt_f1.get_unsafe_only_for_testing(), opt_f2.get_unsafe_only_for_testing()); + CORO_ASSERT_EQ(opt_f2.get_unsafe_only_for_testing(), opt_f3.get_unsafe_only_for_testing()); + CORO_ASSERT_EQ(opt_f3.get_unsafe_only_for_testing(), opt_f4.get_unsafe_only_for_testing()); + + CO_RETURN true; +} + +TYPED_TEST(optimistic_ptr_test, optimistic_ptr_multiple_refs_test) +{ + run_coro_test(*this, [](auto& lib) { return optimistic_ptr_multiple_refs_test(lib); }); +} + +struct object_gone_event : public rpc::service_event +{ + std::weak_ptr svc_; + std::function callback_; + rpc::object object_id_; + rpc::destination_zone destination_; + virtual ~object_gone_event() + { + auto svc = svc_.lock(); + if (svc) + { + // svc->remove_service_event(rpc::service_event::shared_from_this()); + } + } + + static std::shared_ptr create(std::shared_ptr svc, + std::function callback, + rpc::object object_id, + rpc::destination_zone destination) + { + auto ret = std::make_shared(); + ret->object_id_ = object_id; + ret->destination_ = destination; + ret->callback_ = callback; + svc->add_service_event(ret); + ret->svc_ = svc; // do it after addin service + return ret; + } + + /** + * @brief Called when an object is released in a remote zone + * @param object_id The ID of the released object + * @param destination The zone where the object was released + */ + virtual CORO_TASK(void) on_object_released(rpc::object object_id, rpc::destination_zone destination) override + { + if (object_id_ == object_id && destination_ == destination) + { + CO_AWAIT callback_(); + } + CO_RETURN; + } +}; + +// Test 11: optimistic_ptr OBJECT_GONE behavior when remote stub is deleted +template CORO_TASK(bool) optimistic_ptr_object_gone_test(T& lib) +{ + // Get example object (local or remote depending on setup) + auto example = lib.get_example(); + CORO_ASSERT_NE(example, nullptr); + + // Create foo through example (will be local or marshalled depending on setup) + rpc::shared_ptr f; + CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f), 0); + CORO_ASSERT_NE(f, nullptr); + + // Create baz interface (local or marshalled depending on setup) + rpc::shared_ptr baz; + CORO_ASSERT_EQ(CO_AWAIT f->create_baz_interface(baz), 0); + CORO_ASSERT_NE(baz, nullptr); + + // Get object_id directly from the interface (avoids service mutex) + auto baz_object_id = rpc::casting_interface::get_object_id(*baz); + + // Test OBJECT_GONE for REMOTE objects only + // Create optimistic_ptr from shared_ptr + rpc::optimistic_ptr opt_baz; + auto err = CO_AWAIT rpc::make_optimistic(baz, opt_baz); + CORO_ASSERT_EQ(err, rpc::error::OK()); + CORO_ASSERT_NE(opt_baz.get_unsafe_only_for_testing(), nullptr); + + // First call should work - shared_ptr keeps stub alive + auto error1 = CO_AWAIT opt_baz->callback(42); + CORO_ASSERT_EQ(error1, 0); + + // Register for object deletion notification with continuation for verification + auto waiter = std::make_shared(baz_object_id); + + // Schedule verification - handles both local (immediate) and remote (async) cases + // CRITICAL: Pass opt_baz as argument to ensure it lives in the coroutine frame + waiter->schedule( + lib.get_root_service(), + baz, + [](auto opt_baz) -> CORO_TASK(void) + { + // This runs after the object is deleted + // Second call through optimistic_ptr should fail with OBJECT_GONE + // The optimistic_ptr still exists but the remote stub has been deleted + auto error2 = CO_AWAIT opt_baz->callback(43); + EXPECT_EQ(error2, rpc::error::OBJECT_GONE()); + + // The optimistic_ptr itself remains valid (pointer not null) + EXPECT_NE(opt_baz.get_unsafe_only_for_testing(), nullptr); + + CO_RETURN; + }, + opt_baz); // Pass as argument instead of capturing + + // Release the shared_ptr - for remote this triggers async cleanup, for local it's immediate + baz.reset(); + f.reset(); + + // For local objects, run verification immediately; for remote, it runs via async callback + CO_AWAIT waiter->run_if_local(); + + CO_RETURN true; +} + +TYPED_TEST(optimistic_ptr_test, optimistic_ptr_object_gone_test) +{ + GTEST_SKIP() << "skipped for now."; + run_coro_test(*this, [](auto& lib) { return optimistic_ptr_object_gone_test(lib); }); +} + +// Test 12: [post] attribute with optimistic_ptr - fire-and-forget through optimistic pointer +template CORO_TASK(bool) coro_post_with_optimistic_ptr(T& lib) +{ + // Create a foo instance to test with + rpc::shared_ptr i_foo_ptr; + auto ret = CO_AWAIT lib.get_example()->create_foo(i_foo_ptr); + CORO_ASSERT_EQ(ret, rpc::error::OK()); + CORO_ASSERT_NE(i_foo_ptr, nullptr); + + // Convert to optimistic_ptr + rpc::optimistic_ptr opt_foo; + auto opt_ret = CO_AWAIT rpc::make_optimistic(i_foo_ptr, opt_foo); + CORO_ASSERT_EQ(opt_ret, rpc::error::OK()); + + // Clear any existing messages first (through optimistic pointer) + auto clear_ret = CO_AWAIT opt_foo->clear_recorded_messages(); + CORO_ASSERT_EQ(clear_ret, rpc::error::OK()); + + // Test 1: Send multiple [post] messages through optimistic_ptr and verify ordering + const int num_messages = 10; + for (int i = 0; i < num_messages; ++i) + { + // [post] methods through optimistic_ptr - fire-and-forget + auto post_ret = CO_AWAIT opt_foo->record_message(i); + CORO_ASSERT_EQ(post_ret, rpc::error::OK()); + } + + // Give some time for all messages to be processed +#ifdef CANOPY_BUILD_COROUTINE + for (int i = 0; i < num_messages; ++i) + { + lib.get_scheduler()->process_events(std::chrono::milliseconds(1)); + } +#else + std::this_thread::sleep_for(std::chrono::milliseconds(100)); +#endif + + // Retrieve recorded messages and verify ordering (through optimistic pointer) + std::vector recorded_messages; + auto get_ret = CO_AWAIT opt_foo->get_recorded_messages(recorded_messages); + CORO_ASSERT_EQ(get_ret, rpc::error::OK()); + + // Verify all messages were received + CORO_ASSERT_EQ(recorded_messages.size(), num_messages); + + // Verify messages were received in the order they were sent + for (int i = 0; i < num_messages; ++i) + { + CORO_ASSERT_EQ(recorded_messages[i], i); + } + + // Test 2: Clear and verify + clear_ret = CO_AWAIT opt_foo->clear_recorded_messages(); + CORO_ASSERT_EQ(clear_ret, rpc::error::OK()); + + recorded_messages.clear(); + get_ret = CO_AWAIT opt_foo->get_recorded_messages(recorded_messages); + CORO_ASSERT_EQ(get_ret, rpc::error::OK()); + CORO_ASSERT_EQ(recorded_messages.size(), 0); + + // Test 3: Larger batch through optimistic_ptr + const int large_batch = 50; + for (int i = 0; i < large_batch; ++i) + { + auto post_ret = CO_AWAIT opt_foo->record_message(i * 3); // Send multiples of 3 + CORO_ASSERT_EQ(post_ret, rpc::error::OK()); + } + +#ifdef CANOPY_BUILD_COROUTINE + for (int i = 0; i < large_batch; ++i) + { + lib.get_scheduler()->process_events(std::chrono::milliseconds(1)); + } +#else + std::this_thread::sleep_for(std::chrono::milliseconds(150)); +#endif + + recorded_messages.clear(); + get_ret = CO_AWAIT opt_foo->get_recorded_messages(recorded_messages); + CORO_ASSERT_EQ(get_ret, rpc::error::OK()); + CORO_ASSERT_EQ(recorded_messages.size(), large_batch); + + // Verify ordering for large batch + for (int i = 0; i < large_batch; ++i) + { + CORO_ASSERT_EQ(recorded_messages[i], i * 3); + } + + RPC_INFO("Post with optimistic_ptr test completed - all {} messages received in order through optimistic pointer", + large_batch); + + CO_RETURN true; +} + +TYPED_TEST(optimistic_ptr_test, post_with_optimistic_ptr) +{ + run_coro_test(*this, [](auto& lib) { return coro_post_with_optimistic_ptr(lib); }); +} + +// ============================================================================ +// Marshalled optimistic_ptr via IDL tests +// ============================================================================ + +// Test: set and get optimistic_ptr through IDL-marshalled methods +template CORO_TASK(bool) optimistic_ptr_set_and_get_via_idl_test(T& lib) +{ + auto example = lib.get_example(); + CORO_ASSERT_NE(example, nullptr); + + // Create a foo via the example zone + rpc::shared_ptr f; + CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f), rpc::error::OK()); + CORO_ASSERT_NE(f, nullptr); + + // Create an optimistic_ptr from the shared_ptr + rpc::optimistic_ptr opt_f; + auto err = CO_AWAIT rpc::make_optimistic(f, opt_f); + CORO_ASSERT_EQ(err, rpc::error::OK()); + CORO_ASSERT_NE(opt_f.get_unsafe_only_for_testing(), nullptr); + + // Send it to the remote zone via IDL-marshalled set_optimistic_ptr + err = CO_AWAIT example->set_optimistic_ptr(opt_f); + CORO_ASSERT_EQ(err, rpc::error::OK()); + + // Retrieve it back via IDL-marshalled get_optimistic_ptr + rpc::optimistic_ptr opt_f_out; + err = CO_AWAIT example->get_optimistic_ptr(opt_f_out); + CORO_ASSERT_EQ(err, rpc::error::OK()); + CORO_ASSERT_NE(opt_f_out.get_unsafe_only_for_testing(), nullptr); + + // The shared_ptr f is still alive, so calling through the returned optimistic_ptr should succeed + err = CO_AWAIT opt_f_out->do_something_in_val(42); + CORO_ASSERT_EQ(err, rpc::error::OK()); + + CO_RETURN true; +} + +TYPED_TEST(optimistic_ptr_test, optimistic_ptr_set_and_get_via_idl) +{ + run_coro_test(*this, [](auto& lib) { return optimistic_ptr_set_and_get_via_idl_test(lib); }); +} + +// Test: get returns OBJECT_GONE when shared_ptr is released +template CORO_TASK(bool) optimistic_ptr_get_returns_object_gone_when_shared_released_test(T& lib) +{ + auto example = lib.get_example(); + CORO_ASSERT_NE(example, nullptr); + + // Create a foo via the example zone + rpc::shared_ptr f; + CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f), rpc::error::OK()); + CORO_ASSERT_NE(f, nullptr); + + // Create an optimistic_ptr and send to remote + rpc::optimistic_ptr opt_f; + auto err = CO_AWAIT rpc::make_optimistic(f, opt_f); + CORO_ASSERT_EQ(err, rpc::error::OK()); + + err = CO_AWAIT example->set_optimistic_ptr(opt_f); + CORO_ASSERT_EQ(err, rpc::error::OK()); + +#ifdef CANOPY_BUILD_COROUTINE + rpc::event ev; + auto cb = object_gone_event::create( + lib.get_root_service(), + [&]() -> CORO_TASK(void) + { + ev.set(); + CO_RETURN; + }, + rpc::casting_interface::get_object_id(*f), + rpc::casting_interface::get_destination_zone(*f)); + +#endif + // Release the shared_ptr - the underlying object should be destroyed + f.reset(); + +#ifdef CANOPY_BUILD_COROUTINE + CO_AWAIT ev.wait(); + lib.get_root_service()->remove_service_event(cb); + cb.reset(); +#endif + + // Calling through it should return OBJECT_GONE since the shared_ptr is released + err = CO_AWAIT opt_f->do_something_in_val(42); + CORO_ASSERT_EQ(err, rpc::error::OBJECT_GONE()); + + opt_f.reset(); + + // Retrieve via get_optimistic_ptr + rpc::optimistic_ptr opt_f_out; + err = CO_AWAIT example->get_optimistic_ptr(opt_f_out); + CORO_ASSERT_EQ(err, rpc::error::OK()); + + CORO_ASSERT_EQ(nullptr, opt_f_out.get()); + + // clean up example so that it does not trigger an unclean service error + err = CO_AWAIT example->set_optimistic_ptr(nullptr); + CORO_ASSERT_EQ(err, rpc::error::OK()); + + CO_RETURN true; +} + +TYPED_TEST(optimistic_ptr_test, optimistic_ptr_get_returns_object_gone_when_shared_released) +{ + GTEST_SKIP() << "skipped for now."; + run_coro_test(*this, [](auto& lib) { return optimistic_ptr_get_returns_object_gone_when_shared_released_test(lib); }); +} + +// Test: null optimistic_ptr roundtrip +template CORO_TASK(bool) optimistic_ptr_null_roundtrip_test(T& lib) +{ + auto example = lib.get_example(); + CORO_ASSERT_NE(example, nullptr); + + // Create a default (null) optimistic_ptr + rpc::optimistic_ptr opt_null; + CORO_ASSERT_EQ(static_cast(opt_null), false); + + // Send it via set_optimistic_ptr + auto err = CO_AWAIT example->set_optimistic_ptr(opt_null); + CORO_ASSERT_EQ(err, rpc::error::OK()); + + // Retrieve it via get_optimistic_ptr + rpc::optimistic_ptr opt_null_out; + err = CO_AWAIT example->get_optimistic_ptr(opt_null_out); + CORO_ASSERT_EQ(err, rpc::error::OK()); + + // The retrieved pointer should be null + CORO_ASSERT_EQ(static_cast(opt_null_out), false); + CORO_ASSERT_EQ(opt_null_out.get_unsafe_only_for_testing(), nullptr); + + CO_RETURN true; +} + +TYPED_TEST(optimistic_ptr_test, optimistic_ptr_null_roundtrip) +{ + run_coro_test(*this, [](auto& lib) { return optimistic_ptr_null_roundtrip_test(lib); }); +} diff --git a/tests/test_host/post_functionality_test_suite.cpp b/tests/test_host/post_functionality_test_suite.cpp index 3e815e5..bb3b3df 100644 --- a/tests/test_host/post_functionality_test_suite.cpp +++ b/tests/test_host/post_functionality_test_suite.cpp @@ -207,102 +207,3 @@ TYPED_TEST(post_functionality_test, post_with_shared_ptr) { run_coro_test(*this, [](auto& lib) { return coro_test_post_with_shared_ptr(lib); }); } - -// Test for [post] attribute with optimistic_ptr - fire-and-forget through optimistic pointer -template CORO_TASK(bool) coro_post_with_optimistic_ptr(T& lib) -{ - // Create a foo instance to test with - rpc::shared_ptr i_foo_ptr; - auto ret = CO_AWAIT lib.get_example()->create_foo(i_foo_ptr); - CORO_ASSERT_EQ(ret, rpc::error::OK()); - CORO_ASSERT_NE(i_foo_ptr, nullptr); - - // Convert to optimistic_ptr - rpc::optimistic_ptr opt_foo; - auto opt_ret = CO_AWAIT rpc::make_optimistic(i_foo_ptr, opt_foo); - CORO_ASSERT_EQ(opt_ret, rpc::error::OK()); - - // Clear any existing messages first (through optimistic pointer) - auto clear_ret = CO_AWAIT opt_foo->clear_recorded_messages(); - CORO_ASSERT_EQ(clear_ret, rpc::error::OK()); - - // Test 1: Send multiple [post] messages through optimistic_ptr and verify ordering - const int num_messages = 10; - for (int i = 0; i < num_messages; ++i) - { - // [post] methods through optimistic_ptr - fire-and-forget - auto post_ret = CO_AWAIT opt_foo->record_message(i); - CORO_ASSERT_EQ(post_ret, rpc::error::OK()); - } - - // Give some time for all messages to be processed -#ifdef CANOPY_BUILD_COROUTINE - for (int i = 0; i < num_messages; ++i) - { - lib.get_scheduler()->process_events(std::chrono::milliseconds(1)); - } -#else - std::this_thread::sleep_for(std::chrono::milliseconds(100)); -#endif - - // Retrieve recorded messages and verify ordering (through optimistic pointer) - std::vector recorded_messages; - auto get_ret = CO_AWAIT opt_foo->get_recorded_messages(recorded_messages); - CORO_ASSERT_EQ(get_ret, rpc::error::OK()); - - // Verify all messages were received - CORO_ASSERT_EQ(recorded_messages.size(), num_messages); - - // Verify messages were received in the order they were sent - for (int i = 0; i < num_messages; ++i) - { - CORO_ASSERT_EQ(recorded_messages[i], i); - } - - // Test 2: Clear and verify - clear_ret = CO_AWAIT opt_foo->clear_recorded_messages(); - CORO_ASSERT_EQ(clear_ret, rpc::error::OK()); - - recorded_messages.clear(); - get_ret = CO_AWAIT opt_foo->get_recorded_messages(recorded_messages); - CORO_ASSERT_EQ(get_ret, rpc::error::OK()); - CORO_ASSERT_EQ(recorded_messages.size(), 0); - - // Test 3: Larger batch through optimistic_ptr - const int large_batch = 50; - for (int i = 0; i < large_batch; ++i) - { - auto post_ret = CO_AWAIT opt_foo->record_message(i * 3); // Send multiples of 3 - CORO_ASSERT_EQ(post_ret, rpc::error::OK()); - } - -#ifdef CANOPY_BUILD_COROUTINE - for (int i = 0; i < large_batch; ++i) - { - lib.get_scheduler()->process_events(std::chrono::milliseconds(1)); - } -#else - std::this_thread::sleep_for(std::chrono::milliseconds(150)); -#endif - - recorded_messages.clear(); - get_ret = CO_AWAIT opt_foo->get_recorded_messages(recorded_messages); - CORO_ASSERT_EQ(get_ret, rpc::error::OK()); - CORO_ASSERT_EQ(recorded_messages.size(), large_batch); - - // Verify ordering for large batch - for (int i = 0; i < large_batch; ++i) - { - CORO_ASSERT_EQ(recorded_messages[i], i * 3); - } - - RPC_INFO("Post with optimistic_ptr test completed - all {} messages received in order through optimistic pointer", - large_batch); - - CO_RETURN true; -} - -TYPED_TEST(post_functionality_test, post_with_optimistic_ptr) -{ - run_coro_test(*this, [](auto& lib) { return coro_post_with_optimistic_ptr(lib); }); -} diff --git a/tests/test_host/remote_type_test_suite.cpp b/tests/test_host/remote_type_test_suite.cpp index d3eff89..233a13e 100644 --- a/tests/test_host/remote_type_test_suite.cpp +++ b/tests/test_host/remote_type_test_suite.cpp @@ -1694,7 +1694,7 @@ do_something_in_val( CO_RETURN __rpc_ret; } } - if (__rpc_ret >= rpc::error::MIN() && __rpc_ret <= rpc::error::MAX()) + if (rpc::error::is_error(__rpc_ret)) { // if you fall into this rabbit hole ensure that you have added any error offsets compatible with your error // code system to the rpc library this is only here to handle rpc generated errors and not application @@ -1739,7 +1739,7 @@ template CORO_TASK(bool) coro_test_explicit_format_fallback_with_invali auto invalid_encoding = static_cast(999); // now use a hacked do_something_in_val taking the object proxy from the original interface and force feed in a bad encoding - auto __rpc_op = foo_proxy->get_object_proxy(); + auto __rpc_op = foo_proxy->__rpc_get_object_proxy(); error = CO_AWAIT do_something_in_val(test_value, __rpc_op, rpc::VERSION_3, invalid_encoding); // Call should succeed due to automatic fallback @@ -1766,7 +1766,7 @@ template CORO_TASK(bool) coro_test_explicit_version_fallback_using_dire CORO_ASSERT_EQ(CO_AWAIT lib.get_example()->create_foo(foo_proxy), 0); // Get the object proxy for direct testing - auto __rpc_op = foo_proxy->get_object_proxy(); + auto __rpc_op = foo_proxy->__rpc_get_object_proxy(); auto service_proxy = rpc::casting_interface::get_service_proxy(*foo_proxy); auto original_version = service_proxy->get_remote_rpc_version(); diff --git a/tests/test_host/type_test_fixture.h b/tests/test_host/type_test_fixture.h index fade375..1f1caa2 100644 --- a/tests/test_host/type_test_fixture.h +++ b/tests/test_host/type_test_fixture.h @@ -24,7 +24,7 @@ template class type_test : public testing::Test }; // Helper class to wait for object deletion notification and run continuation -class object_deletion_waiter : public rpc::service_event, public std::enable_shared_from_this +class object_deletion_waiter : public rpc::service_event { rpc::object expected_object_id_; std::function continuation_; @@ -79,7 +79,7 @@ class object_deletion_waiter : public rpc::service_event, public std::enable_sha Lambda&& verification_lambda, Args&&... args) { - is_local_ = obj.get()->is_local(); + is_local_ = obj.get()->__rpc_is_local(); continuation_scheduled_ = true; if (is_local_) @@ -102,7 +102,7 @@ class object_deletion_waiter : public rpc::service_event, public std::enable_sha = [service, self, verification_lambda, ... captured = std::forward(args)]() -> CORO_TASK(void) { CO_AWAIT verification_lambda(captured...); - self->continuation_completed_ = true; + ((object_deletion_waiter*)self.get())->continuation_completed_ = true; // Remove event listener after verification service->remove_service_event(self); CO_RETURN; @@ -175,5 +175,5 @@ void run_coro_test(TestFixture& test_fixture, CoroFunc&& coro_function, Args&&.. #else coro_function(lib, std::forward(args)...); #endif - ASSERT_EQ(lib.error_has_occured(), false); + ASSERT_EQ(lib.error_has_occurred(), false); } diff --git a/tests/test_host/type_test_local_suite.cpp b/tests/test_host/type_test_local_suite.cpp index 9e7a866..f0bddb7 100644 --- a/tests/test_host/type_test_local_suite.cpp +++ b/tests/test_host/type_test_local_suite.cpp @@ -195,541 +195,3 @@ TYPED_TEST(type_test, dyanmic_cast_tests) return dyanmic_cast_tests(root_service); }); } - -#ifndef TEST_STL_COMPLIANCE - -template class optimistic_ptr_test : public type_test -{ -}; - -TYPED_TEST_SUITE(optimistic_ptr_test, local_implementations); - -// ============================================================================ -// Optimistic Pointer Tests -// ============================================================================ - -// Test 1: Basic optimistic_ptr construction and lifecycle -CORO_TASK(bool) optimistic_ptr_basic_lifecycle_test(std::shared_ptr root_service) -{ - // Create a shared_ptr to a local object - auto f = rpc::shared_ptr(new foo()); - CORO_ASSERT_NE(f, nullptr); - - // Create optimistic_ptr from shared_ptr - rpc::optimistic_ptr opt_f; - auto err = CO_AWAIT rpc::make_optimistic(f, opt_f); - CORO_ASSERT_EQ(err, rpc::error::OK()); - CORO_ASSERT_NE(opt_f.get_unsafe_only_for_testing(), nullptr); - CORO_ASSERT_EQ(opt_f.get_unsafe_only_for_testing(), f.get()); - - // Test copy constructor - rpc::optimistic_ptr opt_f_copy(opt_f); - CORO_ASSERT_NE(opt_f_copy.get_unsafe_only_for_testing(), nullptr); - CORO_ASSERT_EQ(opt_f_copy.get_unsafe_only_for_testing(), opt_f.get_unsafe_only_for_testing()); - - // Test move constructor - rpc::optimistic_ptr opt_f_move(std::move(opt_f_copy)); - CORO_ASSERT_NE(opt_f_move.get_unsafe_only_for_testing(), nullptr); - CORO_ASSERT_EQ(opt_f_move.get_unsafe_only_for_testing(), - opt_f.get_unsafe_only_for_testing()); // Should point to same local_proxy - CORO_ASSERT_EQ(opt_f_copy.get_unsafe_only_for_testing(), nullptr); // Moved-from should be null - - // Test assignment - rpc::optimistic_ptr opt_f_assigned; - CORO_ASSERT_EQ(opt_f_assigned.get_unsafe_only_for_testing(), nullptr); - opt_f_assigned = opt_f_move; - CORO_ASSERT_NE(opt_f_assigned.get_unsafe_only_for_testing(), nullptr); - CORO_ASSERT_EQ(opt_f_assigned.get_unsafe_only_for_testing(), f.get()); - - // Test reset - opt_f_move.reset(); - CORO_ASSERT_EQ(opt_f_move.get_unsafe_only_for_testing(), nullptr); - - CO_RETURN true; -} - -TYPED_TEST(optimistic_ptr_test, optimistic_ptr_basic_lifecycle_test) -{ - auto& lib = this->get_lib(); - auto root_service = lib.get_root_service(); - run_coro_test(*this, - [root_service](auto& lib) - { - (void)lib; - return optimistic_ptr_basic_lifecycle_test(root_service); - }); -} - -// Test 2: Optimistic pointer weak semantics for local objects -CORO_TASK(bool) optimistic_ptr_weak_semantics_local_test(std::shared_ptr root_service) -{ - rpc::optimistic_ptr opt_f; - - { - // Create a shared_ptr to a local object - auto f = rpc::shared_ptr(new foo()); - CORO_ASSERT_NE(f, nullptr); - - // Create optimistic_ptr from shared_ptr - auto err = CO_AWAIT rpc::make_optimistic(f, opt_f); - CORO_ASSERT_EQ(err, rpc::error::OK()); - CORO_ASSERT_NE(opt_f.get_unsafe_only_for_testing(), nullptr); - - // Verify object is accessible - CORO_ASSERT_EQ(opt_f.get_unsafe_only_for_testing(), f.get()); - // f goes out of scope - object should be deleted - // (optimistic_ptr has weak semantics for local objects) - } - - // opt_f still exists and points to local_proxy (which is still alive via shared_ptr) - // The local_proxy internally has a weak_ptr which will fail to lock - // This is valid behavior per spec - optimistic_ptr has weak semantics - CORO_ASSERT_NE(opt_f.get_unsafe_only_for_testing(), nullptr); // local_proxy still exists - - CO_RETURN true; -} - -TYPED_TEST(optimistic_ptr_test, optimistic_ptr_weak_semantics_local_test) -{ - auto& lib = this->get_lib(); - auto root_service = lib.get_root_service(); - run_coro_test(*this, - [root_service](auto& lib) - { - (void)lib; - return optimistic_ptr_weak_semantics_local_test(root_service); - }); -} - -// Test 3: optimistic_ptr local_proxy pattern test -CORO_TASK(bool) optimistic_ptr_local_proxy_test(std::shared_ptr root_service) -{ - rpc::shared_ptr f; - rpc::optimistic_ptr opt_f; - - // Create a shared_ptr to a local object - f = rpc::shared_ptr(new foo()); - CORO_ASSERT_NE(f, nullptr); - - // Create optimistic_ptr from shared_ptr - // optimistic_ptr should automatically create a local_proxy - auto err = CO_AWAIT rpc::make_optimistic(f, opt_f); - CORO_ASSERT_EQ(err, rpc::error::OK()); - CORO_ASSERT_NE(opt_f.get_unsafe_only_for_testing(), nullptr); - - // Test calling through the optimistic_ptr while object is alive - err = CO_AWAIT opt_f->do_something_in_val(42); - CORO_ASSERT_EQ(err, rpc::error::OK()); - - // Clear the shared_ptr - object will be deleted - f.reset(); - CORO_ASSERT_EQ(f, nullptr); - - // optimistic_ptr still points to the local_proxy - // but calling through it should return OBJECT_GONE - err = CO_AWAIT opt_f->do_something_in_val(42); - CORO_ASSERT_EQ(err, rpc::error::OBJECT_GONE()); - - // The local_proxy is still valid (weak_ptr failed to lock) - CORO_ASSERT_NE(opt_f.get_unsafe_only_for_testing(), nullptr); - - CO_RETURN true; -} - -TYPED_TEST(optimistic_ptr_test, optimistic_ptr_local_proxy_test) -{ - auto& lib = this->get_lib(); - auto root_service = lib.get_root_service(); - run_coro_test(*this, - [root_service](auto& lib) - { - (void)lib; - return optimistic_ptr_local_proxy_test(root_service); - }); -} - -// Test 4: Optimistic pointer semantics (weak for local, shared for remote) -template CORO_TASK(bool) optimistic_ptr_remote_shared_semantics_test(T& lib) -{ - // Get example object (local or remote depending on setup) - auto example = lib.get_example(); - CORO_ASSERT_NE(example, nullptr); - - // Create foo through example (will be local or marshalled depending on setup) - rpc::shared_ptr f; - CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f), 0); - CORO_ASSERT_NE(f, nullptr); - - // Create baz interface (local or marshalled depending on setup) - rpc::shared_ptr baz; - CORO_ASSERT_EQ(CO_AWAIT f->create_baz_interface(baz), 0); - CORO_ASSERT_NE(baz, nullptr); - - // Release f to ensure it's not holding any references to baz via cached_ member - f.reset(); - - // Store raw pointer for later comparison - auto* raw_ptr = baz.get(); - - // Get object_id directly from the interface (avoids service mutex) - auto baz_object_id = rpc::casting_interface::get_object_id(*baz); - - // Create optimistic_ptr - rpc::optimistic_ptr opt_baz; - auto err = CO_AWAIT rpc::make_optimistic(baz, opt_baz); - CORO_ASSERT_EQ(err, rpc::error::OK()); - CORO_ASSERT_NE(opt_baz.get_unsafe_only_for_testing(), nullptr); - - // Can call through optimistic_ptr directly (local_proxy handles weak semantics) - auto error2 = CO_AWAIT opt_baz->callback(42); - CORO_ASSERT_EQ(error2, 0); - - // Register for object deletion notification with continuation for verification - auto waiter = std::make_shared(baz_object_id); - - // Schedule verification - handles both local (immediate) and remote (async) cases - // CRITICAL: Pass opt_baz as argument to ensure it lives in the coroutine frame - waiter->schedule( - lib.get_root_service(), - baz, - [](auto opt_baz) -> CORO_TASK(void) - { - // This runs after the object is deleted - // The object is deleted when the last shared_ptr goes away - // local_proxy pointer is still valid but weak_ptr inside will fail to lock - EXPECT_NE(opt_baz.get_unsafe_only_for_testing(), nullptr); // local_proxy still exists - - // Calling through optimistic_ptr should return OBJECT_GONE (weak_ptr failed to lock) - auto error = CO_AWAIT opt_baz->callback(42); - EXPECT_EQ(error, rpc::error::OBJECT_GONE()); - - CO_RETURN; - }, - opt_baz); // Pass as argument instead of capturing - - // Clear the shared_ptr - for remote this triggers async cleanup, for local it's immediate - baz.reset(); - - // For local objects, run verification immediately; for remote, it runs via async callback - CO_AWAIT waiter->run_if_local(); - - CO_RETURN true; -} - -TYPED_TEST(optimistic_ptr_test, optimistic_ptr_remote_shared_semantics_test) -{ - GTEST_SKIP() << "skipped for now."; - run_coro_test(*this, [](auto& lib) { return optimistic_ptr_remote_shared_semantics_test(lib); }); -} - -// Test 5: Optimistic pointer transparent operator-> for both local and remote -template CORO_TASK(bool) optimistic_ptr_transparent_access_test(T& lib) -{ - auto example = lib.get_example(); - // Test 1: Local object access - { - // Create interface (local or remote depending on setup) - rpc::shared_ptr f_local; - CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f_local), 0); - CORO_ASSERT_NE(f_local, nullptr); - - rpc::optimistic_ptr opt_f_local; - auto err = CO_AWAIT rpc::make_optimistic(f_local, opt_f_local); - CORO_ASSERT_EQ(err, rpc::error::OK()); - - // operator-> works transparently for local object - CORO_ASSERT_NE(opt_f_local.operator->(), nullptr); - CORO_ASSERT_NE(opt_f_local.get_unsafe_only_for_testing(), nullptr); - CORO_ASSERT_EQ(opt_f_local.get_unsafe_only_for_testing(), f_local.get()); - } - - // Test 2: Remote object access - { - // Create interface (local or remote depending on setup) - rpc::shared_ptr baz; - CORO_ASSERT_EQ(CO_AWAIT example->create_baz(baz), 0); - CORO_ASSERT_NE(baz, nullptr); - - rpc::optimistic_ptr opt_baz; - auto err = CO_AWAIT rpc::make_optimistic(baz, opt_baz); - CORO_ASSERT_EQ(err, rpc::error::OK()); - - // operator-> works transparently for remote proxy - CORO_ASSERT_NE(opt_baz.operator->(), nullptr); - CORO_ASSERT_EQ(opt_baz.get_unsafe_only_for_testing(), baz.get()); - - // No bad_local_object exception - works transparently - auto error = CO_AWAIT opt_baz->callback(45); - CORO_ASSERT_EQ(error, 0); - } - - CO_RETURN true; -} - -TYPED_TEST(optimistic_ptr_test, optimistic_ptr_transparent_access_test) -{ - run_coro_test(*this, [](auto& lib) { return optimistic_ptr_transparent_access_test(lib); }); -} - -// Test 7: Circular dependency resolution use case -template CORO_TASK(bool) optimistic_ptr_circular_dependency_test(T& lib) -{ - // Simulate circular dependency scenario: - // - Host owns children (shared_ptr) - // - Children hold optimistic_ptr to host (no RAII ownership) - - // Get example object (local or remote depending on setup) - auto example = lib.get_example(); - CORO_ASSERT_NE(example, nullptr); - - // Create host (will be local or marshalled depending on setup) - rpc::shared_ptr host; - CORO_ASSERT_EQ(CO_AWAIT example->create_foo(host), 0); - CORO_ASSERT_NE(host, nullptr); - - // Create child - rpc::shared_ptr child_ref; - CORO_ASSERT_EQ(CO_AWAIT host->create_baz_interface(child_ref), 0); - - // Child could hold optimistic_ptr back to host (breaking circular RAII ownership) - rpc::optimistic_ptr opt_host; - auto err = CO_AWAIT rpc::make_optimistic(host, opt_host); - CORO_ASSERT_EQ(err, rpc::error::OK()); - CORO_ASSERT_NE(opt_host.get_unsafe_only_for_testing(), nullptr); - - auto host_object_id = rpc::casting_interface::get_object_id(*host); - auto waiter = std::make_shared(host_object_id); - - // Schedule verification - handles both local (immediate) and remote (async) cases - // CRITICAL: Pass opt_host as argument to ensure it lives in the coroutine frame - waiter->schedule( - lib.get_root_service(), - host, - [](auto opt_host) -> CORO_TASK(void) - { - // opt_host still exists but points to deleted object - // This is correct behavior - circular dependency is broken - EXPECT_NE(opt_host.get_unsafe_only_for_testing(), nullptr); // Control block remains - - CO_RETURN; - }, - opt_host); // Pass as argument instead of capturing - - // If we delete host (last shared_ptr), object is destroyed - // even though optimistic_ptr exists (weak semantics) - host.reset(); - - // For local objects, run verification immediately; for remote, it runs via async callback - CO_AWAIT waiter->run_if_local(); - - CO_RETURN true; -} - -TYPED_TEST(optimistic_ptr_test, optimistic_ptr_circular_dependency_test) -{ - GTEST_SKIP() << "skipped for now."; - run_coro_test(*this, [](auto& lib) { return optimistic_ptr_circular_dependency_test(lib); }); -} - -// Test 8: Comparison and nullptr operations -template CORO_TASK(bool) optimistic_ptr_comparison_test(T& lib) -{ - // Get example object (local or remote depending on setup) - auto example = lib.get_example(); - CORO_ASSERT_NE(example, nullptr); - - // Create two separate foo objects - rpc::shared_ptr f1; - CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f1), 0); - CORO_ASSERT_NE(f1, nullptr); - - rpc::shared_ptr f2; - CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f2), 0); - CORO_ASSERT_NE(f2, nullptr); - - rpc::optimistic_ptr opt_f1; - auto err = CO_AWAIT rpc::make_optimistic(f1, opt_f1); - CORO_ASSERT_EQ(err, rpc::error::OK()); - - rpc::optimistic_ptr opt_f2; - err = CO_AWAIT rpc::make_optimistic(f2, opt_f2); - CORO_ASSERT_EQ(err, rpc::error::OK()); - - rpc::optimistic_ptr opt_null; - - // Test equality using get_unsafe_only_for_testing() - CORO_ASSERT_NE(opt_f1.get_unsafe_only_for_testing(), opt_f2.get_unsafe_only_for_testing()); - CORO_ASSERT_EQ(opt_f1.get_unsafe_only_for_testing(), opt_f1.get_unsafe_only_for_testing()); - - // Test nullptr comparison - CORO_ASSERT_EQ(opt_null.get_unsafe_only_for_testing(), nullptr); - CORO_ASSERT_NE(opt_f1.get_unsafe_only_for_testing(), nullptr); - - // Test bool operator - CORO_ASSERT_EQ(static_cast(opt_null), false); - CORO_ASSERT_EQ(static_cast(opt_f1), true); - - // Test assignment to nullptr - opt_f1 = nullptr; - CORO_ASSERT_EQ(opt_f1.get_unsafe_only_for_testing(), nullptr); - - CO_RETURN true; -} - -TYPED_TEST(optimistic_ptr_test, optimistic_ptr_comparison_test) -{ - run_coro_test(*this, [](auto& lib) { return optimistic_ptr_comparison_test(lib); }); -} - -// Test 9: Heterogeneous upcast (statically verifiable) -template CORO_TASK(bool) optimistic_ptr_heterogeneous_upcast_test(T& lib) -{ - // Get example object (local or remote depending on setup) - auto example = lib.get_example(); - CORO_ASSERT_NE(example, nullptr); - - // Create foo through example (will be local or marshalled depending on setup) - rpc::shared_ptr f; - CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f), 0); - CORO_ASSERT_NE(f, nullptr); - - // Create a baz object (implements both i_baz and i_bar) - rpc::shared_ptr baz; - CORO_ASSERT_EQ(CO_AWAIT f->create_baz_interface(baz), 0); - CORO_ASSERT_NE(baz, nullptr); - - // Create optimistic_ptr - rpc::optimistic_ptr opt_baz; - auto err = CO_AWAIT rpc::make_optimistic(baz, opt_baz); - CORO_ASSERT_EQ(err, rpc::error::OK()); - CORO_ASSERT_NE(opt_baz.get_unsafe_only_for_testing(), nullptr); - - // Upcast to i_bar (statically verifiable - should compile) - // Note: This requires i_baz to properly derive from i_bar - // For now, test with same type conversion - rpc::optimistic_ptr opt_baz2(opt_baz); - CORO_ASSERT_NE(opt_baz2.get_unsafe_only_for_testing(), nullptr); - CORO_ASSERT_EQ(opt_baz2.get_unsafe_only_for_testing(), opt_baz.get_unsafe_only_for_testing()); - - CO_RETURN true; -} - -TYPED_TEST(optimistic_ptr_test, optimistic_ptr_heterogeneous_upcast_test) -{ - run_coro_test(*this, [](auto& lib) { return optimistic_ptr_heterogeneous_upcast_test(lib); }); -} - -// Test 10: Multiple optimistic_ptr instances to same object -template CORO_TASK(bool) optimistic_ptr_multiple_refs_test(T& lib) -{ - // Get example object (local or remote depending on setup) - auto example = lib.get_example(); - CORO_ASSERT_NE(example, nullptr); - - // Create foo through example (will be local or marshalled depending on setup) - rpc::shared_ptr f; - CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f), 0); - CORO_ASSERT_NE(f, nullptr); - - // Create multiple optimistic_ptr instances to same object - rpc::optimistic_ptr opt_f1; - auto err = CO_AWAIT rpc::make_optimistic(f, opt_f1); - CORO_ASSERT_EQ(err, rpc::error::OK()); - - rpc::optimistic_ptr opt_f2; - err = CO_AWAIT rpc::make_optimistic(f, opt_f2); - CORO_ASSERT_EQ(err, rpc::error::OK()); - - rpc::optimistic_ptr opt_f3(opt_f1); - rpc::optimistic_ptr opt_f4 = opt_f2; - - // All should point to same object - CORO_ASSERT_EQ(opt_f1.get_unsafe_only_for_testing(), f.get()); - CORO_ASSERT_EQ(opt_f2.get_unsafe_only_for_testing(), f.get()); - CORO_ASSERT_EQ(opt_f3.get_unsafe_only_for_testing(), f.get()); - CORO_ASSERT_EQ(opt_f4.get_unsafe_only_for_testing(), f.get()); - - // All should be equal - CORO_ASSERT_EQ(opt_f1.get_unsafe_only_for_testing(), opt_f2.get_unsafe_only_for_testing()); - CORO_ASSERT_EQ(opt_f2.get_unsafe_only_for_testing(), opt_f3.get_unsafe_only_for_testing()); - CORO_ASSERT_EQ(opt_f3.get_unsafe_only_for_testing(), opt_f4.get_unsafe_only_for_testing()); - - CO_RETURN true; -} - -TYPED_TEST(optimistic_ptr_test, optimistic_ptr_multiple_refs_test) -{ - run_coro_test(*this, [](auto& lib) { return optimistic_ptr_multiple_refs_test(lib); }); -} - -// Test 11: optimistic_ptr OBJECT_GONE behavior when remote stub is deleted -template CORO_TASK(bool) optimistic_ptr_object_gone_test(T& lib) -{ - // Get example object (local or remote depending on setup) - auto example = lib.get_example(); - CORO_ASSERT_NE(example, nullptr); - - // Create foo through example (will be local or marshalled depending on setup) - rpc::shared_ptr f; - CORO_ASSERT_EQ(CO_AWAIT example->create_foo(f), 0); - CORO_ASSERT_NE(f, nullptr); - - // Create baz interface (local or marshalled depending on setup) - rpc::shared_ptr baz; - CORO_ASSERT_EQ(CO_AWAIT f->create_baz_interface(baz), 0); - CORO_ASSERT_NE(baz, nullptr); - - // Get object_id directly from the interface (avoids service mutex) - auto baz_object_id = rpc::casting_interface::get_object_id(*baz); - - // Test OBJECT_GONE for REMOTE objects only - // Create optimistic_ptr from shared_ptr - rpc::optimistic_ptr opt_baz; - auto err = CO_AWAIT rpc::make_optimistic(baz, opt_baz); - CORO_ASSERT_EQ(err, rpc::error::OK()); - CORO_ASSERT_NE(opt_baz.get_unsafe_only_for_testing(), nullptr); - - // First call should work - shared_ptr keeps stub alive - auto error1 = CO_AWAIT opt_baz->callback(42); - CORO_ASSERT_EQ(error1, 0); - - // Register for object deletion notification with continuation for verification - auto waiter = std::make_shared(baz_object_id); - - // Schedule verification - handles both local (immediate) and remote (async) cases - // CRITICAL: Pass opt_baz as argument to ensure it lives in the coroutine frame - waiter->schedule( - lib.get_root_service(), - baz, - [](auto opt_baz) -> CORO_TASK(void) - { - // This runs after the object is deleted - // Second call through optimistic_ptr should fail with OBJECT_GONE - // The optimistic_ptr still exists but the remote stub has been deleted - auto error2 = CO_AWAIT opt_baz->callback(43); - EXPECT_EQ(error2, rpc::error::OBJECT_GONE()); - - // The optimistic_ptr itself remains valid (pointer not null) - EXPECT_NE(opt_baz.get_unsafe_only_for_testing(), nullptr); - - CO_RETURN; - }, - opt_baz); // Pass as argument instead of capturing - - // Release the shared_ptr - for remote this triggers async cleanup, for local it's immediate - baz.reset(); - f.reset(); - - // For local objects, run verification immediately; for remote, it runs via async callback - CO_AWAIT waiter->run_if_local(); - - CO_RETURN true; -} - -TYPED_TEST(optimistic_ptr_test, optimistic_ptr_object_gone_test) -{ - GTEST_SKIP() << "skipped for now."; - run_coro_test(*this, [](auto& lib) { return optimistic_ptr_object_gone_test(lib); }); -} - -#endif // TEST_STL_COMPLIANCE diff --git a/tests/unit_tests/member_ptr/CMakeLists.txt b/tests/unit_tests/member_ptr/CMakeLists.txt index 713e3a6..0835d5e 100644 --- a/tests/unit_tests/member_ptr/CMakeLists.txt +++ b/tests/unit_tests/member_ptr/CMakeLists.txt @@ -30,7 +30,8 @@ if(NOT CANOPY_BUILD_COROUTINE) GTest::gtest_main yas_common rpc::rpc - rpc_types_idl) + rpc_types_idl + member_ptr_test_idl) # Add include directories for RPC headers target_include_directories( diff --git a/tests/unit_tests/passthrough/passthrough_test.cpp b/tests/unit_tests/passthrough/passthrough_test.cpp index 859eb9b..f8df084 100644 --- a/tests/unit_tests/passthrough/passthrough_test.cpp +++ b/tests/unit_tests/passthrough/passthrough_test.cpp @@ -102,7 +102,7 @@ void run_passthrough_test(TestFixture& test_fixture, CoroFunc&& coro_function) #else coro_function(lib); #endif - ASSERT_EQ(lib.error_has_occured(), false); + ASSERT_EQ(lib.error_has_occurred(), false); } // ============================================================================ diff --git a/transports/direct/tests/transport/tests/direct/setup.h b/transports/direct/tests/transport/tests/direct/setup.h index 9e14731..54b58d7 100644 --- a/transports/direct/tests/transport/tests/direct/setup.h +++ b/transports/direct/tests/transport/tests/direct/setup.h @@ -30,7 +30,7 @@ template class in_memory_setup #ifdef CANOPY_BUILD_COROUTINE std::shared_ptr io_scheduler_; #endif - bool error_has_occured_ = false; + bool error_has_occurred_ = false; public: virtual ~in_memory_setup() = default; @@ -47,14 +47,14 @@ template class in_memory_setup #ifdef CANOPY_BUILD_COROUTINE std::shared_ptr get_scheduler() const { return io_scheduler_; } #endif - bool error_has_occured() const { return error_has_occured_; } + bool error_has_occurred() const { return error_has_occurred_; } CORO_TASK(void) check_for_error(CORO_TASK(bool) task) { auto ret = CO_AWAIT task; if (!ret) { - error_has_occured_ = true; + error_has_occurred_ = true; } CO_RETURN; } diff --git a/transports/dodgy/include/transports/dodgy/transport.h b/transports/dodgy/include/transports/dodgy/transport.h index 61bf882..ff6bfd0 100644 --- a/transports/dodgy/include/transports/dodgy/transport.h +++ b/transports/dodgy/include/transports/dodgy/transport.h @@ -209,7 +209,7 @@ namespace rpc::dodgy // Internal send payload helper // rpc::transport override - connect handshake CORO_TASK(int) - inner_connect(rpc::interface_descriptor input_descr, rpc::interface_descriptor& output_descr) override; + inner_connect(connection_settings& input_descr, rpc::interface_descriptor& output_descr) override; CORO_TASK(int) inner_accept() override { CO_RETURN rpc::error::OK(); } // outbound i_marshaller implementations (from rpc::transport) diff --git a/transports/dodgy/interface/dodgy/dodgy.idl b/transports/dodgy/interface/dodgy/dodgy.idl index 3197f0a..8c4f326 100644 --- a/transports/dodgy/interface/dodgy/dodgy.idl +++ b/transports/dodgy/interface/dodgy/dodgy.idl @@ -52,8 +52,16 @@ namespace rpc uint64_t caller_zone_id; // the object id of the callback object from the caller uint64_t caller_object_id; + // the expected interface id of the caller object + uint64_t caller_interface_id; + // the zone id of the destination derived from the caller uint64_t destination_zone_id; + // the expected interface id of the destination object + uint64_t destination_interface_id; + + // the zone id of the zone immediately adjacent to the new zone + uint64_t adjacent_zone_id; }; struct init_client_channel_response diff --git a/transports/dodgy/src/transport.cpp b/transports/dodgy/src/transport.cpp index e2c4611..d2b32d2 100644 --- a/transports/dodgy/src/transport.cpp +++ b/transports/dodgy/src/transport.cpp @@ -37,7 +37,7 @@ namespace rpc::dodgy // Connection handshake CORO_TASK(int) - dodgy_transport::inner_connect(rpc::interface_descriptor input_descr, rpc::interface_descriptor& output_descr) + dodgy_transport::inner_connect(connection_settings& input_descr, rpc::interface_descriptor& output_descr) { RPC_DEBUG("dodgy_transport::connect zone={}", get_zone_id().get_val()); @@ -55,7 +55,10 @@ namespace rpc::dodgy int ret = CO_AWAIT call_peer(rpc::get_version(), init_client_channel_send{.caller_zone_id = get_zone_id().get_val(), .caller_object_id = input_descr.object_id.get_val(), - .destination_zone_id = get_adjacent_zone_id().get_val()}, + .caller_interface_id = input_descr.caller_interface_id.get_val(), + .destination_zone_id = get_adjacent_zone_id().get_val(), + .destination_interface_id = input_descr.destination_interface_id.get_val(), + .adjacent_zone_id = get_zone_id().get_val()}, init_receive); if (ret != rpc::error::OK()) { @@ -856,9 +859,9 @@ namespace rpc::dodgy {request.interface_id}, request.back_channel, out_back_channel); - if (ret != rpc::error::OK()) + if (rpc::error::is_error(ret)) { - RPC_ERROR("failed try_cast"); + RPC_DEBUG("inbound_send error {}", ret); } auto err = CO_AWAIT send_payload(prefix.version, diff --git a/transports/local/include/transports/local/transport.h b/transports/local/include/transports/local/transport.h index 5dd7b72..d846a79 100644 --- a/transports/local/include/transports/local/transport.h +++ b/transports/local/include/transports/local/transport.h @@ -23,13 +23,13 @@ namespace rpc::local parent_transport(std::string name, std::shared_ptr service, std::shared_ptr parent); parent_transport(std::string name, std::shared_ptr parent); - virtual ~parent_transport() DEFAULT_DESTRUCTOR; + virtual ~parent_transport() CANOPY_DEFAULT_DESTRUCTOR; // Override to propagate disconnect to parent zone void set_status(rpc::transport_status status) override; CORO_TASK(int) - inner_connect(rpc::interface_descriptor input_descr, rpc::interface_descriptor& output_descr) override + inner_connect(connection_settings& input_descr, rpc::interface_descriptor& output_descr) override { std::ignore = input_descr; std::ignore = output_descr; @@ -115,7 +115,7 @@ namespace rpc::local { stdex::member_ptr child_; - typedef std::function& parent, std::shared_ptr& child)> @@ -130,13 +130,13 @@ namespace rpc::local set_status(rpc::transport_status::CONNECTED); } - virtual ~child_transport() DEFAULT_DESTRUCTOR; + virtual ~child_transport() CANOPY_DEFAULT_DESTRUCTOR; // Called by parent_transport when child zone disconnects void on_child_disconnected(); CORO_TASK(int) - inner_connect(rpc::interface_descriptor input_descr, rpc::interface_descriptor& output_descr) override + inner_connect(connection_settings& input_descr, rpc::interface_descriptor& output_descr) override { assert(child_entry_point_factory_fn_); std::shared_ptr child; @@ -237,7 +237,7 @@ namespace rpc::local { child_entry_point_factory_fn_ - = [child_entry_point_fn = std::move(child_entry_point_fn)](rpc::interface_descriptor input_descr, + = [child_entry_point_fn = std::move(child_entry_point_fn)](rpc::connection_settings input_descr, rpc::interface_descriptor& output_descr, const std::shared_ptr& parent, std::shared_ptr& child) mutable -> CORO_TASK(int) diff --git a/transports/local/tests/transport/tests/local/setup.h b/transports/local/tests/transport/tests/local/setup.h index 1fbf771..81ca0b7 100644 --- a/transports/local/tests/transport/tests/local/setup.h +++ b/transports/local/tests/transport/tests/local/setup.h @@ -36,7 +36,7 @@ template zone_gen_ = 0; - bool error_has_occured_ = false; + bool error_has_occurred_ = false; bool startup_complete_ = false; bool shutdown_complete_ = false; @@ -45,7 +45,7 @@ template get_scheduler() const { return io_scheduler_; } #endif - bool error_has_occured() const { return error_has_occured_; } + bool error_has_occurred() const { return error_has_occurred_; } virtual ~inproc_setup() = default; @@ -64,7 +64,7 @@ template hst(new host()); @@ -107,9 +104,6 @@ template(new marshalled_tests::example(child_service_ptr, nullptr)); if (use_host_in_child_) CO_AWAIT new_example->set_host(host); @@ -151,7 +145,7 @@ template& new_example, const std::shared_ptr& child_service_ptr) -> CORO_TASK(int) { - example_import_idl_register_stubs(child_service_ptr); - example_shared_idl_register_stubs(child_service_ptr); - example_idl_register_stubs(child_service_ptr); new_example = rpc::shared_ptr(new marshalled_tests::example(child_service_ptr, nullptr)); if (use_host_in_child_) CO_AWAIT new_example->set_host(host); diff --git a/transports/mock_test/include/transports/mock_test/transport.h b/transports/mock_test/include/transports/mock_test/transport.h index 14fe54d..0f3b685 100644 --- a/transports/mock_test/include/transports/mock_test/transport.h +++ b/transports/mock_test/include/transports/mock_test/transport.h @@ -54,7 +54,8 @@ namespace rpc::mock_test std::atomic transport_down_count_{0}; // Optional response handlers for custom behavior - std::function&, const std::vector&, std::vector&)> - send_handler_; + send_handler; + + send_handler send_handler_; std::mutex send_handler_mtx_; void record_call(call_record::call_type type, @@ -111,7 +114,7 @@ namespace rpc::mock_test // outbound i_marshaller implementations CORO_TASK(int) - inner_connect(rpc::interface_descriptor input_descr, rpc::interface_descriptor& output_descr) override; + inner_connect(connection_settings& input_descr, rpc::interface_descriptor& output_descr) override; CORO_TASK(int) inner_accept() override { CO_RETURN rpc::error::OK(); } CORO_TASK(int) diff --git a/transports/mock_test/src/transport.cpp b/transports/mock_test/src/transport.cpp index 6b390ac..077a414 100644 --- a/transports/mock_test/src/transport.cpp +++ b/transports/mock_test/src/transport.cpp @@ -62,7 +62,7 @@ namespace rpc::mock_test } CORO_TASK(int) - mock_transport::inner_connect(rpc::interface_descriptor input_descr, rpc::interface_descriptor& output_descr) + mock_transport::inner_connect(connection_settings& input_descr, rpc::interface_descriptor& output_descr) { std::ignore = input_descr; std::ignore = output_descr; @@ -92,24 +92,30 @@ namespace rpc::mock_test } // Check if custom handler is set + send_handler handler; { std::scoped_lock lock(send_handler_mtx_); if (send_handler_) { - CO_RETURN CO_AWAIT send_handler_(protocol_version, - encoding, - tag, - caller_zone_id, - destination_zone_id, - object_id, - interface_id, - method_id, - in_data, - out_buf_, - in_back_channel, - out_back_channel); + handler = send_handler_; + } + else + { + CO_RETURN rpc::error::OK(); } } + CO_RETURN CO_AWAIT handler(protocol_version, + encoding, + tag, + caller_zone_id, + destination_zone_id, + object_id, + interface_id, + method_id, + in_data, + out_buf_, + in_back_channel, + out_back_channel); // Default successful response CO_RETURN rpc::error::OK(); diff --git a/transports/mock_test/tests/transport/tests/mock_test/setup.h b/transports/mock_test/tests/transport/tests/mock_test/setup.h index 8a47e70..a01d92f 100644 --- a/transports/mock_test/tests/transport/tests/mock_test/setup.h +++ b/transports/mock_test/tests/transport/tests/mock_test/setup.h @@ -28,7 +28,7 @@ class passthrough_setup rpc::destination_zone forward_dest_; rpc::destination_zone reverse_dest_; - bool error_has_occured_ = false; + bool error_has_occurred_ = false; bool startup_complete_ = false; bool shutdown_complete_ = false; @@ -36,7 +36,7 @@ class passthrough_setup #ifdef CANOPY_BUILD_COROUTINE std::shared_ptr get_scheduler() const { return io_scheduler_; } #endif - bool error_has_occured() const { return error_has_occured_; } + bool error_has_occurred() const { return error_has_occurred_; } std::shared_ptr get_service() const { return service_; } std::shared_ptr get_forward_transport() const { return forward_transport_; } std::shared_ptr get_reverse_transport() const { return reverse_transport_; } @@ -49,7 +49,7 @@ class passthrough_setup auto ret = CO_AWAIT task; if (!ret) { - error_has_occured_ = true; + error_has_occurred_ = true; } CO_RETURN; } @@ -119,7 +119,7 @@ class passthrough_setup ASSERT_EQ(startup_complete_, true); #endif RPC_INFO("passthrough_setup::set_up - Checking for errors"); - ASSERT_EQ(error_has_occured_, false); + ASSERT_EQ(error_has_occurred_, false); RPC_INFO("passthrough_setup::set_up - Complete"); } diff --git a/transports/sgx/src/enclave_service_proxy.cpp b/transports/sgx/src/enclave_service_proxy.cpp index bcc9490..b509ee9 100644 --- a/transports/sgx/src/enclave_service_proxy.cpp +++ b/transports/sgx/src/enclave_service_proxy.cpp @@ -47,7 +47,7 @@ namespace rpc } CORO_TASK(int) - enclave_service_proxy::inner_connect(rpc::interface_descriptor input_descr, rpc::interface_descriptor& output_descr) + enclave_service_proxy::inner_connect(connection_settings& input_descr, rpc::interface_descriptor& output_descr) { sgx_launch_token_t token = {0}; int updated = 0; diff --git a/transports/sgx/src/enclave_service_proxy.h b/transports/sgx/src/enclave_service_proxy.h index 95809cb..30d38cc 100644 --- a/transports/sgx/src/enclave_service_proxy.h +++ b/transports/sgx/src/enclave_service_proxy.h @@ -38,7 +38,7 @@ namespace rpc std::string filename); CORO_TASK(int) - inner_connect(rpc::interface_descriptor input_descr, rpc::interface_descriptor& output_descr) override; + inner_connect(connection_settings& input_descr, rpc::interface_descriptor& output_descr) override; CORO_TASK(int) inner_accept() override { CO_RETURN rpc::error::OK(); } int send(uint64_t protocol_version, diff --git a/transports/sgx/tests/transport/tests/sgx/setup.h b/transports/sgx/tests/transport/tests/sgx/setup.h index 233f560..e284fcc 100644 --- a/transports/sgx/tests/transport/tests/sgx/setup.h +++ b/transports/sgx/tests/transport/tests/sgx/setup.h @@ -30,7 +30,7 @@ template zone_gen_ = 0; - bool error_has_occured_ = false; + bool error_has_occurred_ = false; public: virtual ~sgx_setup() = default; @@ -45,14 +45,14 @@ template& host) { i_host_ptr_ = host; } bool get_use_host_in_child() const { return use_host_in_child_; } - bool error_has_occured() const { return error_has_occured_; } + bool error_has_occurred() const { return error_has_occurred_; } CORO_TASK(void) check_for_error(CORO_TASK(bool) task) { auto ret = CO_AWAIT task; if (!ret) { - error_has_occured_ = true; + error_has_occurred_ = true; } CO_RETURN; } @@ -69,9 +69,6 @@ template("host", rpc::zone{++zone_gen_}); - example_import_idl_register_stubs(root_service_); - example_shared_idl_register_stubs(root_service_); - example_idl_register_stubs(root_service_); current_host_service = root_service_; i_host_ptr_ = rpc::shared_ptr(new host()); diff --git a/transports/spsc/include/transports/spsc/transport.h b/transports/spsc/include/transports/spsc/transport.h index 669c56c..4b67f53 100644 --- a/transports/spsc/include/transports/spsc/transport.h +++ b/transports/spsc/include/transports/spsc/transport.h @@ -24,7 +24,7 @@ namespace rpc::spsc class spsc_transport : public rpc::transport { public: - using connection_handler = std::function child_service_ptr, std::shared_ptr)>; @@ -133,7 +133,7 @@ namespace rpc::spsc CORO_TASK(int) call_peer(std::uint64_t protocol_version, SendPayload&& sendPayload, ReceivePayload& receivePayload) { - if (get_status() != rpc::transport_status::CONNECTED && get_status() != rpc::transport_status::CONNECTING) + if (get_status() != rpc::transport_status::CONNECTED) { RPC_ERROR("call_peer: transport is not connected"); CO_RETURN rpc::error::CALL_CANCELLED(); @@ -169,7 +169,11 @@ namespace rpc::spsc rpc::id::get(rpc::get_version())); // Check if the operation was cancelled during shutdown - if (res_payload.error_code != rpc::error::OK()) + if (res_payload.error_code == rpc::error::OBJECT_GONE()) + { + CO_RETURN res_payload.error_code; + } + if (rpc::error::is_critical(res_payload.error_code)) { RPC_ERROR("call_peer returning cancelled error for zone: {} sequence_number: {}", get_service()->get_zone_id().get_val(), @@ -204,7 +208,7 @@ namespace rpc::spsc // Internal send payload helper // rpc::transport override - connect handshake CORO_TASK(int) - inner_connect(rpc::interface_descriptor input_descr, rpc::interface_descriptor& output_descr) override; + inner_connect(connection_settings& input_descr, rpc::interface_descriptor& output_descr) override; CORO_TASK(int) inner_accept() override; diff --git a/transports/spsc/interface/spsc/spsc.idl b/transports/spsc/interface/spsc/spsc.idl index d372095..8c262ec 100644 --- a/transports/spsc/interface/spsc/spsc.idl +++ b/transports/spsc/interface/spsc/spsc.idl @@ -52,8 +52,16 @@ namespace rpc uint64_t caller_zone_id; // the object id of the callback object from the caller uint64_t caller_object_id; + // the expected interface id of the caller object + uint64_t caller_interface_id; + // the zone id of the destination derived from the caller uint64_t destination_zone_id; + // the expected interface id of the destination object + uint64_t destination_interface_id; + + // the zone id of the zone immediately adjacent to the new zone + uint64_t adjacent_zone_id; }; struct init_client_channel_response diff --git a/transports/spsc/src/transport.cpp b/transports/spsc/src/transport.cpp index b1e6313..81d95e3 100644 --- a/transports/spsc/src/transport.cpp +++ b/transports/spsc/src/transport.cpp @@ -18,8 +18,6 @@ namespace rpc::spsc , receive_spsc_queue_(receive_spsc_queue) , connection_handler_(handler) { - // SPSC transport starts in CONNECTING state - // Will transition to CONNECTED after successful connection } std::shared_ptr spsc_transport::create(std::string name, @@ -31,13 +29,18 @@ namespace rpc::spsc { auto transport = std::shared_ptr( new spsc_transport(name, service, adjacent_zone_id, send_spsc_queue, receive_spsc_queue, handler)); + + // Set up the keep alive using member_ptr assignment transport->keep_alive_ = transport; + + // Set the transport status to connected + transport->set_status(rpc::transport_status::CONNECTED); return transport; } // Connection handshake CORO_TASK(int) - spsc_transport::inner_connect(rpc::interface_descriptor input_descr, rpc::interface_descriptor& output_descr) + spsc_transport::inner_connect(connection_settings& input_descr, rpc::interface_descriptor& output_descr) { RPC_DEBUG("spsc_transport::connect zone={}", get_zone_id().get_val()); @@ -45,10 +48,7 @@ namespace rpc::spsc assert(connection_handler_ || !connection_handler_); // Can be null for client side // Schedule onto the scheduler - CO_AWAIT service->get_scheduler()->schedule(); - - // Set transport to CONNECTED - set_status(rpc::transport_status::CONNECTED); + // CO_AWAIT service->get_scheduler()->schedule(); pump_send_and_receive(); @@ -60,7 +60,10 @@ namespace rpc::spsc int ret = CO_AWAIT call_peer(rpc::get_version(), init_client_channel_send{.caller_zone_id = get_zone_id().get_val(), .caller_object_id = input_descr.object_id.get_val(), - .destination_zone_id = get_adjacent_zone_id().get_val()}, + .caller_interface_id = input_descr.caller_interface_id.get_val(), + .destination_zone_id = get_adjacent_zone_id().get_val(), + .destination_interface_id = input_descr.destination_interface_id.get_val(), + .adjacent_zone_id = get_zone_id().get_val()}, init_receive); if (ret != rpc::error::OK()) { @@ -120,9 +123,10 @@ namespace rpc::spsc .payload = std::vector((const char*)in_data.begin, (const char*)in_data.end), .back_channel = in_back_channel}, response); - if (ret != rpc::error::OK()) + + if (rpc::error::is_error(ret)) { - RPC_ERROR("failed spsc_transport::outbound_send call_send"); + RPC_DEBUG("failed spsc_transport::outbound_send call_send"); CO_RETURN ret; } @@ -268,7 +272,7 @@ namespace rpc::spsc RPC_DEBUG("spsc_transport::outbound_release zone={}", get_zone_id().get_val()); // Check transport status - if (get_status() != rpc::transport_status::CONNECTED) + if (get_status() != rpc::transport_status::CONNECTED && get_status() != rpc::transport_status::DISCONNECTING) { RPC_ERROR( "failed spsc_transport::outbound_release - not connected, status = {}", static_cast(get_status())); @@ -412,7 +416,7 @@ namespace rpc::spsc // AND response_task_complete being false). bool stop_loop = false; - while (get_status() != rpc::transport_status::DISCONNECTED && !stop_loop) + while (get_status() == rpc::transport_status::CONNECTED && !stop_loop) { // Receive prefix chunks @@ -693,7 +697,7 @@ namespace rpc::spsc RPC_DEBUG("send_producer_loop started for zone {}", get_zone_id().get_val()); std::span send_data; - while (get_status() == rpc::transport_status::CONNECTED || get_status() == rpc::transport_status::CONNECTING) + while (get_status() == rpc::transport_status::CONNECTED) { auto status = push_message(send_data); if (status == send_queue_status::SEND_QUEUE_EMPTY || status == send_queue_status::SPSC_QUEUE_FULL) @@ -715,21 +719,20 @@ namespace rpc::spsc status = push_message(send_data); } - RPC_DEBUG("send cancellation message {}", get_zone_id().get_val()); + RPC_DEBUG("close connection message {}", get_zone_id().get_val()); - // plop cancellation message onto queue send_payload(rpc::get_version(), message_direction::one_way, close_connection_send{}, 0); - // then flush the queue + // then flush the queue one more time status = push_message(send_data); while (status == send_queue_status::SEND_QUEUE_NOT_EMPTY) { status = push_message(send_data); } - if (status == send_queue_status::SPSC_QUEUE_FULL) { RPC_DEBUG("unable to send cancellation message {}", get_zone_id().get_val()); + CO_RETURN; } RPC_DEBUG("send_producer_loop completed sending for zone {}", get_zone_id().get_val()); @@ -797,9 +800,9 @@ namespace rpc::spsc request.back_channel, out_back_channel); - if (ret != rpc::error::OK()) + if (rpc::error::is_error(ret)) { - RPC_ERROR("failed send"); + RPC_DEBUG("inbound_send error {}", ret); } if (prefix.direction == message_direction::one_way) @@ -877,9 +880,10 @@ namespace rpc::spsc {request.interface_id}, request.back_channel, out_back_channel); - if (ret != rpc::error::OK()) + + if (rpc::error::is_error(ret)) { - RPC_ERROR("failed try_cast"); + RPC_DEBUG("inbound_try_cast error {}", ret); } send_payload(prefix.version, @@ -920,9 +924,9 @@ namespace rpc::spsc request.back_channel, out_back_channel); - if (ret != rpc::error::OK()) + if (rpc::error::is_error(ret)) { - RPC_ERROR("failed add_ref"); + RPC_DEBUG("inbound_add_ref error {}", ret); } send_payload(prefix.version, @@ -959,9 +963,9 @@ namespace rpc::spsc request.back_channel, out_back_channel); - if (ret != rpc::error::OK()) + if (rpc::error::is_error(ret)) { - RPC_ERROR("failed release"); + RPC_DEBUG("inbound_release error {}", ret); } auto count = get_destination_count(); @@ -969,7 +973,7 @@ namespace rpc::spsc if (count <= 0) { RPC_DEBUG("destination_count reached 0, triggering disconnect"); - set_status(rpc::transport_status::DISCONNECTED); + set_status(rpc::transport_status::DISCONNECTING); } RPC_DEBUG("release request complete"); CO_RETURN; @@ -1032,11 +1036,6 @@ namespace rpc::spsc { RPC_DEBUG("create_stub zone: {}", get_zone_id().get_val()); - if (get_status() != rpc::transport_status::CONNECTING) - { - CO_RETURN; - } - init_client_channel_send request; auto err = rpc::from_yas_binary(rpc::span(payload.payload), request); if (!err.empty()) @@ -1044,7 +1043,10 @@ namespace rpc::spsc RPC_ERROR("failed create_stub init_client_channel_send deserialization"); CO_RETURN; } - rpc::interface_descriptor input_descr{{request.caller_object_id}, {request.caller_zone_id}}; + rpc::connection_settings input_descr{.caller_interface_id = request.caller_interface_id, + .destination_interface_id = request.destination_interface_id, + .object_id = request.caller_object_id, + .input_zone_id = request.caller_zone_id}; rpc::interface_descriptor output_interface; int ret = CO_AWAIT connection_handler_(input_descr, output_interface, get_service(), keep_alive_.get_nullable()); @@ -1055,15 +1057,12 @@ namespace rpc::spsc CO_RETURN; } - // Set transport to CONNECTED after successful server-side handshake - set_status(rpc::transport_status::CONNECTED); - send_payload(prefix.version, message_direction::receive, init_client_channel_response{.err_code = rpc::error::OK(), .destination_zone_id = output_interface.destination_zone_id.get_val(), .destination_object_id = output_interface.object_id.get_val(), - .caller_zone_id = input_descr.destination_zone_id.get_val()}, + .caller_zone_id = input_descr.input_zone_id.get_val()}, prefix.sequence_number); CO_RETURN; diff --git a/transports/spsc/tests/transport/tests/spsc/setup.h b/transports/spsc/tests/transport/tests/spsc/setup.h index 6558497..ff999c0 100644 --- a/transports/spsc/tests/transport/tests/spsc/setup.h +++ b/transports/spsc/tests/transport/tests/spsc/setup.h @@ -39,12 +39,12 @@ template zone_gen_ = 0; std::shared_ptr io_scheduler_; - bool error_has_occured_ = false; + bool error_has_occurred_ = false; bool setup_complete_ = false; public: std::shared_ptr get_scheduler() const { return io_scheduler_; } - bool error_has_occured() const { return error_has_occured_; } + bool error_has_occurred() const { return error_has_occurred_; } virtual ~spsc_setup() = default; @@ -63,7 +63,7 @@ template("host", root_zone_id, io_scheduler_); - example_import_idl_register_stubs(root_service_); - example_shared_idl_register_stubs(root_service_); - example_idl_register_stubs(root_service_); peer_service_ = std::make_shared("peer", peer_zone_id, io_scheduler_); - example_import_idl_register_stubs(peer_service_); - example_shared_idl_register_stubs(peer_service_); - example_idl_register_stubs(peer_service_); // Create server-side transport (receives connections) rpc::spsc::spsc_transport::connection_handler handler - = [use_host_in_child = use_host_in_child_](const rpc::interface_descriptor& input_interface, + = [use_host_in_child = use_host_in_child_](const rpc::connection_settings& input_interface, rpc::interface_descriptor& output_interface, std::shared_ptr service, std::shared_ptr transport) -> CORO_TASK(int) @@ -123,10 +117,7 @@ templateadd_transport(root_zone_id.as_destination(), peer_transport); - - // Schedule the pump coroutine - peer_transport->pump_send_and_receive(); + CO_AWAIT peer_transport->accept(); // Create client-side transport (initiates connection) rpc::shared_ptr hst(new host()); @@ -139,9 +130,6 @@ templatepump_send_and_receive(); - auto ret = CO_AWAIT root_service_->connect_to_zone("main child", client_transport, hst, i_example_ptr_); if (ret != rpc::error::OK()) @@ -176,7 +164,7 @@ templateprocess_events(std::chrono::milliseconds(1)); } - ASSERT_EQ(error_has_occured_, false); + ASSERT_EQ(error_has_occurred_, false); } CORO_TASK(void) CoroTearDown() @@ -231,7 +219,7 @@ template child_service_ptr, std::shared_ptr transport)>; diff --git a/transports/tcp/include/transports/tcp/transport.h b/transports/tcp/include/transports/tcp/transport.h index 1dde6e2..9bf58bd 100644 --- a/transports/tcp/include/transports/tcp/transport.h +++ b/transports/tcp/include/transports/tcp/transport.h @@ -19,7 +19,7 @@ namespace rpc::tcp class tcp_transport : public rpc::transport { public: - using connection_handler = std::function child_service_ptr, std::shared_ptr)>; @@ -137,7 +137,7 @@ namespace rpc::tcp rpc::id::get(rpc::get_version())); // If peer has initiated shutdown, we're disconnected - if (get_status() != rpc::transport_status::CONNECTED && get_status() != rpc::transport_status::CONNECTING) + if (get_status() != rpc::transport_status::CONNECTED) { RPC_DEBUG("call_peer: shutting_down_=true, returning CALL_CANCELLED for zone {}", get_service()->get_zone_id().get_val()); @@ -214,7 +214,7 @@ namespace rpc::tcp // Internal send payload helper // rpc::transport override - connect handshake CORO_TASK(int) - inner_connect(rpc::interface_descriptor input_descr, rpc::interface_descriptor& output_descr) override; + inner_connect(connection_settings& input_descr, rpc::interface_descriptor& output_descr) override; CORO_TASK(int) inner_accept() override { CO_RETURN rpc::error::OK(); } diff --git a/transports/tcp/interface/tcp/tcp.idl b/transports/tcp/interface/tcp/tcp.idl index 0cc23a8..b941a89 100644 --- a/transports/tcp/interface/tcp/tcp.idl +++ b/transports/tcp/interface/tcp/tcp.idl @@ -52,9 +52,15 @@ namespace rpc uint64_t caller_zone_id; // the object id of the callback object from the caller uint64_t caller_object_id; + // the expected interface id of the caller object + uint64_t caller_interface_id; + // the zone id of the destination derived from the caller uint64_t destination_zone_id; - // the zone id of the transport that is initiating the connection (adjacent_zone from server's perspective) + // the expected interface id of the destination object + uint64_t destination_interface_id; + + // the zone id of the zone immediately adjacent to the new zone uint64_t adjacent_zone_id; }; diff --git a/transports/tcp/src/transport.cpp b/transports/tcp/src/transport.cpp index 42067b0..bf8dcb9 100644 --- a/transports/tcp/src/transport.cpp +++ b/transports/tcp/src/transport.cpp @@ -36,6 +36,9 @@ namespace rpc::tcp // Set up the keep alive using member_ptr assignment transport->keep_alive_ = transport; + // Set the transport status to connected + transport->set_status(rpc::transport_status::CONNECTED); + return transport; } @@ -56,17 +59,24 @@ namespace rpc::tcp // Connection handshake CORO_TASK(int) - tcp_transport::inner_connect(rpc::interface_descriptor input_descr, rpc::interface_descriptor& output_descr) + tcp_transport::inner_connect(connection_settings& input_descr, rpc::interface_descriptor& output_descr) { RPC_DEBUG("tcp_transport::connect zone={}", get_zone_id().get_val()); + auto service = get_service(); + assert(connection_handler_ || !connection_handler_); // Can be null for client side + + service->get_scheduler()->spawn(pump_send_and_receive()); + // Create the init client channel request init_client_channel_response init_receive; int ret = CO_AWAIT call_peer(rpc::get_version(), - init_client_channel_send{.caller_zone_id = input_descr.destination_zone_id.get_val(), + init_client_channel_send{.caller_zone_id = get_zone_id().get_val(), .caller_object_id = input_descr.object_id.get_val(), + .caller_interface_id = input_descr.caller_interface_id.get_val(), .destination_zone_id = get_adjacent_zone_id().get_val(), - .adjacent_zone_id = get_service()->get_zone_id().get_val()}, + .destination_interface_id = input_descr.destination_interface_id.get_val(), + .adjacent_zone_id = get_zone_id().get_val()}, init_receive); if (ret != rpc::error::OK()) { @@ -84,9 +94,6 @@ namespace rpc::tcp rpc::object output_object_id = {init_receive.destination_object_id}; output_descr = rpc::interface_descriptor(output_object_id, get_adjacent_zone_id().as_destination()); - // Set the transport status to connected - set_status(rpc::transport_status::CONNECTED); - CO_RETURN rpc::error::OK(); } @@ -443,7 +450,7 @@ namespace rpc::tcp // Handle different message types if (payload.payload_fingerprint == rpc::id::get(prefix.version)) { - assert(get_status() == rpc::transport_status::CONNECTING); + assert(get_status() == rpc::transport_status::CONNECTED); get_service()->get_scheduler()->spawn(create_stub(tracker, std::move(prefix), std::move(payload))); } else if (payload.payload_fingerprint == rpc::id::get(prefix.version)) @@ -736,9 +743,9 @@ namespace rpc::tcp request.back_channel, out_back_channel); - if (ret != rpc::error::OK()) + if (rpc::error::is_error(ret)) { - RPC_ERROR("failed send"); + RPC_DEBUG("inbound_send error {}", ret); } if (prefix.direction == message_direction::one_way) @@ -812,9 +819,10 @@ namespace rpc::tcp {request.interface_id}, request.back_channel, out_back_channel); - if (ret != rpc::error::OK()) + + if (rpc::error::is_error(ret)) { - RPC_ERROR("failed try_cast"); + RPC_DEBUG("inbound_send error {}", ret); } auto err = CO_AWAIT send_payload(prefix.version, @@ -856,9 +864,9 @@ namespace rpc::tcp request.back_channel, out_back_channel); - if (ret != rpc::error::OK()) + if (rpc::error::is_error(ret)) { - RPC_ERROR("failed add_ref"); + RPC_DEBUG("inbound_add_ref error {}", ret); } auto err = CO_AWAIT send_payload(prefix.version, @@ -899,9 +907,9 @@ namespace rpc::tcp request.back_channel, out_back_channel); - if (ret != rpc::error::OK()) + if (rpc::error::is_error(ret)) { - RPC_ERROR("failed release"); + RPC_DEBUG("inbound_release error {}", ret); } auto err = CO_AWAIT send_payload(prefix.version, @@ -985,7 +993,10 @@ namespace rpc::tcp RPC_ERROR("failed create_stub init_client_channel_send deserialization"); CO_RETURN; } - rpc::interface_descriptor input_descr{{request.caller_object_id}, {request.caller_zone_id}}; + rpc::connection_settings input_descr{.caller_interface_id = request.caller_interface_id, + .destination_interface_id = request.destination_interface_id, + .object_id = request.caller_object_id, + .input_zone_id = request.caller_zone_id}; rpc::interface_descriptor output_interface; // Update the adjacent zone ID from the handshake message @@ -1002,15 +1013,12 @@ namespace rpc::tcp CO_RETURN; } - // Set transport to CONNECTED after successful server-side handshake - set_status(rpc::transport_status::CONNECTED); - auto send_err = CO_AWAIT send_payload(prefix.version, message_direction::receive, init_client_channel_response{.err_code = rpc::error::OK(), .destination_zone_id = output_interface.destination_zone_id.get_val(), .destination_object_id = output_interface.object_id.get_val(), - .caller_zone_id = input_descr.destination_zone_id.get_val()}, + .caller_zone_id = input_descr.input_zone_id.get_val()}, prefix.sequence_number); if (send_err != rpc::error::OK()) { diff --git a/transports/tcp/tests/transport/tests/tcp/setup.h b/transports/tcp/tests/transport/tests/tcp/setup.h index b6110c2..b552391 100644 --- a/transports/tcp/tests/transport/tests/tcp/setup.h +++ b/transports/tcp/tests/transport/tests/tcp/setup.h @@ -32,12 +32,12 @@ template zone_gen_ = 0; std::shared_ptr io_scheduler_; - bool error_has_occured_ = false; + bool error_has_occurred_ = false; bool setup_complete_ = false; public: std::shared_ptr get_scheduler() const { return io_scheduler_; } - bool error_has_occured() const { return error_has_occured_; } + bool error_has_occurred() const { return error_has_occurred_; } virtual ~tcp_setup() = default; @@ -57,7 +57,8 @@ template("peer", peer_zone_id, io_scheduler_); - example_import_idl_register_stubs(peer_service_); - example_shared_idl_register_stubs(peer_service_); - example_idl_register_stubs(peer_service_); // Create the root service (client side) root_service_ = std::make_shared("host", root_zone_id, io_scheduler_); - example_import_idl_register_stubs(root_service_); - example_shared_idl_register_stubs(root_service_); - example_idl_register_stubs(root_service_); current_host_service = root_service_; @@ -97,7 +92,7 @@ template( - [this, use_host_in_child = use_host_in_child_](const rpc::interface_descriptor& input_descr, + [this, use_host_in_child = use_host_in_child_](const rpc::connection_settings& input_descr, rpc::interface_descriptor& output_interface, std::shared_ptr child_service_ptr, std::shared_ptr transport) -> CORO_TASK(int) @@ -106,10 +101,6 @@ templateadd_transport(input_descr.destination_zone_id, transport); - // Use attach_remote_zone to properly manage object lifetime, like SPSC does auto ret = CO_AWAIT child_service_ptr->attach_remote_zone("service_proxy", transport, @@ -162,9 +153,6 @@ templatespawn(client_transport_->pump_send_and_receive()); - // Connect using the client transport auto ret = CO_AWAIT root_service_->connect_to_zone("main child", client_transport_, hst, i_example_ptr_); @@ -202,7 +190,7 @@ templateprocess_events(std::chrono::milliseconds(1)); } - ASSERT_EQ(error_has_occured_, false); + ASSERT_EQ(error_has_occurred_, false); } CORO_TASK(void) CoroTearDown() @@ -289,7 +277,7 @@ template