Skip to content

Commit efe31bb

Browse files
committed
TRIAGE-608: Fix retry-on-failure throttle logic
1 parent c588b67 commit efe31bb

3 files changed

Lines changed: 25 additions & 17 deletions

File tree

android-core/src/main/java/com/mparticle/internal/UploadHandler.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -252,16 +252,13 @@ void maybePrunePersistedRecords(long nowMillis) {
252252
if (mParticleDBManager == null) {
253253
return;
254254
}
255-
try {
256-
Integer configured = mConfigManager == null ? null : mConfigManager.getPersistenceMaxAgeSeconds();
257-
long maxAgeMillis = (configured == null)
258-
? DEFAULT_PERSISTENCE_MAX_AGE_MILLIS
259-
: configured.longValue() * 1000L;
260-
long cutoffMillis = nowMillis - maxAgeMillis;
261-
mParticleDBManager.deleteRecordsOlderThan(cutoffMillis);
255+
Integer configured = mConfigManager == null ? null : mConfigManager.getPersistenceMaxAgeSeconds();
256+
long maxAgeMillis = (configured == null)
257+
? DEFAULT_PERSISTENCE_MAX_AGE_MILLIS
258+
: configured.longValue() * 1000L;
259+
long cutoffMillis = nowMillis - maxAgeMillis;
260+
if (mParticleDBManager.deleteRecordsOlderThan(cutoffMillis)) {
262261
mLastPersistenceCleanupMillis = nowMillis;
263-
} catch (Exception e) {
264-
Logger.warning(e, "Failed to prune persisted records by age.");
265262
}
266263
}
267264

android-core/src/main/java/com/mparticle/internal/database/services/MParticleDBManager.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import android.os.Handler;
88
import android.os.Looper;
99

10+
import androidx.annotation.CheckResult;
1011
import androidx.annotation.Nullable;
1112

1213
import com.mparticle.MParticle;
@@ -211,21 +212,27 @@ public void deleteMessagesAndSessions(String currentSessionId) {
211212
* <p>
212213
* Deletes any messages and uploads whose {@code CREATED_AT} is strictly less than
213214
* {@code cutoffMillis}, and any sessions whose {@code END_TIME} is strictly less than
214-
* {@code cutoffMillis}. Mirrors the behaviour of the iOS SDK's
215-
* {@code MPPersistenceController.deleteRecordsOlderThan:}.
215+
* {@code cutoffMillis}.
216+
* {@code MPPersistenceController.deleteRecordsOlderThan:}, but reports success so
217+
* callers can decide whether to arm retry/throttle state.
216218
*
217219
* @param cutoffMillis the unix-epoch millisecond cutoff; rows older than this are removed
220+
* @return {@code true} if the transaction committed successfully, {@code false} if any
221+
* exception was caught (and logged) during the sweep
218222
*/
219-
public void deleteRecordsOlderThan(long cutoffMillis) {
223+
@CheckResult
224+
public boolean deleteRecordsOlderThan(long cutoffMillis) {
220225
MPDatabase db = getDatabase();
221226
try {
222227
db.beginTransaction();
223228
MessageService.deleteMessagesOlderThan(db, cutoffMillis);
224229
UploadService.deleteUploadsOlderThan(db, cutoffMillis);
225230
SessionService.deleteSessionsOlderThan(db, cutoffMillis);
226231
db.setTransactionSuccessful();
232+
return true;
227233
} catch (Exception e) {
228234
Logger.warning(e, "Error pruning persisted records older than " + cutoffMillis + " ms.");
235+
return false;
229236
} finally {
230237
db.endTransaction();
231238
}

android-core/src/test/kotlin/com/mparticle/internal/UploadHandlerTest.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ class UploadHandlerTest {
150150
val capturedCutoff = java.util.concurrent.atomic.AtomicLong(Long.MIN_VALUE)
151151
Mockito.doAnswer { invocation ->
152152
capturedCutoff.set(invocation.getArgument(0))
153-
null
153+
true
154154
}.`when`(db).deleteRecordsOlderThan(Mockito.anyLong())
155155

156156
val now = 1_700_000_000_000L
@@ -182,7 +182,7 @@ class UploadHandlerTest {
182182
val capturedCutoff = java.util.concurrent.atomic.AtomicLong(Long.MIN_VALUE)
183183
Mockito.doAnswer { invocation ->
184184
capturedCutoff.set(invocation.getArgument(0))
185-
null
185+
true
186186
}.`when`(db).deleteRecordsOlderThan(Mockito.anyLong())
187187

188188
val now = 1_700_000_000_000L
@@ -194,6 +194,8 @@ class UploadHandlerTest {
194194
@Test
195195
fun testMaybePrunePersistedRecordsHonorsTwentyFourHourThrottle() {
196196
val db = handler.mParticleDBManager
197+
// The sweep must succeed so the throttle arms; otherwise every call retries.
198+
Mockito.`when`(db.deleteRecordsOlderThan(Mockito.anyLong())).thenReturn(true)
197199
val t0 = 1_000_000_000_000L
198200

199201
// First call always runs the sweep.
@@ -213,8 +215,10 @@ class UploadHandlerTest {
213215
fun testMaybePrunePersistedRecordsRetriesAfterFailure() {
214216
val db = handler.mParticleDBManager
215217

216-
// Phase 1 - deleteRecordsOlderThan throws; throttle must NOT be armed.
217-
Mockito.doThrow(RuntimeException("boom")).`when`(db).deleteRecordsOlderThan(Mockito.anyLong())
218+
// Phase 1 - deleteRecordsOlderThan reports failure (false); throttle must NOT be armed.
219+
// This models the real contract: MParticleDBManager.deleteRecordsOlderThan catches
220+
// SQL exceptions internally and signals failure via its return value.
221+
Mockito.`when`(db.deleteRecordsOlderThan(Mockito.anyLong())).thenReturn(false)
218222
val t1 = 1_000_000_000_000L
219223
handler.maybePrunePersistedRecords(t1)
220224
Mockito.verify(db, Mockito.times(1)).deleteRecordsOlderThan(Mockito.anyLong())
@@ -224,7 +228,7 @@ class UploadHandlerTest {
224228
Mockito.verify(db, Mockito.times(2)).deleteRecordsOlderThan(Mockito.anyLong())
225229

226230
// Phase 3 - successful sweep arms the throttle.
227-
Mockito.doNothing().`when`(db).deleteRecordsOlderThan(Mockito.anyLong())
231+
Mockito.`when`(db.deleteRecordsOlderThan(Mockito.anyLong())).thenReturn(true)
228232
handler.maybePrunePersistedRecords(t1 + 120_000L)
229233
Mockito.verify(db, Mockito.times(3)).deleteRecordsOlderThan(Mockito.anyLong())
230234

0 commit comments

Comments
 (0)