Skip to content

Commit c9b227c

Browse files
committed
refactor: improve memory management and session handling in BranchRequestQueue
- Replaced direct instance references with WeakReference in BranchRequestQueue and BranchRequestQueueAdapter to prevent memory leaks. - Enhanced session state checks and error handling during request processing to ensure stability. - Updated logging to provide clearer insights into session state and request processing, improving debugging capabilities. - Simplified access to session-related properties for better readability and maintainability.
1 parent 6631223 commit c9b227c

File tree

2 files changed

+67
-48
lines changed

2 files changed

+67
-48
lines changed

Branch-SDK/src/main/java/io/branch/referral/BranchRequestQueue.kt

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import kotlinx.coroutines.flow.StateFlow
1313
import kotlinx.coroutines.flow.asStateFlow
1414
import kotlinx.coroutines.launch
1515
import kotlinx.coroutines.withContext
16+
import java.lang.ref.WeakReference
1617
import java.util.Collections
1718
import java.util.concurrent.ConcurrentHashMap
1819
import java.util.concurrent.atomic.AtomicBoolean
1920
import java.util.concurrent.atomic.AtomicInteger
20-
import org.json.JSONObject
2121

2222
/**
2323
* Request retry tracking information
@@ -68,23 +68,30 @@ class BranchRequestQueue private constructor(private val context: Context) {
6868
private const val RETRY_DELAY_MS = 100L
6969

7070
@Volatile
71-
private var INSTANCE: BranchRequestQueue? = null
71+
private var INSTANCE: WeakReference<BranchRequestQueue>? = null
7272

7373
@JvmStatic
7474
fun getInstance(context: Context): BranchRequestQueue {
75-
return INSTANCE ?: synchronized(this) {
76-
INSTANCE ?: BranchRequestQueue(context.applicationContext).also {
77-
INSTANCE = it
75+
// Check if we have a valid instance
76+
INSTANCE?.get()?.let { return it }
77+
78+
// Create new instance with proper synchronization
79+
return synchronized(this) {
80+
// Double-check after acquiring lock
81+
INSTANCE?.get() ?: run {
82+
val newInstance = BranchRequestQueue(context.applicationContext)
83+
INSTANCE = WeakReference(newInstance)
7884
BranchLogger.d("DEBUG: BranchRequestQueue instance created")
85+
newInstance
7986
}
8087
}
8188
}
8289

8390
@JvmStatic
8491
fun shutDown() {
8592
BranchLogger.d("DEBUG: BranchRequestQueue.shutDown called")
86-
INSTANCE?.let {
87-
it.shutdown()
93+
INSTANCE?.get()?.let { instance ->
94+
instance.shutdown()
8895
INSTANCE = null
8996
}
9097
BranchLogger.d("DEBUG: BranchRequestQueue.shutDown completed")
@@ -240,7 +247,7 @@ class BranchRequestQueue private constructor(private val context: Context) {
240247
val requestId = generateRequestId(request)
241248

242249
if (!canProcessRequest(request)) {
243-
if (request.isWaitingOnProcessToFinish()) {
250+
if (request.isWaitingOnProcessToFinish) {
244251
val waitLocks = request.printWaitLocks()
245252
BranchLogger.d("DEBUG: Request cannot be processed - waiting on locks: $waitLocks")
246253
}
@@ -263,7 +270,7 @@ class BranchRequestQueue private constructor(private val context: Context) {
263270
BranchLogger.d("DEBUG: Processing request: ${request::class.simpleName}, network count: ${networkCount.get()}")
264271

265272
when {
266-
request.isWaitingOnProcessToFinish() -> {
273+
request.isWaitingOnProcessToFinish -> {
267274
val waitLocks = request.printWaitLocks()
268275
BranchLogger.v("Request $request is waiting on processes to finish")
269276
BranchLogger.d("DEBUG: Request is waiting on processes to finish, active locks: $waitLocks, re-queuing")
@@ -345,12 +352,12 @@ class BranchRequestQueue private constructor(private val context: Context) {
345352
}
346353

347354
// Mark first wait lock time for timeout tracking
348-
if (request.isWaitingOnProcessToFinish()) {
355+
if (request.isWaitingOnProcessToFinish) {
349356
retryInfo.markFirstWaitLock()
350357
}
351358

352359
// Check for stuck locks and try to resolve them
353-
if (request.isWaitingOnProcessToFinish()) {
360+
if (request.isWaitingOnProcessToFinish) {
354361
val waitLocks = request.printWaitLocks()
355362

356363
// Check for timeout-based stuck locks (10 seconds)
@@ -514,10 +521,13 @@ class BranchRequestQueue private constructor(private val context: Context) {
514521

515522
// Additional logging after successful completion
516523
if (request is ServerRequestInitSession) {
517-
val currentState = Branch.init().getCurrentSessionState()
518-
val legacyState = Branch.init().getInitState()
519-
val hasUser = Branch.init().prefHelper_.getRandomizedBundleToken() != PrefHelper.NO_STRING_VALUE
520-
BranchLogger.d("DEBUG: After $request completion - SessionState: $currentState, LegacyState: $legacyState, hasUser: $hasUser")
524+
try {
525+
val legacyState = Branch.init().initState
526+
val hasUser = Branch.init().prefHelper_.getRandomizedBundleToken() != PrefHelper.NO_STRING_VALUE
527+
BranchLogger.d("DEBUG: After $request completion - LegacyState: $legacyState, hasUser: $hasUser")
528+
} catch (e: Exception) {
529+
BranchLogger.d("DEBUG: Could not access session state after request completion: ${e.message}")
530+
}
521531
}
522532
} catch (e: Exception) {
523533
BranchLogger.e("Error in onRequestSucceeded: ${e.message}")
@@ -536,10 +546,10 @@ class BranchRequestQueue private constructor(private val context: Context) {
536546
*/
537547
private fun isSessionValidForRequest(request: ServerRequest): Boolean {
538548
val branch = Branch.init()
539-
val hasSession = !branch.prefHelper_.getSessionID().equals(PrefHelper.NO_STRING_VALUE)
549+
val hasSession = !branch.prefHelper_.sessionID.equals(PrefHelper.NO_STRING_VALUE)
540550
val hasDeviceToken = !branch.prefHelper_.getRandomizedDeviceToken().equals(PrefHelper.NO_STRING_VALUE)
541551
val hasUser = !branch.prefHelper_.getRandomizedBundleToken().equals(PrefHelper.NO_STRING_VALUE)
542-
val sessionInitialized = branch.getInitState() == Branch.SESSION_STATE.INITIALISED
552+
val sessionInitialized = branch.initState == Branch.SESSION_STATE.INITIALISED
543553
val canPerformOperations = branch.canPerformOperations()
544554

545555
return (sessionInitialized || canPerformOperations) && hasSession && hasDeviceToken &&
@@ -579,8 +589,6 @@ class BranchRequestQueue private constructor(private val context: Context) {
579589
*/
580590
private fun tryResolveStuckUserAgentLock(request: ServerRequest) {
581591
try {
582-
val branch = Branch.init()
583-
584592
// Check if user agent is now available
585593
if (!android.text.TextUtils.isEmpty(Branch._userAgentString)) {
586594
BranchLogger.d("DEBUG: User agent is now available: ${Branch._userAgentString}, removing stuck lock")
@@ -618,10 +626,10 @@ class BranchRequestQueue private constructor(private val context: Context) {
618626
val branch = Branch.init()
619627

620628
// Check if session is actually valid now
621-
val hasSession = !branch.prefHelper_.getSessionID().equals(PrefHelper.NO_STRING_VALUE)
629+
val hasSession = !branch.prefHelper_.sessionID.equals(PrefHelper.NO_STRING_VALUE)
622630
val hasDeviceToken = !branch.prefHelper_.getRandomizedDeviceToken().equals(PrefHelper.NO_STRING_VALUE)
623631
val hasUser = !branch.prefHelper_.getRandomizedBundleToken().equals(PrefHelper.NO_STRING_VALUE)
624-
val sessionInitialized = branch.getInitState() == Branch.SESSION_STATE.INITIALISED
632+
val sessionInitialized = branch.initState == Branch.SESSION_STATE.INITIALISED
625633
val canPerformOperations = branch.canPerformOperations()
626634

627635
BranchLogger.d("DEBUG: SDK_INIT_WAIT_LOCK resolution check - hasSession: $hasSession, hasDeviceToken: $hasDeviceToken, hasUser: $hasUser, sessionInitialized: $sessionInitialized, canPerformOperations: $canPerformOperations")
@@ -676,31 +684,31 @@ class BranchRequestQueue private constructor(private val context: Context) {
676684
var updateRequestsInQueue = false
677685

678686
// Process SessionID
679-
if (respJson.has(Defines.Jsonkey.SessionID.getKey())) {
680-
val sessionId = respJson.getString(Defines.Jsonkey.SessionID.getKey())
681-
branch.prefHelper_.setSessionID(sessionId)
687+
if (respJson.has(Defines.Jsonkey.SessionID.key)) {
688+
val sessionId = respJson.getString(Defines.Jsonkey.SessionID.key)
689+
branch.prefHelper_.sessionID = sessionId
682690
updateRequestsInQueue = true
683691
BranchLogger.d("DEBUG: Set SessionID: $sessionId")
684692
}
685693

686694
// Process RandomizedBundleToken - this is what makes hasUser() return true
687-
if (respJson.has(Defines.Jsonkey.RandomizedBundleToken.getKey())) {
688-
val newRandomizedBundleToken = respJson.getString(Defines.Jsonkey.RandomizedBundleToken.getKey())
695+
if (respJson.has(Defines.Jsonkey.RandomizedBundleToken.key)) {
696+
val newRandomizedBundleToken = respJson.getString(Defines.Jsonkey.RandomizedBundleToken.key)
689697
val currentToken = branch.prefHelper_.getRandomizedBundleToken()
690698

691699
if (currentToken != newRandomizedBundleToken) {
692700
// On setting a new Randomized Bundle Token clear the link cache
693701
branch.linkCache_.clear()
694-
branch.prefHelper_.setRandomizedBundleToken(newRandomizedBundleToken)
702+
branch.prefHelper_.randomizedBundleToken = newRandomizedBundleToken
695703
updateRequestsInQueue = true
696704
BranchLogger.d("DEBUG: Set RandomizedBundleToken: $newRandomizedBundleToken (was: $currentToken)")
697705
}
698706
}
699707

700708
// Process RandomizedDeviceToken
701-
if (respJson.has(Defines.Jsonkey.RandomizedDeviceToken.getKey())) {
702-
val deviceToken = respJson.getString(Defines.Jsonkey.RandomizedDeviceToken.getKey())
703-
branch.prefHelper_.setRandomizedDeviceToken(deviceToken)
709+
if (respJson.has(Defines.Jsonkey.RandomizedDeviceToken.key)) {
710+
val deviceToken = respJson.getString(Defines.Jsonkey.RandomizedDeviceToken.key)
711+
branch.prefHelper_.randomizedDeviceToken = deviceToken
704712
updateRequestsInQueue = true
705713
BranchLogger.d("DEBUG: Set RandomizedDeviceToken: $deviceToken")
706714
}
@@ -721,7 +729,7 @@ class BranchRequestQueue private constructor(private val context: Context) {
721729
* Follows SRP - single responsibility for processing eligibility
722730
*/
723731
private fun canProcessRequest(request: ServerRequest): Boolean {
724-
val isWaiting = request.isWaitingOnProcessToFinish()
732+
val isWaiting = request.isWaitingOnProcessToFinish
725733
val needsSession = requestNeedsSession(request)
726734
val hasValidSession = hasValidSession(request)
727735

@@ -782,7 +790,7 @@ class BranchRequestQueue private constructor(private val context: Context) {
782790
}
783791

784792
val branch = Branch.init()
785-
val hasSession = !branch.prefHelper_.getSessionID().equals(PrefHelper.NO_STRING_VALUE)
793+
val hasSession = !branch.prefHelper_.sessionID.equals(PrefHelper.NO_STRING_VALUE)
786794
val hasDeviceToken = !branch.prefHelper_.getRandomizedDeviceToken().equals(PrefHelper.NO_STRING_VALUE)
787795
val hasUser = !branch.prefHelper_.getRandomizedBundleToken().equals(PrefHelper.NO_STRING_VALUE)
788796

@@ -937,7 +945,7 @@ class BranchRequestQueue private constructor(private val context: Context) {
937945
BranchLogger.d("DEBUG: BranchRequestQueue.updateAllRequestsInQueue called")
938946
synchronized(queueList) {
939947
for (req in queueList) {
940-
req.updateEnvironment(context, req.getPost())
948+
req.updateEnvironment(context, req.post)
941949
}
942950
}
943951
BranchLogger.d("DEBUG: BranchRequestQueue.updateAllRequestsInQueue completed")
@@ -1045,11 +1053,9 @@ class BranchRequestQueue private constructor(private val context: Context) {
10451053
/**
10461054
* Print queue state for debugging
10471055
*/
1048-
@OptIn(ExperimentalCoroutinesApi::class)
10491056
fun printQueue() {
10501057
if (BranchLogger.loggingLevel.level >= BranchLogger.BranchLogLevel.VERBOSE.level) {
10511058
val activeCount = activeRequests.size
1052-
val channelSize = if (processingTrigger.isEmpty) 0 else "unknown" // Channel doesn't expose size
10531059
BranchLogger.v("Queue state: ${_queueState.value}, Active requests: $activeCount, Network count: ${networkCount.get()}")
10541060
}
10551061
BranchLogger.d("DEBUG: Queue state: ${_queueState.value}, Queue size: ${getSize()}, Active requests: ${activeRequests.size}, Network count: ${networkCount.get()}")

Branch-SDK/src/main/java/io/branch/referral/BranchRequestQueueAdapter.kt

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.branch.referral
22

33
import android.content.Context
44
import kotlinx.coroutines.*
5+
import java.lang.ref.WeakReference
56

67
/**
78
* Adapter class to integrate the new BranchRequestQueue with existing ServerRequestQueue API
@@ -22,24 +23,32 @@ class BranchRequestQueueAdapter private constructor(context: Context) {
2223
}
2324

2425
companion object {
26+
// Use WeakReference to prevent memory leaks
2527
@Volatile
26-
private var INSTANCE: BranchRequestQueueAdapter? = null
28+
private var INSTANCE: WeakReference<BranchRequestQueueAdapter>? = null
2729

2830
@JvmStatic
2931
fun getInstance(context: Context): BranchRequestQueueAdapter {
30-
return INSTANCE ?: synchronized(this) {
31-
INSTANCE ?: BranchRequestQueueAdapter(context).also {
32-
INSTANCE = it
32+
// Check if we have a valid instance
33+
INSTANCE?.get()?.let { return it }
34+
35+
// Create new instance with proper synchronization
36+
return synchronized(this) {
37+
// Double-check after acquiring lock
38+
INSTANCE?.get() ?: run {
39+
val newInstance = BranchRequestQueueAdapter(context)
40+
INSTANCE = WeakReference(newInstance)
3341
BranchLogger.d("DEBUG: BranchRequestQueueAdapter instance created")
42+
newInstance
3443
}
3544
}
3645
}
3746

3847
@JvmStatic
3948
fun shutDown() {
4049
BranchLogger.d("DEBUG: BranchRequestQueueAdapter.shutDown called")
41-
INSTANCE?.let {
42-
it.shutdown()
50+
INSTANCE?.get()?.let { instance ->
51+
instance.shutdown()
4352
INSTANCE = null
4453
}
4554
BranchLogger.d("DEBUG: BranchRequestQueueAdapter.shutDown completed")
@@ -73,11 +82,15 @@ class BranchRequestQueueAdapter private constructor(context: Context) {
7382
val needsSession = requestNeedsSession(request)
7483
val canPerformOperations = Branch.init().canPerformOperations()
7584
val legacyInitialized = Branch.init().initState == Branch.SESSION_STATE.INITIALISED
76-
val currentSessionState = Branch.init().getCurrentSessionState()
77-
val hasValidSession = Branch.init().hasActiveSession() &&
78-
!Branch.init().prefHelper_.getSessionID().equals(PrefHelper.NO_STRING_VALUE)
85+
val hasValidSession = try {
86+
Branch.init().hasActiveSession() &&
87+
!Branch.init().prefHelper_.getSessionID().equals(PrefHelper.NO_STRING_VALUE)
88+
} catch (e: Exception) {
89+
// Fallback if session state is not accessible
90+
!Branch.init().prefHelper_.getSessionID().equals(PrefHelper.NO_STRING_VALUE)
91+
}
7992

80-
BranchLogger.d("DEBUG: Request needs session: $needsSession, can perform operations: $canPerformOperations, legacy initialized: $legacyInitialized, hasValidSession: $hasValidSession, currentSessionState: $currentSessionState")
93+
BranchLogger.d("DEBUG: Request needs session: $needsSession, can perform operations: $canPerformOperations, legacy initialized: $legacyInitialized, hasValidSession: $hasValidSession")
8194

8295
if (!canPerformOperations && !legacyInitialized &&
8396
request !is ServerRequestInitSession && needsSession) {
@@ -93,9 +106,9 @@ class BranchRequestQueueAdapter private constructor(context: Context) {
93106
BranchLogger.d("DEBUG: Session data is actually valid, not adding SDK_INIT_WAIT_LOCK")
94107
// Don't add wait lock since session is actually ready
95108
}
96-
// If session state is stuck in Initializing without a valid session, try to trigger a reset
97-
else if (currentSessionState is BranchSessionState.Initializing && !hasValidSession) {
98-
BranchLogger.d("DEBUG: Session appears stuck in Initializing state without valid session, attempting to reset")
109+
// If session appears stuck without a valid session, try to allow it to proceed
110+
else if (!hasValidSession && !legacyInitialized) {
111+
BranchLogger.d("DEBUG: Session appears stuck without valid session, attempting to reset")
99112
// Don't add wait lock, let the request proceed and it will trigger proper initialization
100113
} else {
101114
BranchLogger.d("DEBUG: Adding SDK_INIT_WAIT_LOCK for request waiting on session")

0 commit comments

Comments
 (0)