Skip to content

Commit e60a67f

Browse files
authored
ADFA-4165: flawed logic in KeyedDebouncingAction (#1347)
* fix: flawed logic in KeyedDebouncingAction * fix: update variable names Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>
1 parent f92b49a commit e60a67f

6 files changed

Lines changed: 68 additions & 34 deletions

File tree

‎common/src/main/java/com/itsaky/androidide/utils/KeyedDebouncingAction.kt‎

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import com.itsaky.androidide.progress.ICancelChecker
44
import com.itsaky.androidide.tasks.JobCancelChecker
55
import kotlinx.coroutines.CoroutineScope
66
import kotlinx.coroutines.Dispatchers
7+
import kotlinx.coroutines.ExperimentalCoroutinesApi
78
import kotlinx.coroutines.Job
89
import kotlinx.coroutines.channels.Channel
910
import kotlinx.coroutines.currentCoroutineContext
10-
import kotlinx.coroutines.delay
1111
import kotlinx.coroutines.ensureActive
12+
import kotlinx.coroutines.isActive
1213
import kotlinx.coroutines.launch
14+
import kotlinx.coroutines.selects.onTimeout
15+
import kotlinx.coroutines.selects.select
1316
import java.util.concurrent.ConcurrentHashMap
1417
import kotlin.coroutines.CoroutineContext
1518
import kotlin.time.Duration
@@ -47,15 +50,38 @@ class KeyedDebouncingAction<T: Any>(
4750
entry.channel.trySend(key)
4851
}
4952

53+
@OptIn(ExperimentalCoroutinesApi::class)
5054
private fun createEntry(): ActionEntry<T> {
5155
val channel = Channel<T>(Channel.CONFLATED)
5256
val job = scope.launch(actionContext) {
53-
for (latestKey in channel) {
54-
delay(debounceDuration)
57+
while (isActive) {
58+
var latestKey = channel.receive()
59+
var debouncing = true
60+
while (debouncing) {
61+
debouncing = select {
62+
onTimeout(debounceDuration) { false }
63+
channel.onReceive { newKey ->
64+
latestKey = newKey
65+
true
66+
}
67+
}
68+
}
69+
5570
ensureActive()
71+
val actionJob = launch {
72+
val cancelChecker = JobCancelChecker(currentCoroutineContext()[Job])
73+
action(latestKey, cancelChecker)
74+
}
75+
76+
select<Unit> {
77+
actionJob.onJoin {}
78+
channel.onReceive { newerKey ->
79+
actionJob.cancel()
80+
channel.trySend(newerKey)
81+
}
82+
}
5683

57-
val cancelChecker = JobCancelChecker(currentCoroutineContext()[Job])
58-
action(latestKey, cancelChecker)
84+
actionJob.join()
5985
}
6086
}
6187

‎common/src/main/java/com/itsaky/androidide/utils/StrictModeExts.kt‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package com.itsaky.androidide.utils
22

33
import android.os.StrictMode
4+
import org.slf4j.Logger
45
import org.slf4j.LoggerFactory
56

67
@PublishedApi
7-
internal val logger = LoggerFactory.getLogger("StrictModeExts")
8+
internal object StrictModeExts {
9+
val logger: Logger = LoggerFactory.getLogger("StrictModeExts")
10+
}
811

912
inline fun copyThreadPolicy(
1013
source: StrictMode.ThreadPolicy,
@@ -24,7 +27,7 @@ inline fun <R> permitThreadPolicy(
2427
val currentPolicy = StrictMode.getThreadPolicy()
2528
val newPolicy = copyThreadPolicy(currentPolicy, configurePolicy)
2629
return try {
27-
logger.warn("Allowing {} because: {}", action, reason)
30+
StrictModeExts.logger.warn("Allowing {} because: {}", action, reason)
2831
StrictMode.setThreadPolicy(newPolicy)
2932
task()
3033
} finally {

‎lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/CompilationEnvironment.kt‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,7 @@ internal class CompilationEnvironment(
523523
}
524524

525525
fun onFileContentChanged(path: Path) {
526+
val oldKtFile = ktSymbolIndex.getOpenedKtFile(path)
526527
val newContent = FileManager.getDocumentContents(path)
527528
val newKtFile = project.read {
528529
parser.createFile(path.pathString, newContent)
@@ -534,12 +535,14 @@ internal class CompilationEnvironment(
534535
val provider = ProjectStructureProvider.getInstance(project)
535536
provider.registerInMemoryFile(path.pathString, newKtFile.virtualFile)
536537

537-
ktSymbolIndex.openKtFile(path, newKtFile)
538-
ktSymbolIndex.queueOnFileChangedAsync(newKtFile)
539-
fileAnalyzer.schedule(path)
540538
project.write {
539+
val toInvalidate = oldKtFile ?: newKtFile
541540
KaSourceModificationService.getInstance(project)
542-
.handleElementModification(newKtFile, KaElementModificationType.Unknown)
541+
.handleElementModification(toInvalidate, KaElementModificationType.Unknown)
542+
543+
ktSymbolIndex.openKtFile(path, newKtFile)
544+
ktSymbolIndex.queueOnFileChangedAsync(newKtFile)
545+
fileAnalyzer.schedule(path)
543546
}
544547
}
545548

‎lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/IndexWorker.kt‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package com.itsaky.androidide.lsp.kotlin.compiler.index
33
import com.itsaky.androidide.lsp.kotlin.compiler.CompilationEnvironment
44
import com.itsaky.androidide.lsp.kotlin.compiler.modules.backingFilePath
55
import com.itsaky.androidide.lsp.kotlin.compiler.read
6-
import com.itsaky.androidide.lsp.kotlin.utils.toNioPathOrNull
76
import com.itsaky.androidide.progress.ICancelChecker
87
import com.itsaky.androidide.utils.KeyedDebouncingAction
98
import kotlinx.coroutines.CoroutineScope

‎lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/compiler/index/KtSymbolIndex.kt‎

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -144,17 +144,6 @@ internal class KtSymbolIndex(
144144
indexWorker.submitCommand(IndexCommand.RemoveFromIndex(path))
145145
}
146146

147-
suspend fun onFileMoved(from: Path, to: Path) {
148-
// always remove the source file from index
149-
indexWorker.submitCommand(IndexCommand.RemoveFromIndex(from))
150-
151-
val toVf = getVirtualFileOrWarn(to) ?: return
152-
indexWorker.apply {
153-
submitCommand(IndexCommand.ScanSourceFile(toVf))
154-
submitCommand(IndexCommand.IndexSourceFile(toVf))
155-
}
156-
}
157-
158147
fun queueOnFileChangedAsync(ktFile: KtFile) {
159148
scope.launch {
160149
queueOnFileChanged(ktFile)
@@ -165,8 +154,8 @@ internal class KtSymbolIndex(
165154
indexWorker.submitCommand(IndexCommand.IndexModifiedFile(ktFile))
166155
}
167156

168-
fun openKtFile(path: Path, ktFile: KtFile) {
169-
openedFiles[path] = ktFile
157+
fun openKtFile(path: Path, ktFile: KtFile): KtFile? {
158+
return openedFiles.put(path, ktFile)
170159
}
171160

172161
fun closeKtFile(path: Path) {
@@ -175,15 +164,30 @@ internal class KtSymbolIndex(
175164

176165
fun getOpenedKtFile(path: Path) = openedFiles[path]
177166

178-
fun getKtFile(vf: VirtualFile): KtFile? {
179-
val path = vf.toNioPath()
167+
fun getKtFile(vf: VirtualFile): KtFile? =
168+
getKtFile(vf.toNioPath(), vf)
169+
170+
fun getKtFile(path: Path, virtualFile: VirtualFile? = null): KtFile? {
180171
if (!DocumentUtils.isKotlinFile(path)) return null
181172

182-
openedFiles[path]?.also { return it }
183-
ktFileCache.getIfPresent(path)?.also { return it }
173+
openedFiles[path]?.also {
174+
return it
175+
}
176+
177+
ktFileCache.getIfPresent(path)?.also {
178+
return it
179+
}
184180

185-
val ktFile = loadKtFile(vf)
181+
var file = virtualFile
182+
if (file == null) {
183+
file = path.toVirtualFileOrNull()
184+
}
185+
186+
if (file == null) {
187+
return null
188+
}
186189

190+
val ktFile = loadKtFile(file)
187191
ktFileCache.put(path, ktFile)
188192
return ktFile
189193
}

‎lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/diagnostic/KotlinDiagnosticProvider.kt‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import com.itsaky.androidide.lsp.models.DiagnosticSeverity
1010
import com.itsaky.androidide.progress.ICancelChecker
1111
import kotlinx.coroutines.CancellationException
1212
import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
13-
import org.jetbrains.kotlin.analysis.api.analyze
1413
import org.jetbrains.kotlin.analysis.api.components.KaDiagnosticCheckerFilter
1514
import org.jetbrains.kotlin.analysis.api.diagnostics.KaDiagnosticWithPsi
1615
import org.jetbrains.kotlin.analysis.api.diagnostics.KaSeverity
@@ -31,14 +30,14 @@ internal data class KotlinDiagnosticExtra(
3130
context(env: CompilationEnvironment)
3231
internal fun collectDiagnosticsFor(file: Path, cancelChecker: ICancelChecker): DiagnosticResult {
3332
try {
34-
logger.info("Analyzing file: {}", file)
33+
logger.info("analyzing file: {}", file)
3534
return doAnalyze(file, cancelChecker)
3635
} catch (err: Throwable) {
3736
if (err is CancellationException) {
3837
logger.debug("analysis cancelled")
3938
throw err
4039
}
41-
logger.error("An error occurred analyzing file: {}", file, err)
40+
logger.error("an error occurred analyzing file: {}", file, err)
4241
return DiagnosticResult.NO_UPDATE
4342
}
4443
}
@@ -75,7 +74,7 @@ private fun doAnalyze(file: Path, cancelChecker: ICancelChecker): DiagnosticResu
7574
// This should be canceled as well
7675
// The analysis API uses a no-op implementation of
7776
// Intellij's ProgressManager for cancellations, so the following
78-
// is really cancellable at the moment
77+
// isn't really cancellable at the moment
7978
analyzeMaybeDangling(ktFile) {
8079
ktFile.collectDiagnostics(KaDiagnosticCheckerFilter.EXTENDED_AND_COMMON_CHECKERS)
8180
.forEach { diagnostic ->

0 commit comments

Comments
 (0)