@@ -8,40 +8,74 @@ import org.digma.intellij.plugin.auth.AuthManager
88import org.digma.intellij.plugin.common.DisposableAdaptor
99import org.digma.intellij.plugin.common.ExceptionUtils
1010import org.digma.intellij.plugin.common.isProjectValid
11+ import org.digma.intellij.plugin.common.jsonToObject
12+ import org.digma.intellij.plugin.common.objectToJson
1113import org.digma.intellij.plugin.errorreporting.ErrorReporter
1214import org.digma.intellij.plugin.log.Log
1315import org.digma.intellij.plugin.model.rest.AboutResult
16+ import org.digma.intellij.plugin.persistence.PersistenceService
1417import org.digma.intellij.plugin.posthog.ActivityMonitor
15- import org.digma.intellij.plugin.scheduling.blockingOneShotTask
1618import org.digma.intellij.plugin.scheduling.disposingPeriodicTask
1719import org.digma.intellij.plugin.scheduling.oneShotTask
1820import java.util.concurrent.atomic.AtomicReference
19- import kotlin.time.Duration
2021import kotlin.time.Duration.Companion.minutes
21- import kotlin.time.Duration.Companion.seconds
2222
2323/* *
2424 * keep the backend info and tracks it on connection events.
25- * Its necessary because there is code that runs on EDT that may need the backend info. it's possible
26- * in that case to do it on background but then the EDT will wait for the api call, and we don't want that.
2725 */
2826@Service(Service .Level .PROJECT )
2927class BackendInfoHolder (val project : Project ) : DisposableAdaptor {
3028
3129 private val logger: Logger = Logger .getInstance(BackendInfoHolder ::class .java)
3230
33- private var aboutRef: AtomicReference <AboutResult ?> = AtomicReference (null )
31+ /* *
32+ * aboutRef should always have an AboutResult object.
33+ * on startup there are many calls to isCentralized and getAbout. if the AboutResult is null callers will not
34+ * have the correct info. trying to load the info in background may cause issues and may take few seconds
35+ * because it may hit initialization of services and that may take too much time. we have experience that this initialization may take
36+ * few seconds and may cause thread interruptions.
37+ * the solution to the above is to save the about info as json string in persistence every time it is refreshed. and on startup load it from
38+ * persistence. when loading from persistence on startup the info may not be up to date, maybe the backend was updated. but the correct info
39+ * will be populated very soon in the periodic task.
40+ * if there is no connection or the first periodic task didn't update the ref yet then at least we have info from the last IDE session which in
41+ * most cases is probably correct.
42+ * loading from persistence is very fast and we will have info very early on startup to all requesters.
43+ */
44+ private var aboutRef: AtomicReference <AboutResult > = AtomicReference (loadAboutInfoFromPersistence())
3445
3546 companion object {
3647 @JvmStatic
3748 fun getInstance (project : Project ): BackendInfoHolder {
3849 return project.service<BackendInfoHolder >()
3950 }
51+
52+ private fun saveAboutInfoToPersistence (aboutResult : AboutResult ) {
53+ try {
54+ val aboutAsJson = objectToJson(aboutResult)
55+ PersistenceService .getInstance().saveAboutAsJson(aboutAsJson)
56+ }catch (e: Throwable ){
57+ ErrorReporter .getInstance().reportError(" BackendInfoHolder.saveAboutInfoToPersistence" ,e)
58+ }
59+ }
60+
61+ private fun loadAboutInfoFromPersistence (): AboutResult {
62+ return try {
63+ val aboutAsJson = PersistenceService .getInstance().getAboutAsJson()
64+ aboutAsJson?.let {
65+ jsonToObject(it, AboutResult ::class .java)
66+ } ? : AboutResult .UNKNOWN
67+ }catch (e: Throwable ){
68+ ErrorReporter .getInstance().reportError(" BackendInfoHolder.loadAboutInfoFromPersistence" ,e)
69+ AboutResult .UNKNOWN
70+ }
71+ }
72+
4073 }
4174
4275
4376 init {
4477
78+ // schedule a periodic task that will update the backend info as soon as possible and then again every 1 minute
4579 val registered = disposingPeriodicTask(" BackendInfoHolder.periodic" , 1 .minutes.inWholeMilliseconds, false ) {
4680 update()
4781 }
@@ -98,10 +132,10 @@ class BackendInfoHolder(val project: Project) : DisposableAdaptor {
98132 try {
99133 if (isProjectValid(project)) {
100134 Log .log(logger::trace, " updating backend info" )
101- aboutRef.set( AnalyticsService .getInstance(project).about)
102- aboutRef.get()?. let {
103- ActivityMonitor .getInstance(project).registerServerInfo(it )
104- }
135+ val about = AnalyticsService .getInstance(project).about
136+ aboutRef.set(about)
137+ ActivityMonitor .getInstance(project).registerServerInfo(about )
138+ saveAboutInfoToPersistence(about)
105139 Log .log(logger::trace, " backend info updated {}" , aboutRef.get())
106140 }
107141 } catch (e: Throwable ) {
@@ -110,60 +144,28 @@ class BackendInfoHolder(val project: Project) : DisposableAdaptor {
110144 if (! isConnectionException) {
111145 ErrorReporter .getInstance().reportError(project, " BackendInfoHolder.update" , e)
112146 }
113- }
114- }
115-
116-
117- fun getAbout (): AboutResult ? {
118- if (aboutRef.get() == null ) {
119- return getAboutInBackgroundNow()
120- }
121-
122- return aboutRef.get()
123- }
124147
148+ // if update fails run another try immediately. maybe it was a momentary error from AnalyticsService.
149+ // if that will not succeed then the next execution in 1 minute will hopefully succeed
150+ updateInBackground()
125151
126- private fun getAboutInBackgroundNow (): AboutResult ? {
127- if (aboutRef.get() == null ) {
128- return getAboutInBackgroundNowWithTimeout()
129152 }
130- return aboutRef.get()
131153 }
132154
133155
134- fun isCentralized (): Boolean {
135- return aboutRef.get()?.let {
136- it.isCentralize ? : false
137- } ? : getIsCentralizedInBackgroundNow()
138- }
139156
140157
141- private fun getIsCentralizedInBackgroundNow (): Boolean {
142- return getAboutInBackgroundNowWithTimeout()?.isCentralize ? : false
143- }
144-
145158
146- private fun getAboutInBackgroundNowWithTimeout (): AboutResult ? {
147- updateAboutInBackgroundNowWithTimeout(3 .seconds)
159+ fun getAbout (): AboutResult {
148160 return aboutRef.get()
149161 }
150162
151163
152- private fun updateAboutInBackgroundNowWithTimeout (timeout : Duration ) {
153-
154- Log .log(logger::trace, " updating backend info in background with timeout" )
155-
156- val result = blockingOneShotTask(" BackendInfoHolder.updateAboutInBackgroundNowWithTimeout" , timeout.inWholeMilliseconds) {
157- update()
158- }
159-
160- if (result) {
161- Log .log(logger::trace, " backend info updated in background with timeout {}" , aboutRef.get())
162- } else {
163- Log .log(logger::trace, " backend info updated in background failed" )
164- }
164+ fun isCentralized (): Boolean {
165+ return aboutRef.get().isCentralize ? : false
165166 }
166167
168+
167169 fun refresh () {
168170 updateInBackground()
169171 }
0 commit comments