Skip to content

Fix ConcurrentMapNative snapshot views to avoid CME during iteration#5504

Open
dapzthelegend wants to merge 3 commits intoktorio:mainfrom
dapzthelegend:fix/concurrent-map-entries-5480
Open

Fix ConcurrentMapNative snapshot views to avoid CME during iteration#5504
dapzthelegend wants to merge 3 commits intoktorio:mainfrom
dapzthelegend:fix/concurrent-map-entries-5480

Conversation

@dapzthelegend
Copy link
Copy Markdown
Contributor

Summary

Fixes #5480 by returning detached snapshot collections from ConcurrentMapNative view properties.

  • entries now returns a copied set of detached mutable entries
  • keys now returns a copied set
  • values now returns a copied list

This prevents iterating over map views from throwing ConcurrentModificationException when the backing map is mutated concurrently or during teardown flows.

Tests

Added ConcurrentMapNativeTest under ktor-utils/posix/test:

  • entries returns snapshot
  • keys returns snapshot
  • values returns snapshot

Verification

  • ./gradlew :ktor-utils:formatKotlin
  • ./gradlew :ktor-utils:lintKotlin
  • ./gradlew :ktor-utils:assemble
  • ./gradlew :ktor-utils:jvmTest :ktor-utils:macosArm64Test (macosArm64Test is build-gated and skipped by task config)
  • ./gradlew :ktor-utils:linkDebugTestMacosX64
  • ./ktor-utils/build/bin/macosX64/debugTest/test.kexe
  • ./ktor-utils/build/bin/macosX64/debugTest/test.kexe '--ktest_filter=io.ktor.util.collections.ConcurrentMapNativeTest.*'

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 086dd5e5-1b06-4cdf-b06c-81b6b55e7067

📥 Commits

Reviewing files that changed from the base of the PR and between a877d51 and eba9626.

📒 Files selected for processing (1)
  • ktor-utils/posix/src/io/ktor/util/collections/ConcurrentMapNative.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • ktor-utils/posix/src/io/ktor/util/collections/ConcurrentMapNative.kt

📝 Walkthrough

Walkthrough

The posix ConcurrentMap implementation now returns detached, synchronized snapshots for entries, keys, and values rather than returning backing live views. A private DetachedMutableEntry was added to hold a fixed key and a mutable per-entry value. Tests verifying snapshot semantics and entry-based contains/remove were added.

Changes

Cohort / File(s) Summary
ConcurrentMap Snapshot Semantics
ktor-utils/posix/src/io/ktor/util/collections/ConcurrentMapNative.kt
Reworked collection view accessors: entries builds a LinkedHashSet of new DetachedMutableEntry instances (detached snapshot), keys returns a synchronized LinkedHashSet copy of delegate.keys, and values returns a synchronized ArrayList copy of delegate.values. Added private DetachedMutableEntry<K,V> storing a fixed key and a mutable currentValue; setValue updates only the detached instance while equals/hashCode use key and current value.
ConcurrentMap Snapshot Tests
ktor-utils/posix/test/io/ktor/util/collections/ConcurrentMapNativeTest.kt
Added ConcurrentMapNativeTest with tests asserting that entries, keys, and values return snapshots immune to subsequent map mutations, and that the entries snapshot supports value-based contains and remove using a TestMutableEntry helper (equality/hash based on key+value).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing ConcurrentMapNative snapshot views to prevent ConcurrentModificationException during iteration.
Description check ✅ Passed The description follows the template with clear Subsystem, Motivation (referencing #5480), Solution, Tests, and Verification sections.
Linked Issues check ✅ Passed The PR fully addresses issue #5480 by returning detached snapshot collections from entries, keys, and values properties to prevent ConcurrentModificationException during iteration.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing ConcurrentMapNative snapshot views; no unrelated modifications are present in the PR.

✏️ 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.

Copy link
Copy Markdown
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: 2

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

Inline comments:
In `@ktor-utils/posix/src/io/ktor/util/collections/ConcurrentMapNative.kt`:
- Around line 45-54: The Posix implementation in ConcurrentMapNative.kt
currently returns snapshot collections for entries/keys/values (using
LinkedHashSet/ArrayList) which diverges from JVM/Web live-view semantics; change
entries, keys and values to return synchronized live views that proxy mutations
to the underlying delegate map instead of snapshots: implement small wrapper
classes (e.g., an AbstractMutableSet for entries that delegates
contains/add/remove/iterator/size operations to delegate inside
synchronized(lock), similarly for keys and a MutableCollection for values) and
reuse DetachedMutableEntry only for iterator elements if needed so mutation
methods (remove/add/clear) affect delegate; keep synchronization via the
existing lock and ensure iterator.remove and entry.setValue update the
underlying delegate.
- Around line 88-102: DetachedMutableEntry currently only holds key/value and
doesn't override equals/hashCode, causing entry-set containers (e.g., the
LinkedHashSet returned by entries) to use reference equality; implement
value-based equality by overriding equals(other: Any?) and hashCode() on
DetachedMutableEntry to compare key and value (consistent with
MutableMap.MutableEntry contract) and update setValue to not change equality
semantics, then add tests asserting entries.contains and entries.remove work for
distinct entry instances with equal key/value pairs (verifying entry equality
and hash behavior).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cdb14083-bf9d-44b5-95af-3bae8446bb7a

📥 Commits

Reviewing files that changed from the base of the PR and between dd95513 and 3a20b01.

📒 Files selected for processing (2)
  • ktor-utils/posix/src/io/ktor/util/collections/ConcurrentMapNative.kt
  • ktor-utils/posix/test/io/ktor/util/collections/ConcurrentMapNativeTest.kt

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Copy link
Copy Markdown
Contributor Author

Follow-up update pushed for the CodeRabbit review.

  • Commit: a877d51
  • Change: DetachedMutableEntry now implements value-based equals/hashCode (Map.Entry contract).
  • Test: added entries snapshot supports value-based contains and remove in ConcurrentMapNativeTest.

Validation:

  • ./gradlew :ktor-utils:linkDebugTestMacosX64
  • ./ktor-utils/build/bin/macosX64/debugTest/test.kexe '--ktest_filter=io.ktor.util.collections.ConcurrentMapNativeTest.*'
  • Result: 4/4 filtered tests passed.

I also replied in-thread on the snapshot-view comment with rationale for keeping Native snapshot semantics to avoid reintroducing the #5480 CME path.

Copy link
Copy Markdown
Contributor Author

Follow-up update pushed to close the remaining snapshot-semantics thread.

  • Commit: eba9626
  • Change: added an inline rationale on ConcurrentMapNative.entries documenting why Native intentionally returns detached snapshots (#5480) instead of live views.
  • Behavior: no runtime behavior change; this is documentation-only to prevent future cross-platform drift confusion.

Validation:

  • ./gradlew :ktor-utils:compileKotlinMacosArm64 --no-daemon
  • Result: BUILD SUCCESSFUL

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.

ConcurrentMapNative.entries returns raw delegate, causing ConcurrentModificationException during iteration

1 participant