[Jar Repository] [IDEA-328006] LibrarySynchronizationQueue now works properly with disposed libs

The library may be disposed from the moment it got into the queue till the moment it's processed

GitOrigin-RevId: fe8a580766a267db11fb095ca71b589d17a34bbc
This commit is contained in:
Alex Plate
2024-02-13 11:46:19 +02:00
committed by intellij-monorepo-bot
parent d99d8bd0ea
commit c8930055a8
3 changed files with 67 additions and 29 deletions

View File

@@ -21,5 +21,6 @@
<orderEntry type="module" module-name="intellij.platform.workspace.jps" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.backend.workspace" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5" scope="TEST" />
<orderEntry type="library" name="kotlin-test" level="project" />
</component>
</module>

View File

@@ -17,7 +17,7 @@ import org.jetbrains.idea.maven.utils.library.RepositoryUtils
import kotlin.coroutines.coroutineContext
@Service(Service.Level.PROJECT)
internal class LibrarySynchronizationQueue(private val project: Project, private val scope: CoroutineScope) {
class LibrarySynchronizationQueue(private val project: Project, private val scope: CoroutineScope) {
private val synchronizationRequests = Channel<Request>(capacity = Channel.UNLIMITED).apply {
scope.coroutineContext.job.invokeOnCompletion {
close()
@@ -84,7 +84,11 @@ internal class LibrarySynchronizationQueue(private val project: Project, private
if (!coroutineContext.isActive) {
return
}
if (readAction { library.needToReload() }) {
val needToReload = readAction {
if (library.isDisposed) return@readAction false
library.needToReload()
}
if (needToReload) {
RepositoryUtils.reloadDependencies(project, library)
}
}

View File

@@ -2,53 +2,64 @@
package com.intellij.jarRepository
import com.intellij.java.library.getMavenCoordinates
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.runWriteActionAndWait
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.impl.libraries.LibraryEx
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.platform.backend.workspace.WorkspaceModel
import com.intellij.platform.backend.workspace.impl.internal
import com.intellij.testFramework.ApplicationRule
import com.intellij.testFramework.DisposableRule
import com.intellij.testFramework.RuleChain
import com.intellij.testFramework.rules.ProjectModelRule
import com.intellij.testFramework.rules.TempDirectory
import com.intellij.testFramework.junit5.TestApplication
import com.intellij.testFramework.junit5.TestDisposable
import com.intellij.testFramework.rules.ProjectModelExtension
import com.intellij.testFramework.rules.TempDirectoryExtension
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.jetbrains.idea.maven.utils.library.RepositoryLibraryProperties
import org.jetbrains.idea.maven.utils.library.RepositoryUtils
import org.junit.Assert.*
import org.junit.Before
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.extension.RegisterExtension
import java.util.concurrent.TimeUnit
import kotlin.io.path.exists
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
@TestApplication
class RepositoryLibraryTest {
companion object {
@JvmStatic
@get:ClassRule
val applicationRule = ApplicationRule()
}
@TestDisposable
lateinit var disposable: Disposable
private val disposableRule = DisposableRule()
private val projectRule = ProjectModelRule()
private val mavenRepo = TempDirectory()
private val localMavenCache = TempDirectory()
@get:Rule
val rulesChain = RuleChain(localMavenCache, mavenRepo, projectRule, disposableRule)
@JvmField
@RegisterExtension
val projectRule: ProjectModelExtension = ProjectModelExtension()
@JvmField
@RegisterExtension
val mavenRepo = TempDirectoryExtension()
@JvmField
@RegisterExtension
val localMavenCache = TempDirectoryExtension()
private val ARTIFACT_NAME = "myArtifact"
private val GROUP_NAME = "myGroup"
private val LIBRARY_NAME = "NewLibrary"
@Before
@BeforeEach
fun setUp() {
JarRepositoryManager.setLocalRepositoryPath(localMavenCache.root)
MavenRepoFixture(mavenRepo.root).apply {
addLibraryArtifact(group = GROUP_NAME, artifact = ARTIFACT_NAME, version = "1.0")
addLibraryArtifact(group = GROUP_NAME, artifact = ARTIFACT_NAME, version = "1.0-SNAPSHOT")
generateMavenMetadata(GROUP_NAME, ARTIFACT_NAME)
}
@@ -57,17 +68,21 @@ class RepositoryLibraryTest {
)
}
private fun createLibrary(block: (LibraryEx.ModifiableModelEx) -> Unit = {}): Library {
private fun createLibrary(version: String = "1.0", libraryName: String = LIBRARY_NAME, block: (LibraryEx.ModifiableModelEx) -> Unit = {}): Library {
val libraryTable = LibraryTablesRegistrar.getInstance().getLibraryTable(projectRule.project)
val library = runWriteActionAndWait {
projectRule.addProjectLevelLibrary(LIBRARY_NAME) {
projectRule.addProjectLevelLibrary(libraryName) {
it.kind = RepositoryLibraryType.REPOSITORY_LIBRARY_KIND
it.properties = RepositoryLibraryProperties(GROUP_NAME, ARTIFACT_NAME, "1.0", false, emptyList())
it.properties = RepositoryLibraryProperties(GROUP_NAME, ARTIFACT_NAME, version, false, emptyList())
block(it)
}
}
disposableRule.register {
runWriteActionAndWait { libraryTable.removeLibrary(library) }
Disposer.register(disposable) {
runWriteActionAndWait {
if (!library.isDisposed) {
libraryTable.removeLibrary(library)
}
}
}
return library
}
@@ -105,6 +120,24 @@ class RepositoryLibraryTest {
assertTrue(workspaceVersion() > modelVersionBefore)
}
@Test
fun testSynchronizationQueueDoesWorkWithDisposedLibs() = runBlocking {
localMavenCache.rootPath.resolve(GROUP_NAME).resolve(ARTIFACT_NAME).resolve("1.0").resolve("$ARTIFACT_NAME-1.0.jar")
repeat(1) {
val library = createLibrary(version = "1.0-SNAPSHOT", libraryName = "Lib$it")
assertEquals(0, getLibraryRoots(library).size)
assertDoesNotThrow {
val deferred = launch {
LibrarySynchronizationQueue.getInstance(projectRule.project).requestSynchronization(library as LibraryEx)
LibrarySynchronizationQueue.getInstance(projectRule.project).flush()
}
Disposer.dispose(library)
deferred.join()
}
}
}
@Test
fun libraryNoUpdateProjectModel() {
val jar = localMavenCache.rootPath.resolve(GROUP_NAME).resolve(ARTIFACT_NAME).resolve("1.0").resolve("$ARTIFACT_NAME-1.0.jar")