rebuild in case of corrupted storage (test - delete some file on disk, we should rebuild in this case)

GitOrigin-RevId: e34b7183f8c6008e305207f6e9aedf1ab4016180
This commit is contained in:
Vladimir Krivosheev
2025-02-27 10:53:27 +00:00
committed by intellij-monorepo-bot
parent 302057c346
commit 81bce2d6bc
12 changed files with 428 additions and 288 deletions
+3 -3
View File
@@ -59,9 +59,9 @@ archive_override(
http_file(
name = "jps-standalone_http",
url = "https://packages.jetbrains.team/files/p/ij/intellij-build-dependencies/jps/jps-build-standalone-25.02.25.jar",
downloaded_file_path = "jps-build-standalone-19.02.25.jar",
integrity = "sha256-unxZmzPgZrqUU7rW7HTeR9WGnT6fqi9iqUH3olfCwkg=",
url = "https://packages.jetbrains.team/files/p/ij/intellij-build-dependencies/jps/jps-build-standalone-27.02.25.jar",
downloaded_file_path = "jps-build-standalone-27.02.25.jar",
integrity = "sha256-WIGBhdShgsW93qar4Hj0GcrIbuNfqAkrtjGASpDasfM=",
)
http_archive(
@@ -73,7 +73,7 @@ internal fun loadJpsModel(
val digests = TargetConfigurationDigestContainer()
digests.set(TargetConfigurationDigestProperty.KOTLIN_VERSION, KOTLINC_VERSION_HASH)
digests.set(TargetConfigurationDigestProperty.JPS_TRACK_LIB_DEPS, if (isLibTracked) 1 else 0)
digests.set(TargetConfigurationDigestProperty.TOOL_VERSION, 6)
digests.set(TargetConfigurationDigestProperty.TOOL_VERSION, 7)
// properties not needed for us (not implemented for java)
// extension.loadModuleOptions not needed for us (not implemented for java)
+36 -47
View File
@@ -11,9 +11,6 @@ import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.apache.arrow.memory.RootAllocator
@@ -41,13 +38,12 @@ import org.jetbrains.bazel.jvm.jps.state.TargetConfigurationDigestContainer
import org.jetbrains.bazel.jvm.jps.state.TargetConfigurationDigestProperty
import org.jetbrains.bazel.jvm.jps.state.createInitialSourceMap
import org.jetbrains.bazel.jvm.jps.state.saveBuildState
import org.jetbrains.bazel.jvm.jps.storage.AsyncExecutor
import org.jetbrains.bazel.jvm.jps.storage.StorageInitializer
import org.jetbrains.bazel.jvm.kotlin.JvmBuilderFlags
import org.jetbrains.bazel.jvm.kotlin.parseArgs
import org.jetbrains.bazel.jvm.span
import org.jetbrains.bazel.jvm.use
import org.jetbrains.jps.backwardRefs.JavaBackwardReferenceIndexBuilder
import org.jetbrains.jps.incremental.RebuildRequestedException
import org.jetbrains.jps.incremental.relativizer.PathRelativizerService
import org.jetbrains.jps.incremental.storage.BuildDataManager
import org.jetbrains.jps.model.JpsModel
@@ -57,7 +53,6 @@ import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import java.util.concurrent.CancellationException
import java.util.concurrent.CompletableFuture
import kotlin.coroutines.coroutineContext
internal suspend fun incrementalBuild(
@@ -143,11 +138,15 @@ suspend fun buildUsingJps(
val typeAwareRelativizer = relativizer.typeAwareRelativizer as BazelPathTypeAwareRelativizer
val log = RequestLog(out = out, parentSpan = parentSpan, tracer = tracer, relativizer = typeAwareRelativizer)
val abiJar = args.optionalSingle(JvmBuilderFlags.ABI_OUT)?.let { baseDir.resolve(it).normalize() }
val outJar = baseDir.resolve(args.mandatorySingle(JvmBuilderFlags.OUT)).normalize()
val prefix = outJar.fileName.toString().removeSuffix(".jar")
val bazelOutDir = outJar.parent
val dataDir = bazelOutDir.resolve("$cachePrefix$prefix-jps-data")
val outputs = OutputFiles(
outJar = outJar,
abiJar = args.optionalSingle(JvmBuilderFlags.ABI_OUT)?.let { baseDir.resolve(it).normalize() },
dataDir = dataDir,
)
val (jpsModel, targetDigests) = loadJpsModel(
sources = sources,
@@ -166,7 +165,7 @@ suspend fun buildUsingJps(
if (isDebugEnabled) {
parentSpan.setAttribute("isIncrementalCompilation", isIncrementalCompilation)
parentSpan.setAttribute("outJar", outJar.toString())
parentSpan.setAttribute("abiJar", abiJar?.toString() ?: "")
parentSpan.setAttribute("abiJar", outputs.abiJar?.toString() ?: "")
for (kind in TargetConfigurationDigestProperty.entries) {
parentSpan.setAttribute(kind.name, targetDigests.get(kind))
}
@@ -185,7 +184,7 @@ suspend fun buildUsingJps(
)
}
var rebuildReason = validateFileExistence(outJar = outJar, abiJar = abiJar, cacheDir = dataDir)
var rebuildReason = validateFileExistence(outputs = outputs, cacheDir = dataDir)
val buildStateFile = dataDir.resolve("$prefix-state-v1.arrow")
val depStateStorageFile = dataDir.resolve("$prefix-lib-roots-v2.arrow")
val classPath = moduleTarget.module.container.getChild(BazelConfigurationHolder.KIND).classPath
@@ -236,8 +235,7 @@ suspend fun buildUsingJps(
dataDir = dataDir,
targetDigests = targetDigests,
moduleTarget = moduleTarget,
outJar = outJar,
abiJar = abiJar,
outputs = outputs,
relativizer = relativizer,
jpsModel = jpsModel,
dataManager = BazelBuildDataProvider(
@@ -257,12 +255,15 @@ suspend fun buildUsingJps(
isDebugEnabled = isDebugEnabled,
)
}
catch (e: RebuildRequestedException) {
catch (e: CancellationException) {
throw e
}
catch (e: Throwable) {
if (isDebugEnabled) {
log.out.appendLine("rebuild requested: ${e.stackTraceToString()}")
}
parentSpan.recordException(e)
rebuildReason = e.cause!!.message
rebuildReason = e.cause?.message ?: e.message ?: "unknown error"
-1
}
@@ -275,8 +276,7 @@ suspend fun buildUsingJps(
dataDir = dataDir,
targetDigests = targetDigests,
moduleTarget = moduleTarget,
outJar = outJar,
abiJar = abiJar,
outputs = outputs,
relativizer = relativizer,
jpsModel = jpsModel,
dataManager = BazelBuildDataProvider(
@@ -316,6 +316,15 @@ internal fun createJavaBuilder(
}
}
internal class OutputFiles(
@JvmField val outJar: Path,
@JvmField val abiJar: Path?,
dataDir: Path,
) {
@JvmField val cachedJar: Path = dataDir.resolve(outJar.fileName)
@JvmField val cachedAbiJar: Path? = abiJar?.let { dataDir.resolve(it.fileName) }
}
@OptIn(DelicateCoroutinesApi::class)
private suspend fun initAndBuild(
rebuildReason: String?,
@@ -324,8 +333,7 @@ private suspend fun initAndBuild(
dataDir: Path,
targetDigests: TargetConfigurationDigestContainer,
moduleTarget: BazelModuleBuildTarget,
outJar: Path,
abiJar: Path?,
outputs: OutputFiles,
relativizer: PathRelativizerService,
jpsModel: JpsModel,
dataManager: BazelBuildDataProvider,
@@ -340,20 +348,10 @@ private suspend fun initAndBuild(
.setAttribute("isRebuild", isRebuild)
.setAttribute("rebuildReason", rebuildReason ?: "")
.use { span ->
if (isRebuild) {
storageInitializer.clearAndInit(span)
}
storageInitializer.createBuildDataManager(
jpsModel = jpsModel,
moduleTarget = moduleTarget,
isRebuild = isRebuild,
relativizer = relativizer,
buildDataProvider = dataManager,
executor = object : AsyncExecutor {
override fun <T> execute(action: () -> T): CompletableFuture<T> {
return GlobalScope.async { action() }.asCompletableFuture()
}
},
span = span,
)
}
@@ -365,9 +363,6 @@ private suspend fun initAndBuild(
coroutineContext = coroutineContext,
)
val cachedJar = dataDir.resolve(outJar.fileName)
val cachedAbiJar = abiJar?.let { dataDir.resolve(it.fileName) }
val oldJar: Path?
val oldAbiJar: Path?
if (isRebuild) {
@@ -375,11 +370,11 @@ private suspend fun initAndBuild(
oldAbiJar = null
}
else {
oldJar = cachedJar.takeIf { Files.exists(it) }
oldAbiJar = if (cachedJar == null || cachedAbiJar == null) null else cachedAbiJar.takeIf { Files.exists(it) }
oldJar = outputs.cachedJar.takeIf { Files.exists(it) }
oldAbiJar = if (oldJar == null || outputs.cachedAbiJar == null) null else outputs.cachedAbiJar.takeIf { Files.exists(it) }
}
createOutputSink(oldJar = oldJar, oldAbiJar = oldAbiJar, withAbi = abiJar != null).use { outputSink ->
createOutputSink(oldJar = oldJar, oldAbiJar = oldAbiJar, withAbi = outputs.abiJar != null).use { outputSink ->
val exitCode = tracer.spanBuilder("compile")
.setAttribute(AttributeKey.booleanKey("isRebuild"), isRebuild)
.use { span ->
@@ -431,10 +426,7 @@ private suspend fun initAndBuild(
buildDataManager = buildDataManager,
success = exitCode == 0,
moduleTarget = moduleTarget,
outJar = outJar,
abiJar = abiJar,
cachedJar = cachedJar,
cachedAbiJar = cachedAbiJar,
outputs = outputs,
context = context,
targetDigests = targetDigests,
buildDataProvider = dataManager,
@@ -475,10 +467,7 @@ private val stateFileMetaNames: Array<String> = TargetConfigurationDigestPropert
private fun CoroutineScope.postBuild(
moduleTarget: BazelModuleBuildTarget,
outJar: Path,
abiJar: Path?,
cachedJar: Path,
cachedAbiJar: Path?,
outputs: OutputFiles,
context: BazelCompileContext,
targetDigests: TargetConfigurationDigestContainer,
buildDataProvider: BazelBuildDataProvider,
@@ -521,8 +510,8 @@ private fun CoroutineScope.postBuild(
writeJarAndAbi(
tracer = requestLog.tracer,
outputSink = outputSink,
outJar = cachedJar,
abiJar = cachedAbiJar,
outJar = outputs.cachedJar,
abiJar = outputs.cachedAbiJar,
sourceDescriptors = sourceDescriptors,
)
}
@@ -532,9 +521,9 @@ private fun CoroutineScope.postBuild(
// copy to output
withContext(Dispatchers.IO) {
Files.copy(cachedJar, outJar, StandardCopyOption.REPLACE_EXISTING)
if (abiJar != null) {
Files.copy(cachedAbiJar, abiJar, StandardCopyOption.REPLACE_EXISTING)
Files.copy(outputs.cachedJar, outputs.outJar, StandardCopyOption.REPLACE_EXISTING)
if (outputs.abiJar != null) {
Files.copy(outputs.cachedAbiJar, outputs.abiJar, StandardCopyOption.REPLACE_EXISTING)
}
}
}
@@ -1,72 +0,0 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("UnstableApiUsage", "ReplaceGetOrSet")
package org.jetbrains.bazel.jvm.jps
import com.intellij.openapi.util.io.FileUtilRt
import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.trace.Span
import org.jetbrains.bazel.jvm.jps.impl.BazelBuildDataProvider
import org.jetbrains.bazel.jvm.jps.impl.BazelModuleBuildTarget
import org.jetbrains.bazel.jvm.jps.impl.createDataManager
import org.jetbrains.bazel.jvm.jps.storage.AsyncExecutor
import org.jetbrains.jps.incremental.relativizer.PathRelativizerService
import org.jetbrains.jps.incremental.storage.BuildDataManager
import org.jetbrains.jps.model.JpsModel
import java.nio.file.Files
import java.nio.file.Path
internal class StorageInitializer(private val dataDir: Path) {
private var wasCleared = false
fun clearAndInit(@Suppress("unused") span: Span) {
clearStorage()
Files.createDirectories(dataDir)
}
fun createBuildDataManager(
jpsModel: JpsModel,
moduleTarget: BazelModuleBuildTarget,
relativizer: PathRelativizerService,
buildDataProvider: BazelBuildDataProvider,
span: Span,
executor: AsyncExecutor,
): BuildDataManager {
try {
return createDataManager(
dataStorageRoot = dataDir,
// alwaysScanFS doesn't matter, we use our own version of `BuildOperations.ensureFSStateInitialized`,
// see `JpsProjectBuilder.ensureFsStateInitialized`
relativizer = relativizer,
buildDataProvider = buildDataProvider,
executor = executor,
)
}
catch (e: Throwable) {
//storageManager!!.forceClose()
if (wasCleared) {
throw e
}
span.recordException(e, Attributes.of(AttributeKey.stringKey("message"), "cannot open cache storage"))
}
clearStorage()
return createBuildDataManager(
jpsModel = jpsModel,
moduleTarget = moduleTarget,
relativizer = relativizer,
buildDataProvider = buildDataProvider,
executor = executor,
span = span,
)
}
fun clearStorage() {
wasCleared = true
// todo rename and store
FileUtilRt.deleteRecursively(dataDir)
}
}
@@ -22,7 +22,6 @@ import org.jetbrains.jps.dependency.ReferenceID
import org.jetbrains.jps.dependency.impl.DependencyGraphImpl
import org.jetbrains.jps.dependency.impl.PathSourceMapper
import org.jetbrains.jps.incremental.relativizer.PathRelativizerService
import org.jetbrains.jps.incremental.storage.BuildDataManager.Companion.PROCESS_CONSTANTS_NON_INCREMENTAL_PROPERTY
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Future
import java.util.concurrent.locks.ReentrantReadWriteLock
@@ -31,33 +30,21 @@ import java.util.function.Function
import kotlin.concurrent.read
import kotlin.concurrent.write
private val processConstantsIncrementally = !System.getProperty(PROCESS_CONSTANTS_NON_INCREMENTAL_PROPERTY, "false").toBoolean()
private val processConstantsIncrementally = !System.getProperty("compiler.process.constants.non.incremental", "false").toBoolean()
class BuildDataManager internal constructor(
internal class BuildDataManager private constructor(
val dataPaths: BuildDataPaths,
@get:Deprecated("Use {@link #getTargetStateManager()} or, preferably, avoid using internal APIs.") val targetsState: BuildTargetsState,
val relativizer: PathRelativizerService,
private val dataManager: BuildDataProvider,
containerFactory: BazelPersistentMapletFactory,
depGraph: DependencyGraph,
) {
private val depGraph: DependencyGraph
private val depGraph = SynchronizedDependencyGraph(depGraph)
private val depGraphPathMapper: NodeSourcePathMapper
private val targetToStorages = ConcurrentHashMap<Pair<BuildTarget<*>, StorageProvider<StorageOwner>>, StorageOwner>()
init {
try {
depGraph = SynchronizedDependencyGraph(DependencyGraphImpl(containerFactory))
}
catch (e: Throwable) {
try {
close()
}
catch (_: Throwable) {
}
throw e
}
val typeAwareRelativizer = relativizer.typeAwareRelativizer!!
depGraphPathMapper = PathSourceMapper(
Function { typeAwareRelativizer.toAbsolute(it, RelativePathType.SOURCE) },
@@ -65,6 +52,25 @@ class BuildDataManager internal constructor(
)
}
companion object {
fun open(
containerFactory: BazelPersistentMapletFactory,
dataPaths: BuildDataPaths,
relativizer: PathRelativizerService,
targetState: BuildTargetsState,
dataManager: BuildDataProvider,
): BuildDataManager {
val depGraph = DependencyGraphImpl(containerFactory)
return BuildDataManager(
dataPaths = dataPaths,
targetsState = targetState,
dataManager = dataManager,
relativizer = relativizer,
depGraph = depGraph,
)
}
}
@Suppress("unused")
fun getMappings(): Mappings? = null
@@ -165,10 +171,6 @@ class BuildDataManager internal constructor(
fun reportUnhandledRelativizerPaths() {
relativizer.reportUnhandledPaths()
}
companion object {
const val PROCESS_CONSTANTS_NON_INCREMENTAL_PROPERTY: String = "compiler.process.constants.non.incremental"
}
}
private class SynchronizedDependencyGraph(private val delegate: DependencyGraph) : DependencyGraph {
@@ -3,54 +3,20 @@
package org.jetbrains.bazel.jvm.jps.impl
import org.jetbrains.bazel.jvm.jps.storage.AsyncExecutor
import org.jetbrains.bazel.jvm.jps.storage.BazelPersistentMapletFactory
import org.jetbrains.jps.builders.BuildTarget
import org.jetbrains.jps.builders.BuildTargetIndex
import org.jetbrains.jps.builders.BuildTargetRegistry.ModuleTargetSelector
import org.jetbrains.jps.builders.BuildTargetType
import org.jetbrains.jps.builders.impl.BuildTargetChunk
import org.jetbrains.jps.builders.logging.BuildLoggingManager
import org.jetbrains.jps.builders.storage.BuildDataPaths
import org.jetbrains.jps.cmdline.ProjectDescriptor
import org.jetbrains.jps.incremental.CompileContext
import org.jetbrains.jps.incremental.ModuleBuildTarget
import org.jetbrains.jps.incremental.fs.BuildFSState
import org.jetbrains.jps.incremental.relativizer.PathRelativizerService
import org.jetbrains.jps.incremental.storage.BuildDataManager
import org.jetbrains.jps.incremental.storage.BuildTargetsState
import org.jetbrains.jps.indices.IgnoredFileIndex
import org.jetbrains.jps.model.JpsModel
import org.jetbrains.jps.model.module.JpsModule
import java.nio.file.Path
private class BazelBuildDataPaths(private val dir: Path) : BuildDataPaths {
override fun getDataStorageDir() = dir
override fun getTargetsDataRoot(): Path = dir
override fun getTargetTypeDataRootDir(targetType: BuildTargetType<*>): Path = dir.resolve(targetType.typeId)
override fun getTargetDataRootDir(target: BuildTarget<*>): Path = dir.resolve(target.targetType.typeId)
override fun getTargetDataRoot(targetType: BuildTargetType<*>, targetId: String): Path = dir
}
internal fun createDataManager(
dataStorageRoot: Path,
relativizer: PathRelativizerService,
buildDataProvider: BazelBuildDataProvider,
executor: AsyncExecutor,
): BuildDataManager {
val containerFactory = BazelPersistentMapletFactory(dataStorageRoot.resolve("mappings-graph"), executor)
return BuildDataManager(
dataPaths = BazelBuildDataPaths(dataStorageRoot),
targetsState = BuildTargetsState(BazelBuildTargetStateManager),
relativizer = relativizer,
dataManager = buildDataProvider,
containerFactory = containerFactory,
)
}
internal fun createJpsProjectDescriptor(
dataManager: BuildDataManager,
@@ -22,11 +22,11 @@ private const val thresholdPercentage = 0.5
// if output jar doesn't exist, make sure that we do not to use existing cache -
// set `isRebuild` to true and clear caches in this case
internal fun validateFileExistence(outJar: Path, abiJar: Path?, cacheDir: Path): String? {
internal fun validateFileExistence(outputs: OutputFiles, cacheDir: Path): String? {
return when {
!Files.isDirectory(cacheDir) -> "cache dir doesn't exist"
Files.notExists(cacheDir.resolve(outJar.fileName)) -> "cached output jar doesn't exist"
abiJar != null && Files.notExists(cacheDir.resolve(abiJar.fileName)) -> {
Files.notExists(outputs.cachedJar) -> "cached output jar doesn't exist"
outputs.cachedAbiJar != null && Files.notExists(outputs.cachedAbiJar) -> {
"cached output JAR exists but not ABI JAR - something wrong, or we enabled ABI JARs"
}
else -> null
@@ -0,0 +1,55 @@
@file:Suppress("UnstableApiUsage")
package org.jetbrains.bazel.jvm.jps.storage
import com.intellij.platform.util.io.storages.DataExternalizerEx
import com.intellij.platform.util.io.storages.KeyDescriptorEx
import com.intellij.platform.util.io.storages.durablemap.DurableMap
import com.intellij.platform.util.io.storages.durablemap.DurableMapFactory
import com.intellij.util.io.DataExternalizer
import com.intellij.util.io.KeyDescriptor
import org.jetbrains.jps.dependency.Maplet
import java.nio.file.Path
internal class DurableMapMaplet<K : Any, V : Any>(
mapFile: Path,
keyDescriptor: KeyDescriptor<K>,
valueExternalizer: DataExternalizer<V>
) : Maplet<K, V> {
private val map: DurableMap<K, V> = DurableMapFactory
.withDefaults<K, V>(KeyDescriptorEx.adapt(keyDescriptor), DataExternalizerEx.adapt(valueExternalizer))
.open(mapFile)
override fun containsKey(key: K): Boolean {
return map.containsMapping(key)
}
override fun get(key: K): V? {
return map.get(key)
}
override fun put(key: K, value: V?) {
map.put(key, value)
}
override fun remove(key: K) {
map.remove(key)
}
override fun getKeys(): Iterable<K> {
val result = ArrayList<K>(map.size())
map.processKeys {
result.add(it)
true
}
return result
}
override fun close() {
map.close()
}
override fun flush() {
map.force()
}
}
@@ -0,0 +1,147 @@
@file:Suppress("UnstableApiUsage")
package org.jetbrains.bazel.jvm.jps.storage
import com.intellij.util.io.AppendablePersistentMap
import com.intellij.util.io.DataExternalizer
import com.intellij.util.io.KeyDescriptor
import com.intellij.util.io.PersistentMapBase
import com.intellij.util.io.PersistentMapBuilder
import org.jetbrains.jps.dependency.MultiMaplet
import java.io.DataInput
import java.io.DataInputStream
import java.io.DataOutput
import java.nio.file.Path
import kotlin.math.absoluteValue
internal class DurablePersistentMultiMaplet<K : Any, V : Any, C : MutableCollection<V>>(
mapFile: Path,
keyDescriptor: KeyDescriptor<K>,
valueExternalizer: DataExternalizer<V>,
private val collectionFactory: () -> C,
private val emptyCollection: Collection<V>,
) : MultiMaplet<K, V> {
private val map: PersistentMapBase<K, C>
private val valueExternalizer: DataExternalizer<V>
init {
this.valueExternalizer = valueExternalizer
val builder = PersistentMapBuilder.newBuilder<K, C>(mapFile, keyDescriptor, object : DataExternalizer<C> {
override fun save(out: DataOutput, data: C) {
out.writeInt(data.size)
for (value in data) {
valueExternalizer.save(out, value)
}
}
override fun read(`in`: DataInput): C {
val result = collectionFactory()
val stream = `in` as DataInputStream
while (stream.available() > 0) {
var size = stream.readInt()
val isRemoval = size < 0
size = size.absoluteValue
while (size-- > 0) {
val v = valueExternalizer.read(stream)
if (isRemoval) {
result.remove(v)
}
else {
result.add(v)
}
}
}
return result
}
})
map = builder.buildImplementation()
}
override fun containsKey(key: K?): Boolean {
return map.containsKey(key)
}
override fun get(key: K): Collection<V> {
return map.get(key) ?: return emptyCollection
}
override fun put(key: K?, values: Iterable<V>) {
val data = ensureCollection(values)
if (data.isEmpty()) {
map.remove(key)
}
else {
map.put(key, data)
}
}
@Suppress("UNCHECKED_CAST")
private fun ensureCollection(seq: Iterable<V>): C {
return when {
emptyCollection is MutableSet<*> && seq is MutableSet<*> -> seq as C
emptyCollection is MutableList<*> && seq is MutableList<*> -> seq as C
else -> seq.toCollection(collectionFactory())
}
}
override fun remove(key: K?) {
map.remove(key)
}
override fun appendValue(key: K, value: V) {
map.appendData(key, AppendablePersistentMap.ValueDataAppender { out ->
out.writeInt(1)
valueExternalizer.save(out, value)
})
}
override fun appendValues(key: K, values: Iterable<V>) {
val size = values.count()
if (size == 0) {
return
}
map.appendData(key, AppendablePersistentMap.ValueDataAppender { out ->
out.writeInt(size)
for (v in values) {
valueExternalizer.save(out, v)
}
})
}
override fun removeValue(key: K, value: V) {
removeValues(key, listOf(value))
}
override fun removeValues(key: K?, values: Iterable<V>) {
val size = values.count()
if (size == 0) {
return
}
map.appendData(key, AppendablePersistentMap.ValueDataAppender { out ->
out.writeInt(-size)
for (v in values) {
valueExternalizer.save(out, v)
}
})
}
override fun getKeys(): Iterable<K> {
val result = ArrayList<K>(map.keysCount())
map.processExistingKeys {
result.add(it)
true
}
return result
}
override fun close() {
map.close()
}
override fun flush() {
map.force()
}
}
@@ -2,7 +2,6 @@
package org.jetbrains.bazel.jvm.jps.storage
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.util.IntRef
import com.intellij.platform.util.io.storages.appendonlylog.AppendOnlyLog
import com.intellij.platform.util.io.storages.appendonlylog.AppendOnlyLogFactory
@@ -14,42 +13,26 @@ import com.intellij.util.io.IOUtil
import com.intellij.util.io.Unmappable
import com.intellij.util.io.blobstorage.ByteBufferReader
import org.jetbrains.annotations.Nullable
import java.nio.ByteBuffer
import java.nio.file.Path
import java.util.concurrent.CancellationException
import java.util.concurrent.CompletableFuture
import java.util.function.IntPredicate
internal class DurableStringEnumerator(
internal class DurableStringEnumerator private constructor(
private val valuesLog: AppendOnlyLog,
private val valueHashToIdFuture: CompletableFuture<Int2IntMultimap>,
private val valueHashToId: Int2IntMultimap,
) : DurableDataEnumerator<String>, Unmappable, CleanableStorage {
private val valueHashLock = Any()
/** lazily initialized in [valueHashToId] */
private var valueHashToId: Int2IntMultimap? = null
companion object {
const val DATA_FORMAT_VERSION: Int = 1
const val PAGE_SIZE: Int = 8 shl 20
private val VALUES_LOG_FACTORY = AppendOnlyLogFactory
.withDefaults()
.pageSize(PAGE_SIZE)
.failIfDataFormatVersionNotMatch(DATA_FORMAT_VERSION)
.failIfDataFormatVersionNotMatch(1)
.checkIfFileCompatibleEagerly(true)
.cleanIfFileIncompatible()
//fun open(storagePath: Path): DurableStringEnumerator {
// return VALUES_LOG_FACTORY.wrapStorageSafely<DurableStringEnumerator, IOException>(
// storagePath,
// { valuesLog -> DurableStringEnumerator(valuesLog, buildValueToIdIndex(valuesLog)) }
// )
//}
fun openAsync(storagePath: Path, executor: AsyncExecutor): DurableStringEnumerator {
return VALUES_LOG_FACTORY.wrapStorageSafely<DurableStringEnumerator, RuntimeException>(storagePath) { valuesLog ->
DurableStringEnumerator(valuesLog, executor.execute { buildValueToIdIndex(valuesLog) })
fun open(storagePath: Path): DurableStringEnumerator {
return executeOrCloseStorage(VALUES_LOG_FACTORY.open(storagePath)) { valuesLog ->
val valueHashToId = buildValueToIdIndex(valuesLog)
DurableStringEnumerator(valuesLog, valueHashToId)
}
}
}
@@ -67,7 +50,7 @@ internal class DurableStringEnumerator(
val valueHash = hashOf(value)
synchronized(valueHashLock) {
val valueHashToId = valueHashToId()
val valueHashToId = valueHashToId
val foundId = lookupValue(valueHashToId, value, valueHash)
if (foundId != DataEnumerator.NULL_ID) {
return foundId
@@ -86,7 +69,7 @@ internal class DurableStringEnumerator(
val valueHash = hashOf(value)
synchronized(valueHashLock) {
val valueHashToId = valueHashToId()
val valueHashToId = valueHashToId
return lookupValue(valueHashToId, value, valueHash)
}
}
@@ -99,15 +82,6 @@ internal class DurableStringEnumerator(
}
override fun close() {
try {
valueHashToIdFuture.join()
}
catch (_: CancellationException) {
}
catch (e: Throwable) {
logger<DurableStringEnumerator>().info(".valueHashToId computation failed", e)
}
valuesLog.close()
}
@@ -123,15 +97,6 @@ internal class DurableStringEnumerator(
valuesLog.closeAndClean()
}
private fun valueHashToId(): Int2IntMultimap {
var valueHashToId = valueHashToId
if (valueHashToId == null) {
valueHashToId = valueHashToIdFuture.get()
this.valueHashToId = valueHashToId
}
return valueHashToId
}
private fun lookupValue(valueHashToId: Int2IntMultimap, value: String, hash: Int): Int {
val foundIdRef = IntRef(DataEnumerator.NULL_ID)
valueHashToId.lookup(hash, IntPredicate { candidateId ->
@@ -153,18 +118,13 @@ private fun hashOf(value: String): Int {
val hash = value.hashCode()
if (hash == Int2IntMultimap.NO_VALUE) {
//Int2IntMultimap doesn't allow 0 keys/values, hence replace 0 hash with just any value!=0. Hash doesn't
// identify name uniquely anyway, hence this replacement just adds another hash collision -- basically,
// of collisions
// identify name uniquely anyway; hence this replacement just adds another hash collision -- basically, of collisions
return -1 // any value!=0 will do
}
return hash
}
private val stringReader = ByteBufferReader { readString(it) }
private fun readString(buffer: ByteBuffer): String {
return IOUtil.readString(buffer)
}
private val stringReader = ByteBufferReader { IOUtil.readString(it) }
private fun writeString(value: String, valuesLog: AppendOnlyLog): Int {
val valueBytes = value.encodeToByteArray()
@@ -175,7 +135,7 @@ private fun writeString(value: String, valuesLog: AppendOnlyLog): Int {
private fun buildValueToIdIndex(valuesLog: AppendOnlyLog): Int2IntMultimap {
val valueHashToId = Int2IntMultimap()
valuesLog.forEachRecord { logId, buffer ->
val value = readString(buffer)
val value = IOUtil.readString(buffer)
val id = convertLogIdToValueId(logId)
val valueHash = hashOf(value)
valueHashToId.put(valueHash, id)
@@ -184,4 +144,4 @@ private fun buildValueToIdIndex(valuesLog: AppendOnlyLog): Int2IntMultimap {
return valueHashToId
}
private fun convertLogIdToValueId(logId: Long): Int = Math.toIntExact(logId)
private fun convertLogIdToValueId(logId: Long): Int = Math.toIntExact(logId)
@@ -0,0 +1,85 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("UnstableApiUsage", "ReplaceGetOrSet")
package org.jetbrains.bazel.jvm.jps.storage
import com.intellij.openapi.util.io.FileUtilRt
import io.opentelemetry.api.common.AttributeKey
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.trace.Span
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.bazel.jvm.jps.impl.BazelBuildDataProvider
import org.jetbrains.bazel.jvm.jps.impl.BazelBuildTargetStateManager
import org.jetbrains.jps.builders.BuildTarget
import org.jetbrains.jps.builders.BuildTargetType
import org.jetbrains.jps.builders.storage.BuildDataPaths
import org.jetbrains.jps.incremental.relativizer.PathRelativizerService
import org.jetbrains.jps.incremental.storage.BuildDataManager
import org.jetbrains.jps.incremental.storage.BuildTargetsState
import java.nio.file.Files
import java.nio.file.Path
internal class StorageInitializer(private val dataDir: Path) {
private var wasCleared = false
suspend fun createBuildDataManager(
isRebuild: Boolean,
relativizer: PathRelativizerService,
buildDataProvider: BazelBuildDataProvider,
span: Span,
): BuildDataManager {
if (isRebuild) {
wasCleared = true
withContext(Dispatchers.IO) {
FileUtilRt.deleteRecursively(dataDir)
}
}
while (true) {
try {
val containerManager = withContext(Dispatchers.IO) {
Files.createDirectories(dataDir)
BazelPersistentMapletFactory.open(dataDir.resolve("graph"))
}
return executeOrCloseStorage(containerManager) {
BuildDataManager.open(
dataPaths = BazelBuildDataPaths(dataDir),
targetState = BuildTargetsState(BazelBuildTargetStateManager),
relativizer = relativizer,
dataManager = buildDataProvider,
containerFactory = containerManager,
)
}
}
catch (e: Throwable) {
if (wasCleared) {
throw e
}
span.recordException(e, Attributes.of(AttributeKey.stringKey("message"), "cannot open cache storage"))
}
clearStorage()
}
}
fun clearStorage() {
wasCleared = true
// todo rename and store
FileUtilRt.deleteRecursively(dataDir)
}
}
private class BazelBuildDataPaths(private val dir: Path) : BuildDataPaths {
override fun getDataStorageDir() = dir
override fun getTargetsDataRoot(): Path = dir
override fun getTargetTypeDataRootDir(targetType: BuildTargetType<*>): Path = dir.resolve(targetType.typeId)
override fun getTargetDataRootDir(target: BuildTarget<*>): Path = dir.resolve(target.targetType.typeId)
override fun getTargetDataRoot(targetType: BuildTargetType<*>, targetId: String): Path = dir
}
@@ -6,6 +6,7 @@ import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.LoadingCache
import com.intellij.util.io.DataExternalizer
import com.intellij.util.io.KeyDescriptor
import com.intellij.util.io.Unmappable
import org.jetbrains.bazel.jvm.hashSet
import org.jetbrains.jps.dependency.BaseMaplet
import org.jetbrains.jps.dependency.ExternalizableGraphElement
@@ -18,16 +19,13 @@ import org.jetbrains.jps.dependency.impl.CachingMaplet
import org.jetbrains.jps.dependency.impl.CachingMultiMaplet
import org.jetbrains.jps.dependency.impl.GraphDataInputImpl
import org.jetbrains.jps.dependency.impl.GraphDataOutputImpl
import org.jetbrains.jps.dependency.impl.PersistentMaplet
import org.jetbrains.jps.dependency.impl.PersistentMultiMaplet
import org.jetbrains.jps.incremental.storage.runAllCatching
import java.io.Closeable
import java.io.DataInput
import java.io.DataOutput
import java.nio.file.Files
import java.io.IOException
import java.lang.AutoCloseable
import java.nio.file.Path
import java.util.concurrent.CompletableFuture
import java.util.function.Supplier
import kotlin.math.max
import kotlin.math.min
@@ -35,42 +33,33 @@ private const val BASE_CACHE_SIZE = 512
private val cacheSize = BASE_CACHE_SIZE * min(max(1, (Runtime.getRuntime().maxMemory() / 1073741824L).toInt()), 5)
internal interface AsyncExecutor {
fun <T> execute(action: () -> T): CompletableFuture<T>
}
internal class BazelPersistentMapletFactory(
internal class BazelPersistentMapletFactory private constructor(
private val rootDir: Path,
executor: AsyncExecutor,
private val stringEnumerator: DurableStringEnumerator,
) : MapletFactory, Closeable {
private val maps = ArrayList<BaseMaplet<*>>()
private val stringEnumeratorImpl = DurableStringEnumerator.openAsync(getMapFile("strings"), executor)
private val stringEnumerator = object : StringEnumerator {
override fun enumerate(string: String): Int {
return stringEnumeratorImpl.enumerate(string)
}
override fun valueOf(id: Int): String? {
return stringEnumeratorImpl.valueOf(id)
companion object {
internal fun open(rootDir: Path): BazelPersistentMapletFactory {
return BazelPersistentMapletFactory(rootDir, DurableStringEnumerator.open(rootDir.resolve("strings")))
}
}
private val maps = ArrayList<BaseMaplet<*>>()
private val usageInterner: LoadingCache<Usage, Usage> = Caffeine.newBuilder().maximumSize(cacheSize.toLong()).build { it }
private val usageInternerFunction: (Usage) -> Usage = { usageInterner.get(it) }
override fun <K, V> createSetMultiMaplet(
override fun <K : Any, V : Any> createSetMultiMaplet(
storageName: String,
keyExternalizer: Externalizer<K>,
valueExternalizer: Externalizer<V>
): MultiMaplet<K, V> {
val container = CachingMultiMaplet(
PersistentMultiMaplet(
getMapFile(storageName),
GraphKeyDescriptor(keyExternalizer, stringEnumerator),
GraphDataExternalizer(valueExternalizer, stringEnumerator, usageInternerFunction),
Supplier { hashSet() }
DurablePersistentMultiMaplet(
mapFile = rootDir.resolve(storageName),
keyDescriptor = GraphKeyDescriptor(keyExternalizer, stringEnumerator),
valueExternalizer = GraphDataExternalizer(valueExternalizer, stringEnumerator, usageInternerFunction),
collectionFactory = { hashSet() },
emptyCollection = emptySet(),
),
cacheSize,
)
@@ -78,16 +67,21 @@ internal class BazelPersistentMapletFactory(
return container
}
override fun <K, V> createMaplet(
override fun <K : Any, V : Any> createMaplet(
storageName: String,
keyExternalizer: Externalizer<K?>,
valueExternalizer: Externalizer<V?>,
keyExternalizer: Externalizer<K>,
valueExternalizer: Externalizer<V>,
): Maplet<K, V> {
val container = CachingMaplet<K, V>(
PersistentMaplet(
getMapFile(storageName),
val container = CachingMaplet(
//PersistentMaplet(
// getMapFile(storageName),
// GraphKeyDescriptor(keyExternalizer, stringEnumerator),
// GraphDataExternalizer(valueExternalizer, stringEnumerator, usageInternerFunction)
//),
DurableMapMaplet(
rootDir.resolve(storageName),
GraphKeyDescriptor(keyExternalizer, stringEnumerator),
GraphDataExternalizer(valueExternalizer, stringEnumerator, usageInternerFunction)
GraphDataExternalizer(valueExternalizer, stringEnumerator, usageInternerFunction),
),
cacheSize,
)
@@ -100,26 +94,14 @@ internal class BazelPersistentMapletFactory(
for (container in maps) {
yield { container.close() }
}
yield { stringEnumeratorImpl.close() }
yield { stringEnumerator.closeAndUnsafelyUnmap() }
})
}
private fun getMapFile(name: String): Path {
val file = rootDir.resolve(name)
Files.createDirectories(file.parent)
return file
}
}
private interface StringEnumerator {
fun enumerate(string: String): Int
fun valueOf(id: Int): String?
}
private open class GraphDataExternalizer<T>(
private open class GraphDataExternalizer<T : Any>(
private val externalizer: Externalizer<T>,
private val stringEnumerator: StringEnumerator,
private val stringEnumerator: DurableStringEnumerator,
private val elementInterner: ((Usage) -> Usage)?,
) : DataExternalizer<T> {
final override fun save(out: DataOutput, value: T?) {
@@ -136,7 +118,7 @@ private open class GraphDataExternalizer<T>(
object : GraphDataInputImpl(`in`) {
override fun readUTF(): String {
val id = readInt()
return stringEnumerator.valueOf(id) ?: throw IllegalStateException("$id is not valid")
return stringEnumerator.valueOf(id) ?: invalidIdError(id)
}
}
}
@@ -144,7 +126,7 @@ private open class GraphDataExternalizer<T>(
object : GraphDataInputImpl(`in`) {
override fun readUTF(): String {
val id = readInt()
return stringEnumerator.valueOf(id) ?: throw IllegalStateException("$id is not valid")
return stringEnumerator.valueOf(id) ?: invalidIdError(id)
}
override fun <T : ExternalizableGraphElement?> processLoadedGraphElement(element: T?): T? {
@@ -157,9 +139,15 @@ private open class GraphDataExternalizer<T>(
}
}
private class GraphKeyDescriptor<T>(
private fun invalidIdError(id: Int): Nothing {
// throw IOException instead of IllegalStateException because in `PersistentEnumeratorBase.catchCorruption`
// we wrap non-IOException into RuntimeException
throw IOException("$id is not valid")
}
private class GraphKeyDescriptor<T : Any>(
externalizer: Externalizer<T>,
stringEnumerator: StringEnumerator,
stringEnumerator: DurableStringEnumerator,
) : GraphDataExternalizer<T>(externalizer = externalizer, stringEnumerator = stringEnumerator, elementInterner = null), KeyDescriptor<T> {
override fun isEqual(val1: T?, val2: T?): Boolean {
return val1 == val2
@@ -168,4 +156,24 @@ private class GraphKeyDescriptor<T>(
override fun getHashCode(value: T?): Int {
return value.hashCode()
}
}
internal inline fun <Out, In : AutoCloseable> executeOrCloseStorage(storageToClose: In, task: (In) -> Out): Out {
try {
return task(storageToClose)
}
catch (mainEx: Throwable) {
try {
if (storageToClose is Unmappable) {
storageToClose.closeAndUnsafelyUnmap()
}
else {
storageToClose.close()
}
}
catch (closeEx: Throwable) {
mainEx.addSuppressed(closeEx)
}
throw mainEx
}
}