Skip to content

Use a concurrent cache in CraftRegistry#13973

Open
electronicboy wants to merge 1 commit into
dev/26.2from
fix/craftregistry-concurrent-cache
Open

Use a concurrent cache in CraftRegistry#13973
electronicboy wants to merge 1 commit into
dev/26.2from
fix/craftregistry-concurrent-cache

Conversation

@electronicboy

@electronicboy electronicboy commented Jun 20, 2026

Copy link
Copy Markdown
Member

Problem

CraftRegistry#get lazily populates its backing cache, reading and then writing a plain HashMap with no synchronization.

This cache is reached off the main thread while building ItemMeta:

  • CraftItemStack#getItemMeta resolves the item's CraftItemType via CraftRegistry#get (to select the meta subclass), and
  • CraftMetaItem resolves the item's enchantments through CraftRegistry#get.

Plugins routinely build ItemMeta from other threads, so this map is read and written concurrently, which is unsafe for a HashMap.

Change

  • Switch cache to a ConcurrentHashMap.
  • Collapse the get()-then-put() sequence into a single atomic computeIfAbsent.
  • Make lockReferenceHolders volatile, since it is now read from the concurrent get() path and flipped once during startup.

Why this is safe for computeIfAbsent

ConcurrentHashMap#computeIfAbsent forbids the mapping function from updating the same map (recursive update → IllegalStateException/bin live-lock). The registry wrapper constructors registered in PaperRegistries uniformly just store their Holder and defer derived work via Suppliers.memoize(...); the mapping function only touches the NMS registry and Holder.Reference#createStandAlone, never this cache. So it never re-enters the map. Any cross-registry resolution a wrapper may perform later targets a different CraftRegistry instance (different map) and is lazy regardless.

@electronicboy electronicboy requested a review from a team as a code owner June 20, 2026 15:33
@github-project-automation github-project-automation Bot moved this to Awaiting review in Paper PR Queue Jun 20, 2026
@electronicboy electronicboy force-pushed the fix/craftregistry-concurrent-cache branch 2 times, most recently from 7b8d3b3 to 6e4e6fe Compare June 20, 2026 15:56
CraftRegistry#get lazily populates its backing cache, reading and then
writing a plain HashMap with no synchronization. That cache is reached
off the main thread while building ItemMeta: CraftItemStack#getItemMeta
resolves the item's CraftItemType via CraftRegistry#get to select the
meta subclass, and CraftMetaItem resolves the item's enchantments
through CraftRegistry#get as well. Plugins build ItemMeta from other
threads, so this map is read and written concurrently, which is unsafe
for a HashMap.

Switch the cache to a ConcurrentHashMap and collapse the read-then-put
into an atomic computeIfAbsent. This is safe against computeIfAbsent's
recursive-update restriction: the wrapper constructors only store their
holder and defer derived work lazily, so the mapping function never
re-enters this cache. lockReferenceHolders is made volatile as it is now
read from the concurrent path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

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 hardens CraftRegistry#get for off-main-thread access by making its lazy cache population thread-safe, addressing unsafe concurrent reads/writes to a plain HashMap when plugins build ItemMeta asynchronously.

Changes:

  • Replace the backing cache with a ConcurrentHashMap.
  • Use computeIfAbsent to atomically load and cache registry entries.
  • Make lockReferenceHolders volatile to ensure cross-thread visibility from the get() path.

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

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

Labels

None yet

Projects

Status: Awaiting review

Development

Successfully merging this pull request may close these issues.

2 participants