diff --git a/java/java-tests/testSrc/com/intellij/util/indexing/IndexPackTest.java b/java/java-tests/testSrc/com/intellij/util/indexing/IndexPackTest.java index 1f30d96574ef..74789c003a3d 100644 --- a/java/java-tests/testSrc/com/intellij/util/indexing/IndexPackTest.java +++ b/java/java-tests/testSrc/com/intellij/util/indexing/IndexPackTest.java @@ -1,6 +1,7 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.util.indexing; +import com.intellij.openapi.command.impl.DummyProject; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; @@ -195,9 +196,12 @@ public class IndexPackTest extends TestCase { try (UncompressedZipFileSystem fs = new UncompressedZipFileSystem(pack.toPath(), new UncompressedZipFileSystemProvider())) { PlatformTestUtil.startPerformanceTest("read", 6000, () -> { - ReadOnlyIndexPack indexPack = new ReadOnlyIndexPack<>(generateIndexNames(packSize) - .map(name -> createStringLengthIndex(fs.getPath(name, "index"), true)) - .collect(Collectors.toList())); + ReadOnlyIndexPack> indexPack + = new ReadOnlyIndexPack<>(path -> createStringLengthIndex(path, false)); + + generateIndexNames(packSize).map(name -> fs.getPath(name, "index")).forEach(path -> { + indexPack.attach(path, DummyProject.getInstance()); + }); LongAdder recordCount = new LongAdder(); for (int key : keys.toArray()) { diff --git a/platform/lang-impl/src/com/intellij/util/indexing/impl/ReadOnlyIndexPack.java b/platform/lang-impl/src/com/intellij/util/indexing/impl/ReadOnlyIndexPack.java new file mode 100644 index 000000000000..fb7b9ad6d72a --- /dev/null +++ b/platform/lang-impl/src/com/intellij/util/indexing/impl/ReadOnlyIndexPack.java @@ -0,0 +1,151 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.util.indexing.impl; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.util.SmartList; +import com.intellij.util.containers.BidirectionalMap; +import com.intellij.util.containers.MultiMap; +import com.intellij.util.indexing.InvertedIndex; +import com.intellij.util.indexing.StorageException; +import com.intellij.util.indexing.ValueContainer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; + +public class ReadOnlyIndexPack> implements InvertedIndex { + private static final Logger LOG = Logger.getInstance(ReadOnlyIndexPack.class); + + private final Function myIndexGenerator; + private final BidirectionalMap myLoadedIndexes = new BidirectionalMap<>(); + private final MultiMap myProject2Index = new MultiMap<>(); + private final MultiMap myIndex2Project = new MultiMap<>(); + + private final ReentrantReadWriteLock myPackStructureAccessLock = new ReentrantReadWriteLock(); + + public ReadOnlyIndexPack(@NotNull Function generator) { + myIndexGenerator = generator; + } + + public void attach(@NotNull Path indexPath, @NotNull Project project) { + ReentrantReadWriteLock.WriteLock writeLock = myPackStructureAccessLock.writeLock(); + writeLock.lock(); + try { + Index index = myLoadedIndexes.computeIfAbsent(indexPath, myIndexGenerator); + LOG.assertTrue(!myIndex2Project.get(index).contains(project)); + LOG.assertTrue(!myProject2Index.get(project).contains(index)); + } finally { + writeLock.unlock(); + } + } + + public void detachIndex(@NotNull Path indexPath, @NotNull Project project) { + ReentrantReadWriteLock.WriteLock writeLock = myPackStructureAccessLock.writeLock(); + writeLock.lock(); + try { + Index index = myLoadedIndexes.get(indexPath); + LOG.assertTrue(index != null); + LOG.assertTrue(myIndex2Project.remove(index, project)); + tryCleanIndexData(index); + } finally { + writeLock.unlock(); + } + } + + public void detachProject(@NotNull Project project) { + ReentrantReadWriteLock.WriteLock writeLock = myPackStructureAccessLock.writeLock(); + writeLock.lock(); + try { + Collection removedIndexes = myProject2Index.remove(project); + if (removedIndexes != null) { + for (Index index : removedIndexes) { + tryCleanIndexData(index); + } + } + } finally { + writeLock.unlock(); + } + } + + private void tryCleanIndexData(@NotNull Index index) { + assert myPackStructureAccessLock.writeLock().isHeldByCurrentThread(); + if (myIndex2Project.get(index).isEmpty()) { + myLoadedIndexes.removeValue(index); + try { + index.dispose(); + } catch (Exception e) { + LOG.error(e); + } + } + } + + @NotNull + @Override + public ValueContainer getData(@NotNull K k) throws StorageException { + List> result = new SmartList<>(); + ReentrantReadWriteLock.ReadLock readLock = myPackStructureAccessLock.readLock(); + readLock.lock(); + try { + for (InvertedIndex index : myLoadedIndexes.values()) { + ValueContainer currentData = index.getData(k); + if (currentData.size() != 0) { + result.add(currentData); + } + } + return result.isEmpty() ? new ValueContainerImpl<>() : new MergedValueContainer<>(result); + } finally { + readLock.unlock(); + } + } + + @NotNull + @Override + public Computable update(int inputId, @Nullable Input content) { + throw new UnsupportedOperationException("index pack is read-only"); + } + + @Override + public void flush() throws StorageException { + ReentrantReadWriteLock.ReadLock readLock = myPackStructureAccessLock.readLock(); + readLock.lock(); + try { + List exceptions = new SmartList<>(); + for (InvertedIndex index : myLoadedIndexes.values()) { + try { + index.flush(); + } catch (StorageException e) { + exceptions.add(e); + } + } + if (!exceptions.isEmpty()) { + throw exceptions.get(0); + } + } finally { + readLock.unlock(); + } + } + + @Override + public void clear() { + throw new UnsupportedOperationException("index pack is read-only"); + } + + @Override + public void dispose() { + ReentrantReadWriteLock.WriteLock writeLock = myPackStructureAccessLock.writeLock(); + writeLock.lock(); + try { + for (Project project : myProject2Index.keySet()) { + detachProject(project); + } + } finally { + writeLock.unlock(); + } + } +} diff --git a/platform/util/src/com/intellij/util/indexing/impl/ReadOnlyIndexPack.java b/platform/util/src/com/intellij/util/indexing/impl/ReadOnlyIndexPack.java deleted file mode 100644 index 7210ef6247af..000000000000 --- a/platform/util/src/com/intellij/util/indexing/impl/ReadOnlyIndexPack.java +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.util.indexing.impl; - -import com.intellij.openapi.util.Computable; -import com.intellij.util.SmartList; -import com.intellij.util.indexing.InvertedIndex; -import com.intellij.util.indexing.StorageException; -import com.intellij.util.indexing.ValueContainer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -public class ReadOnlyIndexPack> implements InvertedIndex { - @NotNull - private final List myIndexes; - - public ReadOnlyIndexPack(@NotNull List indexes) {myIndexes = indexes;} - - @NotNull - @Override - public ValueContainer getData(@NotNull K k) throws StorageException { - List> result = new SmartList<>(); - for (InvertedIndex index : myIndexes) { - ValueContainer currentData = index.getData(k); - if (currentData.size() != 0) { - result.add(currentData); - } - } - return result.isEmpty() ? new ValueContainerImpl<>() : new MergedValueContainer<>(result); - } - - @NotNull - @Override - public Computable update(int inputId, @Nullable Input content) { - throw new UnsupportedOperationException("index pack is read-only"); - } - - @Override - public void flush() throws StorageException { - List exceptions = new SmartList<>(); - for (InvertedIndex index : myIndexes) { - try { - index.flush(); - } - catch (StorageException e) { - exceptions.add(e); - } - } - if (!exceptions.isEmpty()) { - throw exceptions.get(0); - } - } - - @Override - public void clear() throws StorageException { - throw new UnsupportedOperationException("index pack is read-only"); - } - - @Override - public void dispose() { - List exceptions = new SmartList<>(); - for (InvertedIndex index : myIndexes) { - try { - index.dispose(); - } - catch (RuntimeException e) { - exceptions.add(e); - } - } - if (!exceptions.isEmpty()) { - throw exceptions.get(0); - } - } -}