Skip to content

Commit a9c8f99

Browse files
author
Michael Wilkerson-Barker
authored
Fixed double delete in CAPITimer and add documentation for platform networking CAPI integration (#6994)
* Fixed double delete in CAPITimer and added individual functions for handling callbacks. * Added realm_sync_socket_write_complete() fcn and other updates from review * Don't call handler functions more than once, esp for timers; updated documentation * Updated some comments * Updated changelog after release
1 parent 5d2c411 commit a9c8f99

File tree

4 files changed

+185
-22
lines changed

4 files changed

+185
-22
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
### Fixed
88
* <How do the end-user experience this issue? what was the impact?> ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?)
9-
* None.
9+
* 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).
1010

1111
### Breaking changes
12-
* None.
12+
* 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)).
1313

1414
### Compatibility
1515
* Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5.

src/realm.h

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4061,6 +4061,26 @@ RLM_API bool realm_mongo_collection_find_one_and_delete(realm_mongodb_collection
40614061
realm_userdata_t data, realm_free_userdata_func_t delete_data,
40624062
realm_mongodb_callback_t callback);
40634063

4064+
/**
4065+
* Creates a new sync socket instance for the Sync Client that handles the operations for a custom
4066+
* websocket and event loop implementation.
4067+
* @param userdata CAPI implementation specific pointer containing custom context data that is provided to
4068+
* each of the provided functions.
4069+
* @param userdata_free function that will be called when the sync socket is destroyed to delete userdata. This
4070+
* is required if userdata is not null.
4071+
* @param post_func function that will be called to post a callback handler onto the event loop - use the
4072+
* realm_sync_socket_post_complete() function when the callback handler is scheduled to run.
4073+
* @param create_timer_func function that will be called to create a new timer resource with the callback
4074+
* handler that will be run when the timer expires or an erorr occurs - use the
4075+
* realm_sync_socket_timer_canceled() function if the timer is canceled or the
4076+
* realm_sync_socket_timer_complete() function if the timer expires or an error occurs.
4077+
* @param cancel_timer_func function that will be called when the timer has been canceled by the sync client.
4078+
* @param free_timer_func function that will be called when the timer resource has been destroyed by the sync client.
4079+
* @param websocket_connect_func function that will be called when the sync client creates a websocket.
4080+
* @param websocket_write_func function that will be called when the sync client sends data over the websocket.
4081+
* @param websocket_free_func function that will be called when the sync client closes the websocket conneciton.
4082+
* @return a realm_sync_socket_t pointer suitable for passing to realm_sync_client_config_set_sync_socket()
4083+
*/
40644084
RLM_API realm_sync_socket_t* realm_sync_socket_new(
40654085
realm_userdata_t userdata, realm_free_userdata_func_t userdata_free, realm_sync_socket_post_func_t post_func,
40664086
realm_sync_socket_create_timer_func_t create_timer_func,
@@ -4069,17 +4089,94 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new(
40694089
realm_sync_socket_websocket_async_write_func_t websocket_write_func,
40704090
realm_sync_socket_websocket_free_func_t websocket_free_func);
40714091

4072-
RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback_t* realm_callback, realm_errno_e status,
4073-
const char* reason);
4092+
/**
4093+
* To be called to execute the callback handler provided to the create_timer_func when the timer is
4094+
* complete or an error occurs while processing the timer.
4095+
* @param timer_handler the timer callback handler that was provided when the timer was created.
4096+
* @param status the error code for the error that occurred or RLM_ERR_NONE if the timer expired normally.
4097+
* @param reason a string describing details about the error that occurred or empty string if no error.
4098+
* NOTE: This function must be called by the event loop execution thread.
4099+
*/
4100+
RLM_API void realm_sync_socket_timer_complete(realm_sync_socket_callback_t* timer_handler, realm_errno_e status,
4101+
const char* reason);
40744102

4103+
/**
4104+
* To be called to execute the callback handler provided to the create_timer_func when the timer has been
4105+
* canceled.
4106+
* @param timer_handler the timer callback handler that was provided when the timer was created.
4107+
* NOTE: This function must be called by the event loop execution thread.
4108+
*/
4109+
RLM_API void realm_sync_socket_timer_canceled(realm_sync_socket_callback_t* timer_handler);
4110+
4111+
/**
4112+
* To be called to execute the callback function provided to the post_func when the event loop executes
4113+
* that post'ed operation. The post_handler resource will automatically be destroyed during this
4114+
* operation.
4115+
* @param post_handler the post callback handler that was originally provided to the post_func
4116+
* @param status the error code for the error that occurred or RLM_ERR_NONE if the operation was to be run
4117+
* @param reason a string describing details about the error that occurred or empty string if no error.
4118+
* NOTE: This function must be called by the event loop execution thread.
4119+
*/
4120+
RLM_API void realm_sync_socket_post_complete(realm_sync_socket_callback_t* post_handler, realm_errno_e status,
4121+
const char* reason);
4122+
4123+
/**
4124+
* To be called to execute the callback function provided to the websocket_write_func when the write
4125+
* operation is complete. The write_handler resource will automatically be destroyed during this
4126+
* operation.
4127+
* @param write_handler the write callback handler that was originally provided to the websocket_write_func
4128+
* @param status the error code for the error that occurred or RLM_ERR_NONE if write completed successfully
4129+
* @param reason a string describing details about the error that occurred or empty string if no error.
4130+
* NOTE: This function must be called by the event loop execution thread.
4131+
*/
4132+
RLM_API void realm_sync_socket_write_complete(realm_sync_socket_callback_t* write_handler, realm_errno_e status,
4133+
const char* reason);
4134+
4135+
/**
4136+
* To be called when the websocket successfully connects to the server.
4137+
* @param realm_websocket_observer the websocket observer object that was provided to the websocket_connect_func
4138+
* @param protocol the value of the Sec-WebSocket-Protocol header in the connect response from the server.
4139+
* NOTE: This function must be called by the event loop execution thread and should not be called
4140+
* after the websocket_free_func has been called to release the websocket resources.
4141+
*/
40754142
RLM_API void realm_sync_socket_websocket_connected(realm_websocket_observer_t* realm_websocket_observer,
40764143
const char* protocol);
40774144

4145+
/**
4146+
* To be called when an error occurs - the actual error value will be provided when the websocket_closed
4147+
* function is called. This function informs that the socket object is in an error state and no further
4148+
* TX operations should be performed.
4149+
* @param realm_websocket_observer the websocket observer object that was provided to the websocket_connect_func
4150+
* NOTE: This function must be called by the event loop execution thread and should not be called
4151+
* after the websocket_free_func has been called to release the websocket resources.
4152+
*/
40784153
RLM_API void realm_sync_socket_websocket_error(realm_websocket_observer_t* realm_websocket_observer);
40794154

4155+
/**
4156+
* To be called to provide the received data to the Sync Client when a write operation has completed.
4157+
* The data buffer can be safely discarded after this function has completed.
4158+
* @param realm_websocket_observer the websocket observer object that was provided to the websocket_connect_func
4159+
* @param data a pointer to the buffer that contains the data received over the websocket
4160+
* @param data_size the number of bytes in the data buffer
4161+
* NOTE: This function must be called by the event loop execution thread and should not be called
4162+
* after the websocket_free_func has been called to release the websocket resources.
4163+
*/
40804164
RLM_API void realm_sync_socket_websocket_message(realm_websocket_observer_t* realm_websocket_observer,
40814165
const char* data, size_t data_size);
40824166

4167+
/**
4168+
* To be called when the websocket has been closed, either due to an error or a normal close operation.
4169+
* @param realm_websocket_observer the websocket observer object that was provided to the websocket_connect_func
4170+
* @param was_clean boolean value that indicates whether this is a normal close situation (true), the
4171+
* error was provided by the server via a close message (true), or if the error was
4172+
* generated by the local websocket as a result of some other error (false) (e.g. host
4173+
* unreachable, etc.)
4174+
* @param status the websocket error code that describes why the websocket was closed, or
4175+
* RLM_ERR_WEBSOCKET_OK if the socket was closed normally.
4176+
* @param reason a string describing details about the error that occurred or empty string if no error.
4177+
* NOTE: This function must be called by the event loop execution thread and should not be called
4178+
* after the websocket_free_func has been called to release the websocket resources.
4179+
*/
40834180
RLM_API void realm_sync_socket_websocket_closed(realm_websocket_observer_t* realm_websocket_observer, bool was_clean,
40844181
realm_web_socket_errno_e status, const char* reason);
40854182

src/realm/object-store/c_api/socket_provider.cpp

Lines changed: 83 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
namespace realm::c_api {
88
namespace {
99

10+
// THis class represents the timer resource that is returned to the sync client from the
11+
// CAPI implementation details for canceling and deleting the timer resources.
1012
struct CAPITimer : sync::SyncSocketProvider::Timer {
1113
public:
1214
CAPITimer(realm_userdata_t userdata, int64_t delay_ms, realm_sync_socket_callback_t* handler,
1315
realm_sync_socket_create_timer_func_t create_timer_func,
1416
realm_sync_socket_timer_canceled_func_t cancel_timer_func,
1517
realm_sync_socket_timer_free_func_t free_timer_func)
1618
: m_handler(handler)
19+
, m_userdata(userdata)
1720
, m_timer_create(create_timer_func)
1821
, m_timer_cancel(cancel_timer_func)
1922
, m_timer_free(free_timer_func)
@@ -24,27 +27,65 @@ struct CAPITimer : sync::SyncSocketProvider::Timer {
2427
/// Cancels the timer and destroys the timer instance.
2528
~CAPITimer()
2629
{
30+
// Make sure the timer is stopped, if not already
2731
m_timer_cancel(m_userdata, m_timer);
2832
m_timer_free(m_userdata, m_timer);
2933
realm_release(m_handler);
3034
}
3135

32-
/// Cancel the timer immediately.
36+
// Cancel the timer immediately - the CAPI implementation will need to call the
37+
// realm_sync_socket_timer_canceled function to notify the sync client that the
38+
// timer has been canceled and must be called in the same execution thread as
39+
// the timer complete.
3340
void cancel() override
3441
{
3542
m_timer_cancel(m_userdata, m_timer);
3643
}
3744

3845
private:
46+
// A pointer to the CAPI implementation's timer instance. This is provided by the
47+
// CAPI implementation when the create_timer_func function is called.
3948
realm_sync_socket_timer_t m_timer = nullptr;
4049

41-
realm_userdata_t m_userdata = nullptr;
50+
// A wrapped reference to the callback function to be called when the timer completes,
51+
// is canceled or an error occurs. This is provided by the Sync Client
4252
realm_sync_socket_callback_t* m_handler = nullptr;
53+
54+
// These values were originally provided to the socket_provider instance by the CAPI
55+
// implementation when it was created
56+
realm_userdata_t m_userdata = nullptr;
4357
realm_sync_socket_create_timer_func_t m_timer_create = nullptr;
4458
realm_sync_socket_timer_canceled_func_t m_timer_cancel = nullptr;
4559
realm_sync_socket_timer_free_func_t m_timer_free = nullptr;
4660
};
4761

62+
static void realm_sync_socket_op_complete(realm_sync_socket_callback* realm_callback, realm_errno_e status,
63+
const char* reason)
64+
{
65+
if (realm_callback->get() != nullptr) {
66+
auto complete_status = status == realm_errno_e::RLM_ERR_NONE
67+
? Status::OK()
68+
: Status{static_cast<ErrorCodes::Error>(status), reason};
69+
(*(realm_callback->get()))(complete_status);
70+
// Keep the container, but release the handler so it can't be called twice.
71+
realm_callback->reset();
72+
}
73+
}
74+
75+
RLM_API void realm_sync_socket_timer_complete(realm_sync_socket_callback* timer_handler, realm_errno_e status,
76+
const char* reason)
77+
{
78+
realm_sync_socket_op_complete(timer_handler, status, reason);
79+
}
80+
81+
RLM_API void realm_sync_socket_timer_canceled(realm_sync_socket_callback* timer_handler)
82+
{
83+
realm_sync_socket_op_complete(timer_handler, RLM_ERR_OPERATION_ABORTED, "Timer canceled");
84+
}
85+
86+
// This class represents a websocket instance provided by the CAPI implememtation for sending
87+
// and receiving data and connection state from the websocket. This class is used directly by
88+
// the sync client.
4889
struct CAPIWebSocket : sync::WebSocketInterface {
4990
public:
5091
CAPIWebSocket(realm_userdata_t userdata, realm_sync_socket_connect_func_t websocket_connect_func,
@@ -89,15 +130,24 @@ struct CAPIWebSocket : sync::WebSocketInterface {
89130
}
90131

91132
private:
133+
// A pointer to the CAPI implementation's websocket instance. This is provided by
134+
// the m_websocket_connect() function when this websocket instance is created.
92135
realm_sync_socket_websocket_t m_socket = nullptr;
136+
137+
// A wrapped reference to the websocket observer in the sync client that receives the
138+
// websocket status callbacks. This is provided by the Sync Client.
93139
realm_websocket_observer_t* m_observer = nullptr;
94-
realm_userdata_t m_userdata = nullptr;
95140

141+
// These values were originally provided to the socket_provider instance by the CAPI
142+
// implementation when it was created.
143+
realm_userdata_t m_userdata = nullptr;
96144
realm_sync_socket_connect_func_t m_websocket_connect = nullptr;
97145
realm_sync_socket_websocket_async_write_func_t m_websocket_async_write = nullptr;
98146
realm_sync_socket_websocket_free_func_t m_websocket_free = nullptr;
99147
};
100148

149+
// Represents the websocket observer in the sync client that receives websocket status
150+
// callbacks and passes them along to the WebSocketObserver object.
101151
struct CAPIWebSocketObserver : sync::WebSocketObserver {
102152
public:
103153
CAPIWebSocketObserver(std::unique_ptr<sync::WebSocketObserver> observer)
@@ -131,9 +181,13 @@ struct CAPIWebSocketObserver : sync::WebSocketObserver {
131181
std::unique_ptr<sync::WebSocketObserver> m_observer;
132182
};
133183

184+
// This is the primary resource for providing event loop, timer and websocket
185+
// resources and synchronization for the Sync Client. The CAPI implementation
186+
// needs to implement the "funct_t" functions provided to this class for connecting
187+
// the implementation to the operations called by the Sync Client.
134188
struct CAPISyncSocketProvider : sync::SyncSocketProvider {
135189
realm_userdata_t m_userdata = nullptr;
136-
realm_free_userdata_func_t m_free = nullptr;
190+
realm_free_userdata_func_t m_userdata_free = nullptr;
137191
realm_sync_socket_post_func_t m_post = nullptr;
138192
realm_sync_socket_create_timer_func_t m_timer_create = nullptr;
139193
realm_sync_socket_timer_canceled_func_t m_timer_cancel = nullptr;
@@ -145,7 +199,7 @@ struct CAPISyncSocketProvider : sync::SyncSocketProvider {
145199
CAPISyncSocketProvider() = default;
146200
CAPISyncSocketProvider(CAPISyncSocketProvider&& other)
147201
: m_userdata(std::exchange(other.m_userdata, nullptr))
148-
, m_free(std::exchange(other.m_free, nullptr))
202+
, m_userdata_free(std::exchange(other.m_userdata_free, nullptr))
149203
, m_post(std::exchange(other.m_post, nullptr))
150204
, m_timer_create(std::exchange(other.m_timer_create, nullptr))
151205
, m_timer_cancel(std::exchange(other.m_timer_cancel, nullptr))
@@ -154,7 +208,10 @@ struct CAPISyncSocketProvider : sync::SyncSocketProvider {
154208
, m_websocket_async_write(std::exchange(other.m_websocket_async_write, nullptr))
155209
, m_websocket_free(std::exchange(other.m_websocket_free, nullptr))
156210
{
157-
REALM_ASSERT(m_free);
211+
// userdata_free can be null if userdata is not used
212+
if (m_userdata != nullptr) {
213+
REALM_ASSERT(m_userdata_free);
214+
}
158215
REALM_ASSERT(m_post);
159216
REALM_ASSERT(m_timer_create);
160217
REALM_ASSERT(m_timer_cancel);
@@ -166,9 +223,14 @@ struct CAPISyncSocketProvider : sync::SyncSocketProvider {
166223

167224
~CAPISyncSocketProvider()
168225
{
169-
m_free(m_userdata);
226+
if (m_userdata_free) {
227+
m_userdata_free(m_userdata);
228+
}
170229
}
171230

231+
// Create a websocket object that will be returned to the Sync Client, which is expected to
232+
// begin connecting to the endpoint as soon as the object is created. The state and any data
233+
// received is passed to the socket observer via the helper functions defined below this class.
172234
std::unique_ptr<sync::WebSocketInterface> connect(std::unique_ptr<sync::WebSocketObserver> observer,
173235
sync::WebSocketEndpoint&& endpoint) final
174236
{
@@ -206,7 +268,7 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new(
206268
return wrap_err([&]() {
207269
auto capi_socket_provider = std::make_shared<CAPISyncSocketProvider>();
208270
capi_socket_provider->m_userdata = userdata;
209-
capi_socket_provider->m_free = userdata_free;
271+
capi_socket_provider->m_userdata_free = userdata_free;
210272
capi_socket_provider->m_post = post_func;
211273
capi_socket_provider->m_timer_create = create_timer_func;
212274
capi_socket_provider->m_timer_cancel = cancel_timer_func;
@@ -218,13 +280,18 @@ RLM_API realm_sync_socket_t* realm_sync_socket_new(
218280
});
219281
}
220282

221-
RLM_API void realm_sync_socket_callback_complete(realm_sync_socket_callback* realm_callback, realm_errno_e code,
222-
const char* reason)
283+
RLM_API void realm_sync_socket_post_complete(realm_sync_socket_callback* post_handler, realm_errno_e status,
284+
const char* reason)
285+
{
286+
realm_sync_socket_op_complete(post_handler, status, reason);
287+
realm_release(post_handler);
288+
}
289+
290+
RLM_API void realm_sync_socket_write_complete(realm_sync_socket_callback_t* write_handler, realm_errno_e status,
291+
const char* reason)
223292
{
224-
auto complete_status =
225-
code == realm_errno_e::RLM_ERR_NONE ? Status::OK() : Status{static_cast<ErrorCodes::Error>(code), reason};
226-
(*(realm_callback->get()))(complete_status);
227-
realm_release(realm_callback);
293+
realm_sync_socket_op_complete(write_handler, status, reason);
294+
realm_release(write_handler);
228295
}
229296

230297
RLM_API void realm_sync_socket_websocket_connected(realm_websocket_observer_t* realm_websocket_observer,
@@ -245,10 +312,10 @@ RLM_API void realm_sync_socket_websocket_message(realm_websocket_observer_t* rea
245312
}
246313

247314
RLM_API void realm_sync_socket_websocket_closed(realm_websocket_observer_t* realm_websocket_observer, bool was_clean,
248-
realm_web_socket_errno_e code, const char* reason)
315+
realm_web_socket_errno_e status, const char* reason)
249316
{
250317
realm_websocket_observer->get()->websocket_closed_handler(
251-
was_clean, static_cast<sync::websocket::WebSocketError>(code), reason);
318+
was_clean, static_cast<sync::websocket::WebSocketError>(status), reason);
252319
}
253320

254321
RLM_API void realm_sync_client_config_set_sync_socket(realm_sync_client_config_t* config,

0 commit comments

Comments
 (0)