') + ')', '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); } })(); })(); test(storage): keep hydration crash fixture alive by me2seeks · Pull Request #2576 · apache/maka · GitHub
Skip to content

test(storage): keep hydration crash fixture alive - #2576

Merged
Astro-Han merged 1 commit into
apache:mainfrom
me2seeks:fix/2537-hydration-crash-keepalive
Aug 9, 2026
Merged

test(storage): keep hydration crash fixture alive#2576
Astro-Han merged 1 commit into
apache:mainfrom
me2seeks:fix/2537-hydration-crash-keepalive

Conversation

@me2seeks

Copy link
Copy Markdown
Contributor

Summary

The hydration crash fixture intentionally leaves its top-level hydrate() await pending after writing a partial ownership binding. Once the event loop became empty, Node could detect that unsettled top-level await and exit with code 13 before the parent test delivered SIGKILL.

Keep a referenced timer alive around the hydration call and clear it if the call ever settles normally. The fixture now stays alive until the parent kills it, so the existing signal and staging-state assertions remain strict instead of accepting Node's early exit.

Fixes#2537

中文对照

hydration crash fixture 会在写入部分 ownership binding 后,故意让顶层 hydrate() await 永久挂起。当事件循环变空时,Node 可能把它识别为未完成的 top-level await,并在父测试发送 SIGKILL 前以退出码 13 主动结束。

本 PR 在 hydration 调用期间保留一个 ref'ed timer,并在调用正常结束时清理。这样 fixture 会一直存活到父测试主动终止,原有的 signal 与 staging 状态断言仍保持严格,不会把 Node 的提前退出当作可接受结果。

Verification

  • npm --workspace @maka/storage run test:dist — 770 passed, 14 platform skips
  • Target crash test repeated 50 times — 50/50 passed
  • npx biome check packages/storage/src/__tests__/fixtures/session-bundle-hydration-binding-crash.ts

Checklist

  • The existing test still requires SIGKILL
  • No production code or recovery semantics changed
  • Formatting and affected tests pass locally

@me2seeks

Copy link
Copy Markdown
ContributorAuthor

CI note: the #2537 crash test passed in the affected job, and the Storage workspace completed successfully. The aggregate workspace failure is in the new QuickJS code-mode concurrency tests; current main is failing the same lane (run 31299049684). The Storybook and Desktop E2E failures also match current main's #2574 run (31299103563). Locally, Storage is 770 passed / 14 platform skips and the target test is 50/50. I don't see a #2537-specific follow-up patch here.

@me2seeks
me2seeksforce-pushed the fix/2537-hydration-crash-keepalive branch from 91d8d56 to 5f1f2b7CompareAugust 9, 2026 10:01

@Astro-HanAstro-Han 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.

Verified against the Node 24 unsettled top-level-await behavior: the fixture now stays alive until the parent captures the partial state and sends SIGKILL, while resolve/reject paths still clear the keepalive in finally. No findings.

@Astro-Han
Astro-Han merged commit 70d7fda into apache:mainAug 9, 2026
9 checks passed
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.

test(storage): hydration-crash child exits on its own before SIGKILL, so the crash test asserts null !== 'SIGKILL'

2 participants

@me2seeks@Astro-Han