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

Attempt to gracefully handle some unhandled exceptions. #13

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
37 changes: 25 additions & 12 deletions Assets/DeltaDNA/Runtime/DDNAImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -341,18 +341,31 @@ override internal void RecordPushNotification(Dictionary<string, object> payload
override internal void RequestSessionConfiguration() {
Logger.LogDebug("Requesting session configuration");

var firstSession = PlayerPrefs.HasKey(DDNA.PF_KEY_FIRST_SESSION)
? DateTime.ParseExact(
PlayerPrefs.GetString(DDNA.PF_KEY_FIRST_SESSION),
Settings.EVENT_TIMESTAMP_FORMAT,
CultureInfo.InvariantCulture)
: (DateTime?) null;
var lastSession = PlayerPrefs.HasKey(DDNA.PF_KEY_LAST_SESSION)
? DateTime.ParseExact(
PlayerPrefs.GetString(DDNA.PF_KEY_LAST_SESSION),
Settings.EVENT_TIMESTAMP_FORMAT,
CultureInfo.InvariantCulture)
: (DateTime?) null;
// The session key is becoming invalid somehow. An empty string in PlayerPrefs would do that...
// FormatException: String was not recognized as a valid DateTime.
// at System.DateTimeParse.ParseExact (System.String s, System.String format, System.Globalization.DateTimeFormatInfo dtfi, System.Globalization.DateTimeStyles style)
// at DeltaDNA.DDNAImpl.RequestSessionConfiguration ()
// at DeltaDNA.DDNA.NewSession ()
// at DeltaDNA.DDNAImpl.StartSDK (System.Boolean newPlayer)
// at DeltaDNA.DDNA.StartSDK (DeltaDNA.Configuration config, System.String userID)
DateTime? firstSession = null, lastSession = null;

try {
firstSession = PlayerPrefs.HasKey(DDNA.PF_KEY_FIRST_SESSION)
? DateTime.ParseExact(
PlayerPrefs.GetString(DDNA.PF_KEY_FIRST_SESSION),
Settings.EVENT_TIMESTAMP_FORMAT,
CultureInfo.InvariantCulture)
: (DateTime?)null;
lastSession = PlayerPrefs.HasKey(DDNA.PF_KEY_LAST_SESSION)
? DateTime.ParseExact(
PlayerPrefs.GetString(DDNA.PF_KEY_LAST_SESSION),
Settings.EVENT_TIMESTAMP_FORMAT,
CultureInfo.InvariantCulture)
: (DateTime?)null;
} catch (Exception ex) {
Logger.LogError("Unable to parse session times: " + ex);
}

Engagement engagement = new Engagement("config") { Flavour = "internal" };
engagement.AddParam("timeSinceFirstSession", firstSession != null
Expand Down
104 changes: 85 additions & 19 deletions Assets/DeltaDNA/Runtime/Helpers/EngageCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,51 @@ internal EngageCache(Settings settings){
this.settings = settings;

lock (LOCK) {
CreateDirectory();

cache = Directory
.GetFiles(location)
.ToDictionary(e => Path.GetFileName(e), e => File.ReadAllText(e));
if (File.Exists(location + TIMES)) {
times = File
.ReadAllLines(location + TIMES)
.ToDictionary(
e => e.Split(' ')[0],
e => new DateTime(Convert.ToInt64(e.Split(' ')[1])));
} else {
// Handle the case where the disk is full or can't be written to
// IOException: Disk full. Path /var/mobile/Containers/Data/Application/[HASH]/Library/Caches/deltadna
// at System.IO.Directory.CreateDirectoriesInternal (System.String path)
// at System.IO.Directory.CreateDirectoriesInternal (System.String path)
// at DeltaDNA.EngageCache..ctor (DeltaDNA.Settings settings)
// at DeltaDNA.DDNAImpl..ctor (DeltaDNA.DDNA ddna)
// at DeltaDNA.DDNA.Awake ()
// at UnityEngine.GameObject.AddComponent[T] ()
// at DeltaDNA.Singleton`1[T].get_Instance ()
try {
CreateDirectory();
} catch (Exception ex) {
Logger.LogWarning("Unable to create directory " + location + ": " + ex);
cache = new Dictionary<string, string>();
times = new Dictionary<string, DateTime>();
return;
}

// Handle the case where cached data is not in the expected format for some reason
// IndexOutOfRangeException: Index was outside the bounds of the array.
// at DeltaDNA.EngageCache+<>c.<.ctor>b__6_3 (System.String e)
// at System.Func`2[T,TResult].Invoke (T arg)
// at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement] (System.Collections.Generic.IEnumerable`1[T] source, System.Func`2[T,TResult] keySelector, System.Func`2[T,TResult] elementSelector, System.Collections.Generic.IEqualityComparer`1[T] comparer)
// at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement] (System.Collections.Generic.IEnumerable`1[T] source, System.Func`2[T,TResult] keySelector, System.Func`2[T,TResult] elementSelector)
// at DeltaDNA.EngageCache..ctor (DeltaDNA.Settings settings)
// at DeltaDNA.DDNAImpl..ctor (DeltaDNA.DDNA ddna)
// at DeltaDNA.DDNA.Awake ()
// at UnityEngine.GameObject.AddComponent[T] ()
// at DeltaDNA.Singleton`1[T].get_Instance ()
try {
cache = Directory
.GetFiles(location)
.ToDictionary(e => Path.GetFileName(e), e => File.ReadAllText(e));
if (File.Exists(location + TIMES)) {
times = File
.ReadAllLines(location + TIMES)
.ToDictionary(
e => e.Split(' ')[0],
e => new DateTime(Convert.ToInt64(e.Split(' ')[1])));
} else {
times = new Dictionary<string, DateTime>();
}
} catch (Exception ex) {
Logger.LogError("Unable to deserialize cache: " + ex);
cache = new Dictionary<string, string>();
times = new Dictionary<string, DateTime>();
}
}
Expand Down Expand Up @@ -103,15 +136,48 @@ internal string Get(string decisionPoint, string flavour) {

internal void Save() {
lock (LOCK) {
CreateDirectory();

foreach (var item in cache) {
File.WriteAllText(location + item.Key, item.Value);
// Handle the case where the disk is full or can't be written to
// IOException: Disk full. Path /storage/emulated/0/Android/data/[BUNDLE_ID]/cache/deltadna
// at System.IO.Directory.CreateDirectoriesInternal (System.String path)
// at System.IO.Directory.CreateDirectoriesInternal (System.String path)
// at DeltaDNA.EngageCache.Save ()
// at DeltaDNA.DDNAImpl.OnApplicationPause (System.Boolean pauseStatus)
try {
CreateDirectory();
} catch (Exception ex) {
Logger.LogError("Unable to create directory " + location + ": " + ex);
return;
}

File.WriteAllLines(
location + TIMES,
times.Select(e => e.Key + ' ' + e.Value.Ticks).ToArray());
// Handle the case where the disk is full or can't be written to
// IOException: Disk full. Path /var/mobile/Containers/Data/Application/[HASH]/Library/Caches/deltadna/engagements/times
// at System.IO.FileStream.FlushBuffer ()
// at System.IO.StreamWriter.Dispose (System.Boolean disposing)
// at System.IO.TextWriter.Dispose ()
// at System.IO.File.WriteAllLines (System.String path, System.String[] contents)
// at DeltaDNA.EngageCache.Save ()
// at DeltaDNA.DDNAImpl.OnApplicationPause (System.Boolean pauseStatus)
//
// IOException: Disk full. Path /storage/emulated/0/Android/data/[BUNDLE_ID]/cache/deltadna/engagements/times
// at System.IO.FileStream.FlushBuffer ()
// at System.IO.FileStream.Dispose (System.Boolean disposing)
// at System.IO.Stream.Close ()
// at System.IO.StreamWriter.Dispose (System.Boolean disposing)
// at System.IO.TextWriter.Dispose ()
// at System.IO.File.WriteAllText (System.String path, System.String contents, System.Text.Encoding encoding)
// at DeltaDNA.EngageCache.Save ()
// at DeltaDNA.DDNAImpl.OnApplicationPause (System.Boolean pauseStatus)
try {
foreach (var item in cache) {
File.WriteAllText(location + item.Key, item.Value);
}

File.WriteAllLines(
location + TIMES,
times.Select(e => e.Key + ' ' + e.Value.Ticks).ToArray());
} catch (Exception ex) {
Logger.LogError("Unable to write cache: " + ex);
}
}
}

Expand Down
63 changes: 59 additions & 4 deletions Assets/DeltaDNA/Runtime/Helpers/EventStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,39 @@ public void FlushBuffers()
{
if (_initialised)
{
_infs.Flush();
_outfs.Flush();
// Convert to MemoryStream if there's issues writing the data to disk
// IOException: Disk full. Path /storage/emulated/0/Android/data/[BUNDLE_ID]/files/ddsdk/events/B
// at System.IO.FileStream.FlushBuffer ()
// at DeltaDNA.EventStore.FlushBuffers ()
// at DeltaDNA.DDNAImpl.OnApplicationPause (System.Boolean pauseStatus)
try {
_infs.Flush();
} catch (Exception ex) {
Logger.LogError("Unable to flush \"in\" buffer, converting to MemoryStream: " + ex);
try {
_infs.Dispose();
} catch {
} finally {
_infs = new MemoryStream();
}
}

// Convert to MemoryStream if there's issues writing the data to disk
// IOException: Disk full. Path /storage/emulated/0/Android/data/[BUNDLE_ID]/files/ddsdk/events/B
// at System.IO.FileStream.FlushBuffer ()
// at DeltaDNA.EventStore.FlushBuffers ()
// at DeltaDNA.DDNAImpl.OnApplicationPause (System.Boolean pauseStatus)
try {
_outfs.Flush();
} catch (Exception ex) {
Logger.LogError("Unable to flush \"out\" buffer, converting to MemoryStream: " + ex);
try {
_outfs.Dispose();
} catch {
} finally {
_outfs = new MemoryStream();
}
}
}
}
}
Expand Down Expand Up @@ -304,8 +335,32 @@ public static void ReadEvents(Stream stream, IList<string> events)

public static void SwapStreams(ref Stream sin, ref Stream sout)
{
// Close off our write stream
sin.Flush();
// Convert to MemoryStream if there's issues writing the data to disk
// UnauthorizedAccessException: Access to the path "/storage/emulated/0/Android/data/[BUNDLE_ID]/files/ddsdk/events/B" is denied.
// at System.IO.FileStream.FlushBuffer ()
// at DeltaDNA.EventStore.SwapStreams (System.IO.Stream& sin, System.IO.Stream& sout)
// at DeltaDNA.EventStore.Swap ()
// at DeltaDNA.DDNAImpl+<UploadCoroutine>d__47.MoveNext ()
// at UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress)
//
// IOException: Disk full. Path /storage/emulated/0/Android/data/[BUNDLE_ID]/files/ddsdk/events/B
// at System.IO.FileStream.FlushBuffer ()
// at DeltaDNA.EventStore.SwapStreams (System.IO.Stream& sin, System.IO.Stream& sout)
// at DeltaDNA.EventStore.Swap ()
// at DeltaDNA.DDNAImpl+<UploadCoroutine>d__47.MoveNext ()
// at UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress)
try {
// Close off our write stream
sin.Flush();
} catch (Exception ex) {
Logger.LogError("Unable to flush to disk: " + ex);
try {
sin.Dispose();
} catch {
} finally {
sin = new MemoryStream();
}
}
// Swap the file handles
Stream tmp = sin;
sin = sout;
Expand Down
18 changes: 18 additions & 0 deletions Assets/DeltaDNA/Runtime/Helpers/Network.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ internal static IEnumerator SendRequest(HttpRequest request, Action<int /*status
www.downloadHandler = new DownloadHandlerBuffer();
if (request.HTTPMethod == HttpRequest.HTTPMethodType.POST)
{
// If the request body is null then the request is going to fail on Encoding.UTF8.GetBytes(). This will be
// null if EngageRequest.ToJSON fails. 1001 is an error code used further down.
// ArgumentNullException: String reference not set to an instance of a String.Parameter name: s
// at System.Text.Encoding.GetBytes (System.String s)
// at DeltaDNA.Network+<SendRequest>d__3.MoveNext ()
// at UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress)
// at DeltaDNA.Engage+<Request>d__0.MoveNext ()
// at UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress)
// at DeltaDNA.DDNAImpl.RequestEngagement (DeltaDNA.Engagement engagement, System.Action`1[T] callback)
// at DeltaDNA.DDNAImpl.RequestSessionConfiguration ()
// at DeltaDNA.DDNA.NewSession ()
// at DeltaDNA.DDNAImpl.StartSDK (System.Boolean newPlayer)
// at DeltaDNA.DDNA.StartSDK (DeltaDNA.Configuration config, System.String userID)
if (request.HTTPBody == null) {
completionHandler?.Invoke(1001, null, "Invalid HTTPBody");
yield break;
}

www.method = UnityWebRequest.kHttpVerbPOST;
foreach (var entry in request.getHeaders())
{
Expand Down