') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Fix Discord Link RoleSyncManager thread leak by JRoy · Pull Request #6512 · EssentialsX/Essentials · GitHub
Skip to content

Fix Discord Link RoleSyncManager thread leak - #6512

Merged
mdcfe merged 1 commit into
EssentialsX:2.xfrom
JRoy:fix/6381-discord-link-thread-leak
May 4, 2026
Merged

Fix Discord Link RoleSyncManager thread leak#6512
mdcfe merged 1 commit into
EssentialsX:2.xfrom
JRoy:fix/6381-discord-link-thread-leak

Conversation

@JRoy

@JRoyJRoy commented Apr 12, 2026

Copy link
Copy Markdown
Member

Two issues causing unbounded thread growth:

  1. The repeating sync task was never cancelled on plugin disable,
    leaking the timer itself.

  2. Each sync() call spawns an async task that calls
    acquireUninterruptibly() on a 5-permit semaphore. The timer fires
    up to 50 sync calls per cycle, so 45 threads block indefinitely
    waiting for permits. Before they drain, the next cycle spawns 50
    more. Threads accumulate until OOM.

Fix: cancel the task on disable, and replace acquireUninterruptibly()
with tryAcquire(5s timeout) in both sync() and unSync() so threads
don't block indefinitely — skipped syncs retry on the next cycle.

Fixes#6381

@JRoy
JRoyforce-pushed the fix/6381-discord-link-thread-leak branch from b5944db to 20b0fa4CompareApril 12, 2026 21:11
Two issues causing unbounded thread growth:
1. The repeating sync task was never cancelled on plugin disable,
leaking the timer itself.
2. Each sync() call spawns an async task that calls
acquireUninterruptibly() on a 5-permit semaphore. The timer fires
up to 50 sync calls per cycle, so 45 threads block indefinitely
waiting for permits. Before they drain, the next cycle spawns 50
more. Threads accumulate until OOM.
Fix: cancel the task on disable, and replace acquireUninterruptibly()
with tryAcquire(5s timeout) in both sync() and unSync() so threads
don't block indefinitely — skipped syncs retry on the next cycle.
FixesEssentialsX#6381
@JRoy
JRoyforce-pushed the fix/6381-discord-link-thread-leak branch from 20b0fa4 to a036a0aCompareApril 12, 2026 21:11
@JRoy
JRoy requested a review from mdcfeApril 12, 2026 21:12
@JRoyJRoy closed this May 3, 2026
@JRoy
JRoy deleted the fix/6381-discord-link-thread-leak branch May 3, 2026 01:05
@JRoy
JRoy restored the fix/6381-discord-link-thread-leak branch May 3, 2026 01:05
@JRoyJRoy reopened this May 3, 2026
@mdcfe
mdcfe added this pull request to the merge queueMay 4, 2026
Merged via the queue into EssentialsX:2.x with commit 5db483bMay 4, 2026
2 checks passed
@mdcfe
mdcfe deleted the fix/6381-discord-link-thread-leak branch May 4, 2026 08:25
Euphillya added a commit to Euphillya/Essentials-Folia that referenced this pull request May 6, 2026
EssentialsX@5c45ee2 Add /jailedplayers command to list jailed players (EssentialsX#6517)
EssentialsX@24b7285 fix: reset nickname when player changes Minecraft account name (EssentialsX#6470)
fix: reset nickname when player changes Minecraft account name (EssentialsX#6470) Add lunge to /enchant (EssentialsX#6534)
EssentialsX@39bc0fe Send geoip join messages to console (EssentialsX#6523)
EssentialsX@5db483b Fix Discord Link RoleSyncManager thread leak (EssentialsX#6512)
EssentialsX@79d67e9 Fix essentials.home.bed permission being ignored (EssentialsX#6516)
EssentialsX@c38c925 Fix deposits rejected when account has negative balance (EssentialsX#6515)
@mdcfemdcfe added this to the 2.22.0 milestone May 23, 2026
Sign up for freeto 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.

EssentalsX Discord Link leaking threads

2 participants

@JRoy@mdcfe