From 66c8e4656f476dd82e0672d724257676ca6c3f7d Mon Sep 17 00:00:00 2001 From: Josh Kasten Date: Fri, 8 Mar 2024 19:58:31 -0500 Subject: [PATCH] retry starting new threads Thread.start() can throw OutOfMemoryError errors and we can attempt to start the thread again. App developers can handle onTrimMemory and might be cleaning up memory. While the SDK can't know if this is being done its better to retry then to crash the app. This commit doesn't cover all new thread, just some of the most common ones. Also in v5 most new threads have been migrated to Kotlin coroutines so this patch should not be used outside of this player model branch. --- .../java/com/onesignal/OSBackgroundSync.java | 4 ++- .../src/main/java/com/onesignal/OSUtils.java | 14 ++++++++++ .../main/java/com/onesignal/OneSignal.java | 6 +++-- .../com/onesignal/OneSignalRestClient.java | 27 +++++++++++-------- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSBackgroundSync.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSBackgroundSync.java index e658e80901..b87a4aa86b 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSBackgroundSync.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSBackgroundSync.java @@ -27,6 +27,8 @@ package com.onesignal; +import static com.onesignal.OSUtils.startThreadWithRetry; + import android.app.AlarmManager; import android.app.PendingIntent; import android.app.job.JobInfo; @@ -63,7 +65,7 @@ void doBackgroundSync(Context context, Runnable runnable) { OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "OSBackground sync, calling initWithContext"); OneSignal.initWithContext(context); syncBgThread = new Thread(runnable, getSyncTaskThreadId()); - syncBgThread.start(); + startThreadWithRetry(syncBgThread); } boolean stopSyncBgThread() { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSUtils.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSUtils.java index 8e4483315f..62b6e7f428 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSUtils.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OSUtils.java @@ -691,4 +691,18 @@ static Throwable getRootCauseThrowable(@NonNull Throwable subjectThrowable) { static String getRootCauseMessage(@NonNull Throwable throwable) { return getRootCauseThrowable(throwable).getMessage(); } + + static void startThreadWithRetry(@NonNull Thread thread) { + boolean started = false; + while (!started) { + try { + thread.start(); + started = true; + } catch (OutOfMemoryError ignored) { + try { + Thread.sleep(250); + } catch (InterruptedException ignoreInterrupted) {} + } + } + } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java index 051f3ebf68..25bb6c3fbe 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java @@ -75,6 +75,7 @@ import static com.onesignal.GenerateNotification.BUNDLE_KEY_ACTION_ID; import static com.onesignal.GenerateNotification.BUNDLE_KEY_ANDROID_NOTIFICATION_ID; import static com.onesignal.NotificationBundleProcessor.newJsonArray; +import static com.onesignal.OSUtils.startThreadWithRetry; /** * The main OneSignal class - this is where you will interface with the OneSignal SDK @@ -1488,7 +1489,7 @@ private static void registerUser() { return; } - new Thread(new Runnable() { + Thread thread = new Thread(new Runnable() { public void run() { try { registerUserTask(); @@ -1496,7 +1497,8 @@ public void run() { Log(LOG_LEVEL.FATAL, "FATAL Error registering device!", t); } } - }, "OS_REG_USER").start(); + }, "OS_REG_USER"); + startThreadWithRetry(thread); } private static void registerUserTask() throws JSONException { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalRestClient.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalRestClient.java index eb113d2eb4..462c852b48 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalRestClient.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalRestClient.java @@ -27,6 +27,8 @@ package com.onesignal; +import static com.onesignal.OSUtils.startThreadWithRetry; + import android.net.TrafficStats; import android.os.Build; import androidx.annotation.NonNull; @@ -65,27 +67,30 @@ private static int getThreadTimeout(int timeout) { } public static void put(final String url, final JSONObject jsonBody, final ResponseHandler responseHandler) { - new Thread(new Runnable() { + Thread thread = new Thread(new Runnable() { public void run() { makeRequest(url, "PUT", jsonBody, responseHandler, TIMEOUT, null); } - }, "OS_REST_ASYNC_PUT").start(); + }, "OS_REST_ASYNC_PUT"); + startThreadWithRetry(thread); } public static void post(final String url, final JSONObject jsonBody, final ResponseHandler responseHandler) { - new Thread(new Runnable() { + Thread thread = new Thread(new Runnable() { public void run() { makeRequest(url, "POST", jsonBody, responseHandler, TIMEOUT, null); } - }, "OS_REST_ASYNC_POST").start(); + }, "OS_REST_ASYNC_POST"); + startThreadWithRetry(thread); } public static void get(final String url, final ResponseHandler responseHandler, @NonNull final String cacheKey) { - new Thread(new Runnable() { + Thread thread = new Thread(new Runnable() { public void run() { makeRequest(url, null, null, responseHandler, GET_TIMEOUT, cacheKey); } - }, "OS_REST_ASYNC_GET").start(); + }, "OS_REST_ASYNC_GET"); + startThreadWithRetry(thread); } public static void getSync(final String url, final ResponseHandler responseHandler, @NonNull String cacheKey) { @@ -114,8 +119,8 @@ public void run() { callbackThread[0] = startHTTPConnection(url, method, jsonBody, responseHandler, timeout, cacheKey); } }, "OS_HTTPConnection"); - - connectionThread.start(); + + startThreadWithRetry(connectionThread); // getResponseCode() can hang past it's timeout setting so join it's thread to ensure it is timing out. try { @@ -129,7 +134,7 @@ public void run() { e.printStackTrace(); } } - + private static Thread startHTTPConnection(String url, String method, JSONObject jsonBody, ResponseHandler responseHandler, int timeout, @Nullable String cacheKey) { int httpResponse = -1; HttpURLConnection con = null; @@ -279,7 +284,7 @@ public void run() { handler.onSuccess(response); } }, "OS_REST_SUCCESS_CALLBACK"); - thread.start(); + startThreadWithRetry(thread); return thread; } @@ -293,7 +298,7 @@ public void run() { handler.onFailure(statusCode, response, throwable); } }, "OS_REST_FAILURE_CALLBACK"); - thread.start(); + startThreadWithRetry(thread); return thread; }