Skip to content

fix: prevent clipboard on disk file from growing on every load - #3720

Open
Feniksovich wants to merge 5 commits into
IntellectualSites:mainfrom
Feniksovich:fix/disk-clipboard-file-growth
Open

Feniksovich wants to merge 5 commits into
IntellectualSites:mainfrom
Feniksovich:fix/disk-clipboard-file-growth

Conversation

@Feniksovich

Copy link
Copy Markdown

Overview

Fixes #3719

A player's clipboard file on disk (clipboard/<UUID>.bd) grew every time the clipboard was loaded and closed, e.g. on every rejoin, if the clipboard contained tile entities or entities. After enough rejoins the file exceeded 2 GB (Integer.MAX_VALUE). From then on the clipboard could no longer be loaded, and every join logged:

Error loading clipboard from disk
java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: Size exceeds Integer.MAX_VALUE

Description

Actual behavior before fixes:

  1. loadNBTFromFileFooter passed the total file length as the size argument of FileChannel#map, instead of the footer length. The mapped region ran past the end of the file. Because the channel is writable, the JDK silently extended the file by the size of the block and biome data (this happens even with MapMode.READ_ONLY).
  2. writeNBTToDisk always appended the footer at the current end of the file. It never replaced the existing footer, and since the loader always reads the first footer, every footer appended after it was dead data.

This PR:

  • maps only the footer (braf.length() - biomeBlocksLength) when loading;
  • writes the footer directly after the block and biome data, overwriting the existing footer, and only extends the file if the new footer is larger.

Submitter Checklist

  • Make sure you are opening from a topic branch (/feature/fix/docs/ branch (right side)) and not your main branch.
  • Ensure that the pull request title represents the desired changelog entry.
  • New public fields and methods are annotated with @since TODO.
  • I read and followed the contribution guidelines.

loadNBTFromFileFooter passed the total file length as the size argument
of FileChannel#map, mapping a region past the end of the file. As the
channel is writable, this silently extended the file by the size of the
block and biome data on every load. On close, writeNBTToDisk then
appended a new NBT footer after the existing one.

As a result, clipboards containing tile entities or entities grew on
every rejoin until they exceeded Integer.MAX_VALUE bytes and could no
longer be loaded ("Size exceeds Integer.MAX_VALUE").

Map only the footer when loading and overwrite the existing footer when
saving.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Feniksovich
Feniksovich requested a review from a team as a code owner November 6, 2026 08:45
Copilot AI balanced review requested due to automatic review settings November 6, 2026 08:45

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@dordsor21 dordsor21 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Potentially worth not writing out the clipboard file again if we've not changed I guess as well

@Feniksovich

Copy link
Copy Markdown
Author

I've made close() skip rewriting the NBT footer if nothing changed since the clipboard was loaded. I'm not sure what the best way to do that check is tbh. Right now tile() sets a flag, and entities are compared with a snapshot taken at load time. The flag is also set in initBiome() because resizing the file there cuts off the existing footer. Overriding the entity methods would work too, but it means more overrides.

@Feniksovich
Feniksovich requested a review from dordsor21 November 7, 2026 14:03
@MattBDev

MattBDev commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

@Feniksovich Is this ready to be reviewed again? Just checking if everything you need to do is finished.

Copilot AI 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.

Note

Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.

🟡 Changes recommended

A moderate active issue remains in DiskOptimizedClipboard.java where memory-mapping uses potentially overflowing int-based offset math and an unchecked non-positive mapped length, which can trigger IllegalArgumentException when loading large or truncated clipboards.

1 open issue

Review effort: Balanced

Comment on lines 258 to +262
int biomeBlocksLength = headerSize + (getVolume() << 1) + (hasBiomes ? (int) biomeLength : 0);
MappedByteBuffer tmp = fileChannel.map(FileChannel.MapMode.READ_ONLY, biomeBlocksLength, braf.length());
// FileChannel#map takes a size, not an end position. Mapping past the end of the file would silently grow it.
MappedByteBuffer tmp = fileChannel.map(
FileChannel.MapMode.READ_ONLY,
biomeBlocksLength,
@Feniksovich

Copy link
Copy Markdown
Author

@Feniksovich Is this ready to be reviewed again? Just checking if everything you need to do is finished.

@MattBDev Yes I think so, should we add a guard precondition as Copilot suggested?

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.

Clipboard on disk file grows on every load until it exceeds 2 GB ("Size exceeds Integer.MAX_VALUE")

4 participants