PasteOperation deadlock — Folia region thread bloqué indéfiniment #1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Symptôme
Le serveur
mc-creaclonefreeze progressivement : les régions Folia cessent de ticker une à une. Le Folia Watchdog remonte des erreurs du type :Dans le stacktrace, le thread est bloqué sur
Semaphore.acquire()dansPasteOperation.acquireSlot()(ligne 630). Le sémaphore n'est jamais relâché → le thread scheduler de la région attend indéfiniment.Le bug est cumulatif : plusieurs régions peuvent être affectées simultanément (Overworld + Nether observés). Une région Overworld était bloquée depuis 88h sans que le serveur ne crash.
Impact
Serveur injoinable, nécessite un restart manuel.
À investiguer
release()du sémaphore dansPasteOperationacquireSlotet lereleasecorrespondant → ajouter untry/finallymc-creaclone(preprod)Références
fr.ekaii.litematica.paste.PasteOperation.acquireSlot(ligne 630)fr.ekaii.litematica.paste.FoliaCompat.runOnRegion(lignes 58, 65)Corrigé en v0.4.2 ✅
Oui, c'était encore le bug. Le jar déployé sur
mc-creacloneétait toujours le 0.4.1 vulnérable. Les logs le confirment :has not responded/jour) du 28/05 au 11/06, puis restart manuel le 11/06 à 18:33 — une région Overworld était bloquée 419 740 s ≈ 116 h.PasteOperation.java:293(pass 2) et:345(balayage physique) — pas la boucle pass 1.C'était dormant uniquement parce qu'aucun gros paste n'a re-déclenché depuis le restart — le code vulnérable tournait toujours.
Cause racine
Le fix v0.4.1 n'avait déplacé hors du thread de région que la boucle de pass 1. Les boucles pass 2 (
pass1All.thenCompose(...)) et balayage physique différé (pass2All.thenCompose(...)) utilisaient unthenComposesimple (non-async) → leur corps s'exécute sur le thread qui a complété la future amont, laquelle est complétée pardone.complete(null)à l'intérieur deFoliaCompat.runOnRegion, donc sur un thread de tick de région Folia.Ces boucles appelaient alors le
acquireSlot()bloquant sur ce thread de région. Quand la même région avait plus de tâches-chunk en attente que de permitsCHUNK_THROTTLElibres (32), elle se garait dansSemaphore.acquire()en attendant des permits que seule elle pouvait libérer en exécutant ses propres tâches en file → auto-deadlock. Cumulatif (Overworld + Nether), et le Watchdog log sans jamais crasher → région gelée indéfiniment.Correctif (deux couches)
thenComposeAsync(..., offRegionExecutor)(pool async Folia / worker async Paper). ChaqueacquireSlot()tourne sur un worker async, jamais un thread de région → un thread bloqué ne peut plus geler une région.acquireSlot()utilisetryAcquire(60 s)et renvoie si un permit a réellement été pris. En cas de timeout, le chunk est dispatché sans throttle + warning loggé, au lieu de bloquer indéfiniment. Le gardereleasedOnceest initialisé (!acquired) pour ne jamais sur-libérer un permit non pris. Soupape de sécurité : même un permit fuité ne peut que dégrader le throttle, jamais geler un thread.Aucun changement config/schéma vs 0.4.1 — drop-in. Comportement en charge normale inchangé (throttle de 32 tâches-chunk en vol préservé ; le chemin timeout ne se déclenche jamais en usage normal).
Déployé
4b87014(main)LitematicaFolia v0.4.2+26.1.2chargé sans erreur, conteneurhealthy, 0has not respondedpost-boot⚠️ Validation : la cause du deadlock est éliminée par analyse + compilation + chargement propre en prod. Le test end-to-end idéal reste un gros paste via un vrai client Litematica/Servux — je n'en avais pas de connecté au moment du déploiement. À confirmer au prochain gros paste, mais le mécanisme d'auto-deadlock n'existe structurellement plus.