Skip to content

fix(#4904): synchronized replaced with lock in Files#4941

Open
eshabakhov wants to merge 5 commits intoobjectionary:masterfrom
eshabakhov:#4904
Open

fix(#4904): synchronized replaced with lock in Files#4941
eshabakhov wants to merge 5 commits intoobjectionary:masterfrom
eshabakhov:#4904

Conversation

@eshabakhov
Copy link

@eshabakhov eshabakhov commented Mar 24, 2026

Summary

This PR solves #4904

Changes

  • synchronized statements replaced with lock in Files.java

Test

Run the following commands to verify:

# Compile and run tests
mvn clean install -pl eo-runtime -am

Summary by CodeRabbit

  • Refactor
    • Improved internal synchronization for file handling by replacing intrinsic synchronization with explicit locking to strengthen thread-safety.
    • Ensures locks are always released on error to improve reliability of concurrent operations.
    • Updated an error message for write failures for clearer diagnostics.
    • No changes to public interfaces or external behavior.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaced synchronized access to the streams map in Files with a dedicated ReentrantLock (initialized in the constructor) and used lock.lock() / finally { lock.unlock(); } in read, write, and close. Adjusted the write error message to "can't write".

Changes

Cohort / File(s) Summary
Lock-Based Synchronization Refactor
eo-runtime/src/main/java/EOorg/EOeolang/EOfs/Files.java
Replaced synchronized (this.streams) with a private ReentrantLock field; initialized lock in constructor; wrapped critical sections in read, write, and close with lock.lock() / finally { lock.unlock(); }; updated write error message to "can't write".

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • maxonfjvipon

Poem

🐇
I nudged the streams with gentle care,
Took out the sync and placed a snare.
lock.lock() I held so tight,
finally unlock — all sleeps tonight.
Hoppity code, now neat and fair.

🚥 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 a ReentrantLock in the Files class, directly addressing issue #4904.
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 unit tests (beta)
  • Create PR with unit tests

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.

@eshabakhov eshabakhov changed the title fix(objectionary#4904): synchronized replaced with lock in Files fix(#4904): synchronized replaced with lock in Files Mar 24, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 24, 2026

🚀 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 221.260 195.217 -26.043 -11.77% ms/op Average Time

✅ Performance gain: benchmarks.XmirBench.xmirToEO is faster by 26.043 ms/op (11.77%)

Copy link
Contributor

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@eo-runtime/src/main/java/EOorg/EOeolang/EOfs/Files.java`:
- Line 21: The Files class declares a field as "Lock lock" but only imports
ReentrantLock, causing a compilation error; fix by either adding the missing
import "java.util.concurrent.locks.Lock" or change the field declaration to use
"ReentrantLock lock" so the type matches the existing import; update the
declaration in the Files class where the field "lock" is defined to use the
chosen type consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2e1d29e6-89d8-4e11-8822-d014c7922f9f

📥 Commits

Reviewing files that changed from the base of the PR and between 51411b6 and 323f12c.

📒 Files selected for processing (1)
  • eo-runtime/src/main/java/EOorg/EOeolang/EOfs/Files.java

@eshabakhov eshabakhov marked this pull request as draft March 24, 2026 16:57
@eshabakhov eshabakhov marked this pull request as ready for review March 24, 2026 16:58
@eshabakhov eshabakhov closed this Mar 24, 2026
@eshabakhov eshabakhov reopened this Mar 24, 2026
Copy link
Contributor

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@eo-runtime/src/main/java/EOorg/EOeolang/EOfs/Files.java`:
- Around line 112-115: In the Files.write code path where it checks
this.streams.containsKey(name) and throws new ExFailure(..., name), update the
error message string to say "File output stream with name %s is absent, can't
write" (replace "can't read" with "can't write") so the ExFailure thrown by the
write method accurately reflects the operation and uses the same ExFailure
constructor call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d6b652c8-6150-41e5-a0b1-7ead309a6e14

📥 Commits

Reviewing files that changed from the base of the PR and between aa08515 and bb9b94d.

📒 Files selected for processing (1)
  • eo-runtime/src/main/java/EOorg/EOeolang/EOfs/Files.java

Copy link
Contributor

@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: 1

🧹 Nitpick comments (2)
eo-runtime/src/main/java/EOorg/EOeolang/EOfs/Files.java (2)

29-35: Remove resolved TODO and obsolete suppression.

@todo #4884`` is already implemented, and @SuppressWarnings("PMD.AvoidSynchronizedStatement") is now stale. Keeping both is misleading and weakens future static checks.

🧹 Suggested cleanup
- * `@todo` `#4884`:30min Use ReentrantLock instead of 'synchronized' in Files.
- *  We should use ReentrantLock instead of 'synchronized' to avoid potential
- *  deadlocks when multiple AtOnce attributes are used together.
- *  Moreover, 'synchronized' keyword is forbidden by qulice.
  */
-@SuppressWarnings("PMD.AvoidSynchronizedStatement")
 final class Files {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eo-runtime/src/main/java/EOorg/EOeolang/EOfs/Files.java` around lines 29 -
35, Remove the now-resolved `@todo` and the obsolete suppression: delete the
comment block containing "@todo `#4884`:30min Use ReentrantLock instead of
'synchronized' in Files." and remove the
`@SuppressWarnings`("PMD.AvoidSynchronizedStatement") annotation from the Files
class declaration so the code no longer contains stale TODOs or misleading
suppression; ensure the class declaration reads simply "final class Files {"
(leave other code unchanged).

86-148: Add concurrent regression tests for the new lock-based behavior.

This PR changes synchronization semantics, but eo-runtime/src/test/java/EOorg/EOeolang/EOfs/FilesTest.java currently validates only single-threaded flows. Please add tests for concurrent read/write/close interleavings to protect this change from regressions.

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

In `@eo-runtime/src/main/java/EOorg/EOeolang/EOfs/Files.java` around lines 86 -
148, The Files class introduced lock-based synchronization for stream operations
(methods read, write, close) but lacks concurrent tests; add multithreaded
regression tests in EOorg/EOeolang/EOfs/FilesTest to exercise interleavings of
Files.read, Files.write, and Files.close (and opening if needed) under
concurrent load, asserting correctness and absence of races/IOExceptions where
appropriate; use multiple threads (Executors/Futures), coordinate with
latches/barriers to force interleavings, perform repeated iterations to catch
flakiness, and include tests that ensure closed streams cause expected ExFailure
while concurrent reads/writes produce consistent data to prevent regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@eo-runtime/src/main/java/EOorg/EOeolang/EOfs/Files.java`:
- Around line 55-58: open() currently mutates this.streams without acquiring
this.lock while read(), write(), and close() do acquire the lock, causing a race
that can create duplicate streams; modify open() to follow the same lock pattern
as read()/write()/close(): acquire this.lock before checking/using this.streams,
verify the key is still absent (or present as appropriate) before creating and
putting new InputStream/OutputStream entries via putIfAbsent, and ensure the
lock is released in a finally block so only one thread can create streams for a
given key and avoid leaking resources.

---

Nitpick comments:
In `@eo-runtime/src/main/java/EOorg/EOeolang/EOfs/Files.java`:
- Around line 29-35: Remove the now-resolved `@todo` and the obsolete suppression:
delete the comment block containing "@todo `#4884`:30min Use ReentrantLock instead
of 'synchronized' in Files." and remove the
`@SuppressWarnings`("PMD.AvoidSynchronizedStatement") annotation from the Files
class declaration so the code no longer contains stale TODOs or misleading
suppression; ensure the class declaration reads simply "final class Files {"
(leave other code unchanged).
- Around line 86-148: The Files class introduced lock-based synchronization for
stream operations (methods read, write, close) but lacks concurrent tests; add
multithreaded regression tests in EOorg/EOeolang/EOfs/FilesTest to exercise
interleavings of Files.read, Files.write, and Files.close (and opening if
needed) under concurrent load, asserting correctness and absence of
races/IOExceptions where appropriate; use multiple threads (Executors/Futures),
coordinate with latches/barriers to force interleavings, perform repeated
iterations to catch flakiness, and include tests that ensure closed streams
cause expected ExFailure while concurrent reads/writes produce consistent data
to prevent regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 051621bb-ccf6-4051-a9cd-49bf803a53fe

📥 Commits

Reviewing files that changed from the base of the PR and between 09a6ea9 and fb4b1dc.

📒 Files selected for processing (1)
  • eo-runtime/src/main/java/EOorg/EOeolang/EOfs/Files.java

Comment on lines 55 to 58
private Files() {
this.streams = new ConcurrentHashMap<>(0);
this.lock = new ReentrantLock();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify lock usage consistency across Files methods (read-only).
fd 'Files.java$' -t f | while read -r file; do
  echo "=== $file ==="
  rg -n -C2 'void open\(|void read\(|void write\(|void close\(|this\.lock\.lock\(' "$file"
done

Repository: objectionary/eo

Length of output: 1124


Guard open() with the same lock to keep synchronization consistent.

read(), write(), and close() now acquire this.lock, but open() mutates this.streams without it. This creates a race condition: multiple threads can simultaneously check putIfAbsent(), both open the file streams, and leak resources. Wrap open() in the same lock pattern and verify the key exists before creating streams.

🔧 Suggested fix
 void open(final String name) throws IOException {
-    final Path path = Paths.get(name);
-    this.streams.putIfAbsent(
-        name,
-        new Object[]{
-            java.nio.file.Files.newInputStream(path),
-            java.nio.file.Files.newOutputStream(path, StandardOpenOption.APPEND),
-        }
-    );
+    this.lock.lock();
+    try {
+        if (this.streams.containsKey(name)) {
+            return;
+        }
+        final Path path = Paths.get(name);
+        this.streams.put(
+            name,
+            new Object[]{
+                java.nio.file.Files.newInputStream(path),
+                java.nio.file.Files.newOutputStream(path, StandardOpenOption.APPEND),
+            }
+        );
+    } finally {
+        this.lock.unlock();
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eo-runtime/src/main/java/EOorg/EOeolang/EOfs/Files.java` around lines 55 -
58, open() currently mutates this.streams without acquiring this.lock while
read(), write(), and close() do acquire the lock, causing a race that can create
duplicate streams; modify open() to follow the same lock pattern as
read()/write()/close(): acquire this.lock before checking/using this.streams,
verify the key is still absent (or present as appropriate) before creating and
putting new InputStream/OutputStream entries via putIfAbsent, and ensure the
lock is released in a finally block so only one thread can create streams for a
given key and avoid leaking resources.

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.

1 participant