diff --git a/build-common/src/org/jetbrains/kotlin/incremental/IncrementalCompilationContext.kt b/build-common/src/org/jetbrains/kotlin/incremental/IncrementalCompilationContext.kt index 9450e675638..3b3ccda69cd 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/IncrementalCompilationContext.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/IncrementalCompilationContext.kt @@ -18,7 +18,16 @@ class IncrementalCompilationContext( val storeFullFqNamesInLookupCache: Boolean = false, val transaction: CompilationTransaction = DummyCompilationTransaction(), val reporter: ICReporter = DoNothingICReporter, + /** + * Controls whether changes in lookup cache should be tracked. Required for the classpath snapshots based IC approach + */ val trackChangesInLookupCache: Boolean = false, + /** + * Controls whether any changes should be propagated to FS until we decide that the compilation is successful or not + * + * Required for optimizing Gradle side outputs backup + */ + val keepIncrementalCompilationCachesInMemory: Boolean = false, ) { constructor( rootProjectDir: File?, diff --git a/build-common/src/org/jetbrains/kotlin/incremental/storage/BasicMap.kt b/build-common/src/org/jetbrains/kotlin/incremental/storage/BasicMap.kt index cc0ab9b8a0f..6289f7078a6 100644 --- a/build-common/src/org/jetbrains/kotlin/incremental/storage/BasicMap.kt +++ b/build-common/src/org/jetbrains/kotlin/incremental/storage/BasicMap.kt @@ -30,7 +30,13 @@ abstract class BasicMap, V>( valueExternalizer: DataExternalizer, protected val icContext: IncrementalCompilationContext, ) { - protected val storage: LazyStorage = CachingLazyStorage(storageFile, keyDescriptor, valueExternalizer) + protected val storage: LazyStorage = CachingLazyStorage(storageFile, keyDescriptor, valueExternalizer).let { + if (icContext.keepIncrementalCompilationCachesInMemory) { + InMemoryStorageWrapper(it) + } else { + it + } + } protected val pathConverter get() = icContext.pathConverter diff --git a/build-common/src/org/jetbrains/kotlin/incremental/storage/InMemoryStorageWrapper.kt b/build-common/src/org/jetbrains/kotlin/incremental/storage/InMemoryStorageWrapper.kt new file mode 100644 index 00000000000..b35cd4c7c74 --- /dev/null +++ b/build-common/src/org/jetbrains/kotlin/incremental/storage/InMemoryStorageWrapper.kt @@ -0,0 +1,117 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.incremental.storage + +import java.util.* +import kotlin.collections.LinkedHashMap + +/** + * An in-memory wrapper for [origin] that keeps all the write operations in-memory. + * Flushes all the changes to the [origin] on [flush] invocation. + * [resetInMemoryChanges] should be called to reset in-memory changes of this wrapper. + */ +class InMemoryStorageWrapper(val origin: LazyStorage) : LazyStorage { + private val inMemoryStorage = LinkedHashMap>() + private val removedKeys = hashSetOf() + private var isCleanRequested = false + + override val keys: Collection + get() = if (isCleanRequested) inMemoryStorage.keys else (origin.keys - removedKeys) + inMemoryStorage.keys + + fun resetInMemoryChanges() { + isCleanRequested = false + inMemoryStorage.clear() + removedKeys.clear() + } + + override fun clean() { + inMemoryStorage.clear() + removedKeys.clear() + isCleanRequested = true + } + + override fun flush(memoryCachesOnly: Boolean) { + if (isCleanRequested) { + origin.clean() + } else { + for (key in removedKeys) { + origin.remove(key) + } + } + for ((key, valueWrapper) in inMemoryStorage) { + if (valueWrapper.isAppend) { + origin.append(key, valueWrapper.value) + origin[key] // trigger chunks compaction + } else { + origin[key] = valueWrapper.value + } + } + + clean() + isCleanRequested = false + + origin.flush(memoryCachesOnly) + } + + override fun close() { + origin.close() + } + + override fun append(key: K, value: V) { + @Suppress("UNCHECKED_CAST") + when (value) { + is Collection<*> -> { + when { + key in inMemoryStorage -> { + // the key's value was set in-memory, so we keep changing it without additional marking as append + (inMemoryStorage.getValue(key).value as MutableCollection).addAll(value) + } + key !in removedKeys && key in origin -> { + val collection = copyCollection(origin[key] as Collection<*>) + collection.addAll(value) + inMemoryStorage[key] = ValueWrapper(collection as V, isAppend = true) + } + else -> { + set(key, value) + } + } + } + else -> error("The value is expected to implement the Collection interface") + } + } + + override fun remove(key: K) { + removedKeys.add(key) + inMemoryStorage.remove(key) + } + + override fun set(key: K, value: V) { + @Suppress("UNCHECKED_CAST") + inMemoryStorage[key] = ValueWrapper(if (value is Collection<*>) copyCollection(value) as V else value) + } + + private fun copyCollection(collection: Collection<*>): MutableCollection { + val newCollection = when (collection) { + is Set<*> -> TreeSet() + else -> ArrayList(collection.size) + } + newCollection.addAll(collection) + return newCollection + } + + override fun get(key: K): V? = when (key) { + in inMemoryStorage -> inMemoryStorage.getValue(key).value + !in removedKeys -> origin[key] + else -> null + } + + override fun contains(key: K) = key in inMemoryStorage || (key !in removedKeys && key in origin) + + private fun Map.getValue(key: K) = + this[key] ?: error("$key was unexpectedly removed from in-memory storage. Seems to be a multithreading issue") + + private class ValueWrapper(val value: V, val isAppend: Boolean = false) +} \ No newline at end of file