From 1d9e631061478d304b247fbdc185196b5f154c4c Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Thu, 21 Sep 2023 14:15:57 -0400 Subject: [PATCH 1/7] Fixed double delete in CAPITimer and added individual functions for handling callbacks. --- src/realm.h | 85 ++++++++++++++++++- .../object-store/c_api/socket_provider.cpp | 54 ++++++++++-- test/object-store/c_api/c_api.cpp | 3 +- 3 files changed, 133 insertions(+), 9 deletions(-) diff --git a/src/realm.h b/src/realm.h index e05f99f3df0..d56375a4bdf 100644 --- a/src/realm.h +++ b/src/realm.h @@ -4061,6 +4061,25 @@ RLM_API bool realm_mongo_collection_find_one_and_delete(realm_mongodb_collection realm_userdata_t data, realm_free_userdata_func_t delete_data, realm_mongodb_callback_t callback); +/** + * Creates a new sync socket instance for the Sync Client that handles the operations for a custom + * websocket and event loop implementation. + * @param userdata CAPI implementation specific pointer containing custom context data that is provided to + * each of the provided function. + * @param userdata_free function that will be called when the sync socket is destroyed to delete userdata. + * @param post_func function that will be called to post a callback handler onto the event loop - use the + * realm_sync_socket_post_complete() function when the callback handler is scheduled to run. + * @param create_timer_func function that will be called to create a new timer resource with the callback + * handler that will be run when the timer expires or an erorr occurs - use the + * realm_sync_socket_timer_canceled() function if the timer is canceled or the + * realm_sync_socket_timer_complete() function if the timer expires or an error occurs. + * @param cancel_timer_func function that will be called when the timer has been canceled by the sync client. + * @param free_timer_func function that will be called when the timer resource has been destroyed by the sync client. + * @param websocket_connect_func function that will be called when the sync client creates a websocket. + * @param websocket_write_func function that will be called when the sync client sends data over the websocket. + * @param websocket_free_func function that will be called when the sync client closes the websocket conneciton. + * @return a realm_sync_socket_t pointer suitable for passing to realm_sync_client_config_set_sync_socket() + */ RLM_API realm_sync_socket_t* realm_sync_socket_new( realm_userdata_t userdata, realm_free_userdata_func_t userdata_free, realm_sync_socket_post_func_t post_func, realm_sync_socket_create_timer_func_t create_timer_func, @@ -4069,17 +4088,79 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new( realm_sync_socket_websocket_async_write_func_t websocket_write_func, realm_sync_socket_websocket_free_func_t websocket_free_func); -RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback_t* realm_callback, realm_errno_e status, - const char* reason); +/** + * This function is to be called by the CAPI Implementer when the timer expires or an error occurs. + * @param timer_handler the timer callback handler that was provided when the timer was created. + * @param code the error code for the error that occurred or RLM_ERR_NONE if the timer expired normally. + * @param reason a string describing details about the error that occurred or empty string if no error. + * NOTE: This function must be called in the same "thread" as the timer canceled and SyncSocket::Post operations. + */ +RLM_API void realm_sync_socket_timer_complete(realm_sync_socket_callback_t* timer_handler, realm_errno_e code, + const char* reason); + +/** + * This function is to be called by the CAPI implementer when the timer is canceled by the Sync Client, either + * directly or as a result of the CAPITimer object being destroyed. + * @param timer_handler the timer callback handler that was provided when the timer was created. + * NOTE: This function must be called in the same "thread" as the timer complete and SyncSocket::Post operations. + */ +RLM_API void realm_sync_socket_timer_canceled(realm_sync_socket_callback_t* timer_handler); + +/** + * To be called to execute the callback function provided to the post_func when the event loop executes + * that post'ed operation. The realm_callback resource will automatically be destroyed during this + * operation. + * @param post_handler the post callback handler that was originally provided to the post_func when + */ +RLM_API void realm_sync_socket_post_complete(realm_sync_socket_callback_t* post_handler, realm_errno_e status, + const char* reason); + +/** + * To be called when the websocket successfully connects to the server. + * @param realm_websocket_observer the websocket observer object that was provided to the websocket_connect_func + * @param protocol the value of the Sec-WebSocket-Protocol header in the connect response from the server. + * NOTE: This function should not be called after the websocket_free_func has been called to release + * the websocket resources. + */ RLM_API void realm_sync_socket_websocket_connected(realm_websocket_observer_t* realm_websocket_observer, const char* protocol); +/** + * To be called when an error occurs - the actual error value with be provided when the websocket_closed + * function is called. This function informs that the socket object is in an error state and no further + * TX operations should be performed. + * @param realm_websocket_observer the websocket observer object that was provided to the websocket_connect_func + * NOTE: This function should not be called after the websocket_free_func has been called to release + * the websocket resources. + */ RLM_API void realm_sync_socket_websocket_error(realm_websocket_observer_t* realm_websocket_observer); +/** + * To be called to provide the received data to the Sync Client when a write operation has completed. + * The data buffer can be safely discarded after this function has completed. + * @param realm_websocket_observer the websocket observer object that was provided to the websocket_connect_func + * @param data a pointer to the buffer that contains the data received over the websocket + * @param data_size the number of bytes in the data buffer + * NOTE: This function should not be called after the websocket_free_func has been called to release + * the websocket resources. + */ RLM_API void realm_sync_socket_websocket_message(realm_websocket_observer_t* realm_websocket_observer, const char* data, size_t data_size); +/** + * To be called when the websocket has been closed, either due to an error or a normal close operation. + * @param realm_websocket_observer the websocket observer object that was provided to the websocket_connect_func + * @param was_clean boolean value that indicates whether this is a normal close situation (true), the + * error was provided by the server via a close message (true), or if the error was + * generated by the local websocket as a result of some other error (false) (e.g. host + * unreachable, etc.) + * @param status the websocket error code that describes why the websocket was closed, or + * RLM_ERR_WEBSOCKET_OK if the socket was closed normally. + * @param reason a string describing details about the error that occurred or empty string if no error. + * NOTE: This function should not be called after the websocket_free_func has been called to release + * the websocket resources. + */ RLM_API void realm_sync_socket_websocket_closed(realm_websocket_observer_t* realm_websocket_observer, bool was_clean, realm_web_socket_errno_e status, const char* reason); diff --git a/src/realm/object-store/c_api/socket_provider.cpp b/src/realm/object-store/c_api/socket_provider.cpp index 47c005005b5..9dc552c85a8 100644 --- a/src/realm/object-store/c_api/socket_provider.cpp +++ b/src/realm/object-store/c_api/socket_provider.cpp @@ -7,6 +7,8 @@ namespace realm::c_api { namespace { +// THis class represents the timer resource that is returned to the sync client from the +// CAPI implementation details for canceling and deleting the timer resources. struct CAPITimer : sync::SyncSocketProvider::Timer { public: CAPITimer(realm_userdata_t userdata, int64_t delay_ms, realm_sync_socket_callback_t* handler, @@ -29,22 +31,48 @@ struct CAPITimer : sync::SyncSocketProvider::Timer { realm_release(m_handler); } - /// Cancel the timer immediately. + // Cancel the timer immediately - the CAPI implementation will need to call the + // realm_sync_socket_timer_canceled function to notify the sync client that the + // timer has been canceled and must be called in the same execution thread as + // the timer complete. void cancel() override { m_timer_cancel(m_userdata, m_timer); } private: + // A pointer to the CAPI implementation's timer instance. This is provided by the + // CAPI implementation when the create_timer_func function is called. realm_sync_socket_timer_t m_timer = nullptr; - realm_userdata_t m_userdata = nullptr; + // A wrapped reference to the callback function to be called when the timer completes, + // is canceled or an error occurs. This is provided by the Sync Client realm_sync_socket_callback_t* m_handler = nullptr; + + // These values were originally provided to the socket_provider instance by the CAPI + // implementation when it was created + realm_userdata_t m_userdata = nullptr; realm_sync_socket_create_timer_func_t m_timer_create = nullptr; realm_sync_socket_timer_canceled_func_t m_timer_cancel = nullptr; realm_sync_socket_timer_free_func_t m_timer_free = nullptr; }; +RLM_API void realm_sync_socket_timer_complete(realm_sync_socket_callback* timer_handler, realm_errno_e code, + const char* reason) +{ + auto complete_status = + code == realm_errno_e::RLM_ERR_NONE ? Status::OK() : Status{static_cast(code), reason}; + (*(timer_handler->get()))(complete_status); +} + +RLM_API void realm_sync_socket_timer_canceled(realm_sync_socket_callback* timer_handler) +{ + realm_sync_socket_timer_complete(timer_handler, RLM_ERR_OPERATION_ABORTED, "Operation canceled"); +} + +// This class represents a websocket instance provided by the CAPI implememtation for sending +// and receiving data and connection state from the websocket. This class is used directly by +// the sync client. struct CAPIWebSocket : sync::WebSocketInterface { public: CAPIWebSocket(realm_userdata_t userdata, realm_sync_socket_connect_func_t websocket_connect_func, @@ -89,15 +117,24 @@ struct CAPIWebSocket : sync::WebSocketInterface { } private: + // A pointer to the CAPI implementation's websocket instance. This is provided by + // the m_websocket_connect() function when this websocket instance is created. realm_sync_socket_websocket_t m_socket = nullptr; + + // A wrapped reference to the websocket observer in the sync client that receives the + // websocket status callbacks. This is provided by the Sync Client. realm_websocket_observer_t* m_observer = nullptr; - realm_userdata_t m_userdata = nullptr; + // These values were originally provided to the socket_provider instance by the CAPI + // implementation when it was created. + realm_userdata_t m_userdata = nullptr; realm_sync_socket_connect_func_t m_websocket_connect = nullptr; realm_sync_socket_websocket_async_write_func_t m_websocket_async_write = nullptr; realm_sync_socket_websocket_free_func_t m_websocket_free = nullptr; }; +// Represents the websocket observer in the sync client that receives websocket status +// callbacks and passes them along to the WebSocketObserver object. struct CAPIWebSocketObserver : sync::WebSocketObserver { public: CAPIWebSocketObserver(std::unique_ptr observer) @@ -131,6 +168,10 @@ struct CAPIWebSocketObserver : sync::WebSocketObserver { std::unique_ptr m_observer; }; +// This is the primary resource for providing event loop, timer and websocket +// resources and synchronization for the Sync Client. The CAPI implementation +// needs to implement the "funct_t" functions provided to this class for connecting +// the implementation to the operations called by the Sync Client. struct CAPISyncSocketProvider : sync::SyncSocketProvider { realm_userdata_t m_userdata = nullptr; realm_free_userdata_func_t m_free = nullptr; @@ -169,6 +210,9 @@ struct CAPISyncSocketProvider : sync::SyncSocketProvider { m_free(m_userdata); } + // Create a websocket object that will be returned to the Sync Client, which is expected to + // begin connecting to the endpoint as soon as the object is created. The state and any data + // received is passed to the socket observer via the helper functions defined below this class. std::unique_ptr connect(std::unique_ptr observer, sync::WebSocketEndpoint&& endpoint) final { @@ -218,8 +262,8 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new( }); } -RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback* realm_callback, realm_errno_e code, - const char* reason) +RLM_API void realm_sync_socket_post_complete(realm_sync_socket_callback* realm_callback, realm_errno_e code, + const char* reason) { auto complete_status = code == realm_errno_e::RLM_ERR_NONE ? Status::OK() : Status{static_cast(code), reason}; diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index 481dec645e6..54779495d3e 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -6093,8 +6093,7 @@ TEST_CASE("C API app: websocket provider", "[sync][app][c_api][baas]") { auto test_data = static_cast(userdata); REQUIRE(test_data); auto cb = [callback_copy = callback](Status s) { - realm_sync_socket_callback_complete(callback_copy, static_cast(s.code()), - s.reason().c_str()); + realm_sync_socket_post_complete(callback_copy, static_cast(s.code()), s.reason().c_str()); }; test_data->socket_provider->post(std::move(cb)); }; From f56d7700909d0a58aa674c4cc4159cebb1c47d66 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Thu, 21 Sep 2023 15:05:25 -0400 Subject: [PATCH 2/7] Updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a025da54478..190683b318a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,11 +16,13 @@ * If a user was logged out while an access token refresh was in progress, the refresh completing would mark the user as logged in again and the user would be in an inconsistent state ([PR #6837](https://github.com/realm/realm-core/pull/6837), since v10.0.0). * If querying over a geospatial dataset that had some objects with a type property set to something other than 'Point' (case insensitive) an exception would have been thrown. Instead of disrupting the query, those objects are now just ignored. ([PR 6989](https://github.com/realm/realm-core/issues/6989), since the introduction of geospatial) * The Swift package failed to link required libraries when building for macCatalyst. +* Fixed issue with double delete when using the CAPI for timers in platform networking ([#6993](https://github.com/realm/realm-core/issues/6993), since v13.3.0). ### Breaking changes * SyncUser::provider_type() and realm_user_get_auth_provider() have been removed. Users don't have provider types; identities do. `SyncUser::is_anonymous()` is a more correct version of checking if the provider type is anonymous ([PR #6837](https://github.com/realm/realm-core/pull/6837)). * SyncUser no longer has a `local_identity()`. `identity()` has been guaranteed to be unique per App ever since v10 ([PR #6837](https://github.com/realm/realm-core/pull/6837)). * SyncUser no longer overrides operator==. Pointer equality should be used to compare sync users ([PR #6837](https://github.com/realm/realm-core/pull/6837)). +* Platform Networking CAPI has been updated to provide 3 different functions (instead of 1) for executing callback handlers depending on purpose ([PR #6994](https://github.com/realm/realm-core/pull/6994)). ### Compatibility * Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5. From 32cb70d822b21c9bba515e332163eb2a5bcd584c Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Fri, 22 Sep 2023 15:08:10 -0400 Subject: [PATCH 3/7] Added realm_sync_socket_write_complete() fcn and other updates from review --- src/realm.h | 49 +++++++++++------- .../object-store/c_api/socket_provider.cpp | 51 ++++++++++++------- 2 files changed, 66 insertions(+), 34 deletions(-) diff --git a/src/realm.h b/src/realm.h index d56375a4bdf..a816af43bea 100644 --- a/src/realm.h +++ b/src/realm.h @@ -4065,8 +4065,9 @@ RLM_API bool realm_mongo_collection_find_one_and_delete(realm_mongodb_collection * Creates a new sync socket instance for the Sync Client that handles the operations for a custom * websocket and event loop implementation. * @param userdata CAPI implementation specific pointer containing custom context data that is provided to - * each of the provided function. - * @param userdata_free function that will be called when the sync socket is destroyed to delete userdata. + * each of the provided functions. + * @param userdata_free function that will be called when the sync socket is destroyed to delete userdata. This + * is required if userdata is not null. * @param post_func function that will be called to post a callback handler onto the event loop - use the * realm_sync_socket_post_complete() function when the callback handler is scheduled to run. * @param create_timer_func function that will be called to create a new timer resource with the callback @@ -4091,48 +4092,62 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new( /** * This function is to be called by the CAPI Implementer when the timer expires or an error occurs. * @param timer_handler the timer callback handler that was provided when the timer was created. - * @param code the error code for the error that occurred or RLM_ERR_NONE if the timer expired normally. + * @param status the error code for the error that occurred or RLM_ERR_NONE if the timer expired normally. * @param reason a string describing details about the error that occurred or empty string if no error. - * NOTE: This function must be called in the same "thread" as the timer canceled and SyncSocket::Post operations. + * NOTE: This function must be called by the event loop execution thread. */ -RLM_API void realm_sync_socket_timer_complete(realm_sync_socket_callback_t* timer_handler, realm_errno_e code, +RLM_API void realm_sync_socket_timer_complete(realm_sync_socket_callback_t* timer_handler, realm_errno_e status, const char* reason); /** * This function is to be called by the CAPI implementer when the timer is canceled by the Sync Client, either * directly or as a result of the CAPITimer object being destroyed. * @param timer_handler the timer callback handler that was provided when the timer was created. - * NOTE: This function must be called in the same "thread" as the timer complete and SyncSocket::Post operations. + * NOTE: This function must be called by the event loop execution thread. */ RLM_API void realm_sync_socket_timer_canceled(realm_sync_socket_callback_t* timer_handler); /** * To be called to execute the callback function provided to the post_func when the event loop executes - * that post'ed operation. The realm_callback resource will automatically be destroyed during this + * that post'ed operation. The post_handler resource will automatically be destroyed during this * operation. - * @param post_handler the post callback handler that was originally provided to the post_func when + * @param post_handler the post callback handler that was originally provided to the post_func + * @param status the error code for the error that occurred or RLM_ERR_NONE if the operation was to be run + * @param reason a string describing details about the error that occurred or empty string if no error. + * NOTE: This function must be called by the event loop execution thread. */ RLM_API void realm_sync_socket_post_complete(realm_sync_socket_callback_t* post_handler, realm_errno_e status, const char* reason); +/** + * To be called to execute the callback function provided to the websocket_write_func when the write + * operation is complete. The write_handler resource will automatically be destroyed during this + * operation. + * @param write_handler the write callback handler that was originally provided to the websocket_write_func + * @param status the error code for the error that occurred or RLM_ERR_NONE if write completed successfully + * @param reason a string describing details about the error that occurred or empty string if no error. + * NOTE: This function must be called by the event loop execution thread. + */ +RLM_API void realm_sync_socket_write_complete(realm_sync_socket_callback_t* write_handler, realm_errno_e status, + const char* reason); /** * To be called when the websocket successfully connects to the server. * @param realm_websocket_observer the websocket observer object that was provided to the websocket_connect_func * @param protocol the value of the Sec-WebSocket-Protocol header in the connect response from the server. - * NOTE: This function should not be called after the websocket_free_func has been called to release - * the websocket resources. + * NOTE: This function must be called by the event loop execution thread and should not be called + * after the websocket_free_func has been called to release the websocket resources. */ RLM_API void realm_sync_socket_websocket_connected(realm_websocket_observer_t* realm_websocket_observer, const char* protocol); /** - * To be called when an error occurs - the actual error value with be provided when the websocket_closed + * To be called when an error occurs - the actual error value will be provided when the websocket_closed * function is called. This function informs that the socket object is in an error state and no further * TX operations should be performed. * @param realm_websocket_observer the websocket observer object that was provided to the websocket_connect_func - * NOTE: This function should not be called after the websocket_free_func has been called to release - * the websocket resources. + * NOTE: This function must be called by the event loop execution thread and should not be called + * after the websocket_free_func has been called to release the websocket resources. */ RLM_API void realm_sync_socket_websocket_error(realm_websocket_observer_t* realm_websocket_observer); @@ -4142,8 +4157,8 @@ RLM_API void realm_sync_socket_websocket_error(realm_websocket_observer_t* realm * @param realm_websocket_observer the websocket observer object that was provided to the websocket_connect_func * @param data a pointer to the buffer that contains the data received over the websocket * @param data_size the number of bytes in the data buffer - * NOTE: This function should not be called after the websocket_free_func has been called to release - * the websocket resources. + * NOTE: This function must be called by the event loop execution thread and should not be called + * after the websocket_free_func has been called to release the websocket resources. */ RLM_API void realm_sync_socket_websocket_message(realm_websocket_observer_t* realm_websocket_observer, const char* data, size_t data_size); @@ -4158,8 +4173,8 @@ RLM_API void realm_sync_socket_websocket_message(realm_websocket_observer_t* rea * @param status the websocket error code that describes why the websocket was closed, or * RLM_ERR_WEBSOCKET_OK if the socket was closed normally. * @param reason a string describing details about the error that occurred or empty string if no error. - * NOTE: This function should not be called after the websocket_free_func has been called to release - * the websocket resources. + * NOTE: This function must be called by the event loop execution thread and should not be called + * after the websocket_free_func has been called to release the websocket resources. */ RLM_API void realm_sync_socket_websocket_closed(realm_websocket_observer_t* realm_websocket_observer, bool was_clean, realm_web_socket_errno_e status, const char* reason); diff --git a/src/realm/object-store/c_api/socket_provider.cpp b/src/realm/object-store/c_api/socket_provider.cpp index 9dc552c85a8..3402c26b8eb 100644 --- a/src/realm/object-store/c_api/socket_provider.cpp +++ b/src/realm/object-store/c_api/socket_provider.cpp @@ -16,6 +16,7 @@ struct CAPITimer : sync::SyncSocketProvider::Timer { realm_sync_socket_timer_canceled_func_t cancel_timer_func, realm_sync_socket_timer_free_func_t free_timer_func) : m_handler(handler) + , m_userdata(userdata) , m_timer_create(create_timer_func) , m_timer_cancel(cancel_timer_func) , m_timer_free(free_timer_func) @@ -57,17 +58,23 @@ struct CAPITimer : sync::SyncSocketProvider::Timer { realm_sync_socket_timer_free_func_t m_timer_free = nullptr; }; -RLM_API void realm_sync_socket_timer_complete(realm_sync_socket_callback* timer_handler, realm_errno_e code, - const char* reason) +static void realm_sync_socket_op_complete(realm_sync_socket_callback* realm_callback, realm_errno_e status, + const char* reason) { auto complete_status = - code == realm_errno_e::RLM_ERR_NONE ? Status::OK() : Status{static_cast(code), reason}; - (*(timer_handler->get()))(complete_status); + status == realm_errno_e::RLM_ERR_NONE ? Status::OK() : Status{static_cast(status), reason}; + (*(realm_callback->get()))(complete_status); +} + +RLM_API void realm_sync_socket_timer_complete(realm_sync_socket_callback* timer_handler, realm_errno_e status, + const char* reason) +{ + realm_sync_socket_op_complete(timer_handler, status, reason); } RLM_API void realm_sync_socket_timer_canceled(realm_sync_socket_callback* timer_handler) { - realm_sync_socket_timer_complete(timer_handler, RLM_ERR_OPERATION_ABORTED, "Operation canceled"); + realm_sync_socket_op_complete(timer_handler, RLM_ERR_OPERATION_ABORTED, "Timer canceled"); } // This class represents a websocket instance provided by the CAPI implememtation for sending @@ -174,7 +181,7 @@ struct CAPIWebSocketObserver : sync::WebSocketObserver { // the implementation to the operations called by the Sync Client. struct CAPISyncSocketProvider : sync::SyncSocketProvider { realm_userdata_t m_userdata = nullptr; - realm_free_userdata_func_t m_free = nullptr; + realm_free_userdata_func_t m_userdata_free = nullptr; realm_sync_socket_post_func_t m_post = nullptr; realm_sync_socket_create_timer_func_t m_timer_create = nullptr; realm_sync_socket_timer_canceled_func_t m_timer_cancel = nullptr; @@ -186,7 +193,7 @@ struct CAPISyncSocketProvider : sync::SyncSocketProvider { CAPISyncSocketProvider() = default; CAPISyncSocketProvider(CAPISyncSocketProvider&& other) : m_userdata(std::exchange(other.m_userdata, nullptr)) - , m_free(std::exchange(other.m_free, nullptr)) + , m_userdata_free(std::exchange(other.m_userdata_free, nullptr)) , m_post(std::exchange(other.m_post, nullptr)) , m_timer_create(std::exchange(other.m_timer_create, nullptr)) , m_timer_cancel(std::exchange(other.m_timer_cancel, nullptr)) @@ -195,7 +202,10 @@ struct CAPISyncSocketProvider : sync::SyncSocketProvider { , m_websocket_async_write(std::exchange(other.m_websocket_async_write, nullptr)) , m_websocket_free(std::exchange(other.m_websocket_free, nullptr)) { - REALM_ASSERT(m_free); + // userdata_free can be null if userdata is not used + if (m_userdata != nullptr) { + REALM_ASSERT(m_userdata_free); + } REALM_ASSERT(m_post); REALM_ASSERT(m_timer_create); REALM_ASSERT(m_timer_cancel); @@ -207,7 +217,9 @@ struct CAPISyncSocketProvider : sync::SyncSocketProvider { ~CAPISyncSocketProvider() { - m_free(m_userdata); + if (m_userdata_free) { + m_userdata_free(m_userdata); + } } // Create a websocket object that will be returned to the Sync Client, which is expected to @@ -250,7 +262,7 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new( return wrap_err([&]() { auto capi_socket_provider = std::make_shared(); capi_socket_provider->m_userdata = userdata; - capi_socket_provider->m_free = userdata_free; + capi_socket_provider->m_userdata_free = userdata_free; capi_socket_provider->m_post = post_func; capi_socket_provider->m_timer_create = create_timer_func; capi_socket_provider->m_timer_cancel = cancel_timer_func; @@ -262,13 +274,18 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new( }); } -RLM_API void realm_sync_socket_post_complete(realm_sync_socket_callback* realm_callback, realm_errno_e code, +RLM_API void realm_sync_socket_post_complete(realm_sync_socket_callback* post_handler, realm_errno_e status, const char* reason) { - auto complete_status = - code == realm_errno_e::RLM_ERR_NONE ? Status::OK() : Status{static_cast(code), reason}; - (*(realm_callback->get()))(complete_status); - realm_release(realm_callback); + realm_sync_socket_op_complete(post_handler, status, reason); + realm_release(post_handler); +} + +RLM_API void realm_sync_socket_write_complete(realm_sync_socket_callback_t* write_handler, realm_errno_e status, + const char* reason) +{ + realm_sync_socket_op_complete(write_handler, status, reason); + realm_release(write_handler); } RLM_API void realm_sync_socket_websocket_connected(realm_websocket_observer_t* realm_websocket_observer, @@ -289,10 +306,10 @@ RLM_API void realm_sync_socket_websocket_message(realm_websocket_observer_t* rea } RLM_API void realm_sync_socket_websocket_closed(realm_websocket_observer_t* realm_websocket_observer, bool was_clean, - realm_web_socket_errno_e code, const char* reason) + realm_web_socket_errno_e status, const char* reason) { realm_websocket_observer->get()->websocket_closed_handler( - was_clean, static_cast(code), reason); + was_clean, static_cast(status), reason); } RLM_API void realm_sync_client_config_set_sync_socket(realm_sync_client_config_t* config, From 2adff54d7974254cfcebb8b8e1570f9673ce80d5 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Fri, 22 Sep 2023 15:10:19 -0400 Subject: [PATCH 4/7] Updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 190683b318a..ba97ee426c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ * SyncUser::provider_type() and realm_user_get_auth_provider() have been removed. Users don't have provider types; identities do. `SyncUser::is_anonymous()` is a more correct version of checking if the provider type is anonymous ([PR #6837](https://github.com/realm/realm-core/pull/6837)). * SyncUser no longer has a `local_identity()`. `identity()` has been guaranteed to be unique per App ever since v10 ([PR #6837](https://github.com/realm/realm-core/pull/6837)). * SyncUser no longer overrides operator==. Pointer equality should be used to compare sync users ([PR #6837](https://github.com/realm/realm-core/pull/6837)). -* Platform Networking CAPI has been updated to provide 3 different functions (instead of 1) for executing callback handlers depending on purpose ([PR #6994](https://github.com/realm/realm-core/pull/6994)). +* Platform Networking CAPI has been updated to provide separate functions (instead of 1) for executing callback handlers depending on purpose ([PR #6994](https://github.com/realm/realm-core/pull/6994)). ### Compatibility * Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5. From a21588d369bfce212b432be02a5b1b863630b08d Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Fri, 22 Sep 2023 17:38:55 -0400 Subject: [PATCH 5/7] Don't call handler functions more than once, esp for timers; updated documentation --- src/realm.h | 7 ++++--- src/realm/object-store/c_api/socket_provider.cpp | 11 ++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/realm.h b/src/realm.h index a816af43bea..d6e666abde7 100644 --- a/src/realm.h +++ b/src/realm.h @@ -4090,7 +4090,8 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new( realm_sync_socket_websocket_free_func_t websocket_free_func); /** - * This function is to be called by the CAPI Implementer when the timer expires or an error occurs. + * To be called to execute the callback handler provided to the create_timer_func when the timer is + * complete or an error occurs while processing the timer. * @param timer_handler the timer callback handler that was provided when the timer was created. * @param status the error code for the error that occurred or RLM_ERR_NONE if the timer expired normally. * @param reason a string describing details about the error that occurred or empty string if no error. @@ -4100,8 +4101,8 @@ RLM_API void realm_sync_socket_timer_complete(realm_sync_socket_callback_t* time const char* reason); /** - * This function is to be called by the CAPI implementer when the timer is canceled by the Sync Client, either - * directly or as a result of the CAPITimer object being destroyed. + * To be called to execute the callback handler provided to the create_timer_func when the timer has been + * canceled. * @param timer_handler the timer callback handler that was provided when the timer was created. * NOTE: This function must be called by the event loop execution thread. */ diff --git a/src/realm/object-store/c_api/socket_provider.cpp b/src/realm/object-store/c_api/socket_provider.cpp index 3402c26b8eb..c4e992511be 100644 --- a/src/realm/object-store/c_api/socket_provider.cpp +++ b/src/realm/object-store/c_api/socket_provider.cpp @@ -61,9 +61,14 @@ struct CAPITimer : sync::SyncSocketProvider::Timer { static void realm_sync_socket_op_complete(realm_sync_socket_callback* realm_callback, realm_errno_e status, const char* reason) { - auto complete_status = - status == realm_errno_e::RLM_ERR_NONE ? Status::OK() : Status{static_cast(status), reason}; - (*(realm_callback->get()))(complete_status); + if (*realm_callback) { + auto complete_status = status == realm_errno_e::RLM_ERR_NONE + ? Status::OK() + : Status{static_cast(status), reason}; + (*(realm_callback->get()))(complete_status); + // Keep the container, but release the handler + realm_callback->reset(); + } } RLM_API void realm_sync_socket_timer_complete(realm_sync_socket_callback* timer_handler, realm_errno_e status, From 0705a8b4a5ae1a76094fc985bf681fe4cea07734 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Mon, 25 Sep 2023 01:09:01 -0400 Subject: [PATCH 6/7] Updated some comments --- src/realm/object-store/c_api/socket_provider.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/realm/object-store/c_api/socket_provider.cpp b/src/realm/object-store/c_api/socket_provider.cpp index c4e992511be..0dfcf0a3996 100644 --- a/src/realm/object-store/c_api/socket_provider.cpp +++ b/src/realm/object-store/c_api/socket_provider.cpp @@ -27,6 +27,7 @@ struct CAPITimer : sync::SyncSocketProvider::Timer { /// Cancels the timer and destroys the timer instance. ~CAPITimer() { + // Make sure the timer is stopped, if not already m_timer_cancel(m_userdata, m_timer); m_timer_free(m_userdata, m_timer); realm_release(m_handler); @@ -61,12 +62,12 @@ struct CAPITimer : sync::SyncSocketProvider::Timer { static void realm_sync_socket_op_complete(realm_sync_socket_callback* realm_callback, realm_errno_e status, const char* reason) { - if (*realm_callback) { + if (realm_callback->get() != nullptr) { auto complete_status = status == realm_errno_e::RLM_ERR_NONE ? Status::OK() : Status{static_cast(status), reason}; (*(realm_callback->get()))(complete_status); - // Keep the container, but release the handler + // Keep the container, but release the handler so it can't be called twice. realm_callback->reset(); } } From b84ede822fc273c65c28e592642b285cfd018965 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Mon, 25 Sep 2023 12:50:21 -0400 Subject: [PATCH 7/7] Updated changelog after release --- CHANGELOG.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ded8a8d171..e5347c9694d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,10 @@ ### Fixed * ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?) -* None. +* Fixed issue with double delete when using the CAPI for timers in platform networking ([#6993](https://github.com/realm/realm-core/issues/6993), since v13.3.0). ### Breaking changes -* None. +* Platform Networking CAPI has been updated to provide separate functions (instead of 1) for executing callback handlers depending on purpose ([PR #6994](https://github.com/realm/realm-core/pull/6994)). ### Compatibility * Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5. @@ -38,13 +38,11 @@ * If a user was logged out while an access token refresh was in progress, the refresh completing would mark the user as logged in again and the user would be in an inconsistent state ([PR #6837](https://github.com/realm/realm-core/pull/6837), since v10.0.0). * If querying over a geospatial dataset that had some objects with a type property set to something other than 'Point' (case insensitive) an exception would have been thrown. Instead of disrupting the query, those objects are now just ignored. ([PR 6989](https://github.com/realm/realm-core/issues/6989), since the introduction of geospatial) * The Swift package failed to link required libraries when building for macCatalyst. -* Fixed issue with double delete when using the CAPI for timers in platform networking ([#6993](https://github.com/realm/realm-core/issues/6993), since v13.3.0). ### Breaking changes * SyncUser::provider_type() and realm_user_get_auth_provider() have been removed. Users don't have provider types; identities do. `SyncUser::is_anonymous()` is a more correct version of checking if the provider type is anonymous ([PR #6837](https://github.com/realm/realm-core/pull/6837)). * SyncUser no longer has a `local_identity()`. `identity()` has been guaranteed to be unique per App ever since v10 ([PR #6837](https://github.com/realm/realm-core/pull/6837)). * SyncUser no longer overrides operator==. Pointer equality should be used to compare sync users ([PR #6837](https://github.com/realm/realm-core/pull/6837)). -* Platform Networking CAPI has been updated to provide separate functions (instead of 1) for executing callback handlers depending on purpose ([PR #6994](https://github.com/realm/realm-core/pull/6994)). ### Compatibility * Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5.