Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose ReconnectBackoffOptions on SyncTimeoutOptions #3661

Merged
merged 5 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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:<connection id>] Session[1]: log message`) to make correlating sync activity to server logs easier during troubleshooting. (Core 14.11.2)
Expand Down
24 changes: 22 additions & 2 deletions Realm/Realm/Native/AppConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
}
20 changes: 15 additions & 5 deletions Realm/Realm/Sync/App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down
80 changes: 80 additions & 0 deletions Realm/Realm/Sync/ReconnectBackoffOptions.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Options for configuring the reconnection delay used by the sync client.
/// </summary>
/// <remarks>
/// 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 <see cref="ReconnectDelayInterval"/> and multiplies by <see cref="ReconnectDelayBackoffMultiplier"/>
/// until it reaches <see cref="MaxReconnectDelayInterval"/>.
/// </remarks>
public class ReconnectBackoffOptions
{
/// <summary>
/// Gets or sets the maximum amount of time to wait before a reconnection attempt.
/// </summary>
/// <remarks>
/// Defaults to 5 minutes.
/// </remarks>
/// <value>
/// The maximum amount of time to wait before a reconnection attempt.
/// </value>
public TimeSpan MaxReconnectDelayInterval { get; set; } = TimeSpan.FromMinutes(5);

/// <summary>
/// Gets or sets the initial amount of time to wait before a reconnection attempt.
/// </summary>
/// <remarks>
/// Defaults to 1 second.
/// </remarks>
/// <value>
/// The initial amount of time to wait before a reconnection attempt.
/// </value>
public TimeSpan ReconnectDelayInterval { get; set; } = TimeSpan.FromSeconds(1);

/// <summary>
/// Gets or sets the multiplier to apply to the accumulated reconnection delay before a new reconection attempt.
/// </summary>
/// <remarks>
/// Defaults to 2.
/// </remarks>
/// <value>
/// The delay multiplier.
/// </value>
public int ReconnectDelayBackoffMultiplier { get; set; } = 2;

/// <summary>
/// Gets or sets the jitter randomization factor to apply to the delay.
/// </summary>
/// <remarks>
/// 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.
/// <br />
/// Defaults to 4.
/// </remarks>
/// <value>
/// The jitter randomization factor to apply to the delay.
/// </value>
public int DelayJitterDivisor { get; set; } = 4;
}
}
8 changes: 8 additions & 0 deletions Realm/Realm/Sync/SyncTimeoutOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,13 @@ public class SyncTimeoutOptions
/// </remarks>
/// <value>The window in which a drop in connectivity is considered transient.</value>
public TimeSpan FastReconnectLimit { get; set; } = TimeSpan.FromMinutes(1);

/// <summary>
/// Gets or sets the options for the reconnection behavior of the sync client.
/// </summary>
/// <value>
/// The options controlling how long the sync client waits before attempting to reconnect.
/// </value>
public ReconnectBackoffOptions ReconnectBackoffOptions { get; set; } = new ();
}
}
49 changes: 35 additions & 14 deletions wrappers/src/app_cs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,28 @@ namespace realm {
std::function<StringCallbackT> s_string_callback;
std::function<ApiKeysCallbackT> 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;
Expand All @@ -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;
};
Expand Down Expand Up @@ -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);
Expand Down
Loading