Skip to content

Commit

Permalink
Merge pull request #2647 from digma-ai/fix-interruped-in-BackendInfoH…
Browse files Browse the repository at this point in the history
…older

fix interrupted exception in BackendInfoHolder Closes #2639
  • Loading branch information
shalom938 authored Jan 6, 2025
2 parents b2e0b95 + be6d3b8 commit f8db91d
Show file tree
Hide file tree
Showing 15 changed files with 114 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import static org.digma.intellij.plugin.common.JsonUtilsKt.objectToJsonNoException;
import static org.digma.intellij.plugin.common.StringUtilsKt.argsToString;
import static org.digma.intellij.plugin.log.Log.API_LOGGER_NAME;
import static org.digma.intellij.plugin.model.rest.AboutResultKt.UNKNOWN_APPLICATION_VERSION;


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

//dev environment may return unknown
if ("unknown".equalsIgnoreCase(backendVersion)) {
if (UNKNOWN_APPLICATION_VERSION.equalsIgnoreCase(backendVersion)) {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.digma.intellij.plugin.common.newerThan
import org.digma.intellij.plugin.errorreporting.ErrorReporter
import org.digma.intellij.plugin.log.Log
import org.digma.intellij.plugin.model.rest.AboutResult
import org.digma.intellij.plugin.model.rest.UNKNOWN_APPLICATION_VERSION
import org.digma.intellij.plugin.model.rest.activation.DiscoveredDataResponse
import org.digma.intellij.plugin.persistence.PersistenceService
import org.digma.intellij.plugin.posthog.ActivityMonitor
Expand Down Expand Up @@ -309,7 +310,7 @@ class UserActivationService : DisposableAdaptor {


private fun isBackendVersion03116OrHigher(about: AboutResult): Boolean {
if (about.applicationVersion == "unknown") {
if (about.applicationVersion == UNKNOWN_APPLICATION_VERSION) {
return true
}
val currentServerVersion = ComparableVersion(about.applicationVersion)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,74 @@ import org.digma.intellij.plugin.auth.AuthManager
import org.digma.intellij.plugin.common.DisposableAdaptor
import org.digma.intellij.plugin.common.ExceptionUtils
import org.digma.intellij.plugin.common.isProjectValid
import org.digma.intellij.plugin.common.jsonToObject
import org.digma.intellij.plugin.common.objectToJson
import org.digma.intellij.plugin.errorreporting.ErrorReporter
import org.digma.intellij.plugin.log.Log
import org.digma.intellij.plugin.model.rest.AboutResult
import org.digma.intellij.plugin.persistence.PersistenceService
import org.digma.intellij.plugin.posthog.ActivityMonitor
import org.digma.intellij.plugin.scheduling.blockingOneShotTask
import org.digma.intellij.plugin.scheduling.disposingPeriodicTask
import org.digma.intellij.plugin.scheduling.oneShotTask
import java.util.concurrent.atomic.AtomicReference
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds

/**
* keep the backend info and tracks it on connection events.
* Its necessary because there is code that runs on EDT that may need the backend info. it's possible
* in that case to do it on background but then the EDT will wait for the api call, and we don't want that.
*/
@Service(Service.Level.PROJECT)
class BackendInfoHolder(val project: Project) : DisposableAdaptor {

private val logger: Logger = Logger.getInstance(BackendInfoHolder::class.java)

private var aboutRef: AtomicReference<AboutResult?> = AtomicReference(null)
/**
* aboutRef should always have an AboutResult object.
* on startup there are many calls to isCentralized and getAbout. if the AboutResult is null callers will not
* have the correct info. trying to load the info in background may cause issues and may take few seconds
* because it may hit initialization of services and that may take too much time. we have experience that this initialization may take
* few seconds and may cause thread interruptions.
* 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
* persistence. when loading from persistence on startup the info may not be up to date, maybe the backend was updated. but the correct info
* will be populated very soon in the periodic task.
* 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
* most cases is probably correct.
* loading from persistence is very fast and we will have info very early on startup to all requesters.
*/
private var aboutRef: AtomicReference<AboutResult> = AtomicReference(loadAboutInfoFromPersistence())

companion object {
@JvmStatic
fun getInstance(project: Project): BackendInfoHolder {
return project.service<BackendInfoHolder>()
}

private fun saveAboutInfoToPersistence(aboutResult: AboutResult) {
try {
val aboutAsJson = objectToJson(aboutResult)
PersistenceService.getInstance().saveAboutAsJson(aboutAsJson)
}catch (e:Throwable){
ErrorReporter.getInstance().reportError("BackendInfoHolder.saveAboutInfoToPersistence",e)
}
}

private fun loadAboutInfoFromPersistence(): AboutResult {
return try {
val aboutAsJson = PersistenceService.getInstance().getAboutAsJson()
aboutAsJson?.let {
jsonToObject(it, AboutResult::class.java)
} ?: AboutResult.UNKNOWN
}catch (e:Throwable){
ErrorReporter.getInstance().reportError("BackendInfoHolder.loadAboutInfoFromPersistence",e)
AboutResult.UNKNOWN
}
}

}


init {

//schedule a periodic task that will update the backend info as soon as possible and then again every 1 minute
val registered = disposingPeriodicTask("BackendInfoHolder.periodic", 1.minutes.inWholeMilliseconds, false) {
update()
}
Expand Down Expand Up @@ -98,10 +132,10 @@ class BackendInfoHolder(val project: Project) : DisposableAdaptor {
try {
if (isProjectValid(project)) {
Log.log(logger::trace, "updating backend info")
aboutRef.set(AnalyticsService.getInstance(project).about)
aboutRef.get()?.let {
ActivityMonitor.getInstance(project).registerServerInfo(it)
}
val about = AnalyticsService.getInstance(project).about
aboutRef.set(about)
ActivityMonitor.getInstance(project).registerServerInfo(about)
saveAboutInfoToPersistence(about)
Log.log(logger::trace, "backend info updated {}", aboutRef.get())
}
} catch (e: Throwable) {
Expand All @@ -110,60 +144,28 @@ class BackendInfoHolder(val project: Project) : DisposableAdaptor {
if (!isConnectionException) {
ErrorReporter.getInstance().reportError(project, "BackendInfoHolder.update", e)
}
}
}


fun getAbout(): AboutResult? {
if (aboutRef.get() == null) {
return getAboutInBackgroundNow()
}

return aboutRef.get()
}

//if update fails run another try immediately. maybe it was a momentary error from AnalyticsService.
// if that will not succeed then the next execution in 1 minute will hopefully succeed
updateInBackground()

private fun getAboutInBackgroundNow(): AboutResult? {
if (aboutRef.get() == null) {
return getAboutInBackgroundNowWithTimeout()
}
return aboutRef.get()
}


fun isCentralized(): Boolean {
return aboutRef.get()?.let {
it.isCentralize ?: false
} ?: getIsCentralizedInBackgroundNow()
}


private fun getIsCentralizedInBackgroundNow(): Boolean {
return getAboutInBackgroundNowWithTimeout()?.isCentralize ?: false
}


private fun getAboutInBackgroundNowWithTimeout(): AboutResult? {
updateAboutInBackgroundNowWithTimeout(3.seconds)
fun getAbout(): AboutResult {
return aboutRef.get()
}


private fun updateAboutInBackgroundNowWithTimeout(timeout: Duration) {

Log.log(logger::trace, "updating backend info in background with timeout")

val result = blockingOneShotTask("BackendInfoHolder.updateAboutInBackgroundNowWithTimeout", timeout.inWholeMilliseconds) {
update()
}

if (result) {
Log.log(logger::trace, "backend info updated in background with timeout {}", aboutRef.get())
} else {
Log.log(logger::trace, "backend info updated in background failed")
}
fun isCentralized(): Boolean {
return aboutRef.get().isCentralize ?: false
}


fun refresh() {
updateInBackground()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.digma.intellij.plugin.analytics

import com.intellij.openapi.project.Project
import org.digma.intellij.plugin.startup.DigmaProjectActivity

class BackendInfoHolderStarter:DigmaProjectActivity() {
override fun executeProjectStartup(project: Project) {
//initialize BackendInfoHolder as early as possible so it will populate its info from the backend as soon as possible
BackendInfoHolder.getInstance(project)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,34 @@ package org.digma.intellij.plugin.analytics

import com.intellij.openapi.project.Project
import org.digma.intellij.plugin.common.findActiveProject
import org.digma.intellij.plugin.model.rest.UNKNOWN_APPLICATION_VERSION
import org.digma.intellij.plugin.model.rest.version.BackendDeploymentType

fun isCentralized(project: Project): Boolean {
return BackendInfoHolder.getInstance(project).isCentralized()
}

fun getBackendVersion(project: Project): String {
return BackendInfoHolder.getInstance(project).getAbout()?.applicationVersion ?: "unknown"
return BackendInfoHolder.getInstance(project).getAbout().applicationVersion
}

fun getBackendVersion(): String {
val project = findActiveProject()
return project?.let {
BackendInfoHolder.getInstance(it).getAbout()?.applicationVersion ?: "unknown"
} ?: "unknown"
BackendInfoHolder.getInstance(it).getAbout().applicationVersion
} ?: UNKNOWN_APPLICATION_VERSION
}

fun getBackendDeploymentType(): String {
val project = findActiveProject()
return project?.let {
BackendInfoHolder.getInstance(it).getAbout()?.deploymentType?.name ?: "unknown"
} ?: "unknown"
BackendInfoHolder.getInstance(it).getAbout().deploymentType?.name ?: BackendDeploymentType.Unknown.name
} ?: BackendDeploymentType.Unknown.name
}

fun isCentralized(): Boolean {
val project = findActiveProject()
return project?.let {
BackendInfoHolder.getInstance(it).getAbout()?.isCentralize ?: false
BackendInfoHolder.getInstance(it).isCentralized()
} ?:false
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ fun objectToJson(value: Any): String {
return sharedObjectMapper.writeValueAsString(value)
}

fun <T> jsonToObject(jsonStr: String, type: Class<T>): T {
return sharedObjectMapper.readValue(jsonStr, type)
}


fun objectToJsonNoException(value: Any?): String {
return try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,6 @@ internal data class PersistenceData(
var latestDownloadedUiVersion: String? = null,
var isFirstRunAfterPersistDockerCompose: Boolean = true,
var lastUnpackedOtelJarsPluginVersion: String? = null,
var aboutAsJson: String? = null

)
Original file line number Diff line number Diff line change
Expand Up @@ -459,4 +459,12 @@ class PersistenceService {
state.lastUnpackedOtelJarsPluginVersion = pluginVersion
}

fun saveAboutAsJson(aboutAsJson: String) {
state.aboutAsJson = aboutAsJson
}

fun getAboutAsJson():String?{
return state.aboutAsJson
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import org.digma.intellij.plugin.model.rest.version.BackendDeploymentType
import java.beans.ConstructorProperties


const val UNKNOWN_APPLICATION_VERSION = "unknown"

@JsonIgnoreProperties(ignoreUnknown = true)
data class AboutResult @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
@ConstructorProperties("applicationVersion", "deploymentType", "isCentralize","site")
Expand All @@ -14,4 +16,10 @@ constructor(
val deploymentType: BackendDeploymentType? = BackendDeploymentType.Unknown,
val isCentralize: Boolean?,
val site: String?
)
){

companion object{
@JvmStatic
val UNKNOWN = AboutResult(UNKNOWN_APPLICATION_VERSION, BackendDeploymentType.Unknown, false, null)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import com.intellij.openapi.wm.*;
import com.intellij.ui.content.ContentFactory;
import com.intellij.util.ui.JBUI;
import org.digma.intellij.plugin.analytics.AnalyticsService;
import org.digma.intellij.plugin.analytics.*;
import org.digma.intellij.plugin.common.Backgroundable;
import org.digma.intellij.plugin.log.Log;
import org.digma.intellij.plugin.persistence.PersistenceService;
Expand Down Expand Up @@ -50,6 +50,8 @@ public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindo

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import org.digma.intellij.plugin.common.EDT
import org.digma.intellij.plugin.common.newerThan
import org.digma.intellij.plugin.icons.AppIcons
import org.digma.intellij.plugin.loadstatus.LoadStatusService
import org.digma.intellij.plugin.model.rest.UNKNOWN_APPLICATION_VERSION
import org.digma.intellij.plugin.persistence.NotificationsPersistenceState
import org.digma.intellij.plugin.posthog.ActivityMonitor
import org.digma.intellij.plugin.posthog.UserActionOrigin
Expand Down Expand Up @@ -142,7 +143,11 @@ class LoadStatusPanel(val project: Project) : DigmaResettablePanel() {
private fun shouldDisplayCloseButton(): Boolean
{

val version = BackendInfoHolder.getInstance(project).getAbout()?.applicationVersion ?: return false
val version = BackendInfoHolder.getInstance(project).getAbout().applicationVersion
if(version == UNKNOWN_APPLICATION_VERSION){
return false
}


val currentBackendVersion = ComparableVersion(version)
val closeButtonBackendVersion = ComparableVersion("0.3.25")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import org.digma.intellij.plugin.docker.LocalInstallationFacade
import org.digma.intellij.plugin.errorreporting.ErrorReporter
import org.digma.intellij.plugin.idea.frameworks.SpringBootMicrometerConfigureDepsService
import org.digma.intellij.plugin.log.Log
import org.digma.intellij.plugin.model.rest.UNKNOWN_APPLICATION_VERSION
import org.digma.intellij.plugin.model.rest.environment.Env
import org.digma.intellij.plugin.model.rest.navigation.CodeLocation
import org.digma.intellij.plugin.observability.ObservabilityChanged
Expand Down Expand Up @@ -349,11 +350,11 @@ private constructor(
try {

val spanEnvironments = if (scope?.at("/span/spanCodeObjectId")?.asText(null) != null) {
val version = BackendInfoHolder.getInstance(project).getAbout()?.applicationVersion
val version = BackendInfoHolder.getInstance(project).getAbout().applicationVersion

val currentBackendVersion = ComparableVersion(version)
val spanEnvironmentsVersion = ComparableVersion("0.3.94")
if (currentBackendVersion.newerThan(spanEnvironmentsVersion)) {
if (version == UNKNOWN_APPLICATION_VERSION || currentBackendVersion.newerThan(spanEnvironmentsVersion)) {
AnalyticsService.getInstance(project).getSpanEnvironmentsStats(scope.at("/span/spanCodeObjectId").asText())
} else {
listOf()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ import org.digma.intellij.plugin.analytics.BackendInfoHolder
import org.digma.intellij.plugin.docker.DigmaInstallationStatus
import org.digma.intellij.plugin.docker.LocalInstallationFacade
import org.digma.intellij.plugin.errorreporting.ErrorReporter
import org.digma.intellij.plugin.model.rest.AboutResult
import org.digma.intellij.plugin.model.rest.environment.Env
import org.digma.intellij.plugin.model.rest.insights.SpanEnvironment
import org.digma.intellij.plugin.model.rest.navigation.CodeLocation
import org.digma.intellij.plugin.model.rest.version.BackendDeploymentType
import org.digma.intellij.plugin.scope.ScopeContext
import org.digma.intellij.plugin.scope.SpanScope
import org.digma.intellij.plugin.ui.common.isJaegerButtonEnabled
Expand Down Expand Up @@ -122,7 +120,7 @@ fun updateDigmaEngineStatus(cefBrowser: CefBrowser, status: DigmaInstallationSta
}

fun sendBackendAboutInfo(cefBrowser: CefBrowser, project: Project) {
val about = BackendInfoHolder.getInstance(project).getAbout() ?: AboutResult("unknown", BackendDeploymentType.Unknown, false, null)
val about = BackendInfoHolder.getInstance(project).getAbout()
val message = BackendInfoMessage(about)
serializeAndExecuteWindowPostMessageJavaScript(cefBrowser, message, project)
}
Expand Down
Loading

0 comments on commit f8db91d

Please sign in to comment.