refactor(RimeDispatcher): use SpscArrayQueue to improve performance#2030
refactor(RimeDispatcher): use SpscArrayQueue to improve performance#2030WhiredPlanck wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces the JCTools queue implementation to optimize RimeDispatcher’s internal job dispatching path, aiming to reduce overhead compared to standard Java concurrent queues.
Changes:
- Add
org.jctools:jctools-coreto the version catalog and app dependencies. - Refactor
RimeDispatcherto useSpscArrayQueueplus aSemaphore-based wakeup mechanism instead of a blocking queue. - Adjust dispatcher stop/unblock behavior to work with the new queueing model.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
gradle/libs.versions.toml |
Adds the jctools-core library entry to the version catalog. |
app/build.gradle.kts |
Adds jctools-core as an app implementation dependency. |
app/src/main/java/com/osfans/trime/core/RimeDispatcher.kt |
Switches dispatcher queueing from a blocking queue to SpscArrayQueue + Semaphore signaling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Yes. Case one:
Case two:
|
I connot reproduce. Everything is OK with default schemas and themes. I need time to take investigate, but I'm on a business trip. |
|
Copied from copilot analysis: Now let me analyze the PR changes in detail: Potential Deadlock AnalysisYes, there is a critical deadlock risk in this PR. Here's the issue: The ProblemThe refactored code introduces a classic semaphore + queue signaling deadlock pattern: // In the dispatcher loop (lines 88-101 in the PR)
while (isActive && isRunning.get()) {
semaphore.acquireUninterruptibly() // ⚠️ Blocks waiting
while (true) {
val block = queue.poll() ?: break
block.run()
}
}And in if (isRunning.compareAndSet(true, false)) {
runBlocking { // ⚠️ Blocks on the calling thread
semaphore.release()
mutex.withLock { // ⚠️ Tries to acquire mutex
val rest = queue.toList()
queue.clear()
}
}
}The Deadlock Scenario
Root CauseThe original code's
This breaks the original mutex protection because the dispatcher loop continues to run while stop() holds the mutex. Recommended FixThe fun start() {
internalScope.launch {
if (isRunning.compareAndSet(false, true)) {
mutex.withLock {
Timber.d("nativeStartup()")
controller.nativeStartup()
}
try {
while (isActive && isRunning.get()) {
semaphore.acquireUninterruptibly()
while (true) {
val block = queue.poll() ?: break
block.run()
}
}
} finally {
mutex.withLock {
Timber.i("nativeFinalize()")
controller.nativeFinalize()
}
}
}
}
}Or better yet: Use a different synchronization mechanism (like |
|
@goofyz I just adapted from fcitx5-android, actually I think the queue may not be the worst performance bottleneck now. |
Pull request
Issue tracker
Fixes will automatically close the related issues
Fixes #N/A
Feature
Describe features of this pull request
JCTools is a lightweight library providing high performance lockless queue such as SpscArrayQueue, while "Spsc" means "Single producer and single consumer". It's more appropriate than ConcurrentLinkedQueue or LinkedBlockingQueue for the dedicated event dispatcher, in our case, RimeDispatcher, to process API calling events.
Code of conduct
Code style
make sytle-lintBuild pass
make debugManually test
Code Review
Daily build
Login and download artifact at https://github.com/osfans/trime/actions
Additional Info