Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
### Fixes

- IL2CPP line numbers now work on Android x86/x86_64 builds. il2cpp fails to report the image UUID there, so the SDK falls back to looking the debug image up by name ([#2817](https://github.com/getsentry/sentry-unity/pull/2817))
- When targeting WebGL or Nintendo Switch without native support, `UnityWebRequestTransport` no longer opens a connection while the platform reports no network, and backs off exponentially (1s up to 300s) after a connection error instead of retrying on every envelope. ([#2833](https://github.com/getsentry/sentry-unity/pull/2833))
- When targeting Nintendo Switch, the SDK now utilizes sentry-switch to poll the network status before sending. ([#2833](https://github.com/getsentry/sentry-unity/pull/2833))

### Dependencies

Expand Down
6 changes: 6 additions & 0 deletions package-dev/Plugins/Switch/sentry_native_stubs.c
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,12 @@ const char* sentry_switch_utils_get_default_user_id(void)
return "";
}

int sentry_switch_utils_is_network_available(void)
{
/* No native SDK to ask - assume usable and let the transport's backoff take over */
return 1;
}

/*
* =============================================================================
* Utility Functions
Expand Down
15 changes: 15 additions & 0 deletions src/Sentry.Unity.Native/SentryNativeSwitch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,20 @@ public static class SentryNativeSwitch
[DllImport("__Internal")]
private static extern IntPtr sentry_switch_utils_get_default_user_id();

[DllImport("__Internal")]
private static extern int sentry_switch_utils_is_network_available();

private static IDiagnosticLogger? Logger;

/// <summary>
/// Whether the console currently has a usable network.
/// </summary>
/// <remarks>
/// <c>Application.internetReachability</c> reports the console as reachable while it is offline,
/// and every send attempted in that state raises the system's "connect to the internet" dialog.
/// </remarks>
internal static bool IsNetworkAvailable() => sentry_switch_utils_is_network_available() == 1;

/// <summary>
/// Configures the native support for Nintendo Switch.
/// </summary>
Expand Down Expand Up @@ -72,6 +84,9 @@ internal static void Configure(SentryUnityOptions options, RuntimePlatform platf
return;
}

Logger?.LogDebug("Using the native SDK to determine network availability.");
options.NetworkAvailabilityProbe = IsNetworkAvailable;

Logger?.LogDebug("Mounting temporary storage for sentry-switch.");

if (sentry_switch_utils_mount() != 1)
Expand Down
3 changes: 3 additions & 0 deletions src/Sentry.Unity/Integrations/IApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal interface IApplication
string UnityVersion { get; }
string PersistentDataPath { get; }
RuntimePlatform Platform { get; }
NetworkReachability InternetReachability { get; }
}

/// <summary>
Expand Down Expand Up @@ -54,6 +55,8 @@ private ApplicationAdapter()

public RuntimePlatform Platform => Application.platform;

public NetworkReachability InternetReachability => Application.internetReachability;

private void OnLogMessageReceived(string condition, string stackTrace, LogType type)
=> LogMessageReceived?.Invoke(condition, stackTrace, type);

Expand Down
2 changes: 2 additions & 0 deletions src/Sentry.Unity/SentryUnityOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,8 @@ internal string? DefaultUserId
/// </summary>
internal Action? NativeSupportCloseCallback { get; set; } = null;

internal Func<bool>? NetworkAvailabilityProbe { get; set; } = null;

internal List<string> SdkIntegrationNames { get; set; } = new();

internal ISentryUnityInfo UnityInfo { get; private set; }
Expand Down
48 changes: 46 additions & 2 deletions src/Sentry.Unity/UnityWebRequestTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
using System.Threading.Tasks;
using Sentry.Extensibility;
using Sentry.Http;
using Sentry.Internal;
using Sentry.Internal.Extensions;
using Sentry.Protocol.Envelopes;
using Sentry.Unity.Integrations;
using UnityEngine;
using UnityEngine.Networking;

namespace Sentry.Unity;
Expand Down Expand Up @@ -50,14 +54,31 @@ private IEnumerator SendAndTrack(Envelope envelope)
internal class UnityWebRequestTransport : HttpTransportBase
{
private readonly SentryUnityOptions _options;
private readonly IApplication _application;

public UnityWebRequestTransport(SentryUnityOptions options)
private const double MaxBackoffSeconds = 300.0;
private double _backoffSeconds;
private double _retryAfterRealtime;

public UnityWebRequestTransport(SentryUnityOptions options, IApplication? application = null)
: base(options)
=> _options = options;
{
_options = options;
_application = application ?? ApplicationAdapter.Instance;
}

// adapted HttpTransport.SendEnvelopeAsync()
internal IEnumerator SendEnvelopeAsync(Envelope envelope)
{
// This transport opens a connection per envelope, causing a prompt to appear on platforms
// like Nintendo Switch while offline.
if (!IsNetworkAvailable() || Time.realtimeSinceStartupAsDouble < _retryAfterRealtime)
{
_options.LogDebug("Network unavailable or backing off. Dropping envelope instead of attempting to send.");
_options.ClientReportRecorder.RecordDiscardedEvents(DiscardReason.NetworkError, envelope);
yield break;
Comment thread
bitsandfoxes marked this conversation as resolved.
}

using var processedEnvelope = ProcessEnvelope(envelope);
if (processedEnvelope.Items.Count > 0)
{
Expand All @@ -66,6 +87,19 @@ internal IEnumerator SendEnvelopeAsync(Envelope envelope)
var www = CreateWebRequest(httpRequest);
yield return www.SendWebRequest();

if (www.result == UnityWebRequest.Result.ConnectionError)
{
_backoffSeconds = Math.Min(MaxBackoffSeconds, _backoffSeconds > 0.0 ? _backoffSeconds * 2 : 1.0);
_retryAfterRealtime = Time.realtimeSinceStartupAsDouble + _backoffSeconds;

_options.LogWarning("Failed to send request: {0}. Backing off for {1:F1}s.", www.error, _backoffSeconds);
_options.ClientReportRecorder.RecordDiscardedEvents(DiscardReason.NetworkError, processedEnvelope);
yield break;
}

_backoffSeconds = 0.0;
_retryAfterRealtime = 0.0;

var response = GetResponse(www);
if (response is not null)
{
Expand All @@ -74,6 +108,16 @@ internal IEnumerator SendEnvelopeAsync(Envelope envelope)
}
}

private bool IsNetworkAvailable()
{
if (_options.NetworkAvailabilityProbe is { } probe)
{
return probe();
}

return _application.InternetReachability != NetworkReachability.NotReachable;
}

private UnityWebRequest CreateWebRequest(HttpRequestMessage message)
{
using var contentStream = ReadStreamFromHttpContent(message.Content);
Expand Down
79 changes: 79 additions & 0 deletions test/Sentry.Unity.Tests/UnityWebRequestTransportTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using NUnit.Framework;
using Sentry.Protocol.Envelopes;
using Sentry.Unity.Tests.Stubs;
using UnityEngine;

namespace Sentry.Unity.Tests;

[TestFixture]
public class UnityWebRequestTransportTests
{
private static SentryUnityOptions CreateOptions() => new()
{
Dsn = SentryTests.TestDsn
};

private static Envelope CreateEnvelope() => Envelope.FromEvent(new SentryEvent());

/// <summary>
/// A coroutine that yields has created a <see cref="UnityEngine.Networking.UnityWebRequest"/>,
/// which is what raises the system connection dialog on platforms like the Nintendo Switch.
/// </summary>
[Test]
public void SendEnvelopeAsync_NetworkNotReachable_DoesNotOpenAConnection()
{
var application = new TestApplication { InternetReachability = NetworkReachability.NotReachable };
var transport = new UnityWebRequestTransport(CreateOptions(), application);

var enumerator = transport.SendEnvelopeAsync(CreateEnvelope());

Assert.IsFalse(enumerator.MoveNext(), "The transport attempted to send while offline.");
}

[Test]
public void SendEnvelopeAsync_NetworkReachable_AttemptsToSend()
{
var application = new TestApplication
{
InternetReachability = NetworkReachability.ReachableViaLocalAreaNetwork
};
var transport = new UnityWebRequestTransport(CreateOptions(), application);

var enumerator = transport.SendEnvelopeAsync(CreateEnvelope());

Assert.IsTrue(enumerator.MoveNext(), "The transport did not attempt to send while online.");
}

/// <summary>
/// The probe has the final say: on Nintendo Switch reachability claims the console is reachable
/// while it is offline, which is the state the dialog gets raised in.
/// </summary>
[Test]
public void SendEnvelopeAsync_ProbeReportsNoNetwork_OverridesReachability()
{
var application = new TestApplication
{
InternetReachability = NetworkReachability.ReachableViaLocalAreaNetwork
};
var options = CreateOptions();
options.NetworkAvailabilityProbe = () => false;
var transport = new UnityWebRequestTransport(options, application);

var enumerator = transport.SendEnvelopeAsync(CreateEnvelope());

Assert.IsFalse(enumerator.MoveNext(), "The transport ignored the platform's network probe.");
}

[Test]
public void SendEnvelopeAsync_ProbeReportsNetwork_OverridesReachability()
{
var application = new TestApplication { InternetReachability = NetworkReachability.NotReachable };
var options = CreateOptions();
options.NetworkAvailabilityProbe = () => true;
var transport = new UnityWebRequestTransport(options, application);

var enumerator = transport.SendEnvelopeAsync(CreateEnvelope());

Assert.IsTrue(enumerator.MoveNext(), "The transport ignored the platform's network probe.");
}
}
1 change: 1 addition & 0 deletions test/SharedClasses/TestApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public TestApplication(
public string UnityVersion { get; set; }
public string PersistentDataPath { get; set; }
public RuntimePlatform Platform { get; set; }
public NetworkReachability InternetReachability { get; set; } = NetworkReachability.ReachableViaLocalAreaNetwork;

private void OnLogMessageReceived(string condition, string stacktrace, LogType type)
=> LogMessageReceived?.Invoke(condition, stacktrace, type);
Expand Down
Loading