mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 22:51:17 +07:00
jdk-downloader - cache download list of JDKs for 15 minutes hours (in-memory only)
GitOrigin-RevId: 04112837530db86f91b2648d34d104c4f3b9d06e
This commit is contained in:
committed by
intellij-monorepo-bot
parent
589351e694
commit
8bf3398e57
@@ -41,7 +41,7 @@ class JdkAuto : UnknownSdkResolver, JdkDownloaderBase {
|
||||
indicator.pushState()
|
||||
indicator.text = "Downloading JDK list..."
|
||||
try {
|
||||
JdkListDownloader.downloadModelForJdkInstaller(indicator)
|
||||
JdkListDownloader.getInstance().downloadModelForJdkInstaller(indicator)
|
||||
} catch(e: ProcessCanceledException) {
|
||||
throw e
|
||||
} catch (t: Throwable) {
|
||||
|
||||
@@ -40,7 +40,7 @@ internal class JdkDownloader : SdkDownload, JdkDownloaderBase {
|
||||
val items = runTaskAndReportError(project,
|
||||
"Downloading the list of available JDKs...",
|
||||
"Failed to download the list of installable JDKs") {
|
||||
JdkListDownloader.downloadForUI(it)
|
||||
JdkListDownloader.getInstance().downloadForUI(it)
|
||||
} ?: return
|
||||
|
||||
if (project?.isDisposed == true) return
|
||||
|
||||
@@ -5,8 +5,13 @@ import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.intellij.openapi.application.impl.ApplicationInfoImpl
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.util.BuildNumber
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
@@ -22,6 +27,10 @@ import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.lang.RuntimeException
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.read
|
||||
import kotlin.concurrent.write
|
||||
|
||||
/** describes vendor + product part of the UI **/
|
||||
data class JdkProduct(
|
||||
@@ -292,7 +301,12 @@ object JdkListParser {
|
||||
}
|
||||
}
|
||||
|
||||
object JdkListDownloader {
|
||||
class JdkListDownloader {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance() = service<JdkListDownloader>()
|
||||
}
|
||||
|
||||
private val feedUrl: String
|
||||
get() {
|
||||
val registry = runCatching { Registry.get("jdk.downloader.url").asString() }.getOrNull()
|
||||
@@ -313,13 +327,28 @@ object JdkListDownloader {
|
||||
* Entries are sorter from the best suggested to the worst suggested items.
|
||||
*/
|
||||
fun downloadModelForJdkInstaller(progress: ProgressIndicator?): List<JdkItem> {
|
||||
return downloadForUI(progress)
|
||||
return downloadJdksListWithCache(feedUrl, progress)
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all entries suitable for UI download, there can be some unlisted entries that are ignored here by intent
|
||||
*/
|
||||
fun downloadForUI(progress: ProgressIndicator?, feedUrl: String = JdkListDownloader.feedUrl): List<JdkItem> {
|
||||
fun downloadForUI(progress: ProgressIndicator?, feedUrl: String? = null) : List<JdkItem> {
|
||||
return downloadJdksListWithCache(feedUrl, progress)
|
||||
}
|
||||
|
||||
private val jdksListCache = CachedValueWithTTL<List<JdkItem>>(15 to TimeUnit.MINUTES)
|
||||
|
||||
private fun downloadJdksListWithCache(feedUrl: String?, progress: ProgressIndicator?): List<JdkItem> {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val feedUrl = feedUrl ?: this.feedUrl
|
||||
|
||||
return jdksListCache.getOrCompute(feedUrl, listOf()) {
|
||||
downloadJdksListNoCache(feedUrl, progress)
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadJdksListNoCache(feedUrl: String, progress: ProgressIndicator?): List<JdkItem> {
|
||||
// download XZ packed version of the data (several KBs packed, several dozen KBs unpacked) and process it in-memory
|
||||
val rawDataXZ = try {
|
||||
downloadJdkList(feedUrl, progress)
|
||||
@@ -353,10 +382,58 @@ object JdkListDownloader {
|
||||
SystemInfo.isLinux -> "linux"
|
||||
else -> error("Unsupported OS")
|
||||
}
|
||||
return JdkListParser.parseJdkList(json, JdkPredicate(ApplicationInfoImpl.getShadowInstance().build, expectedOS))
|
||||
return ImmutableList.copyOf(JdkListParser.parseJdkList(json, JdkPredicate(ApplicationInfoImpl.getShadowInstance().build, expectedOS)))
|
||||
}
|
||||
catch (t: Throwable) {
|
||||
throw RuntimeException("Failed to process the downloaded list of available JDKs from $feedUrl. ${t.message}", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CachedValueWithTTL<T : Any>(
|
||||
private val ttl: Pair<Int, TimeUnit>
|
||||
) {
|
||||
private val lock = ReentrantReadWriteLock()
|
||||
private var cachedUrl: String? = null
|
||||
private var value: T? = null
|
||||
private var computed = 0L
|
||||
|
||||
private fun now() = System.currentTimeMillis()
|
||||
private operator fun Long.plus(ttl: Pair<Int, TimeUnit>): Long = this + ttl.second.toMillis(ttl.first.toLong())
|
||||
|
||||
private inline fun readValueOrNull(expectedUrl: String, onValue: (T) -> Unit) {
|
||||
if (cachedUrl != expectedUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
val value = this.value
|
||||
if (value != null && computed + ttl > now()) {
|
||||
onValue(value)
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrCompute(url: String, defaultOrFailure: T, compute: () -> T): T {
|
||||
lock.read {
|
||||
readValueOrNull(url) { return it }
|
||||
}
|
||||
|
||||
lock.write {
|
||||
//double checked
|
||||
readValueOrNull(url) { return it }
|
||||
|
||||
val value = runCatching(compute).getOrElse {
|
||||
if (it is ProcessCanceledException) {
|
||||
throw it
|
||||
}
|
||||
Logger.getInstance(javaClass).warn("Failed to compute value. ${it.message}", it)
|
||||
defaultOrFailure
|
||||
}
|
||||
|
||||
ProgressManager.checkCanceled()
|
||||
this.value = value
|
||||
computed = now()
|
||||
cachedUrl = url
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,20 +10,41 @@ class JdkDownloaderIntegrationTest : BasePlatformTestCase() {
|
||||
@Test
|
||||
fun `test default model can be downloaded and parsed`() {
|
||||
lateinit var lastError: Throwable
|
||||
run {
|
||||
repeat(5) {
|
||||
val result = runCatching {
|
||||
val data = JdkListDownloader.downloadForUI(null)
|
||||
Assert.assertTrue(data.isNotEmpty())
|
||||
}
|
||||
if (result.isSuccess) return
|
||||
lastError = result.exceptionOrNull()!!
|
||||
|
||||
if (lastError.message?.startsWith("Failed to download list of available JDKs") == true) {
|
||||
Thread.sleep(5000)
|
||||
}
|
||||
else throw lastError
|
||||
repeat(5) {
|
||||
val result = runCatching {
|
||||
val data = JdkListDownloader.getInstance().downloadForUI(null)
|
||||
Assert.assertTrue(data.isNotEmpty())
|
||||
}
|
||||
if (result.isSuccess) return
|
||||
lastError = result.exceptionOrNull()!!
|
||||
|
||||
if (lastError.message?.startsWith("Failed to download list of available JDKs") == true) {
|
||||
Thread.sleep(5000)
|
||||
}
|
||||
else throw lastError
|
||||
}
|
||||
throw RuntimeException("Failed to download JDK list within several tries", lastError)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test default model is cached`() {
|
||||
lateinit var lastError: Throwable
|
||||
repeat(5) {
|
||||
|
||||
val downloader = JdkListDownloader.getInstance()
|
||||
val packs = List(10) { runCatching { downloader.downloadForUI(null) }.getOrNull() }.filterNotNull()
|
||||
|
||||
if (packs.size < 3) {
|
||||
return@repeat
|
||||
}
|
||||
|
||||
//must return cached value
|
||||
packs.forEach { p1 ->
|
||||
packs.forEach { p2 ->
|
||||
Assert.assertSame(p1, p2)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
throw RuntimeException("Failed to download JDK list within several tries", lastError)
|
||||
}
|
||||
|
||||
@@ -764,6 +764,7 @@
|
||||
<registryKey key="java.detector.include.embedded" defaultValue="false" description="Include embedded JetBrains Runtime" />
|
||||
|
||||
<sdkDownload implementation="com.intellij.openapi.projectRoots.impl.jdkDownloader.JdkDownloader" />
|
||||
<applicationService serviceImplementation="com.intellij.openapi.projectRoots.impl.jdkDownloader.JdkListDownloader"/>
|
||||
<unknownSdkResolver implementation="com.intellij.openapi.projectRoots.impl.jdkDownloader.JdkAuto"/>
|
||||
<registryKey key="jdk.auto.setup" defaultValue="true" description="Attempt to use local or downloadable SDK to configure project"/>
|
||||
<registryKey key="jdk.downloader" defaultValue="true" description="Suggest JDK downloads"/>
|
||||
|
||||
Reference in New Issue
Block a user