Skip to content

Commit f8db91d

Browse files
authored
Merge pull request #2647 from digma-ai/fix-interruped-in-BackendInfoHolder
fix interrupted exception in BackendInfoHolder Closes #2639
2 parents b2e0b95 + be6d3b8 commit f8db91d

File tree

15 files changed

+114
-67
lines changed

15 files changed

+114
-67
lines changed

ide-common/src/main/java/org/digma/intellij/plugin/analytics/AnalyticsService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import static org.digma.intellij.plugin.common.JsonUtilsKt.objectToJsonNoException;
4848
import static org.digma.intellij.plugin.common.StringUtilsKt.argsToString;
4949
import static org.digma.intellij.plugin.log.Log.API_LOGGER_NAME;
50+
import static org.digma.intellij.plugin.model.rest.AboutResultKt.UNKNOWN_APPLICATION_VERSION;
5051

5152

5253
public class AnalyticsService implements Disposable {
@@ -578,7 +579,7 @@ private boolean backendVersionOlderThen(String version) {
578579
String backendVersion = BackendInfoHolder.getInstance(project).getAbout().getApplicationVersion();
579580

580581
//dev environment may return unknown
581-
if ("unknown".equalsIgnoreCase(backendVersion)) {
582+
if (UNKNOWN_APPLICATION_VERSION.equalsIgnoreCase(backendVersion)) {
582583
return false;
583584
}
584585

ide-common/src/main/kotlin/org/digma/intellij/plugin/activation/UserActivationService.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import org.digma.intellij.plugin.common.newerThan
1414
import org.digma.intellij.plugin.errorreporting.ErrorReporter
1515
import org.digma.intellij.plugin.log.Log
1616
import org.digma.intellij.plugin.model.rest.AboutResult
17+
import org.digma.intellij.plugin.model.rest.UNKNOWN_APPLICATION_VERSION
1718
import org.digma.intellij.plugin.model.rest.activation.DiscoveredDataResponse
1819
import org.digma.intellij.plugin.persistence.PersistenceService
1920
import org.digma.intellij.plugin.posthog.ActivityMonitor
@@ -309,7 +310,7 @@ class UserActivationService : DisposableAdaptor {
309310

310311

311312
private fun isBackendVersion03116OrHigher(about: AboutResult): Boolean {
312-
if (about.applicationVersion == "unknown") {
313+
if (about.applicationVersion == UNKNOWN_APPLICATION_VERSION) {
313314
return true
314315
}
315316
val currentServerVersion = ComparableVersion(about.applicationVersion)

ide-common/src/main/kotlin/org/digma/intellij/plugin/analytics/BackendInfoHolder.kt

Lines changed: 51 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,74 @@ import org.digma.intellij.plugin.auth.AuthManager
88
import org.digma.intellij.plugin.common.DisposableAdaptor
99
import org.digma.intellij.plugin.common.ExceptionUtils
1010
import org.digma.intellij.plugin.common.isProjectValid
11+
import org.digma.intellij.plugin.common.jsonToObject
12+
import org.digma.intellij.plugin.common.objectToJson
1113
import org.digma.intellij.plugin.errorreporting.ErrorReporter
1214
import org.digma.intellij.plugin.log.Log
1315
import org.digma.intellij.plugin.model.rest.AboutResult
16+
import org.digma.intellij.plugin.persistence.PersistenceService
1417
import org.digma.intellij.plugin.posthog.ActivityMonitor
15-
import org.digma.intellij.plugin.scheduling.blockingOneShotTask
1618
import org.digma.intellij.plugin.scheduling.disposingPeriodicTask
1719
import org.digma.intellij.plugin.scheduling.oneShotTask
1820
import java.util.concurrent.atomic.AtomicReference
19-
import kotlin.time.Duration
2021
import 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)
2927
class 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
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.digma.intellij.plugin.analytics
2+
3+
import com.intellij.openapi.project.Project
4+
import org.digma.intellij.plugin.startup.DigmaProjectActivity
5+
6+
class BackendInfoHolderStarter:DigmaProjectActivity() {
7+
override fun executeProjectStartup(project: Project) {
8+
//initialize BackendInfoHolder as early as possible so it will populate its info from the backend as soon as possible
9+
BackendInfoHolder.getInstance(project)
10+
}
11+
}

ide-common/src/main/kotlin/org/digma/intellij/plugin/analytics/BackendUtils.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,34 @@ package org.digma.intellij.plugin.analytics
22

33
import com.intellij.openapi.project.Project
44
import org.digma.intellij.plugin.common.findActiveProject
5+
import org.digma.intellij.plugin.model.rest.UNKNOWN_APPLICATION_VERSION
6+
import org.digma.intellij.plugin.model.rest.version.BackendDeploymentType
57

68
fun isCentralized(project: Project): Boolean {
79
return BackendInfoHolder.getInstance(project).isCentralized()
810
}
911

1012
fun getBackendVersion(project: Project): String {
11-
return BackendInfoHolder.getInstance(project).getAbout()?.applicationVersion ?: "unknown"
13+
return BackendInfoHolder.getInstance(project).getAbout().applicationVersion
1214
}
1315

1416
fun getBackendVersion(): String {
1517
val project = findActiveProject()
1618
return project?.let {
17-
BackendInfoHolder.getInstance(it).getAbout()?.applicationVersion ?: "unknown"
18-
} ?: "unknown"
19+
BackendInfoHolder.getInstance(it).getAbout().applicationVersion
20+
} ?: UNKNOWN_APPLICATION_VERSION
1921
}
2022

2123
fun getBackendDeploymentType(): String {
2224
val project = findActiveProject()
2325
return project?.let {
24-
BackendInfoHolder.getInstance(it).getAbout()?.deploymentType?.name ?: "unknown"
25-
} ?: "unknown"
26+
BackendInfoHolder.getInstance(it).getAbout().deploymentType?.name ?: BackendDeploymentType.Unknown.name
27+
} ?: BackendDeploymentType.Unknown.name
2628
}
2729

2830
fun isCentralized(): Boolean {
2931
val project = findActiveProject()
3032
return project?.let {
31-
BackendInfoHolder.getInstance(it).getAbout()?.isCentralize ?: false
33+
BackendInfoHolder.getInstance(it).isCentralized()
3234
} ?:false
3335
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ fun objectToJson(value: Any): String {
1111
return sharedObjectMapper.writeValueAsString(value)
1212
}
1313

14+
fun <T> jsonToObject(jsonStr: String, type: Class<T>): T {
15+
return sharedObjectMapper.readValue(jsonStr, type)
16+
}
17+
1418

1519
fun objectToJsonNoException(value: Any?): String {
1620
return try {

ide-common/src/main/kotlin/org/digma/intellij/plugin/persistence/PersistenceData.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,5 +101,6 @@ internal data class PersistenceData(
101101
var latestDownloadedUiVersion: String? = null,
102102
var isFirstRunAfterPersistDockerCompose: Boolean = true,
103103
var lastUnpackedOtelJarsPluginVersion: String? = null,
104+
var aboutAsJson: String? = null
104105

105106
)

ide-common/src/main/kotlin/org/digma/intellij/plugin/persistence/PersistenceService.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,4 +459,12 @@ class PersistenceService {
459459
state.lastUnpackedOtelJarsPluginVersion = pluginVersion
460460
}
461461

462+
fun saveAboutAsJson(aboutAsJson: String) {
463+
state.aboutAsJson = aboutAsJson
464+
}
465+
466+
fun getAboutAsJson():String?{
467+
return state.aboutAsJson
468+
}
469+
462470
}

model/src/main/kotlin/org/digma/intellij/plugin/model/rest/AboutResult.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import org.digma.intellij.plugin.model.rest.version.BackendDeploymentType
66
import java.beans.ConstructorProperties
77

88

9+
const val UNKNOWN_APPLICATION_VERSION = "unknown"
10+
911
@JsonIgnoreProperties(ignoreUnknown = true)
1012
data class AboutResult @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
1113
@ConstructorProperties("applicationVersion", "deploymentType", "isCentralize","site")
@@ -14,4 +16,10 @@ constructor(
1416
val deploymentType: BackendDeploymentType? = BackendDeploymentType.Unknown,
1517
val isCentralize: Boolean?,
1618
val site: String?
17-
)
19+
){
20+
21+
companion object{
22+
@JvmStatic
23+
val UNKNOWN = AboutResult(UNKNOWN_APPLICATION_VERSION, BackendDeploymentType.Unknown, false, null)
24+
}
25+
}

src/main/java/org/digma/intellij/plugin/toolwindow/DigmaSidePaneToolWindowFactory.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import com.intellij.openapi.wm.*;
88
import com.intellij.ui.content.ContentFactory;
99
import com.intellij.util.ui.JBUI;
10-
import org.digma.intellij.plugin.analytics.AnalyticsService;
10+
import org.digma.intellij.plugin.analytics.*;
1111
import org.digma.intellij.plugin.common.Backgroundable;
1212
import org.digma.intellij.plugin.log.Log;
1313
import org.digma.intellij.plugin.persistence.PersistenceService;
@@ -50,6 +50,8 @@ public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindo
5050

5151
//initialize AnalyticsService early so the UI can detect the connection status when created
5252
AnalyticsService.getInstance(project);
53+
//initialize BackendInfoHolder early so it will populate its info soon
54+
BackendInfoHolder.getInstance(project);
5355

5456
Log.log(LOGGER::debug, "createToolWindowContent for project {}", project);
5557

0 commit comments

Comments
 (0)