Skip to content

Commit 5857cb0

Browse files
committed
blocking refresh
1 parent c42ca82 commit 5857cb0

File tree

4 files changed

+127
-5
lines changed

4 files changed

+127
-5
lines changed

ide-common/src/main/kotlin/org/digma/intellij/plugin/auth/AuthManager.kt

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,24 @@ import com.intellij.openapi.diagnostic.Logger
77
import com.intellij.openapi.project.Project
88
import com.intellij.openapi.util.Disposer
99
import com.intellij.util.Alarm
10+
import kotlinx.coroutines.CancellationException
1011
import kotlinx.coroutines.CoroutineName
1112
import kotlinx.coroutines.CoroutineScope
1213
import kotlinx.coroutines.ExperimentalCoroutinesApi
1314
import kotlinx.coroutines.Job
1415
import kotlinx.coroutines.async
16+
import kotlinx.coroutines.delay
17+
import kotlinx.coroutines.isActive
1518
import kotlinx.coroutines.launch
19+
import kotlinx.coroutines.withTimeoutOrNull
20+
import kotlinx.datetime.Clock
1621
import org.digma.intellij.plugin.analytics.AnalyticsProvider
1722
import org.digma.intellij.plugin.analytics.AnalyticsUrlProvider
1823
import org.digma.intellij.plugin.analytics.AuthenticationException
1924
import org.digma.intellij.plugin.analytics.AuthenticationProvider
2025
import org.digma.intellij.plugin.analytics.RestAnalyticsProvider
2126
import org.digma.intellij.plugin.auth.account.CredentialsHolder
27+
import org.digma.intellij.plugin.auth.account.DigmaAccountManager
2228
import org.digma.intellij.plugin.auth.account.DigmaDefaultAccountHolder
2329
import org.digma.intellij.plugin.auth.account.LoginHandler
2430
import org.digma.intellij.plugin.auth.account.LoginResult
@@ -33,7 +39,12 @@ import java.lang.reflect.Method
3339
import java.lang.reflect.Proxy
3440
import java.util.concurrent.CopyOnWriteArrayList
3541
import java.util.concurrent.Semaphore
42+
import kotlin.math.max
43+
import kotlin.time.Duration.Companion.ZERO
44+
import kotlin.time.Duration.Companion.minutes
3645
import kotlin.time.Duration.Companion.seconds
46+
import kotlin.time.DurationUnit
47+
import kotlin.time.toDuration
3748

3849

3950
@Service(Service.Level.APP)
@@ -57,16 +68,26 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
5768
private val refreshTokenStrategy = AuthManagerLockingRefreshStrategy(cs)
5869
// private val refreshTokenStrategy = AuthManagerNonLockingRefreshStrategy(cs)
5970

71+
// private val autoRefreshJob: Job
72+
// private var autoRefreshWaitingJob: Job? = null
73+
6074
companion object {
6175
@JvmStatic
6276
fun getInstance(): AuthManager {
6377
return service<AuthManager>()
6478
}
6579
}
6680

81+
82+
init {
83+
// autoRefreshJob = autoRefreshJob(cs)
84+
}
85+
6786
override fun dispose() {
6887
myAnalyticsProvider.close()
6988
loginOrRefreshAsyncJob?.cancel()
89+
// autoRefreshWaitingJob?.cancel()
90+
// autoRefreshJob.cancel()
7091
}
7192

7293
private fun createMyAnalyticsProvider(): RestAnalyticsProvider {
@@ -133,7 +154,7 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
133154

134155

135156
/**
136-
* this method is used for best effort to do login or refresh early on startup.
157+
* this method is used for best effort to do log in or refresh early on startup.
137158
* when connection to the server is ok it will succeed either new login or refresh if necessary.
138159
* it is non-blocking but does not allow more than one job at a time, if called concurrently and
139160
* myAuthJob is active the method will just return doing nothing.
@@ -156,6 +177,8 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
156177
Log.log(logger::trace, "starting loginOrRefreshAsync job, analytics url {}", myAnalyticsProvider.apiUrl)
157178
val loginHandler = LoginHandler.createLoginHandler(project, myAnalyticsProvider)
158179
loginHandler.loginOrRefresh()
180+
//wake up the auto refresh to check the next auto refresh time
181+
// autoRefreshWaitingJob?.cancel()
159182
} catch (e: Throwable) {
160183
Log.warnWithException(logger, e, "error in loginOrRefreshAsync {}", e)
161184
ErrorReporter.getInstance().reportError("AuthManager.loginOrRefresh", e)
@@ -188,7 +211,10 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
188211
try {
189212
Log.log(logger::trace, "starting job loginSynchronously, analytics url {}", myAnalyticsProvider.apiUrl)
190213
val loginHandler = LoginHandler.createLoginHandler(myAnalyticsProvider)
191-
loginHandler.login(user, password)
214+
val loginResult = loginHandler.login(user, password)
215+
//wake up the auto refresh to check the next auto refresh time
216+
// autoRefreshWaitingJob?.cancel()
217+
loginResult
192218
} catch (e: Throwable) {
193219
Log.warnWithException(logger, e, "error in loginSynchronously {}", e)
194220
ErrorReporter.getInstance().reportError("AuthManager.login", e)
@@ -349,6 +375,86 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
349375
}
350376

351377

378+
//auto refresh one minute before expiration
379+
//Experimental
380+
private fun autoRefreshJob(cs: CoroutineScope): Job {
381+
382+
Log.log(logger::trace, "launching autoRefreshJob")
383+
return cs.launch(CoroutineName("autoRefreshJob")) {
384+
385+
Log.log(logger::trace, "${coroutineContext[CoroutineName]} auto refresh job started")
386+
var delay = 1.minutes
387+
while (isActive) {
388+
389+
try {
390+
Log.log(logger::trace, "${coroutineContext[CoroutineName]} looking for account")
391+
392+
val account = DigmaDefaultAccountHolder.getInstance().account
393+
if (account != null) {
394+
Log.log(logger::trace, "${coroutineContext[CoroutineName]} found account {}", account)
395+
396+
val credentials = try {
397+
//exception here may happen if we change the credentials structure,which doesn't happen too much,
398+
// user will be redirected to log in again
399+
DigmaAccountManager.getInstance().findCredentials(account)
400+
} catch (_: Throwable) {
401+
null
402+
}
403+
404+
if (credentials != null) {
405+
406+
Log.log(logger::trace, "${coroutineContext[CoroutineName]} found credentials for account {}", account)
407+
408+
val expireIn =
409+
max(0, (credentials.expirationTime - Clock.System.now().toEpochMilliseconds())).toDuration(DurationUnit.MILLISECONDS)
410+
Log.log(logger::trace, "${coroutineContext[CoroutineName]} credentials for account expires in {}", expireIn)
411+
if (expireIn <= 1.minutes) {
412+
Log.log(logger::trace, "${coroutineContext[CoroutineName]} refreshing credentials for account {}", account)
413+
val loginHandler = LoginHandler.createLoginHandler(null, myAnalyticsProvider)
414+
loginHandler.refresh(account, credentials)
415+
Log.log(logger::trace, "${coroutineContext[CoroutineName]} credentials for account refreshed {}", account)
416+
//immediately loop again and compute the next delay
417+
delay = ZERO
418+
} else {
419+
delay = expireIn - 1.minutes
420+
Log.log(
421+
logger::trace,
422+
"${coroutineContext[CoroutineName]} credentials for account expires in more then 1 minute, waiting {}",
423+
delay
424+
)
425+
}
426+
} else {
427+
Log.log(logger::trace, "${coroutineContext[CoroutineName]} credentials for account not found, waiting 1 minute")
428+
delay = 1.minutes
429+
}
430+
} else {
431+
Log.log(logger::trace, "${coroutineContext[CoroutineName]} account not found, waiting 10 minutes")
432+
delay = 10.minutes
433+
}
434+
435+
436+
// autoRefreshWaitingJob = launch {
437+
// Log.log(logger::trace, "${coroutineContext[CoroutineName]} in autoRefreshJob.autoRefreshWaitingJob waiting {}",delay)
438+
// delay(delay.inWholeMilliseconds)
439+
// Log.log(logger::trace, "${coroutineContext[CoroutineName]} in autoRefreshJob.autoRefreshWaitingJob done")
440+
// }
441+
// try {
442+
// withTimeoutOrNull(delay.inWholeMilliseconds) {
443+
// autoRefreshWaitingJob?.join()
444+
// }
445+
// } catch (e: CancellationException) {
446+
// Log.log(logger::trace, "${coroutineContext[CoroutineName]} autoRefreshJob.autoRefreshWaitingJob woke up")
447+
// }
448+
449+
450+
} catch (e: Throwable) {
451+
Log.warnWithException(logger, e, "${coroutineContext[CoroutineName]} error in auto refresh job")
452+
}
453+
}
454+
}
455+
}
456+
457+
352458
private inner class MyAuthInvocationHandler(private val analyticsProvider: RestAnalyticsProvider) : InvocationHandler {
353459

354460
override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
@@ -426,6 +532,8 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
426532
Log.log(logger::trace, "starting lockingLoginOrRefresh job, analytics url {}", myAnalyticsProvider.apiUrl)
427533
val loginHandler = LoginHandler.createLoginHandler(null, myAnalyticsProvider)
428534
loginHandler.loginOrRefresh(onAuthenticationError)
535+
//wake up the auto refresh to check the next auto refresh time
536+
// autoRefreshWaitingJob?.cancel()
429537
} catch (e: Throwable) {
430538
Log.warnWithException(logger, e, "error in lockingLoginOrRefresh job {}", e)
431539
ErrorReporter.getInstance().reportError("AuthManager.loginOrRefresh", e)
@@ -462,6 +570,8 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
462570

463571

464572
//this strategy may do multiple refresh one after the other
573+
//this class was an experiment, it has been tested to work, but we don't use it
574+
@Suppress("unused")
465575
private inner class AuthManagerNonLockingRefreshStrategy(private val cs: CoroutineScope) : RefreshStrategy {
466576

467577
private val logger: Logger = Logger.getInstance(AuthManagerNonLockingRefreshStrategy::class.java)
@@ -480,6 +590,8 @@ class AuthManager(private val cs: CoroutineScope) : Disposable {
480590
Log.log(logger::trace, "starting nonLockingLoginOrRefresh job, analytics url {}", myAnalyticsProvider.apiUrl)
481591
val loginHandler = LoginHandler.createLoginHandler(myAnalyticsProvider)
482592
loginHandler.loginOrRefresh(onAuthenticationError)
593+
//wake up the auto refresh to check the next auto refresh time
594+
// autoRefreshWaitingJob?.cancel()
483595
} catch (e: Throwable) {
484596
Log.warnWithException(logger, e, "error in nonLockingLoginOrRefresh job {}", e)
485597
ErrorReporter.getInstance().reportError("AuthManager.loginOrRefresh", e)

ide-common/src/main/kotlin/org/digma/intellij/plugin/auth/NoOpLoginHandler.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package org.digma.intellij.plugin.auth
22

33
import com.intellij.openapi.diagnostic.Logger
4+
import org.digma.intellij.plugin.auth.account.DigmaAccount
45
import org.digma.intellij.plugin.auth.account.LoginHandler
56
import org.digma.intellij.plugin.auth.account.LoginResult
7+
import org.digma.intellij.plugin.auth.credentials.DigmaCredentials
68
import org.digma.intellij.plugin.log.Log
79

810

@@ -21,4 +23,9 @@ class NoOpLoginHandler(private val errorMessage: String) : LoginHandler {
2123
Log.log(logger::trace, "login called, error message {}", errorMessage)
2224
return LoginResult(false, null, errorMessage)
2325
}
26+
27+
override suspend fun refresh(account: DigmaAccount, credentials: DigmaCredentials): Boolean {
28+
Log.log(logger::trace, "refresh called, error message {}", errorMessage)
29+
return false
30+
}
2431
}

ide-common/src/main/kotlin/org/digma/intellij/plugin/auth/account/AbstractLoginHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ abstract class AbstractLoginHandler(protected val analyticsProvider: RestAnalyti
6868
}
6969

7070

71-
suspend fun refresh(account: DigmaAccount, credentials: DigmaCredentials): Boolean {
71+
override suspend fun refresh(account: DigmaAccount, credentials: DigmaCredentials): Boolean {
7272

7373
return try {
7474

ide-common/src/main/kotlin/org/digma/intellij/plugin/auth/account/LoginHandler.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,11 @@ interface LoginHandler {
6868
//does login or refresh if necessary, return true if did successful login or successful refresh,
6969
// or when no need to do anything. return false if failed.
7070
//we don't really rely on the returned value, mostly for logging
71+
//todo: can be true always because its called only from onAuthenticationException
7172
suspend fun loginOrRefresh(onAuthenticationError: Boolean = false): Boolean
7273

74+
suspend fun refresh(account: DigmaAccount, credentials: DigmaCredentials): Boolean
75+
7376

7477
suspend fun logout(): Boolean {
7578
return try {
@@ -132,11 +135,11 @@ interface LoginHandler {
132135
// }
133136

134137
suspend fun trace(format: String, vararg args: Any?) {
135-
Log.log(logger::trace, "${coroutineContext[CoroutineName]}: $format", args)
138+
Log.log(logger::trace, "${coroutineContext[CoroutineName]}: $format", *args)
136139
}
137140

138141
suspend fun warnWithException(e: Throwable, format: String, vararg args: Any?) {
139-
Log.warnWithException(logger, e, "${coroutineContext[CoroutineName]}: $format", args)
142+
Log.warnWithException(logger, e, "${coroutineContext[CoroutineName]}: $format", *args)
140143
}
141144

142145

0 commit comments

Comments
 (0)