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

fix event loss in network failure #64

Merged
merged 5 commits into from
Sep 20, 2023
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
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
Loading