Do not put texture-less profiles on player heads - #3063
Merged
Conversation
A PlayerProfile that has a UUID and a name but no textures property makes the server resolve it against the Mojang session server every time the head is shown. On a top ten panel that is ten lookups per open, which quickly returns HTTP 429 and still renders a default skin. HeadGetter created exactly that profile whenever its own texture fetch failed, and handed it to requesters anyway: the "only if the texture is usable" check tested for a null profile, which createProfile never returns. The failed lookup also overwrote any good cached entry, so a single rate limited call cost a working head for the whole cache period and kept the loop running. - HeadCache gains hasTexture() and only calls setOwnerProfile when a skin is actually known, so a failed lookup yields a plain head instead of one the server keeps trying to resolve. - HeadGetter only notifies requesters when the texture is usable, and no longer lets a failed fetch evict a cached head that has one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014HtnKzNE649pBrCdqMm7nw
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Problem
Opening a panel full of player heads (a top ten, for example) can spam the console with:
Those lookups are made by the server, not by us. A
PlayerProfilethat carries a UUID and a name but notexturesproperty is incomplete, so the server resolves it against the Mojang session server every time the head is shown — see PaperMC/Paper#13727 and the discussion on PaperMC/Paper#13692. Ten heads in a panel is ten lookups per open, which reaches HTTP 429 quickly and still renders a default skin at the end of it.HeadGetterproduces precisely that profile whenever its own texture fetch comes back empty:createProfile()always returns a profile, with or without a skin, andgetTextureFromUUID()swallows every failure and returnsnull. It then hands the result to requesters regardless, because the check guarding delivery —— tests for a null profile, which
createProfile()never returns. The comment describes an intent the code never implemented.The failed lookup was also written straight over the cache, so one rate-limited call replaced a working head with a broken one for the whole cache period. Since the broken profile then triggers a fresh server-side lookup on every panel open, the failure sustains itself.
Changes
HeadCachegainshasTexture(), andgetPlayerHead()only callssetOwnerProfile()when a skin is actually known. With no profile attached there is nothing for the server to resolve, so a failed lookup degrades to a plain head instead of one that keeps hitting Mojang.HeadGetteronly notifies requesters when the texture is usable, making the existing comment true, and no longer lets a failed fetch evict a cached head that already has a texture.No API is removed;
hasTexture()is additive.Testing
./gradlew compileJavais clean andPanelTest, which exercisesHeadGetter, passes. Other*PanelTestclasses in the tree fail identically before and after this change (UnfinishedStubbingExceptionduring static init), unrelated to heads.Note for server owners
Setting
use-cache-server: trueis still worth doing independently. The defaults (heads-per-call: 9,ticks-between-calls: 10) allow roughly 18 direct requests per second tosessionserver.mojang.comfrom BentoBox's own fetcher, which is enough to trip the rate limit that starts this cycle.🤖 Generated with Claude Code
https://claude.ai/code/session_014HtnKzNE649pBrCdqMm7nw