diff --git a/CHANGELOG.md b/CHANGELOG.md index 598008bf0c..4d2a830307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## vNext (TBD) ### Enhancements +* Introduce a `ReconnectBackoffOptions` property on `SyncTimeoutOptions` that allows control over the delay the sync client applies before attempting to reconnect. (PR [#3661](https://github.com/realm/realm-dotnet/pull/3661)). * Role and permissions changes no longer require a client reset to update the local realm. (Core 14.11.0) * On Windows devices Device Sync will additionally look up SSL certificates in the Windows Trusted Root Certification Authorities certificate store when establishing a connection. (Core 14.11.0) * Sync log statements now include the app services connection id in their prefix (e.g `Connection[1:] Session[1]: log message`) to make correlating sync activity to server logs easier during troubleshooting. (Core 14.11.2) diff --git a/Realm/Realm/Native/AppConfiguration.cs b/Realm/Realm/Native/AppConfiguration.cs index 8dc1c83ee7..65362d0ccd 100644 --- a/Realm/Realm/Native/AppConfiguration.cs +++ b/Realm/Realm/Native/AppConfiguration.cs @@ -83,6 +83,15 @@ internal MetadataPersistenceMode? MetadataPersistence internal IntPtr managed_websocket_provider; + internal SyncTimeoutOptions sync_timeout_options; + + [MarshalAs(UnmanagedType.U1)] + internal bool use_cache; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SyncTimeoutOptions + { internal UInt64 sync_connect_timeout_ms; internal UInt64 sync_connection_linger_time_ms; @@ -93,7 +102,18 @@ internal MetadataPersistenceMode? MetadataPersistence internal UInt64 sync_fast_reconnect_limit; - [MarshalAs(UnmanagedType.U1)] - internal bool use_cache; + internal ReconnectBackoffOptions reconnect_backoff_options; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct ReconnectBackoffOptions + { + internal UInt64 max_resumption_delay_interval_ms; + + internal UInt64 resumption_delay_interval_ms; + + internal int resumption_delay_backoff_multiplier; + + internal int delay_jitter_divisor; } } diff --git a/Realm/Realm/Sync/App.cs b/Realm/Realm/Sync/App.cs index 88036677bc..6e8aff0f81 100644 --- a/Realm/Realm/Sync/App.cs +++ b/Realm/Realm/Sync/App.cs @@ -174,11 +174,21 @@ public static App Create(AppConfiguration config) MetadataPersistence = config.MetadataPersistenceMode, default_request_timeout_ms = (ulong)config.DefaultRequestTimeout.TotalMilliseconds, managed_http_client = GCHandle.ToIntPtr(clientHandle), - sync_connection_linger_time_ms = (ulong)syncTimeouts.ConnectionLingerTime.TotalMilliseconds, - sync_connect_timeout_ms = (ulong)syncTimeouts.ConnectTimeout.TotalMilliseconds, - sync_fast_reconnect_limit = (ulong)syncTimeouts.FastReconnectLimit.TotalMilliseconds, - sync_ping_keep_alive_period_ms = (ulong)syncTimeouts.PingKeepAlivePeriod.TotalMilliseconds, - sync_pong_keep_alive_timeout_ms = (ulong)syncTimeouts.PongKeepAliveTimeout.TotalMilliseconds, + sync_timeout_options = new Native.SyncTimeoutOptions + { + sync_connection_linger_time_ms = (ulong)syncTimeouts.ConnectionLingerTime.TotalMilliseconds, + sync_connect_timeout_ms = (ulong)syncTimeouts.ConnectTimeout.TotalMilliseconds, + sync_fast_reconnect_limit = (ulong)syncTimeouts.FastReconnectLimit.TotalMilliseconds, + sync_ping_keep_alive_period_ms = (ulong)syncTimeouts.PingKeepAlivePeriod.TotalMilliseconds, + sync_pong_keep_alive_timeout_ms = (ulong)syncTimeouts.PongKeepAliveTimeout.TotalMilliseconds, + reconnect_backoff_options = new Native.ReconnectBackoffOptions + { + max_resumption_delay_interval_ms = (ulong)syncTimeouts.ReconnectBackoffOptions.MaxReconnectDelayInterval.TotalMilliseconds, + resumption_delay_interval_ms = (ulong)syncTimeouts.ReconnectBackoffOptions.ReconnectDelayInterval.TotalMilliseconds, + resumption_delay_backoff_multiplier = syncTimeouts.ReconnectBackoffOptions.ReconnectDelayBackoffMultiplier, + delay_jitter_divisor = syncTimeouts.ReconnectBackoffOptions.DelayJitterDivisor + } + }, use_cache = config.UseAppCache, }; diff --git a/Realm/Realm/Sync/ReconnectBackoffOptions.cs b/Realm/Realm/Sync/ReconnectBackoffOptions.cs new file mode 100644 index 0000000000..493ed660f3 --- /dev/null +++ b/Realm/Realm/Sync/ReconnectBackoffOptions.cs @@ -0,0 +1,80 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License") +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +using System; + +namespace Realms.Sync +{ + /// + /// Options for configuring the reconnection delay used by the sync client. + /// + /// + /// The sync client employs an exponential backoff delay strategy when reconnecting to the server. + /// In order to not spam the network interface the sync client performs an increasing wait before reconnecting. + /// The wait starts from and multiplies by + /// until it reaches . + /// + public class ReconnectBackoffOptions + { + /// + /// Gets or sets the maximum amount of time to wait before a reconnection attempt. + /// + /// + /// Defaults to 5 minutes. + /// + /// + /// The maximum amount of time to wait before a reconnection attempt. + /// + public TimeSpan MaxReconnectDelayInterval { get; set; } = TimeSpan.FromMinutes(5); + + /// + /// Gets or sets the initial amount of time to wait before a reconnection attempt. + /// + /// + /// Defaults to 1 second. + /// + /// + /// The initial amount of time to wait before a reconnection attempt. + /// + public TimeSpan ReconnectDelayInterval { get; set; } = TimeSpan.FromSeconds(1); + + /// + /// Gets or sets the multiplier to apply to the accumulated reconnection delay before a new reconection attempt. + /// + /// + /// Defaults to 2. + /// + /// + /// The delay multiplier. + /// + public int ReconnectDelayBackoffMultiplier { get; set; } = 2; + + /// + /// Gets or sets the jitter randomization factor to apply to the delay. + /// + /// + /// The reconnection delay is subtracted by a value derived from this divisor so that if a lot of clients lose connection and reconnect at the same time the server won't be overwhelmed. + ///
+ /// Defaults to 4. + ///
+ /// + /// The jitter randomization factor to apply to the delay. + /// + public int DelayJitterDivisor { get; set; } = 4; + } +} \ No newline at end of file diff --git a/Realm/Realm/Sync/SyncTimeoutOptions.cs b/Realm/Realm/Sync/SyncTimeoutOptions.cs index 0cec1717b7..f8af541bfe 100644 --- a/Realm/Realm/Sync/SyncTimeoutOptions.cs +++ b/Realm/Realm/Sync/SyncTimeoutOptions.cs @@ -104,5 +104,13 @@ public class SyncTimeoutOptions /// /// The window in which a drop in connectivity is considered transient. public TimeSpan FastReconnectLimit { get; set; } = TimeSpan.FromMinutes(1); + + /// + /// Gets or sets the options for the reconnection behavior of the sync client. + /// + /// + /// The options controlling how long the sync client waits before attempting to reconnect. + /// + public ReconnectBackoffOptions ReconnectBackoffOptions { get; set; } = new (); } } diff --git a/wrappers/src/app_cs.cpp b/wrappers/src/app_cs.cpp index ccf3f694d1..d499baaf64 100644 --- a/wrappers/src/app_cs.cpp +++ b/wrappers/src/app_cs.cpp @@ -61,6 +61,28 @@ namespace realm { std::function s_string_callback; std::function s_api_keys_callback; + struct SyncTimeoutOptions { + uint64_t sync_connect_timeout_ms; + + uint64_t sync_connection_linger_time_ms; + + uint64_t sync_ping_keep_alive_period_ms; + + uint64_t sync_pong_keep_alive_timeout_ms; + + uint64_t sync_fast_reconnect_limit; + + struct { + uint64_t max_resumption_delay_interval_ms; + + uint64_t resumption_delay_interval_ms; + + int resumption_delay_backoff_multiplier; + + int delay_jitter_divisor; + } reconnect_backoff_info; + }; + struct AppConfiguration { uint16_t* app_id; @@ -82,15 +104,7 @@ namespace realm { void* managed_websocket_provider; - uint64_t sync_connect_timeout_ms; - - uint64_t sync_connection_linger_time_ms; - - uint64_t sync_ping_keep_alive_period_ms; - - uint64_t sync_pong_keep_alive_timeout_ms; - - uint64_t sync_fast_reconnect_limit; + SyncTimeoutOptions sync_timeout_options; bool use_cache; }; @@ -149,11 +163,18 @@ extern "C" { } config.base_file_path = Utf16StringAccessor(app_config.base_file_path, app_config.base_file_path_len); - config.sync_client_config.timeouts.connection_linger_time = app_config.sync_connection_linger_time_ms; - config.sync_client_config.timeouts.connect_timeout = app_config.sync_connect_timeout_ms; - config.sync_client_config.timeouts.fast_reconnect_limit = app_config.sync_fast_reconnect_limit; - config.sync_client_config.timeouts.ping_keepalive_period = app_config.sync_ping_keep_alive_period_ms; - config.sync_client_config.timeouts.pong_keepalive_timeout = app_config.sync_pong_keep_alive_timeout_ms; + + auto& timeout_options = config.sync_client_config.timeouts; + const auto& managed_timeout_options = app_config.sync_timeout_options; + timeout_options.connection_linger_time = managed_timeout_options.sync_connection_linger_time_ms; + timeout_options.connect_timeout = managed_timeout_options.sync_connect_timeout_ms; + timeout_options.fast_reconnect_limit = managed_timeout_options.sync_fast_reconnect_limit; + timeout_options.ping_keepalive_period = managed_timeout_options.sync_ping_keep_alive_period_ms; + timeout_options.pong_keepalive_timeout = managed_timeout_options.sync_pong_keep_alive_timeout_ms; + timeout_options.reconnect_backoff_info.max_resumption_delay_interval = std::chrono::milliseconds(managed_timeout_options.reconnect_backoff_info.max_resumption_delay_interval_ms); + timeout_options.reconnect_backoff_info.resumption_delay_interval = std::chrono::milliseconds(managed_timeout_options.reconnect_backoff_info.resumption_delay_interval_ms); + timeout_options.reconnect_backoff_info.resumption_delay_backoff_multiplier = managed_timeout_options.reconnect_backoff_info.resumption_delay_backoff_multiplier; + timeout_options.reconnect_backoff_info.delay_jitter_divisor = managed_timeout_options.reconnect_backoff_info.delay_jitter_divisor; if (app_config.managed_websocket_provider) { config.sync_client_config.socket_provider = make_websocket_provider(app_config.managed_websocket_provider);