Skip to content

Commit

Permalink
fix event loss in network failure (#64)
Browse files Browse the repository at this point in the history
* update coroutine and sovran versions

* update error reporting logic

* update httpclient to report network exception

* fix the issue that system status not toggled in a network failure

* use semaphore to force synchronization instead of using task.wait
  • Loading branch information
wenxi-zeng authored Sep 20, 2023
1 parent eca3213 commit 38822bb
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 60 deletions.
4 changes: 2 additions & 2 deletions Analytics-CSharp/Analytics-CSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@
<Folder Include="Segment\Analytics\Utilities\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Coroutine.NET" Version="1.3.0" />
<PackageReference Include="Coroutine.NET" Version="1.4.0" />
<PackageReference Include="Serialization.NET" Version="1.4.0" />
<PackageReference Include="Sovran.NET" Version="1.3.0" />
<PackageReference Include="Sovran.NET" Version="1.4.0" />
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
Expand Down
36 changes: 23 additions & 13 deletions Analytics-CSharp/Segment/Analytics/Analytics.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Threading;
using global::System;
using global::System.Runtime.Serialization;
using global::System.Threading.Tasks;
Expand Down Expand Up @@ -208,22 +209,31 @@ private void Startup()
Add(new StartupQueue());
Add(new ContextPlugin());

// use Wait() for this coroutine to force completion,
// use semaphore for this coroutine to force completion,
// since Store must be setup before any event call happened.
// Note: Task.Wait() forces internal async methods to run in a synchronized way,
// we should avoid of doing it whenever possible.
SemaphoreSlim semaphore = new SemaphoreSlim(0);
AnalyticsScope.Launch(AnalyticsDispatcher, async () =>
{
// load memory with initial value
_userInfo = UserInfo.DefaultState(Storage);
await Store.Provide(_userInfo);
await Store.Provide(System.DefaultState(Configuration, Storage));
await Storage.Initialize();
}).Wait();

// check settings over the network,
// we don't have to Wait() here, because events are piped in
// StartupQueue until settings is ready
try
{
// load memory with initial value
_userInfo = UserInfo.DefaultState(Storage);
await Store.Provide(_userInfo);
await Store.Provide(System.DefaultState(Configuration, Storage));
await Storage.Initialize();
}
catch (Exception e)
{
ReportInternalError(AnalyticsErrorType.StorageUnknown, e, message: "Unknown Error when restoring settings from storage");
}
finally
{
semaphore.Release();
}
});
semaphore.Wait();

// check settings over the network
AnalyticsScope.Launch(AnalyticsDispatcher, async () =>
{
if (Configuration.AutoAddSegmentDestination)
Expand Down
9 changes: 2 additions & 7 deletions Analytics-CSharp/Segment/Analytics/Errors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static class ExtensionMethods
public static void ReportInternalError(this Analytics analytics, AnalyticsError error)
{
analytics.Configuration.AnalyticsErrorHandler?.OnExceptionThrown(error);
Analytics.ReportInternalError(error);
Analytics.ReportInternalError(error, error.Message);
}

/// <summary>
Expand All @@ -55,12 +55,7 @@ public static void ReportInternalError(this Analytics analytics, AnalyticsErrorT
{
var error = new AnalyticsError(type, message, exception);
analytics.Configuration.AnalyticsErrorHandler?.OnExceptionThrown(error);
Analytics.ReportInternalError(error);
}

public static void ToAnalyticsErrorHandler(this ICoroutineExceptionHandler handler)
{

Analytics.ReportInternalError(error, error.Message);
}
}

Expand Down
26 changes: 14 additions & 12 deletions Analytics-CSharp/Segment/Analytics/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,23 @@ private async Task CheckSettings()
UpdateType updateType = hasSettings ? UpdateType.Refresh : UpdateType.Initial;

await Store.Dispatch<System.ToggleRunningAction, System>(new System.ToggleRunningAction(false));

Settings? settings = null;
await Scope.WithContext(NetworkIODispatcher, async () =>
{
Settings? settings = await httpClient.Settings();
await Scope.WithContext(AnalyticsDispatcher, async () =>
{
if (settings != null)
{
await Store.Dispatch<System.UpdateSettingsAction, System>(new System.UpdateSettingsAction(settings.Value));
Update(settings.Value, updateType);
}
await Store.Dispatch<System.ToggleRunningAction, System>(new System.ToggleRunningAction(true));
});
settings = await httpClient.Settings();
});

if (settings != null)
{
await Store.Dispatch<System.UpdateSettingsAction, System>(new System.UpdateSettingsAction(settings.Value));
}
else
{
settings = systemState._settings;
}

Update(settings.Value, updateType);
await Store.Dispatch<System.ToggleRunningAction, System>(new System.ToggleRunningAction(true));
}
}
}
68 changes: 42 additions & 26 deletions Analytics-CSharp/Segment/Analytics/Utilities/HTTPClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,49 +72,65 @@ public HTTPClient(string apiKey, string apiHost = null, string cdnHost = null)
public virtual async Task<Settings?> Settings()
{
string settingsURL = SegmentURL(_cdnHost, "/projects/" + _apiKey + "/settings");
Response response = await DoGet(settingsURL);
Settings? result = null;

if (!response.IsSuccessStatusCode)
try
{
AnalyticsRef?.ReportInternalError(AnalyticsErrorType.NetworkUnexpectedHttpCode, message: "Error " + response.StatusCode + " getting from settings url");
Response response = await DoGet(settingsURL);
if (!response.IsSuccessStatusCode)
{
AnalyticsRef?.ReportInternalError(AnalyticsErrorType.NetworkUnexpectedHttpCode, message: "Error " + response.StatusCode + " getting from settings url");
}
else
{
string json = response.Content;
result = JsonUtility.FromJson<Settings>(json);
}
}
else
catch (Exception e)
{
string json = response.Content;
result = JsonUtility.FromJson<Settings>(json);
AnalyticsRef?.ReportInternalError(AnalyticsErrorType.NetworkUnknown, e, "Unknown network error when getting from settings url");
}

return result;
}

public virtual async Task<bool> Upload(byte[] data)
{
string uploadURL = SegmentURL(_apiHost, "/b");
Response response = await DoPost(uploadURL, data);

if (!response.IsSuccessStatusCode)
try
{
Analytics.Logger.Log(LogLevel.Error, message: "Error " + response.StatusCode + " uploading to url");
Response response = await DoPost(uploadURL, data);

switch (response.StatusCode)
if (!response.IsSuccessStatusCode)
{
case var n when n >= 1 && n < 300:
return false;
case var n when n >= 300 && n < 400:
AnalyticsRef?.ReportInternalError(AnalyticsErrorType.NetworkUnexpectedHttpCode, message: "Response code: " + n);
return false;
case 429:
AnalyticsRef?.ReportInternalError(AnalyticsErrorType.NetworkServerLimited, message: "Response code: 429");
return false;
case var n when n >= 400 && n < 500:
AnalyticsRef?.ReportInternalError(AnalyticsErrorType.NetworkServerRejected, message: "Response code: " + n + ". Payloads were rejected by server. Marked for removal.");
return true;
default:
return false;
Analytics.Logger.Log(LogLevel.Error, message: "Error " + response.StatusCode + " uploading to url");

switch (response.StatusCode)
{
case var n when n >= 1 && n < 300:
return false;
case var n when n >= 300 && n < 400:
AnalyticsRef?.ReportInternalError(AnalyticsErrorType.NetworkUnexpectedHttpCode, message: "Response code: " + n);
return false;
case 429:
AnalyticsRef?.ReportInternalError(AnalyticsErrorType.NetworkServerLimited, message: "Response code: 429");
return false;
case var n when n >= 400 && n < 500:
AnalyticsRef?.ReportInternalError(AnalyticsErrorType.NetworkServerRejected, message: "Response code: " + n + ". Payloads were rejected by server. Marked for removal.");
return true;
default:
return false;
}
}

return true;
}
catch (Exception e)
{
AnalyticsRef?.ReportInternalError(AnalyticsErrorType.NetworkUnknown, e, "Unknown network error when uploading to url");
}

return true;
return false;
}

/// <summary>
Expand Down

0 comments on commit 38822bb

Please sign in to comment.