Skip to content

Use a concurrent cache in CraftRegistry - #13973

Open
electronicboy wants to merge 1 commit into
mainfrom
fix/craftregistry-concurrent-cache
Open

Use a concurrent cache in CraftRegistry#13973
electronicboy wants to merge 1 commit into
mainfrom
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 3 times, most recently from 6e4e6fe to 61ed18e Compare June 20, 2026 16:02
@electronicboy
electronicboy requested a review from Copilot June 20, 2026 16:29

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.

Base automatically changed from dev/26.2 to main June 28, 2026 19:57
@Warriorrrr
Warriorrrr force-pushed the fix/craftregistry-concurrent-cache branch from 61ed18e to 25b565b Compare July 28, 2026 20:23
@papermc-projects papermc-projects Bot moved this from Awaiting review to Delayed approval in Paper PR Queue Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Delayed approval

Development

Successfully merging this pull request may close these issues.

3 participants