Repository navigation
Fix ConcurrentModificationException in MonsterInformationProvider caches - #352
Open
asaf-shitrit wants to merge 1 commit into
Open
asaf-shitrit wants to merge 1 commit into
asaf-shitrit wants to merge 1 commit into
Conversation
continentDrops was a plain HashMap accessed via computeIfAbsent from getRelevantGlobalDrops, which runs on channel threads during monster death. Concurrent kills on maps in the same continent race on the same map entry and can throw ConcurrentModificationException. That call sits at the top of MapleMap.dropFromMonster, before any item is created, so the exception aborts the whole drop routine and the monster's drops are silently lost. mobAttackInfo, mobBossCache and mobNameCache are the same pattern (lazy caches populated from packet handlers on channel threads), so they get the same treatment. loadContinentDrops doesn't mutate the map it's called from, so switching to ConcurrentHashMap is a safe drop-in fix. Found while running multiple automated bot clients killing monsters concurrently, much faster than human play - which is why this is rarely seen in normal play.
MinatoNami
added a commit
to MinatoNami/Cosmic-AI
that referenced
this pull request
Sep 20, 2026
…current-map #patch
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.
The bug
MonsterInformationProvider.continentDropsis a plainHashMap, populated lazily viacomputeIfAbsentingetRelevantGlobalDrops:This is called from
MapleMap.dropFromMonster, which runs on channel threads during monster-death handling (killMonster->damageMonster->dropFromMonster). When monsters in the same continent die concurrently on different channel threads, they race oncomputeIfAbsentfor the samecontinentIdkey, and a plainHashMapisn't safe for concurrent structural modification:Why it matters: the
getRelevantGlobalDropscall sits near the top ofdropFromMonster, before any drop item is created. When it throws, the exception propagates out of the whole method and aborts the drop routine entirely - the monster's normal drops, quest drops, and global drops are all silently lost. Nothing rolls back or retries; the only trace is the stack trace in the log.Sibling fields
mobAttackInfo,mobBossCache, andmobNameCacheare the same pattern: plainHashMaps used as lazy caches, read and written from packet handlers on channel threads (Monsterattack info lookups,isBoss()checks,getMobNameFromIdfrom mob-vs-mob damage handlers). They have the same concurrent-access exposure ascontinentDrops, so they're fixed the same way here.Other
HashMapfields in the same class (drops,dropsChancePool,extraMultiEquipDrops,mobAttackAnimationTime,mobSkillAnimationTime) were left alone - out of scope for this fix, and would need their own look.The fix
Switch the four affected fields to
ConcurrentHashMap.loadContinentDropsonly readsglobaldropsand doesn't mutatecontinentDropsitself, socomputeIfAbsenton aConcurrentHashMapis safe here (no re-entrant modification).clearDrops()'scontinentDrops.clear()still works the same way.How this was found
Found while running multiple automated bot clients that killed monsters concurrently, much faster than a human plays - over 8 minutes of continuous concurrent kills, not a single expected drop appeared. That's also why this is rarely seen in normal play: it needs genuinely concurrent kills in the same continent to race.
Testing
mvn compilesucceedsmvn testpasses (no existing tests cover this class)