Skip to content

bug(#4891): synchronized block replacement with ReentrantLock in StrictXmir#4895

Merged
yegor256 merged 1 commit into
objectionary:masterfrom
h1alexbel:4891
Mar 22, 2026
Merged

bug(#4891): synchronized block replacement with ReentrantLock in StrictXmir#4895
yegor256 merged 1 commit into
objectionary:masterfrom
h1alexbel:4891

Conversation

@h1alexbel

@h1alexbel h1alexbel commented Feb 20, 2026

Copy link
Copy Markdown
Member

see #4891

Summary by CodeRabbit

  • Refactor
    • Enhanced thread-safety mechanisms in the parser core for improved performance and stability.
    • Simplified internal method signatures and streamlined call flows for better maintainability.

@coderabbitai

coderabbitai Bot commented Feb 20, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The StrictXmir class synchronization mechanism is refactored to use a static reentrant lock instead of per-call synchronization blocks. The tmp parameter is removed from fetch, copied, and downloaded methods, simplifying their signatures. Related imports and exception handling are updated accordingly.

Changes

Cohort / File(s) Summary
Synchronization Refactoring
eo-parser/src/main/java/org/eolang/parser/StrictXmir.java
Replaced per-call synchronized blocks with a global ReentrantLock (LOCK); removed tmp parameter from fetch, copied, and downloaded methods; updated method signatures and call sites; adjusted reset method to work with new fetch signature; updated SuppressWarnings annotations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A lock so grand, reentrant and true,
Replaced the old synchronization brew,
Method calls lighter, parameters few,
Cleaner and simpler—a rabbit's review! 🔐

🚥 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 accurately describes the main change: replacing synchronized blocks with ReentrantLock in StrictXmir class, which is the primary objective of this pull request.
Docstring Coverage ✅ Passed Docstring coverage is 100.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 (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Performance Analysis

All benchmarks are within the acceptable range. No critical degradation detected (threshold is 100%). Please refer to the detailed report for more information.

Click to see the detailed report
Test Base Score PR Score Change % Change Unit Mode
benchmarks.XmirBench.xmirToEO 173.016 177.908 4.892 2.83% ms/op Average Time

⚠️ Performance loss: benchmarks.XmirBench.xmirToEO is slower by 4.892 ms/op (2.83%)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
eo-parser/src/main/java/org/eolang/parser/StrictXmir.java (2)

80-85: ⚠️ Potential issue | 🟡 Minor

Stale Javadoc on second constructor

The comment "Synchronization by XML is necessary…" no longer describes the actual mechanism. Per-instance thread safety is now handled by Synced<>(Sticky<>(...)), and cross-instance file-write synchronization is via the static LOCK — not by locking on the XML object.

📝 Proposed fix
-    /**
-     * Ctor.
-     * Synchronization by XML is necessary in case we're trying to validate the same
-     * {`@link` XML} in multiple threads. In such case the path to XSD scheme inside XML should
-     * be updated only once.
-     * `@param` before The XML source
-     * `@param` tmp The directory with cached XSD files
-     */
+    /**
+     * Ctor.
+     * Uses a static lock to ensure that XSD files are written to the cache directory
+     * only once, even when multiple threads construct {`@link` StrictXmir} concurrently.
+     * `@param` before The XML source
+     * `@param` tmp The directory with cached XSD files
+     */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eo-parser/src/main/java/org/eolang/parser/StrictXmir.java` around lines 80 -
85, The Javadoc for the second StrictXmir constructor is stale: it says
synchronization is done "by XML" but the code now uses Synced<>(Sticky<>(...))
for per-instance safety and a static LOCK for cross-instance file writes; update
the comment on the constructor in class StrictXmir to describe the current
mechanism (per-instance thread safety via Synced and Sticky wrappers and
cross-instance file-write synchronization via the static LOCK) and remove the
reference to locking on the XML object, mentioning the relevant symbols Synced,
Sticky and LOCK and the XML parameter name to clarify behavior.

211-238: ⚠️ Potential issue | 🟠 Major

Global static lock held during blocking I/O (disk write and network download)

LOCK is a single static instance shared across all StrictXmir instances. In downloaded, it is held for the entire duration of the network fetch — including up to three retry attempts, each of which can block for seconds. During that window, every other thread that calls either copied or downloaded (even for a completely unrelated XSD file) is queued behind the same lock.

The standard fix is a double-checked locking pattern around the I/O section. Because File.exists() queries the OS filesystem (which provides its own cross-thread visibility guarantee), checking it outside the lock is safe — there is no partial-object publication hazard here (unlike Java object references).

⚡ Proposed fix: double-checked locking in both methods
 private static File copied(final String uri, final Path path) {
     final File file = path.toFile();
-    StrictXmir.LOCK.lock();
-    try {
-        if (!file.exists()) {
+    if (!file.exists()) {
+        StrictXmir.LOCK.lock();
+        try {
+          if (!file.exists()) {
             if (file.getParentFile().mkdirs()) {
                 Logger.debug(StrictXmir.class, "Directory for %[file]s created", path);
             }
             try {
                 Files.write(
                     path,
                     new IoCheckedBytes(
                         new BytesOf(new ResourceOf("XMIR.xsd"))
                     ).asBytes()
                 );
                 Logger.debug(StrictXmir.class, "XSD copied to %[file]s", path);
             } catch (final IOException ex) {
                 throw new IllegalArgumentException(
                     String.format("Failed to save %s to %s", uri, path),
                     ex
                 );
             }
+          }
+        } finally {
+            StrictXmir.LOCK.unlock();
         }
-    } finally {
-        StrictXmir.LOCK.unlock();
     }
     return file;
 }
 private static File downloaded(final String uri, final Path path) {
     final File abs = path.toFile().getAbsoluteFile();
-    StrictXmir.LOCK.lock();
-    try {
-        if (!abs.exists()) {
+    if (!abs.exists()) {
+        StrictXmir.LOCK.lock();
+        try {
+          if (!abs.exists()) {
             // ... directory creation, retry loop ...
+          }
+        } finally {
+            StrictXmir.LOCK.unlock();
         }
-    } finally {
-        StrictXmir.LOCK.unlock();
     }
     return abs;
 }

After the first successful write, all subsequent callers skip the lock entirely on the fast-path exists() check, eliminating contention for the common case.

Also applies to: 247-298

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eo-parser/src/main/java/org/eolang/parser/StrictXmir.java` around lines 211 -
238, The code currently holds StrictXmir.LOCK for the entire blocking I/O
operation in copied (and similarly in downloaded), which serializes unrelated
threads; change both copied and downloaded to use double-checked locking:
perform a fast-path check like if (file.exists()) return file before acquiring
StrictXmir.LOCK, then inside the lock re-check file.exists() and only perform
the write/download when it still does not exist; keep the existing try/finally
that unlocks LOCK and preserve the existing exception handling and logging but
ensure the lock only wraps the minimal critical section that creates/writes the
file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@eo-parser/src/main/java/org/eolang/parser/StrictXmir.java`:
- Around line 80-85: The Javadoc for the second StrictXmir constructor is stale:
it says synchronization is done "by XML" but the code now uses
Synced<>(Sticky<>(...)) for per-instance safety and a static LOCK for
cross-instance file writes; update the comment on the constructor in class
StrictXmir to describe the current mechanism (per-instance thread safety via
Synced and Sticky wrappers and cross-instance file-write synchronization via the
static LOCK) and remove the reference to locking on the XML object, mentioning
the relevant symbols Synced, Sticky and LOCK and the XML parameter name to
clarify behavior.
- Around line 211-238: The code currently holds StrictXmir.LOCK for the entire
blocking I/O operation in copied (and similarly in downloaded), which serializes
unrelated threads; change both copied and downloaded to use double-checked
locking: perform a fast-path check like if (file.exists()) return file before
acquiring StrictXmir.LOCK, then inside the lock re-check file.exists() and only
perform the write/download when it still does not exist; keep the existing
try/finally that unlocks LOCK and preserve the existing exception handling and
logging but ensure the lock only wraps the minimal critical section that
creates/writes the file.

@h1alexbel

Copy link
Copy Markdown
Member Author

@maxonfjvipon please check

@maxonfjvipon

Copy link
Copy Markdown
Member

@yegor256 please check

@yegor256 yegor256 merged commit 8f9ab28 into objectionary:master Mar 22, 2026
26 checks passed
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.

3 participants