mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 14:23:28 +07:00
MVStore backed storage option for the dependency graph
GitOrigin-RevId: cb46daccff31af708046687e31c1a4203ced51be
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a7a6116df7
commit
095f69b36c
@@ -1940,6 +1940,7 @@ org.jetbrains.jps.dependency.Delta
|
||||
- a:isSourceOnly():Z
|
||||
org.jetbrains.jps.dependency.DependencyGraph
|
||||
- java.io.Closeable
|
||||
- java.io.Flushable
|
||||
- org.jetbrains.jps.dependency.Graph
|
||||
- a:createDelta(java.lang.Iterable,java.lang.Iterable,Z):org.jetbrains.jps.dependency.Delta
|
||||
- differentiate(org.jetbrains.jps.dependency.Delta,org.jetbrains.jps.dependency.DifferentiateParameters):org.jetbrains.jps.dependency.DifferentiateResult
|
||||
@@ -1981,8 +1982,9 @@ org.jetbrains.jps.dependency.ExternalizableGraphElement
|
||||
org.jetbrains.jps.dependency.Externalizer
|
||||
- org.jetbrains.jps.dependency.DataReader
|
||||
- org.jetbrains.jps.dependency.DataWriter
|
||||
- s:forAnyGraphElement():org.jetbrains.jps.dependency.Externalizer
|
||||
- s:forGraphElement(org.jetbrains.jps.dependency.DataReader):org.jetbrains.jps.dependency.Externalizer
|
||||
- a:createStorage(I):java.lang.Object[]
|
||||
- s:forAnyGraphElement(java.util.function.Function):org.jetbrains.jps.dependency.Externalizer
|
||||
- s:forGraphElement(org.jetbrains.jps.dependency.DataReader,java.util.function.Function):org.jetbrains.jps.dependency.Externalizer
|
||||
org.jetbrains.jps.dependency.FactoredExternalizableGraphElement
|
||||
- org.jetbrains.jps.dependency.ExternalizableGraphElement
|
||||
- a:getFactorData():org.jetbrains.jps.dependency.ExternalizableGraphElement
|
||||
|
||||
@@ -4,12 +4,13 @@ package org.jetbrains.jps.dependency;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.Flushable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A representation of the main dependency storage
|
||||
*/
|
||||
public interface DependencyGraph extends Graph, Closeable {
|
||||
public interface DependencyGraph extends Graph, Closeable, Flushable {
|
||||
|
||||
Delta createDelta(Iterable<NodeSource> sourcesToProcess, Iterable<NodeSource> deletedSources, boolean isSourceOnly);
|
||||
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.jps.dependency;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface Externalizer<T> extends DataReader<T>, DataWriter<T> {
|
||||
T[] createStorage(int size);
|
||||
|
||||
static <T extends ExternalizableGraphElement> Externalizer<T> forGraphElement(DataReader<? extends T> reader) {
|
||||
static <T extends ExternalizableGraphElement> Externalizer<T> forGraphElement(DataReader<? extends T> reader, Function<Integer, T[]> arrayFactory) {
|
||||
return new Externalizer<>() {
|
||||
@Override
|
||||
public T[] createStorage(int size) {
|
||||
return arrayFactory.apply(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T load(GraphDataInput in) throws IOException {
|
||||
return reader.load(in);
|
||||
@@ -19,8 +26,13 @@ public interface Externalizer<T> extends DataReader<T>, DataWriter<T> {
|
||||
};
|
||||
}
|
||||
|
||||
static <T extends ExternalizableGraphElement> Externalizer<T> forAnyGraphElement() {
|
||||
static <T extends ExternalizableGraphElement> Externalizer<T> forAnyGraphElement(Function<Integer, T[]> arrayFactory) {
|
||||
return new Externalizer<>() {
|
||||
@Override
|
||||
public T[] createStorage(int size) {
|
||||
return arrayFactory.apply(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T load(GraphDataInput in) throws IOException {
|
||||
return in.readGraphElement();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.jps.dependency.impl;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -18,7 +18,7 @@ public abstract class BackDependencyIndexImpl implements BackDependencyIndex {
|
||||
protected BackDependencyIndexImpl(@NotNull String name, @NotNull MapletFactory cFactory) {
|
||||
myName = name;
|
||||
// important: if multiple implementations of ReferenceID are available, change to createMultitypeExternalizer
|
||||
Externalizer<ReferenceID> ext = Externalizer.forGraphElement(JvmNodeReferenceID::new);
|
||||
Externalizer<ReferenceID> ext = Externalizer.forGraphElement(JvmNodeReferenceID::new, JvmNodeReferenceID[]::new);
|
||||
myMap = cFactory.createSetMultiMaplet(name, ext, ext);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.jps.dependency.impl;
|
||||
|
||||
import org.h2.mvstore.DataUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ByteBufferDataInput implements DataInput {
|
||||
private final ByteBuffer myBuf;
|
||||
|
||||
public ByteBufferDataInput(ByteBuffer buf) {
|
||||
myBuf = buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFully(@NotNull byte[] b) throws IOException {
|
||||
try {
|
||||
myBuf.get(b, 0, b.length);
|
||||
}
|
||||
catch (BufferUnderflowException e) {
|
||||
throw new EOFException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFully(@NotNull byte[] b, int off, int len) throws IOException {
|
||||
try {
|
||||
myBuf.get(b, off, len);
|
||||
}
|
||||
catch (BufferUnderflowException e) {
|
||||
throw new EOFException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int skipBytes(int n) throws IOException {
|
||||
int skip = Math.min(n, myBuf.remaining());
|
||||
myBuf.position(myBuf.position() + skip);
|
||||
return skip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readBoolean() throws IOException {
|
||||
return myBuf.get() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte readByte() throws IOException {
|
||||
return myBuf.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedByte() throws IOException {
|
||||
return ((int)readByte()) & 0xFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readShort() throws IOException {
|
||||
return myBuf.getShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedShort() throws IOException {
|
||||
return ((int)readShort()) & 0xFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char readChar() throws IOException {
|
||||
return myBuf.getChar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() throws IOException {
|
||||
return myBuf.getInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLong() throws IOException {
|
||||
return myBuf.getLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float readFloat() throws IOException {
|
||||
return myBuf.getFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double readDouble() throws IOException {
|
||||
return myBuf.getDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readLine() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String readUTF() throws IOException {
|
||||
return DataUtils.readString(myBuf);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,28 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.jps.dependency.impl;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.intellij.openapi.util.LowMemoryWatcher;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.util.SystemProperties;
|
||||
import com.intellij.util.io.DataExternalizer;
|
||||
import com.intellij.util.io.KeyDescriptor;
|
||||
import com.intellij.util.io.PersistentStringEnumerator;
|
||||
import com.intellij.util.io.StorageLockContext;
|
||||
import it.unimi.dsi.fastutil.Hash;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jps.api.GlobalOptions;
|
||||
import org.jetbrains.jps.builders.storage.BuildDataCorruptedException;
|
||||
import org.jetbrains.jps.dependency.*;
|
||||
import org.jetbrains.jps.javac.Iterators;
|
||||
import org.jetbrains.jps.dependency.Externalizer;
|
||||
import org.jetbrains.jps.dependency.Maplet;
|
||||
import org.jetbrains.jps.dependency.MapletFactory;
|
||||
import org.jetbrains.jps.dependency.MultiMaplet;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
public final class Containers {
|
||||
|
||||
public static MapletFactory createPersistentContainerFactory(String rootDirPath) throws IOException {
|
||||
//return new PersistentMVStoreMapletFactory(Path.of(rootDirPath).resolve("dep-graph.mv").toString(), IncProjectBuilder.MAX_BUILDER_THREADS);
|
||||
return new PersistentMapletFactory(rootDirPath);
|
||||
}
|
||||
|
||||
@@ -70,139 +64,4 @@ public final class Containers {
|
||||
};
|
||||
}
|
||||
|
||||
private static final class PersistentMapletFactory implements MapletFactory, Closeable {
|
||||
private static final int BASE_CACHE_SIZE = 512 * (SystemProperties.getBooleanProperty(GlobalOptions.COMPILE_PARALLEL_OPTION, false)? 2 : 1);
|
||||
private final String myRootDirPath;
|
||||
private final PersistentStringEnumerator myStringTable;
|
||||
private final List<BaseMaplet<?>> myMaps = new ArrayList<>();
|
||||
private final Enumerator myEnumerator;
|
||||
private final Function<Object, Object> myDataInterner;
|
||||
private final LoadingCache<Object, Object> myInternerCache;
|
||||
private final LowMemoryWatcher myMemWatcher;
|
||||
private final int myCacheSize;
|
||||
|
||||
PersistentMapletFactory(String rootDirPath) throws IOException {
|
||||
myRootDirPath = rootDirPath;
|
||||
// Important: The enumerator will be called from PHM data externalizers. A PHM acquires the page_cache lock before externalizing => this enumerator should use a separate StorageLockContext to avoid deadlocks
|
||||
myStringTable = new PersistentStringEnumerator(getMapFile("string-table"), 1024 * 4, true, new StorageLockContext());
|
||||
myEnumerator = new Enumerator() {
|
||||
@Override
|
||||
public String toString(int num) throws IOException {
|
||||
return myStringTable.valueOf(num);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toNumber(String str) throws IOException {
|
||||
return myStringTable.enumerate(str);
|
||||
}
|
||||
};
|
||||
final int maxGb = (int)(Runtime.getRuntime().maxMemory() / 1_073_741_824L);
|
||||
myCacheSize = BASE_CACHE_SIZE * Math.min(Math.max(1, maxGb), 5); // increase by BASE_CACHE_SIZE for every additional Gb
|
||||
|
||||
myInternerCache = Caffeine.newBuilder().maximumSize(myCacheSize).build(key -> key);
|
||||
myDataInterner = elem -> elem instanceof Usage? myInternerCache.get(elem) : elem;
|
||||
myMemWatcher = LowMemoryWatcher.register(() -> {
|
||||
myInternerCache.invalidateAll();
|
||||
myStringTable.force();
|
||||
for (BaseMaplet<?> map : myMaps) {
|
||||
try {
|
||||
map.flush();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V> MultiMaplet<K, V> createSetMultiMaplet(String storageName, Externalizer<K> keyExternalizer, Externalizer<V> valueExternalizer) {
|
||||
MultiMaplet<K, V> container = new CachingMultiMaplet<>(
|
||||
new PersistentMultiMaplet<>(getMapFile(storageName), new GraphKeyDescriptor<>(keyExternalizer, myEnumerator), new GraphDataExternalizer<>(valueExternalizer, myEnumerator, myDataInterner), () -> (Set<V>)new HashSet<V>()),
|
||||
myCacheSize
|
||||
);
|
||||
myMaps.add(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V> Maplet<K, V> createMaplet(String storageName, Externalizer<K> keyExternalizer, Externalizer<V> valueExternalizer) {
|
||||
Maplet<K, V> container = new CachingMaplet<>(
|
||||
new PersistentMaplet<>(getMapFile(storageName), new GraphKeyDescriptor<>(keyExternalizer, myEnumerator), new GraphDataExternalizer<>(valueExternalizer, myEnumerator, myDataInterner)),
|
||||
myCacheSize
|
||||
);
|
||||
myMaps.add(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
myMemWatcher.stop();
|
||||
Throwable ex = null;
|
||||
for (Closeable container : Iterators.flat(myMaps, Iterators.asIterable(myStringTable))) {
|
||||
try {
|
||||
container.close();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
if (ex == null) {
|
||||
ex = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
myMaps.clear();
|
||||
myInternerCache.invalidateAll();
|
||||
if (ex instanceof IOException) {
|
||||
throw new BuildDataCorruptedException((IOException)ex);
|
||||
}
|
||||
else if (ex != null) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Path getMapFile(final String name) {
|
||||
final File file = new File(myRootDirPath, name);
|
||||
FileUtil.createIfDoesntExist(file);
|
||||
return file.toPath();
|
||||
}
|
||||
}
|
||||
|
||||
private static class GraphDataExternalizer<T> implements DataExternalizer<T> {
|
||||
private final Externalizer<T> myExternalizer;
|
||||
private final @Nullable Enumerator myEnumerator;
|
||||
private final @Nullable Function<Object, Object> myObjectInterner;
|
||||
|
||||
GraphDataExternalizer(Externalizer<T> externalizer, @Nullable Enumerator enumerator, @Nullable Function<Object, Object> objectInterner) {
|
||||
myExternalizer = externalizer;
|
||||
myEnumerator = enumerator;
|
||||
myObjectInterner = objectInterner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, T value) throws IOException {
|
||||
myExternalizer.save(GraphDataOutputImpl.wrap(out, myEnumerator), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T read(@NotNull DataInput in) throws IOException {
|
||||
return myExternalizer.load(GraphDataInputImpl.wrap(in, myEnumerator, myObjectInterner));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class GraphKeyDescriptor<T> extends GraphDataExternalizer<T> implements KeyDescriptor<T> {
|
||||
|
||||
GraphKeyDescriptor(Externalizer<T> externalizer, @Nullable Enumerator enumerator) {
|
||||
super(externalizer, enumerator, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqual(T val1, T val2) {
|
||||
return val1.equals(val2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHashCode(T value) {
|
||||
return value.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.jetbrains.jps.dependency.*;
|
||||
import org.jetbrains.jps.dependency.java.JvmNodeReferenceID;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.Flushable;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -27,9 +28,9 @@ public abstract class GraphImpl implements Graph {
|
||||
addIndex(myDependencyIndex = new NodeDependenciesIndex(cFactory));
|
||||
|
||||
// important: if multiple implementations of NodeSource are available, change to generic graph element externalizer
|
||||
Externalizer<NodeSource> srcExternalizer = Externalizer.forGraphElement(PathSource::new);
|
||||
myNodeToSourcesMap = cFactory.createSetMultiMaplet("node-sources-map", Externalizer.forGraphElement(JvmNodeReferenceID::new), srcExternalizer);
|
||||
mySourceToNodesMap = cFactory.createSetMultiMaplet("source-nodes-map", srcExternalizer, Externalizer.forAnyGraphElement());
|
||||
Externalizer<NodeSource> srcExternalizer = Externalizer.forGraphElement(PathSource::new, NodeSource[]::new);
|
||||
myNodeToSourcesMap = cFactory.createSetMultiMaplet("node-sources-map", Externalizer.forGraphElement(JvmNodeReferenceID::new, JvmNodeReferenceID[]::new), srcExternalizer);
|
||||
mySourceToNodesMap = cFactory.createSetMultiMaplet("source-nodes-map", srcExternalizer, Externalizer.forAnyGraphElement(Node<?, ?>[]::new));
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
closeIgnoreErrors();
|
||||
@@ -94,5 +95,11 @@ public abstract class GraphImpl implements Graph {
|
||||
((Closeable)myContainerFactory).close();
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
if (myContainerFactory instanceof Flushable) {
|
||||
((Flushable)myContainerFactory).flush();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -64,6 +64,11 @@ public final class LoggingDependencyGraph extends LoggingGraph implements Depend
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
getDelegate().flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.jps.dependency.impl;
|
||||
|
||||
import org.h2.mvstore.MVMap;
|
||||
import org.h2.mvstore.MVStore;
|
||||
import org.h2.mvstore.type.DataType;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jps.dependency.Maplet;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class PersistentMVStoreMaplet<K, V> implements Maplet<K, V> {
|
||||
private final MVMap<K, V> myMap;
|
||||
|
||||
public PersistentMVStoreMaplet(MVStore store, String mapName, DataType<K> keyType, DataType<V> valueType) {
|
||||
myMap = store.openMap(mapName, new MVMap.Builder<K, V>().keyType(keyType).valueType(valueType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(K key) {
|
||||
return myMap.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable V get(K key) {
|
||||
return myMap.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K key, V value) {
|
||||
if (value == null) {
|
||||
myMap.remove(key);
|
||||
}
|
||||
else {
|
||||
myMap.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(K key) {
|
||||
myMap.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<K> getKeys() {
|
||||
return myMap.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.jps.dependency.impl;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.intellij.openapi.util.LowMemoryWatcher;
|
||||
import com.intellij.util.SystemProperties;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import org.h2.mvstore.MVMap;
|
||||
import org.h2.mvstore.MVStore;
|
||||
import org.h2.mvstore.WriteBuffer;
|
||||
import org.h2.mvstore.type.BasicDataType;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jps.api.GlobalOptions;
|
||||
import org.jetbrains.jps.builders.storage.BuildDataCorruptedException;
|
||||
import org.jetbrains.jps.dependency.*;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.Flushable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
// suitable for relatively small amounts of stored data
|
||||
final class PersistentMVStoreMapletFactory implements MapletFactory, Closeable, Flushable {
|
||||
private static final int BASE_CACHE_SIZE = 512 * (SystemProperties.getBooleanProperty(GlobalOptions.COMPILE_PARALLEL_OPTION, false)? 2 : 1);
|
||||
private static final int ALLOWED_STORE_COMPACTION_TIME_MS = -1; // -1 for full-compact, 0 to disable compaction
|
||||
private final MVSEnumerator myEnumerator;
|
||||
private final Function<Object, Object> myDataInterner;
|
||||
private final LoadingCache<Object, Object> myInternerCache;
|
||||
//private final LoadingCache<String, String> myStringsInternerCache;
|
||||
private final LowMemoryWatcher myMemWatcher;
|
||||
private final int myCacheSize;
|
||||
private final MVStore myStore;
|
||||
|
||||
PersistentMVStoreMapletFactory(String filePath, int maxBuilderThreads) {
|
||||
// todo: need transaction store for transactions?
|
||||
myStore = new MVStore.Builder()
|
||||
.fileName(filePath)
|
||||
.autoCommitDisabled() // all read-write operations are expected to be initiated via Graph APIs, otherwise deadlocks are possible because of incorrect lock acquisition sequence
|
||||
.autoCompactFillRate(70)
|
||||
.cacheSize(8)
|
||||
.compress()
|
||||
.cacheConcurrency(getConcurrencyLevel(maxBuilderThreads))
|
||||
.open();
|
||||
myStore.setVersionsToKeep(0);
|
||||
|
||||
// MVStore counter-based enumerator?
|
||||
myEnumerator = new MVSEnumerator(myStore);
|
||||
final int maxGb = (int) (Runtime.getRuntime().maxMemory() / 1_073_741_824L);
|
||||
myCacheSize = BASE_CACHE_SIZE * Math.min(Math.max(1, maxGb), 5); // increase by BASE_CACHE_SIZE for every additional Gb
|
||||
|
||||
myInternerCache = Caffeine.newBuilder().maximumSize(myCacheSize).build(key -> key);
|
||||
//myStringsInternerCache = Caffeine.newBuilder().maximumSize(myCacheSize).build(key -> key);
|
||||
myDataInterner = elem -> elem instanceof Usage? myInternerCache.get(elem) : /*elem instanceof String? myStringsInternerCache.get((String) elem) :*/ elem;
|
||||
myMemWatcher = LowMemoryWatcher.register(() -> {
|
||||
myInternerCache.invalidateAll();
|
||||
//myStringsInternerCache.invalidateAll();
|
||||
flush();
|
||||
});
|
||||
}
|
||||
|
||||
private static int getConcurrencyLevel(int builderThreads) {
|
||||
int result = 1, next = 1;
|
||||
while (next <= builderThreads) {
|
||||
result = next;
|
||||
next *= 2;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V> MultiMaplet<K, V> createSetMultiMaplet(String storageName, Externalizer<K> keyExternalizer, Externalizer<V> valueExternalizer) {
|
||||
PersistentMVStoreMultiMaplet<K, V, Set<V>> maplet = new PersistentMVStoreMultiMaplet<K, V, Set<V>>(
|
||||
myStore, storageName, new GraphDataType<>(keyExternalizer, myEnumerator, myDataInterner), new GraphDataType<>(valueExternalizer, myEnumerator, myDataInterner), HashSet::new, Set[]::new
|
||||
);
|
||||
return new CachingMultiMaplet<>(maplet, myCacheSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V> Maplet<K, V> createMaplet(String storageName, Externalizer<K> keyExternalizer, Externalizer<V> valueExternalizer) {
|
||||
PersistentMVStoreMaplet<K, V> maplet = new PersistentMVStoreMaplet<>(
|
||||
myStore, storageName, new GraphDataType<>(keyExternalizer, myEnumerator, myDataInterner), new GraphDataType<>(valueExternalizer, myEnumerator, myDataInterner)
|
||||
);
|
||||
return new CachingMaplet<>(maplet, myCacheSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
myMemWatcher.stop();
|
||||
Throwable ex = null;
|
||||
try {
|
||||
myStore.commit();// first commit all open maps, that might use enumerator for serialization
|
||||
myEnumerator.flush(); // save enumerator state
|
||||
myStore.close(ALLOWED_STORE_COMPACTION_TIME_MS); // completely close the store commiting the rest of unsaved data
|
||||
}
|
||||
catch (Throwable e) {
|
||||
ex = e;
|
||||
}
|
||||
myInternerCache.invalidateAll();
|
||||
if (ex instanceof IOException) {
|
||||
throw new BuildDataCorruptedException((IOException) ex);
|
||||
}
|
||||
else if (ex != null) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
// maintain consistent state between the content in maps and in the enumerator
|
||||
if (myStore.tryCommit() >= 0L) { // first save data from the maps => this may add additional entries to the enumerator.
|
||||
if (myEnumerator.flush()) { // then store enumerator data
|
||||
myStore.commit(); // if any data were stored, commit finally
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class GraphDataType<T> extends BasicDataType<T> {
|
||||
private final Externalizer<T> myExternalizer;
|
||||
private final @Nullable Enumerator myEnumerator;
|
||||
private final @Nullable Function<Object, Object> myObjectInterner;
|
||||
|
||||
GraphDataType(Externalizer<T> externalizer, @Nullable Enumerator enumerator, @Nullable Function<Object, Object> objectInterner) {
|
||||
myExternalizer = externalizer;
|
||||
myEnumerator = enumerator;
|
||||
myObjectInterner = objectInterner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(T a, T b) {
|
||||
return a.toString().compareTo(b.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMemoryEstimationAllowed() {
|
||||
return false; // todo?
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMemory(T obj) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(WriteBuffer buff, T value) {
|
||||
try {
|
||||
myExternalizer.save(GraphDataOutputImpl.wrap(new WriteBufferDataOutput(buff), myEnumerator), value);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T read(ByteBuffer buff) {
|
||||
try {
|
||||
return myExternalizer.load(GraphDataInputImpl.wrap(new ByteBufferDataInput(buff), myEnumerator, myObjectInterner));
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T[] createStorage(int size) {
|
||||
return myExternalizer.createStorage(size);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MVSEnumerator implements Enumerator {
|
||||
private final Object2IntMap<String> myToIntMap = new Object2IntOpenHashMap<>();
|
||||
private final Int2ObjectMap<String> myToStringMap = new Int2ObjectOpenHashMap<>();
|
||||
private final MVMap<String, Integer> myStoreMap;
|
||||
private Object2IntMap<String> myDelta = new Object2IntOpenHashMap<>();
|
||||
|
||||
MVSEnumerator(MVStore store) {
|
||||
myStoreMap = store.openMap("string-table");
|
||||
for (Map.Entry<String, Integer> entry : myStoreMap.entrySet()) {
|
||||
String str = entry.getKey();
|
||||
int num = entry.getValue();
|
||||
myToIntMap.put(str, num);
|
||||
myToStringMap.put(num, str);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String toString(int num) {
|
||||
return myToStringMap.get(num);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int toNumber(String str) {
|
||||
int currentSize = myToIntMap.size();
|
||||
int num = myToIntMap.getOrDefault(str, currentSize);
|
||||
if (num == currentSize) { // not in map yet
|
||||
myToIntMap.put(str, num);
|
||||
myToStringMap.put(num, str);
|
||||
myDelta.put(str, num);
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
public boolean flush() {
|
||||
Object2IntMap<String> delta;
|
||||
synchronized (this) {
|
||||
if (myDelta.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
delta = myDelta;
|
||||
myDelta = new Object2IntOpenHashMap<>();
|
||||
}
|
||||
myStoreMap.putAll(delta);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.jps.dependency.impl;
|
||||
|
||||
import org.h2.mvstore.MVMap;
|
||||
import org.h2.mvstore.MVStore;
|
||||
import org.h2.mvstore.WriteBuffer;
|
||||
import org.h2.mvstore.type.BasicDataType;
|
||||
import org.h2.mvstore.type.DataType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.jps.dependency.MultiMaplet;
|
||||
import org.jetbrains.jps.javac.Iterators;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class PersistentMVStoreMultiMaplet<K, V, C extends Collection<V>> implements MultiMaplet<K, V> {
|
||||
private final MVMap<K, C> myMap;
|
||||
private final C myEmptyCollection;
|
||||
private final Supplier<? extends C> myCollectionFactory;
|
||||
private final MVMap.DecisionMaker<C> myAppendDecisionMaker;
|
||||
private final MVMap.DecisionMaker<C> myRemoveDecisionMaker;
|
||||
|
||||
public PersistentMVStoreMultiMaplet(MVStore store, String mapName, DataType<K> keyType, DataType<V> valueType, Supplier<? extends C> collectionFactory, Function<Integer, C[]> collectionArrayFactory) {
|
||||
myCollectionFactory = collectionFactory;
|
||||
try {
|
||||
C col = collectionFactory.get();
|
||||
//noinspection unchecked
|
||||
myEmptyCollection = col instanceof List? (C)Collections.emptyList() : col instanceof Set? (C)Collections.emptySet() : col;
|
||||
if (col instanceof Set) {
|
||||
myAppendDecisionMaker = new AppendSetDecisionMaker();
|
||||
myRemoveDecisionMaker = new RemoveSetDecisionMaker();
|
||||
}
|
||||
else {
|
||||
myAppendDecisionMaker = new AppendDecisionMaker();
|
||||
myRemoveDecisionMaker = new RemoveDecisionMaker();
|
||||
}
|
||||
MVMap.Builder<K, C> mapBuilder = new MVMap.Builder<K, C>().keyType(keyType).valueType(new BasicDataType<>() {
|
||||
@Override
|
||||
public boolean isMemoryEstimationAllowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMemory(C obj) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(WriteBuffer buff, C col) {
|
||||
buff.putInt(col.size());
|
||||
for (V value : col) {
|
||||
valueType.write(buff, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public C read(ByteBuffer buff) {
|
||||
C acc = myCollectionFactory.get();
|
||||
int size = buff.getInt();
|
||||
while (size-- > 0) {
|
||||
acc.add(valueType.read(buff));
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public C[] createStorage(int size) {
|
||||
//noinspection unchecked
|
||||
return collectionArrayFactory.apply(size);
|
||||
}
|
||||
});
|
||||
myMap = store.openMap(mapName, mapBuilder);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(K key) {
|
||||
return myMap.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull C get(K key) {
|
||||
C col = myMap.get(key);
|
||||
return col != null? col : myEmptyCollection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K key, @NotNull Iterable<? extends V> values) {
|
||||
//noinspection unchecked
|
||||
C data = ensureCollection(values);
|
||||
if (data.isEmpty()) {
|
||||
myMap.remove(key);
|
||||
}
|
||||
else {
|
||||
myMap.put(key, data);
|
||||
}
|
||||
}
|
||||
|
||||
/** @noinspection unchecked*/
|
||||
private C ensureCollection(Iterable<? extends V> seq) {
|
||||
if (myEmptyCollection instanceof Set && seq instanceof Set) {
|
||||
return (C)seq;
|
||||
}
|
||||
if (myEmptyCollection instanceof List && seq instanceof List) {
|
||||
return (C)seq;
|
||||
}
|
||||
if (myEmptyCollection.getClass().isInstance(seq)) {
|
||||
return (C)seq;
|
||||
}
|
||||
return Iterators.collect(seq, myCollectionFactory.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(K key) {
|
||||
myMap.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendValue(K key, V value) {
|
||||
appendValues(key, Collections.singleton(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendValues(K key, @NotNull Iterable<? extends V> values) {
|
||||
if (!Iterators.isEmpty(values)) {
|
||||
myMap.operate(key, ensureCollection(values), myAppendDecisionMaker);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeValue(K key, V value) {
|
||||
removeValues(key, Collections.singleton(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeValues(K key, @NotNull Iterable<? extends V> values) {
|
||||
if (!Iterators.isEmpty(values)) {
|
||||
myMap.operate(key, ensureCollection(values), myRemoveDecisionMaker);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Iterable<K> getKeys() {
|
||||
return myMap.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no impl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
// no impl
|
||||
}
|
||||
|
||||
|
||||
private class AppendDecisionMaker extends MVMap.DecisionMaker<C> {
|
||||
@Override
|
||||
public MVMap.Decision decide(C existingValue, C providedValue) {
|
||||
if (providedValue == null || providedValue.isEmpty()) {
|
||||
return MVMap.Decision.ABORT;
|
||||
}
|
||||
return MVMap.Decision.PUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends C> T selectValue(T existingValue, T providedValue) {
|
||||
if (existingValue == null || existingValue.isEmpty()) {
|
||||
return providedValue;
|
||||
}
|
||||
//noinspection unchecked
|
||||
T c = (T)myCollectionFactory.get();
|
||||
c.addAll(existingValue);
|
||||
if (c.addAll(providedValue)) {
|
||||
return c;
|
||||
}
|
||||
return existingValue;
|
||||
}
|
||||
}
|
||||
|
||||
private class AppendSetDecisionMaker extends AppendDecisionMaker {
|
||||
@Override
|
||||
public MVMap.Decision decide(C existingValue, C providedValue) {
|
||||
if (super.decide(existingValue, providedValue) == MVMap.Decision.ABORT) {
|
||||
return MVMap.Decision.ABORT;
|
||||
}
|
||||
if (existingValue == null || existingValue.isEmpty()) {
|
||||
return MVMap.Decision.PUT;
|
||||
}
|
||||
for (V v : providedValue) {
|
||||
if (!existingValue.contains(v)) {
|
||||
return MVMap.Decision.PUT;
|
||||
}
|
||||
}
|
||||
return MVMap.Decision.ABORT;
|
||||
}
|
||||
}
|
||||
|
||||
private class RemoveDecisionMaker extends MVMap.DecisionMaker<C> {
|
||||
@Override
|
||||
public MVMap.Decision decide(C existingValue, C providedValue) {
|
||||
// if nothing to remove or nothing to remove from
|
||||
if (providedValue == null || providedValue.isEmpty() || existingValue == null || existingValue.isEmpty()) {
|
||||
return MVMap.Decision.ABORT;
|
||||
}
|
||||
return MVMap.Decision.PUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends C> T selectValue(T existingValue, T providedValue) {
|
||||
// both provided and existing values are non-empty collections
|
||||
//noinspection unchecked
|
||||
T c = (T)myCollectionFactory.get();
|
||||
c.addAll(existingValue);
|
||||
if (c.removeAll(providedValue)) {
|
||||
return c;
|
||||
}
|
||||
return existingValue;
|
||||
}
|
||||
}
|
||||
|
||||
private class RemoveSetDecisionMaker extends RemoveDecisionMaker {
|
||||
@Override
|
||||
public MVMap.Decision decide(C existingValue, C providedValue) {
|
||||
if (super.decide(existingValue, providedValue) == MVMap.Decision.ABORT) {
|
||||
return MVMap.Decision.ABORT;
|
||||
}
|
||||
for (V v : providedValue) {
|
||||
if (existingValue.contains(v)) {
|
||||
return MVMap.Decision.PUT;
|
||||
}
|
||||
}
|
||||
return MVMap.Decision.ABORT;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.jps.dependency.impl;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.intellij.openapi.util.LowMemoryWatcher;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.util.SystemProperties;
|
||||
import com.intellij.util.io.DataExternalizer;
|
||||
import com.intellij.util.io.KeyDescriptor;
|
||||
import com.intellij.util.io.PersistentStringEnumerator;
|
||||
import com.intellij.util.io.StorageLockContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jps.api.GlobalOptions;
|
||||
import org.jetbrains.jps.builders.storage.BuildDataCorruptedException;
|
||||
import org.jetbrains.jps.dependency.*;
|
||||
import org.jetbrains.jps.javac.Iterators;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
final class PersistentMapletFactory implements MapletFactory, Closeable {
|
||||
private static final int BASE_CACHE_SIZE = 512 * (SystemProperties.getBooleanProperty(GlobalOptions.COMPILE_PARALLEL_OPTION, false)? 2 : 1);
|
||||
private final String myRootDirPath;
|
||||
private final PersistentStringEnumerator myStringTable;
|
||||
private final List<BaseMaplet<?>> myMaps = new ArrayList<>();
|
||||
private final Enumerator myEnumerator;
|
||||
private final Function<Object, Object> myDataInterner;
|
||||
private final LoadingCache<Object, Object> myInternerCache;
|
||||
private final LowMemoryWatcher myMemWatcher;
|
||||
private final int myCacheSize;
|
||||
|
||||
PersistentMapletFactory(String rootDirPath) throws IOException {
|
||||
myRootDirPath = rootDirPath;
|
||||
// Important: The enumerator will be called from PHM data externalizers. A PHM acquires the page_cache lock before externalizing => this enumerator should use a separate StorageLockContext to avoid deadlocks
|
||||
myStringTable = new PersistentStringEnumerator(getMapFile("string-table"), 1024 * 4, true, new StorageLockContext());
|
||||
myEnumerator = new Enumerator() {
|
||||
@Override
|
||||
public String toString(int num) throws IOException {
|
||||
return myStringTable.valueOf(num);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toNumber(String str) throws IOException {
|
||||
return myStringTable.enumerate(str);
|
||||
}
|
||||
};
|
||||
final int maxGb = (int) (Runtime.getRuntime().maxMemory() / 1_073_741_824L);
|
||||
myCacheSize = BASE_CACHE_SIZE * Math.min(Math.max(1, maxGb), 5); // increase by BASE_CACHE_SIZE for every additional Gb
|
||||
|
||||
myInternerCache = Caffeine.newBuilder().maximumSize(myCacheSize).build(key -> key);
|
||||
myDataInterner = elem -> elem instanceof Usage? myInternerCache.get(elem) : elem;
|
||||
myMemWatcher = LowMemoryWatcher.register(() -> {
|
||||
myInternerCache.invalidateAll();
|
||||
myStringTable.force();
|
||||
for (BaseMaplet<?> map : myMaps) {
|
||||
try {
|
||||
map.flush();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V> MultiMaplet<K, V> createSetMultiMaplet(String storageName, Externalizer<K> keyExternalizer, Externalizer<V> valueExternalizer) {
|
||||
PersistentMultiMaplet<K, V, Set<V>> maplet = new PersistentMultiMaplet<>(
|
||||
getMapFile(storageName), new GraphKeyDescriptor<>(keyExternalizer, myEnumerator, null), new GraphDataExternalizer<>(valueExternalizer, myEnumerator, myDataInterner), HashSet::new
|
||||
);
|
||||
MultiMaplet<K, V> container = new CachingMultiMaplet<>(maplet, myCacheSize);
|
||||
myMaps.add(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V> Maplet<K, V> createMaplet(String storageName, Externalizer<K> keyExternalizer, Externalizer<V> valueExternalizer) {
|
||||
Maplet<K, V> container = new CachingMaplet<>(
|
||||
new PersistentMaplet<>(getMapFile(storageName), new GraphKeyDescriptor<>(keyExternalizer, myEnumerator, null), new GraphDataExternalizer<>(valueExternalizer, myEnumerator, myDataInterner)),
|
||||
myCacheSize
|
||||
);
|
||||
myMaps.add(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
myMemWatcher.stop();
|
||||
Throwable ex = null;
|
||||
for (Closeable container : Iterators.flat(myMaps, Iterators.asIterable(myStringTable))) {
|
||||
try {
|
||||
container.close();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
if (ex == null) {
|
||||
ex = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
myMaps.clear();
|
||||
myInternerCache.invalidateAll();
|
||||
if (ex instanceof IOException) {
|
||||
throw new BuildDataCorruptedException((IOException) ex);
|
||||
}
|
||||
else if (ex != null) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Path getMapFile(final String name) {
|
||||
final File file = new File(myRootDirPath, name);
|
||||
FileUtil.createIfDoesntExist(file);
|
||||
return file.toPath();
|
||||
}
|
||||
|
||||
private static class GraphDataExternalizer<T> implements DataExternalizer<T> {
|
||||
private final Externalizer<T> myExternalizer;
|
||||
private final @Nullable Enumerator myEnumerator;
|
||||
private final @Nullable Function<Object, Object> myObjectInterner;
|
||||
|
||||
GraphDataExternalizer(Externalizer<T> externalizer, @Nullable Enumerator enumerator, @Nullable Function<Object, Object> objectInterner) {
|
||||
myExternalizer = externalizer;
|
||||
myEnumerator = enumerator;
|
||||
myObjectInterner = objectInterner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, T value) throws IOException {
|
||||
myExternalizer.save(GraphDataOutputImpl.wrap(out, myEnumerator), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T read(@NotNull DataInput in) throws IOException {
|
||||
return myExternalizer.load(GraphDataInputImpl.wrap(in, myEnumerator, myObjectInterner));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class GraphKeyDescriptor<T> extends GraphDataExternalizer<T> implements KeyDescriptor<T> {
|
||||
|
||||
GraphKeyDescriptor(Externalizer<T> externalizer, @Nullable Enumerator enumerator, @Nullable Function<Object, Object> objectInterner) {
|
||||
super(externalizer, enumerator, objectInterner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqual(T val1, T val2) {
|
||||
return val1.equals(val2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHashCode(T value) {
|
||||
return value.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.jps.dependency.impl;
|
||||
|
||||
import org.h2.mvstore.WriteBuffer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
public class WriteBufferDataOutput implements DataOutput {
|
||||
private final WriteBuffer myBuf;
|
||||
|
||||
public WriteBufferDataOutput(WriteBuffer buf) {
|
||||
myBuf = buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
myBuf.putInt(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NotNull byte[] b) throws IOException {
|
||||
myBuf.put(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NotNull byte[] b, int off, int len) throws IOException {
|
||||
myBuf.put(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBoolean(boolean v) throws IOException {
|
||||
writeByte(v? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeByte(int v) throws IOException {
|
||||
myBuf.put(((byte)(v & 0xFF)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeShort(int v) throws IOException {
|
||||
myBuf.putShort((short)(v & 0xFFFF));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeChar(int v) throws IOException {
|
||||
myBuf.putChar((char)(v & 0xFFFF));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeInt(int v) throws IOException {
|
||||
myBuf.putInt(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeLong(long v) throws IOException {
|
||||
myBuf.putLong(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFloat(float v) throws IOException {
|
||||
myBuf.putFloat(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDouble(double v) throws IOException {
|
||||
myBuf.putDouble(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBytes(@NotNull String s) throws IOException {
|
||||
int length = s.length();
|
||||
for (int idx = 0; idx < length; idx++) {
|
||||
writeByte(s.charAt(idx));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeChars(@NotNull String s) throws IOException {
|
||||
int length = s.length();
|
||||
for (int idx = 0; idx < length; idx++) {
|
||||
myBuf.putChar(s.charAt(idx));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeUTF(@NotNull String s) throws IOException {
|
||||
int length = s.length();
|
||||
myBuf.putVarInt(length).putStringData(s, length);
|
||||
}
|
||||
}
|
||||
@@ -228,6 +228,11 @@ enum JvmProtoMemberValueExternalizer implements Externalizer<Object> {
|
||||
this.dataType = dataType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] createStorage(int size) {
|
||||
return new Object[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void save(GraphDataOutput out, Object v) throws IOException;
|
||||
|
||||
|
||||
@@ -463,6 +463,16 @@ public final class BuildDataManager {
|
||||
mappings.flush(memoryCachesOnly);
|
||||
}
|
||||
}
|
||||
|
||||
GraphConfiguration graphConfig = getDependencyGraph();
|
||||
if (graphConfig != null) {
|
||||
try {
|
||||
graphConfig.getGraph().flush();
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
@@ -819,6 +829,17 @@ public final class BuildDataManager {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
delegate.flush();
|
||||
}
|
||||
finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user