Skip to content

Conversation

@albertocavalcante
Copy link
Owner

@albertocavalcante albertocavalcante commented Jan 30, 2026

Summary

Summary by CodeRabbit

  • Refactor
    • Enhanced internal cache management and statistics tracking to improve reliability during concurrent operations.

✏️ Tip: You can customize this high-level summary in your review settings.

Consistent synchronization on index lists to prevent races between buildIndexes and removeFromIndexes.

Atomic map removal when lists become empty.
Copilot AI review requested due to automatic review settings January 30, 2026 11:20
@coderabbitai
Copy link

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

Single-file refactoring of GroovySemanticDB cache statistics and indexing structures to improve thread-safety. Replaces non-thread-safe primitive Long counters with AtomicLong, switches indexing from synchronizedList to CopyOnWriteArrayList, and updates all related operations to use atomic operations and proper synchronization patterns.

Changes

Cohort / File(s) Summary
Thread-Safe Cache & Index Refactoring
semantics/core/src/main/kotlin/com/github/albertocavalcante/gvy/semantics/db/GroovySemanticDB.kt
Replaced primitive Long counters (cacheHits, cacheMisses) with AtomicLong for thread-safe cache statistics. Updated all counter operations to use AtomicLong API (incrementAndGet, set, get). Switched symbolIndex and occurrenceIndex from Collections.synchronizedList to CopyOnWriteArrayList with explicit synchronization around modifications. Updated hit rate calculation and cache clearing logic accordingly.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #915: Related thread-safety improvements to GroovySemanticDB internal data structures, previously using Collections.synchronizedList for index synchronization.

Poem

🐰 Hops of joy for atomic grace,
Locks and waits now find their place,
Counters dance with thread-safe care,
Copy-on-write floats through the air,
Safe caches bound, no longer bare!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(concurrency): refine synchronization in GroovySemanticDB' directly addresses the main change: improving thread-safety through AtomicLong counters and CopyOnWriteArrayList usage.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/concurrency-bugs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @albertocavalcante, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request focuses on enhancing the concurrency and thread safety within the GroovySemanticDB class. The changes involve adopting more robust synchronization mechanisms for critical shared data structures, specifically by using atomic types for performance statistics and implementing more explicit thread-safe list management for internal indexes. This ensures reliable behavior and data integrity under concurrent access patterns.

Highlights

  • Thread-Safe Cache Statistics: The cacheHits and cacheMisses counters have been upgraded from simple Long variables to AtomicLong objects, ensuring thread-safe increments, resets, and reads in a concurrent environment.
  • Refined Index Synchronization: The buildIndexes method now utilizes CopyOnWriteArrayList for the symbol and occurrence indexes, coupled with explicit synchronized blocks during list additions, to enhance concurrency and prevent race conditions when modifying these shared lists.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request significantly improves the thread safety of GroovySemanticDB by introducing atomic counters for cache statistics and using concurrent collections for symbol and occurrence indexes. These changes are well-aligned with the goal of refining synchronization for a concurrent environment. My review includes a couple of suggestions to remove redundant synchronized blocks when adding to CopyOnWriteArrayList, which will simplify the code and improve performance without compromising thread safety.

Comment on lines +199 to +202
val list = symbolIndex.computeIfAbsent(symbol.symbol) { CopyOnWriteArrayList() }
synchronized(list) {
list.add(uri to symbol)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The synchronized(list) block is redundant here. CopyOnWriteArrayList is a thread-safe collection, and its add method is already internally synchronized. You can simplify this operation and make it more efficient by removing the explicit synchronized block.

Suggested change
val list = symbolIndex.computeIfAbsent(symbol.symbol) { CopyOnWriteArrayList() }
synchronized(list) {
list.add(uri to symbol)
}
symbolIndex.computeIfAbsent(symbol.symbol) { CopyOnWriteArrayList() }.add(uri to symbol)

Comment on lines +207 to +210
val list = occurrenceIndex.computeIfAbsent(occurrence.symbol) { CopyOnWriteArrayList() }
synchronized(list) {
list.add(uri to occurrence)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the symbolIndex update, the synchronized(list) block here is redundant because CopyOnWriteArrayList.add() is already thread-safe. Removing the unnecessary synchronization will simplify the code and avoid the overhead of double-locking.

Suggested change
val list = occurrenceIndex.computeIfAbsent(occurrence.symbol) { CopyOnWriteArrayList() }
synchronized(list) {
list.add(uri to occurrence)
}
occurrenceIndex.computeIfAbsent(occurrence.symbol) { CopyOnWriteArrayList() }.add(uri to occurrence)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens the concurrency behavior of GroovySemanticDB, focusing on making cache statistics updates thread-safe and refining how symbol/occurrence indexes are synchronized.

Changes:

  • Replaced non-atomic cache hit/miss counters with AtomicLong and updated all usages to be thread-safe.
  • Updated getStatistics() to read snapshot values from the atomic counters for consistent cache metrics.
  • Switched symbol and occurrence index list implementations to CopyOnWriteArrayList and adjusted index-building logic accordingly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +199 to +202
val list = symbolIndex.computeIfAbsent(symbol.symbol) { CopyOnWriteArrayList() }
synchronized(list) {
list.add(uri to symbol)
}
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching the index lists to CopyOnWriteArrayList while still wrapping all mutations and reads in synchronized(list) combines two synchronization mechanisms and incurs copy-on-write overhead on every add/remove without an accompanying gain in thread-safety. Since these lists are updated whenever documents are (re)indexed, this can negatively impact performance; consider either reverting to a simple mutable list guarded by synchronized(list) or simplifying the locking to rely on CopyOnWriteArrayList semantics with a different strategy for the compound "if empty then remove" operations.

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
semantics/core/src/main/kotlin/com/github/albertocavalcante/gvy/semantics/db/GroovySemanticDB.kt (1)

196-240: ⚠️ Potential issue | 🟠 Major

Fix race condition where list can be removed from index while being mutated.

computeIfAbsent() returns a list reference that another thread can remove from the map via removeFromIndexes before the add() executes. Subsequent mutations then occur on a list no longer in the map, silently losing index entries. Use ConcurrentHashMap.compute() to mutate the list and map atomically within a single operation.

🔧 Suggested atomic update pattern
 private fun buildIndexes(uri: URI, doc: SemanticDocument) {
     // Index symbols
     doc.symbols.forEach { symbol ->
-        val list = symbolIndex.computeIfAbsent(symbol.symbol) { CopyOnWriteArrayList() }
-        synchronized(list) {
-            list.add(uri to symbol)
-        }
+        symbolIndex.compute(symbol.symbol) { _, list ->
+            val updated = list ?: CopyOnWriteArrayList()
+            updated.add(uri to symbol)
+            updated
+        }
     }

     // Index occurrences
     doc.occurrences.forEach { occurrence ->
-        val list = occurrenceIndex.computeIfAbsent(occurrence.symbol) { CopyOnWriteArrayList() }
-        synchronized(list) {
-            list.add(uri to occurrence)
-        }
+        occurrenceIndex.compute(occurrence.symbol) { _, list ->
+            val updated = list ?: CopyOnWriteArrayList()
+            updated.add(uri to occurrence)
+            updated
+        }
     }
 }

 private fun removeFromIndexes(uri: URI, doc: SemanticDocument) {
     // Remove symbols from index and cache
     doc.symbols.forEach { symbol ->
-        symbolIndex[symbol.symbol]?.let { list ->
-            synchronized(list) {
-                list.removeIf { it.first == uri }
-                if (list.isEmpty()) {
-                    symbolIndex.remove(symbol.symbol, list)
-                    cacheLock.write { symbolCache.remove(symbol.symbol) }
-                }
-            }
-        }
+        var removed = false
+        symbolIndex.computeIfPresent(symbol.symbol) { _, list ->
+            list.removeIf { it.first == uri }
+            if (list.isEmpty()) {
+                removed = true
+                null
+            } else {
+                list
+            }
+        }
+        if (removed) {
+            cacheLock.write { symbolCache.remove(symbol.symbol) }
+        }
     }

     // Remove occurrences from index
     doc.occurrences.forEach { occurrence ->
-        occurrenceIndex[occurrence.symbol]?.let { list ->
-            synchronized(list) {
-                list.removeIf { it.first == uri }
-                if (list.isEmpty()) {
-                    occurrenceIndex.remove(occurrence.symbol, list)
-                }
-            }
-        }
+        occurrenceIndex.computeIfPresent(occurrence.symbol) { _, list ->
+            list.removeIf { it.first == uri }
+            if (list.isEmpty()) null else list
+        }
     }
 }

@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants