[java] IDEA-337233 Delay Checking for JDK updates

GitOrigin-RevId: 21edb1ec5a0cea2fb3f9ea049330985b697a0c27
This commit is contained in:
Yuriy Artamonov
2024-03-24 23:10:15 +00:00
committed by intellij-monorepo-bot
parent 605e5da097
commit 7c0ce1b768
2 changed files with 72 additions and 122 deletions
@@ -2,158 +2,103 @@
package com.intellij.openapi.projectRoots.impl.jdkDownloader
import com.intellij.execution.wsl.WslDistributionManager
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.ControlFlowException
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.extensions.ExtensionNotApplicableException
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.openapi.progress.coroutineToIndicator
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectBundle
import com.intellij.openapi.projectRoots.JavaSdkType
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.*
import com.intellij.openapi.roots.ModuleRootEvent
import com.intellij.openapi.roots.ModuleRootListener
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.roots.ui.configuration.UnknownSdk
import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.registry.Registry
import com.intellij.util.concurrency.AppExecutorUtil
import com.intellij.platform.ide.progress.withBackgroundProgress
import com.intellij.util.text.VersionComparatorUtil
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicLong
import kotlinx.coroutines.delay
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.time.Duration.Companion.seconds
/**
* This extension point is used to collect
* additional [Sdk] instances to check for a possible
* JDK update
* Used to collect additional [Sdk] instances to check for a possible JDK update.
*/
private val EP_NAME = ExtensionPointName<JdkUpdateCheckContributor>("com.intellij.jdkUpdateCheckContributor")
private val EP_NAME: ExtensionPointName<JdkUpdateCheckContributor> =
ExtensionPointName("com.intellij.jdkUpdateCheckContributor")
interface JdkUpdateCheckContributor {
/**
* Executed from any thread (possibly without read-lock) to
* collect SDKs, which should be considered for
* JDK Update check
* Executed from any thread (possibly without read-lock) to collect SDKs, which should be considered for JDK Update check.
*/
fun contributeJdks(project: Project): List<Sdk>
}
private fun isEnabled(project: Project) = !project.isDefault &&
Registry.`is`("jdk.updater") &&
!ApplicationManager.getApplication().isUnitTestMode &&
!ApplicationManager.getApplication().isHeadlessEnvironment
private class JdkUpdaterStartup : ProjectActivity {
override suspend fun execute(project: Project) {
if (isEnabled(project)) {
project.service<JdkUpdatesCollector>().updateNotifications()
}
}
private fun isEnabled(project: Project): Boolean {
return !project.isDefault &&
!project.isDisposed &&
Registry.`is`("jdk.updater") &&
!ApplicationManager.getApplication().isUnitTestMode &&
!ApplicationManager.getApplication().isHeadlessEnvironment
}
private val LOG = logger<JdkUpdatesCollector>()
@Service(Service.Level.PROJECT)
private class JdkUpdatesCollectorQueue : UnknownSdkCollectorQueue(7_000)
@Service(Service.Level.PROJECT)
internal class JdkUpdatesCollector(private val project: Project) : Disposable {
override fun dispose(): Unit = Unit
private class JdkUpdaterStartup : ProjectActivity {
init {
schedule()
}
private fun isEnabled() = isEnabled(project)
private fun schedule() {
if (!isEnabled()) return
val future = AppExecutorUtil.getAppScheduledExecutorService().scheduleWithFixedDelay(
Runnable {
if (project.isDisposed) return@Runnable
try {
updateNotifications()
}
catch (t: Throwable) {
if (t is ControlFlowException) return@Runnable
LOG.warn("Failed to complete JDK Update check. ${t.message}", t)
}
},
12,
12,
TimeUnit.HOURS
)
Disposer.register(this, Disposable { future.cancel(false) })
val myLastKnownModificationId = AtomicLong(-100_500)
project.messageBus
.connect(this)
.subscribe(
ModuleRootListener.TOPIC, object : ModuleRootListener {
override fun rootsChanged(event: ModuleRootEvent) {
if (event.isCausedByFileTypesChange) return
//an optimization - we do not scan for JDKs if there we no ProjectRootManager modifications change
//this avoids VirtualFilePointers invalidation
val newCounterValue = ProjectRootManager.getInstance(project).modificationCount
if (myLastKnownModificationId.getAndSet(newCounterValue) == newCounterValue) return
updateNotifications()
}
})
}
fun updateNotifications() {
if (!isEnabled()) {
return
if (ApplicationManager.getApplication().isUnitTestMode) {
throw ExtensionNotApplicableException.create()
}
}
project.service<JdkUpdatesCollectorQueue>().queue(object: UnknownSdkTrackerTask {
override fun createCollector(): UnknownSdkCollector? {
if (!isEnabled()) return null
return object : UnknownSdkCollector(project) {
override fun getContributors(): List<UnknownSdkContributor> {
return super.getContributors() + EP_NAME.extensionList.map {
object : UnknownSdkContributor {
override fun contributeUnknownSdks(project: Project) = listOf<UnknownSdk>()
override fun contributeKnownSdks(project: Project): List<Sdk> = it.contributeJdks(project)
override suspend fun execute(project: Project) {
delay(60.seconds) // no hurry!
if (isEnabled(project)) {
updateNotifications(project)
}
}
suspend fun updateNotifications(project: Project) {
val knownSdks = suspendCancellableCoroutine<Collection<Sdk>> { continuation ->
project.service<JdkUpdatesCollectorQueue>().queue(object: UnknownSdkTrackerTask {
override fun createCollector(): UnknownSdkCollector {
return object : UnknownSdkCollector(project) {
override fun getContributors(): List<UnknownSdkContributor> {
return super.getContributors() + EP_NAME.extensionList.map {
object : UnknownSdkContributor {
override fun contributeUnknownSdks(project: Project) = listOf<UnknownSdk>()
override fun contributeKnownSdks(project: Project): List<Sdk> = it.contributeJdks(project)
}
}
}
}
}
}
override fun onLookupCompleted(snapshot: UnknownSdkSnapshot) {
if (!isEnabled()) return
//this callback happens in the GUI thread!
val knownSdks = snapshot
.knownSdks
.filter { it.sdkType is JavaSdkType && it.sdkType !is DependentSdkType }
if (knownSdks.isEmpty()) return
ProgressManager.getInstance().run(
object : Task.Backgroundable(project, ProjectBundle.message("progress.title.checking.for.jdk.updates"), true, ALWAYS_BACKGROUND) {
override fun run(indicator: ProgressIndicator) {
updateWithSnapshot(knownSdks.distinct().sortedBy { it.name }, indicator)
}
override fun onLookupCompleted(snapshot: UnknownSdkSnapshot) {
if (!isEnabled(project)) {
continuation.resumeWith(Result.success(emptyList()))
return
}
)
// this callback happens in the GUI thread!
val knownSdks = snapshot.knownSdks
.filter { it.sdkType is JavaSdkType && it.sdkType !is DependentSdkType }
continuation.resumeWith(Result.success(knownSdks))
}
})
}
if (knownSdks.isEmpty()) return
withBackgroundProgress(project, ProjectBundle.message("progress.title.checking.for.jdk.updates")) {
coroutineToIndicator {
updateWithSnapshot(knownSdks.distinct().sortedBy { it.name }, ProgressManager.getInstance().progressIndicator)
}
})
}
}
private fun updateWithSnapshot(knownSdks: List<Sdk>, indicator: ProgressIndicator) {
@@ -191,9 +136,12 @@ internal class JdkUpdatesCollector(private val project: Project) : Disposable {
noUpdatesFor -= jdk
}
//handle the case, when a JDK is no longer requiring an update
// handle the case, when a JDK is no longer requiring an update
for (jdk in noUpdatesFor) {
notifications.hideNotification(jdk)
}
}
}
@Service(Service.Level.PROJECT)
private class JdkUpdatesCollectorQueue : UnknownSdkCollectorQueue(7_000)
@@ -4,8 +4,10 @@ package com.intellij.openapi.projectRoots.impl.jdkDownloader
import com.intellij.ide.actions.SettingsEntryPointAction
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.components.*
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.util.Alarm
import com.intellij.util.ui.update.MergingUpdateQueue
@@ -13,7 +15,6 @@ import com.intellij.util.ui.update.Update
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
@Service(Service.Level.APP)
class JdkUpdaterNotifications : Disposable {
private val lock = ReentrantLock()
@@ -30,9 +31,9 @@ class JdkUpdaterNotifications : Disposable {
pendingNotifications.values.sortedBy { it.persistentId }.map { it.updateAction }
}
invokeLater {
ApplicationManager.getApplication().invokeLater({
SettingsEntryPointAction.updateState()
}
}, ModalityState.nonModal())
}
})
}
@@ -72,6 +73,7 @@ class JdkUpdaterNotifications : Disposable {
fun getActions() : List<JdkUpdateNotification.JdkUpdateSuggestionAction> = pendingActionsCopy
}
class JdkSettingsActionRegistryActionProvider : SettingsEntryPointAction.ActionProvider {
override fun getUpdateActions(context: DataContext): List<JdkUpdateNotification.JdkUpdateSuggestionAction> = service<JdkUpdaterNotifications>().getActions()
internal class JdkSettingsActionRegistryActionProvider : SettingsEntryPointAction.ActionProvider {
override fun getUpdateActions(context: DataContext): List<JdkUpdateNotification.JdkUpdateSuggestionAction> =
service<JdkUpdaterNotifications>().getActions()
}