Skip to content

script/mach: Increase stack size of ScriptThread/StyleThread to 8MiB to match recursion depth of other browsers#43888

Merged
yezhizhen merged 3 commits into
servo:mainfrom
yezhizhen:fix-overflow
Apr 6, 2026
Merged

script/mach: Increase stack size of ScriptThread/StyleThread to 8MiB to match recursion depth of other browsers#43888
yezhizhen merged 3 commits into
servo:mainfrom
yezhizhen:fix-overflow

Conversation

@yezhizhen

@yezhizhen yezhizhen commented Apr 3, 2026

Copy link
Copy Markdown
Member

TL;DR: We increase stack size of ScriptThread to 8MiB, and set Stylo stack size environment var
to 8 MiB for all builds. This only reserves virtual memory space which is
basically unlimited for 64-bit machine,
matches the recursion depth of Chromium for the example, which also uses 8MiB.

Stylo stack increase is necessary to prevent overflow when refreshing/navigating to the example,
probably because initial load restyle incrementally but not refresh.

Testing: Added a Servo-specific test.


For example in #43845, we get stack overflow when we got more than 394 nested shadow roots.
For Chromium, it happens for more than 1631:
image
For Firefox, it overflows for more than 1052.

Initially I thought we didn't implement this optimally, and have unnecessary recursion depth.
But the real reason is explained in Rust std:

The default stack size is platform-dependent and subject to change. Currently, it is 2 MiB on all Tier-1 platforms.

For Chromium, the visual studio dumpbin shows the stack size :

Dump of file C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
OPTIONAL HEADER VALUES
          800000 size of stack reserve

This is hex value, which is $8*16^5$, exactly 8MiB.

After we make the same change in Servo, we are fine at 1601 and overflows at 1602.
This matches Chromium behaviour, defeating firefox, and should not create much overhead,
as this only reserves virtual memory space:
I tried to increase the value to 512MiB, but task manager still says 73MB RAM used,
and we won't crash even with 10000 nested shadow roots.
But that is just for more evidence and uncalled for.

Fixes: #43845

@yezhizhen yezhizhen requested a review from xiaochengh April 3, 2026 05:39
@yezhizhen yezhizhen requested a review from gterzian as a code owner April 3, 2026 05:39
@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Apr 3, 2026
@yezhizhen yezhizhen changed the title script: Increase stack size of script thread to 8MiB to match recursion depth of other browsers script: Increase stack size of ScriptThread to 8MiB to match recursion depth of other browsers Apr 3, 2026
@yezhizhen yezhizhen added the T-linux-wpt Do a try run of the WPT label Apr 3, 2026
@github-actions github-actions Bot removed the T-linux-wpt Do a try run of the WPT label Apr 3, 2026
@github-actions

github-actions Bot commented Apr 3, 2026

Copy link
Copy Markdown

🔨 Triggering try run (#23935508368) for Linux (WPT)

@github-actions

github-actions Bot commented Apr 3, 2026

Copy link
Copy Markdown

Test results for linux-wpt from try job (#23935508368):

Flaky unexpected result (33)
  • TIMEOUT /FileAPI/url/url-in-tags-revoke.window.html (#19978)
    • PASS [expected TIMEOUT] subtest: Fetching a blob URL immediately before revoking it works in <script> tags.
  • TIMEOUT [expected OK] /IndexedDB/worker-termination-aborts-upgrade.window.html (#42177)
    • TIMEOUT [expected FAIL] subtest: Worker Termination Aborts a Pending Upgrade

      Test timed out
      

  • CRASH [expected ERROR] /_webgl/conformance/extensions/oes-texture-float-with-canvas.html
  • CRASH [expected OK] /_webgl/conformance/glsl/bugs/sampler-struct-function-arg.html
  • CRASH [expected OK] /_webgl/conformance/rendering/draw-with-changing-start-vertex-bug.html
  • CRASH [expected OK] /content-security-policy/meta/sandbox-iframe.html (#43478)
  • OK /css/css-cascade/layer-cssom-order-reverse.html (#36094)
    • FAIL [expected PASS] subtest: Delete layer invalidates @font-face

      assert_equals: expected "220px" but got "133px"
      

  • PASS [expected FAIL] /css/css-ui/appearance-menulist-button-002.tentative.html
  • FAIL [expected PASS] /css/css-ui/compute-kind-widget-generated/grouped-kind-of-widget-fallback-border-block-start-style-001.html
  • OK /fetch/content-length/api-and-duplicate-headers.any.html (#35873)
    • FAIL [expected PASS] subtest: fetch() and duplicate Content-Length/Content-Type headers

      promise_test: Unhandled rejection with value: object "TypeError: Network error: HTTP failure: client error (SendRequest)"
      

  • OK /html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-window-open.html (#28691)
    • FAIL [expected PASS] subtest: load event does not fire on window.open('about:blank')

      assert_unreached: load should not be fired Reached unreachable code
      

  • OK /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click.html (#28697)
    • FAIL [expected PASS] subtest: aElement.click() before the load event must NOT replace

      assert_equals: expected "http://web-platform.test:8000/common/blank.html?thereplacement" but got "http://web-platform.test:8000/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/code-injector.html?pipe=sub(none)&code=%0A%20%20%20%20const%20a%20%3D%20document.createElement(%22a%22)%3B%0A%20%20%20%20a.href%20%3D%20%22%2Fcommon%2Fblank.html%3Fthereplacement%22%3B%0A%20%20%20%20document.currentScript.before(a)%3B%0A%20%20%20%20a.click()%3B%0A%20%20"
      

  • CRASH [expected OK] /html/browsers/history/the-history-interface/combination_history_006.html
  • OK /html/browsers/history/the-history-interface/traverse_the_history_2.html (#21383)
    • PASS [expected FAIL] subtest: Multiple history traversals, last would be aborted
  • ERROR [expected OK] /html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html (#43710)
  • TIMEOUT [expected OK] /html/interaction/focus/the-autofocus-attribute/autofocus-dialog.html (#29087)
    • TIMEOUT [expected FAIL] subtest: <dialog>-contained autofocus element gets focused when the dialog is shown

      Test timed out
      

  • OK /html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-location-replace-set-src.html (#32697)
    • PASS [expected FAIL] subtest: Navigating iframe loading='lazy' and then setting src: location.replace
  • TIMEOUT [expected OK] /html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_other_frame_popup.sub.html (#39702)
    • TIMEOUT [expected FAIL] subtest: Sandboxed iframe can not navigate other frame's popup

      Test timed out
      

  • OK /html/semantics/forms/form-submission-0/jsurl-form-submit.tentative.html (#36489)
    • PASS [expected FAIL] subtest: Verifies that form submissions scheduled inside javascript: urls take precedence over the javascript: url's return value.
  • TIMEOUT [expected OK] /html/user-activation/navigation-state-reset-sameorigin.html
    • TIMEOUT [expected PASS] subtest: Post-navigation state reset.

      Test timed out
      

  • PASS [expected FAIL] /png/apng/acTL-plays-one.html (#41218)
  • OK /pointerevents/compat/pointerevent_touch_target_after_pointerdown_target_removed.tentative.html (#42813)
    • PASS [expected FAIL] subtest: After a pointerdown listener moves the target to different position, touch events should be fired on the pointerdown target, but pointer events should be fired on the pointerdown target parent
    • PASS [expected FAIL] subtest: After a pointerdown listener moves the target to different position, touchmove event should be fired on the pointerdown target parent
  • TIMEOUT [expected OK] /preload/modulepreload-sri-importmap.html (#43354)
    • TIMEOUT [expected PASS] subtest: Script should not be loaded if modulepreload's integrity is invalid

      Test timed out
      

  • OK /resource-timing/test_resource_timing.https.html (#25216)
    • PASS [expected FAIL] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (xmlhttprequest)
  • CRASH [expected OK] /svg/animations/svglengthlist-animation-4.html
  • TIMEOUT /trusted-types/trusted-types-navigation.html?06-10 (#37920)
    • TIMEOUT [expected FAIL] subtest: Navigate a frame via anchor with javascript:-urls in report-only mode.

      Test timed out
      

    • NOTRUN [expected TIMEOUT] subtest: Navigate a frame via anchor with javascript:-urls w/ default policy in report-only mode.
  • OK /webdriver/tests/classic/element_send_keys/file_upload.py
    • FAIL [expected PASS] subtest: test_empty_text

      webdriver.error.NoSuchWindowException: no such window (404): No such window
      

  • OK /webdriver/tests/classic/execute_async_script/promise.py
    • FAIL [expected PASS] subtest: test_promise_resolve

      AssertionError: no such window (404): No such window
      

  • OK /webdriver/tests/classic/execute_script/execute.py
    • ERROR [expected PASS] subtest: test_no_top_browsing_context

      setup error: webdriver.error.NoSuchElementException: no such element (404)
      

  • OK /webdriver/tests/classic/find_element_from_shadow_root/find.py
    • FAIL [expected PASS] subtest: test_null_parameter_value

      webdriver.error.NoSuchWindowException: no such window (404): No such window
      

  • OK /webdriver/tests/classic/get_element_attribute/get.py
    • ERROR [expected PASS] subtest: test_no_top_browsing_context

      setup error: webdriver.error.NoSuchElementException: no such element (404)
      

  • OK /webdriver/tests/classic/navigate_to/file.py
    • FAIL [expected PASS] subtest: test_file_protocol

      webdriver.error.NoSuchWindowException: no such window (404)
      

  • OK /webdriver/tests/classic/perform_actions/wheel_events.py
    • FAIL [expected PASS] subtest: test_scroll_on_iframe_with_overflow_scroll[delta-x]

      webdriver.error.TimeoutException: timeout (500): Timed out after 0.5 seconds with message: Didn't receive all events: expected at least 1, got 0
      

Stable unexpected results that are known to be intermittent (16)
  • OK /IndexedDB/idbdatabase_deleteObjectStore.any.html (#43823)
    • PASS [expected FAIL] subtest: Deleted object store's name should be removed from database's list. Attempting to use a deleted IDBObjectStore should throw an InvalidStateError
  • OK /_mozilla/css/offset_properties_inline.html (#40543)
    • FAIL [expected PASS] subtest: offsetTop

      assert_equals: offsetTop of #inline-1 should be 0. expected 0 but got -1
      

    • FAIL [expected PASS] subtest: offsetLeft

      assert_equals: offsetLeft of #inline-2 should be 40. expected 40 but got 25
      

  • FAIL [expected PASS] /_mozilla/mozilla/sslfail.html (#10760)
  • TIMEOUT [expected OK] /_mozilla/mozilla/window_resize_event.html (#36741)
    • TIMEOUT [expected PASS] subtest: Popup onresize event fires after resizeTo

      Test timed out
      

  • OK /_webgl/conformance/textures/misc/texture-upload-size.html (#21770)
    • FAIL [expected PASS] subtest: WebGL test #45

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #47

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #49

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #51

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • PASS [expected FAIL] subtest: WebGL test #53
    • PASS [expected FAIL] subtest: WebGL test #55
    • PASS [expected FAIL] subtest: WebGL test #57
    • PASS [expected FAIL] subtest: WebGL test #59
    • FAIL [expected PASS] subtest: WebGL test #61

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #63

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • And 6 more unexpected results...
  • OK /css/css-fonts/generic-family-keywords-001.html (#37467)
    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted generic(khmer-mul)

      assert_equals: quoted generic(khmer-mul) matches  @font-face rule expected 50 but got 30
      

  • OK /css/css-fonts/generic-family-keywords-003.html (#38994)
    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted cursive (drawing text in a canvas)

      assert_equals: quoted cursive matches  @font-face rule expected 125 but got 40
      

    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted fantasy (drawing text in a canvas)

      assert_equals: quoted fantasy matches  @font-face rule expected 125 but got 40
      

    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted monospace (drawing text in a canvas)

      assert_equals: quoted monospace matches  @font-face rule expected 125 but got 40
      

    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted system-ui (drawing text in a canvas)

      assert_equals: quoted system-ui matches  @font-face rule expected 125 but got 40
      

    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted generic(kai) (drawing text in a canvas)

      assert_equals: quoted generic(kai) matches  @font-face rule expected 125 but got 40
      

    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted generic(khmer-mul) (drawing text in a canvas)

      assert_equals: quoted generic(khmer-mul) matches  @font-face rule expected 125 but got 40
      

    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted generic(nastaliq) (drawing text in a canvas)

      assert_equals: quoted generic(nastaliq) matches  @font-face rule expected 125 but got 40
      

    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted ui-sans-serif (drawing text in a canvas)
  • OK /dom/nodes/moveBefore/iframe-document-preserve.window.html (#43152)
    • PASS [expected FAIL] subtest: moveBefore(): cross-origin iframe is preserved: remove self
  • OK /fetch/metadata/generated/css-font-face.https.sub.tentative.html (#32732)
    • PASS [expected FAIL] subtest: sec-fetch-mode
  • OK /html/browsers/browsing-the-web/navigating-across-documents/005.html (#27062)
    • FAIL [expected PASS] subtest: Link with onclick navigation and href navigation

      assert_equals: expected "href" but got "click"
      

  • OK /html/browsers/history/the-history-interface/traverse_the_history_5.html (#21383)
    • PASS [expected FAIL] subtest: Multiple history traversals, last would be aborted
  • TIMEOUT /html/interaction/focus/the-autofocus-attribute/supported-elements.html (#24145)
    • PASS [expected TIMEOUT] subtest: Non-HTMLElement should not support autofocus
    • TIMEOUT [expected NOTRUN] subtest: Host element with delegatesFocus should support autofocus

      Test timed out
      

  • OK [expected TIMEOUT] /infrastructure/testdriver/click_nested.html (#43887)
    • FAIL [expected NOTRUN] subtest: TestDriver click method with multiple windows and nested iframe

      can't access property "document", child.frames[2] is undefined
      

  • OK /mixed-content/tentative/autoupgrades/mixed-content-cors.https.sub.html (#41123)
    • PASS [expected FAIL] subtest: Cross-Origin video should get upgraded even if CORS is set
  • OK /navigation-timing/test-navigation-type-reload.html (#33334)
    • FAIL [expected PASS] subtest: Reload domContentLoadedEventEnd > Original domContentLoadedEventEnd

      assert_true: Reload domContentLoadedEventEnd > Original domContentLoadedEventEnd expected true got false
      

    • FAIL [expected PASS] subtest: Reload domContentLoadedEventStart > Original domContentLoadedEventStart

      assert_true: Reload domContentLoadedEventStart > Original domContentLoadedEventStart expected true got false
      

  • OK [expected ERROR] /webxr/render_state_update.https.html (#27535)

@github-actions

github-actions Bot commented Apr 3, 2026

Copy link
Copy Markdown

✨ Try run (#23935508368) succeeded.

@TimvdLippe TimvdLippe 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.

Should we add a crash test for this to our Servo only tests?

@servo-highfive servo-highfive removed the S-awaiting-review There is new code that needs to be reviewed. label Apr 3, 2026
@yezhizhen

Copy link
Copy Markdown
Member Author

Should we add a crash test for this to our Servo only tests?

Sounds good to me.

@yezhizhen yezhizhen marked this pull request as draft April 3, 2026 06:27
@yezhizhen

yezhizhen commented Apr 3, 2026

Copy link
Copy Markdown
Member Author

Should we add a crash test for this to our Servo only tests?

Ehh. Got into trouble. When running below directly as file, it is fine with this PR.
But when running with test-wpt, we overflow StyleThread: thread 'StyleThread#1' (29572) has overflowed its stack.

<!doctype html>
<div id="X"></div>
<script>
    let p = X
    for (let i=0;i<1551;i++) {
        let x = document.createElement('div')
        let r = x.attachShadow({
            mode:'open', clonable: true
        })
        p.appendChild(x)
        p = r
    }
    p.innerHTML = `Hello!`
</script>

I don't know why StyleThread overflow only triggered by wpt test.
@Loirooriol I don't think we can do the same for StyleThread? Would it be hard to export to upstream?

@yezhizhen

This comment was marked as outdated.

@yezhizhen

yezhizhen commented Apr 4, 2026

Copy link
Copy Markdown
Member Author

I don't know why StyleThread overflow only triggered by wpt test.

It seems because, wpt test load about:blank first, and then load the target page. This triggers style thread overflow..

Directly refreshing or navigating to the target page can also trigger the StyleThread overflow :/

navigate.mp4
refresh.mp4

@jschwe

jschwe commented Apr 4, 2026

Copy link
Copy Markdown
Member

FYI, you can increase the stack size of the style threads via SERVO_STYLE_THREAD_STACK_SIZE_KB when building servo. It defaults to 512 (and I think 256 for firefox).

@yezhizhen

yezhizhen commented Apr 4, 2026

Copy link
Copy Markdown
Member Author

FYI, you can increase the stack size of the style threads via SERVO_STYLE_THREAD_STACK_SIZE_KB when building servo. It defaults to 512 (and I think 256 for firefox).

It seems the default is 2048 based on output in target/release/deps/style-{hash}.d

# env-dep:OUT_DIR=D:\\servo\\target\\release\\build\\stylo-128f0fbe36d6194e\\out
# env-dep:SERVO_STYLE_THREAD_STACK_SIZE_KB=2048

That's kind strange: it seems we only set the variable for debug build and ASAN builds..

if self.config["build"]["mode"] in ["", "dev"]:
# Increase stylo thread stack size to 2 MiB for debug builds since the stack usage is higher
# and crashes have been reported. The default is 512 KiB.
env["SERVO_STYLE_THREAD_STACK_SIZE_KB"] = env.get("SERVO_STYLE_THREAD_STACK_SIZE_KB", str(2 * 1024))

How come we also get 2048 for release?

https://github.com/servo/stylo/blob/6cfce6f3293b5e13c2246838380e514b8b206a27/style/parallel.rs#L31-L49

@yezhizhen

Copy link
Copy Markdown
Member Author

How come we also get 2048 for release?

Filed #43927

Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
@yezhizhen yezhizhen changed the title script: Increase stack size of ScriptThread to 8MiB to match recursion depth of other browsers script: Increase stack size of ScriptThread/StyleThread to 8MiB to match recursion depth of other browsers Apr 6, 2026
Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
@yezhizhen yezhizhen changed the title script: Increase stack size of ScriptThread/StyleThread to 8MiB to match recursion depth of other browsers script/mach: Increase stack size of ScriptThread/StyleThread to 8MiB to match recursion depth of other browsers Apr 6, 2026
@yezhizhen yezhizhen marked this pull request as ready for review April 6, 2026 02:05
@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Apr 6, 2026
@yezhizhen yezhizhen added this pull request to the merge queue Apr 6, 2026
@servo-highfive servo-highfive added the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Apr 6, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Apr 6, 2026
@servo-highfive servo-highfive added S-tests-failed The changes caused existing tests to fail. and removed S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. labels Apr 6, 2026
@yezhizhen yezhizhen added this pull request to the merge queue Apr 6, 2026
@servo-highfive servo-highfive added S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. and removed S-tests-failed The changes caused existing tests to fail. labels Apr 6, 2026
Merged via the queue into servo:main with commit 0353f11 Apr 6, 2026
42 checks passed
@yezhizhen yezhizhen deleted the fix-overflow branch April 6, 2026 03:40
@servo-highfive servo-highfive removed the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Apr 6, 2026
env["TARGET_CFLAGS"] += " -fsanitize=address"
env["TARGET_CXXFLAGS"] += " -fsanitize=address"

# Set servo style thread stack size to 8 MB for ASAN builds since the stack usage is higher.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this removed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use 8 for all builds unconditionally.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I believe we might want to investigate that. If Firefox can work fine with 256K stack size for the style threads, then there might be a deeper issue on our side if we need to increase that all the way up to 8MB

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-awaiting-review There is new code that needs to be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

400 nested shadow roots crashes Servo

4 participants