Skip to content

indexeddb: Handle schema changes asynchronously - #46615

Merged
jdm merged 4 commits into
servo:mainfrom
mrobinson:indexeddb-schema-operation-async-stream
Jul 21, 2026
Merged

indexeddb: Handle schema changes asynchronously#46615
jdm merged 4 commits into
servo:mainfrom
mrobinson:indexeddb-schema-operation-async-stream

Conversation

@mrobinson

@mrobinson mrobinson commented Jul 18, 2026

Copy link
Copy Markdown
Member

Previously schema changes were sent as synchronous operations, but those
happen on a different thread than asynchronous operations, which meant
they could race. This change routes schema changes to the asynchronous
IndexedDB path so that they can be handled in a consistent order with
other asynchronous operations.

Testing: This fixes some WPT subtests and solves some flakiness issues.
Fixes: #43776.
Fixes: #45919.
Fixes: #43823.

@mrobinson
mrobinson requested a review from gterzian as a code owner July 18, 2026 14:18
@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Jul 18, 2026
@mrobinson mrobinson added the T-linux-wpt Do a try run of the WPT label Jul 18, 2026
@github-actions github-actions Bot removed the T-linux-wpt Do a try run of the WPT label Jul 18, 2026
@github-actions

Copy link
Copy Markdown

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

@github-actions

Copy link
Copy Markdown

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

Flaky unexpected result (31)
  • TIMEOUT [expected OK] /IndexedDB/idb-binary-key-roundtrip.any.html
    • TIMEOUT [expected PASS] subtest: Binary keys can be supplied using the view type Uint8Array

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Binary keys can be supplied using the view type Uint8ClampedArray

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Binary keys can be supplied using the view type Int8Array

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Binary keys can be supplied using the view type Uint16Array

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Binary keys can be supplied using the view type Int16Array

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Binary keys can be supplied using the view type Uint32Array

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Binary keys can be supplied using the view type Int32Array

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Binary keys can be supplied using the view type Float16Array

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Binary keys can be supplied using the view type Float32Array

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Binary keys can be supplied using the view type Float64Array

      Test timed out
      

    • And 6 more unexpected results...
  • TIMEOUT [expected OK] /IndexedDB/idbdatabase_createObjectStore.any.worker.html
    • TIMEOUT [expected FAIL] subtest: Both with empty name

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Returns an instance of IDBObjectStore

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Create 1000 object stores, add one item and delete

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Empty name

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Attempting to create an existing object store with a different keyPath throw ConstraintError

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Object store 'name' and 'keyPath' properties are correctly set

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Attempt to create an object store outside of a version change transaction

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Attempt to create an object store that already exists

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Object store's name appears in database's list

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Attempt to create an object store with an invalid key path

      Test timed out
      

    • And 17 more unexpected results...
  • ERROR [expected OK] /_mozilla/mozilla/iframe-unblock-onload.html
  • TIMEOUT [expected OK] /_webgl/conformance/reading/read-pixels-test.html
  • CRASH [expected OK] /_webgl/conformance/uniforms/no-over-optimization-on-uniform-array-06.html
  • TIMEOUT [expected OK] /_webgl/conformance2/sync/sync-webgl-specific.html
  • OK /cookies/partitioned-cookies/partitioned-cookies-samesite-attribute.https.html (#45255)
    • FAIL [expected PASS] subtest: In embedded cross-site contexts, partitioned cookies can only be set with explicit SameSite=None

      assert_equals: expected 0 but got 3
      

  • CRASH [expected OK] /css/compositing/canvas-composite-modes.html
  • FAIL [expected PASS] /css/css-backgrounds/background-size-041.html
  • FAIL [expected PASS] /css/css-sizing/dynamic-available-size-iframe.html (#45763)
  • OK [expected TIMEOUT] /fetch/api/redirect/redirect-keepalive.https.any.html (#32153)
    • PASS [expected TIMEOUT] subtest: [keepalive][iframe][load] mixed content redirect; setting up
  • TIMEOUT [expected OK] /fetch/content-encoding/gzip/big-gzip-body.https.any.worker.html
    • TIMEOUT [expected PASS] subtest: large gzip data should be decompressed successfully

      Test timed out
      

    • NOTRUN [expected FAIL] subtest: large gzip data should be decompressed successfully with byte stream
  • TIMEOUT [expected OK] /fetch/content-encoding/zstd/big-zstd-body.https.any.html
    • TIMEOUT [expected PASS] subtest: large zstd data should be decompressed successfully

      Test timed out
      

    • NOTRUN [expected FAIL] subtest: large zstd data should be decompressed successfully with byte stream
  • ERROR [expected TIMEOUT] /html/browsers/browsing-the-web/history-traversal/pageswap/pageswap-initial-navigation.html (#40387)
  • OK /html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin-fragment.html (#20768)
    • FAIL [expected PASS] subtest: Tests that a fragment navigation in the unload handler will not block the initial navigation

      assert_equals: expected "" but got "#fragment"
      

  • OK /html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin.window.html (#29049)
    • FAIL [expected PASS] subtest: Same-origin navigation started from unload handler must be ignored

      assert_equals: expected "?pass" but got "?fail"
      

  • OK /html/browsers/browsing-the-web/navigating-across-documents/refresh/same-document-refresh.html (#34597)
    • FAIL [expected PASS] subtest: Same-Document Referrer from Refresh

      assert_equals: original page loads expected "http://web-platform.test:8000/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/refresh-with-section.sub.html?url=%23section" but got "http://web-platform.test:8000/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/refresh-with-section.sub.html?url=%23section#section"
      

  • OK /html/browsers/history/the-history-interface/traverse_the_history_3.html (#21383)
    • PASS [expected FAIL] subtest: Multiple history traversals, last would be aborted
  • CRASH [expected ERROR] /html/browsers/history/the-location-interface/location_replace_session_history.html (#41896)
  • TIMEOUT [expected OK] /html/interaction/focus/the-autofocus-attribute/update-the-rendering.html (#24145)
    • TIMEOUT [expected FAIL] subtest: "Flush autofocus candidates" should be happen before a scroll event and animation frame callbacks

      Test timed out
      

  • OK /html/semantics/embedded-content/media-elements/media_fragment_seek.html (#24114)
    • FAIL [expected PASS] subtest: Video should seek to time specified in media fragment syntax

      assert_equals: expected 3 but got 0
      

  • OK /html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-reload-location-reload.html (#32595)
    • FAIL [expected PASS] subtest: Reloading iframe loading='lazy' before it is loaded: location.reload

      uncaught exception: Error: assert_equals: expected "http://web-platform.test:8000/html/semantics/embedded-content/the-iframe-element/support/blank.htm?src" but got "about:blank"
      

  • 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
      

  • CRASH [expected ERROR] /html/semantics/forms/the-select-element/customizable-select/select-appearance-button-after-span.html
  • TIMEOUT [expected OK] /html/user-activation/navigation-state-reset-sameorigin.html (#45151)
    • TIMEOUT [expected FAIL] subtest: Post-navigation state reset.

      Test timed out
      

  • ERROR [expected OK] /intersection-observer/v2/animated-opacity.html (#40827)
  • OK /pointerevents/compat/pointerevent_touch_target_after_pointerdown_target_removed.tentative.html (#42813)
    • FAIL [expected PASS] subtest: After a pointerdown listener removes its target, touch events should be fired on the touchstart target even though an orphan and pointer events should be fired on the parent

      assert_equals: expected "[ { type: pointerdown, target: Element node <div id=\"pointerDownTarget\"></div> }, { type: touchstart, target: Element node <div id=\"pointerDownTarget\"></div> }, { type: pointermove, target: Element node <div id=\"pointerDownTargetParent\"></div> }, { type: pointerup, target: Element node <div id=\"pointerDownTargetParent\"></div> }, { type: touchend, target: Element node <div id=\"pointerDownTarget\"></div> } ]" but got "[ { type: pointerdown, target: Element node <div id=\"pointerDownTarget\"></div> }, { type: touchstart, target: Element node <div id=\"pointerDownTarget\"></div> }, { type: pointermove, target: Element node <div id=\"pointerDownTargetParent\"></div> }, { type: pointerup, target: Element node <div id=\"pointerDownTargetParent\"></div> }, { type: touchend, target: Element node <div id=\"pointerDownTarget\"></div> }, { type: pointermove, target: Element node <div id=\"pointerDownTargetParent\"></div> }, { type: pointerdown, target: Element node <div id=\"pointerDownTargetParent\"></div> } ]"
      

    • FAIL [expected PASS] subtest: After a pointerdown listener removes its target, touchmove event should be fired on the pointerdown target

      assert_equals: expected "[ { type: pointerdown, target: Element node <div id=\"pointerDownTarget\"></div> }, { type: touchstart, target: Element node <div id=\"pointerDownTarget\"></div> }, { type: pointermove, target: Element node <div id=\"pointerDownTargetParent\"></div> }, { type: touchmove, target: Element node <div id=\"pointerDownTarget\"></div> }, { type: pointerup, target: Element node <div id=\"pointerDownTargetParent\"></div> }, { type: touchend, target: Element node <div id=\"pointerDownTarget\"></div> } ]" but got "[ { type: pointerdown, target: Element node <div id=\"pointerDownTarget\"></div> }, { type: touchstart, target: Element node <div id=\"pointerDownTarget\"></div> }, { type: pointermove, target: Element node <div id=\"pointerDownTargetParent\"></div> }, { type: touchmove, target: Element node <div id=\"pointerDownTarget\"></div> }, { type: pointerup, target: Element node <div id=\"pointerDownTargetParent\"></div> }, { type: touchend, target: Element node <div id=\"pointerDownTarget\"></div> }, { type: pointermove, target: Element node <div id=\"pointerDownTargetParent\"></div> }, { type: pointerdown, target: Element node <div id=\"pointerDownTargetParent\"></div> } ]"
      

  • CRASH [expected OK] /trusted-types/script-enforcement-016.html
  • TIMEOUT /trusted-types/trusted-types-navigation.html?06-10 (#37920)
    • FAIL [expected PASS] subtest: Navigate a frame via anchor with javascript:-urls in report-only mode.

      promise_test: Unhandled rejection with value: "Unexpected message received: \"No securitypolicyviolation reported!\""
      

  • OK /webdriver/tests/classic/perform_actions/pointer_pen.py
    • FAIL [expected PASS] subtest: test_pen_pointer_in_shadow_tree[outer-closed]

      AssertionError
      

    • FAIL [expected PASS] subtest: test_pen_pointer_in_shadow_tree[inner-open]

      AssertionError
      

    • FAIL [expected PASS] subtest: test_pen_pointer_in_shadow_tree[inner-closed]

      AssertionError
      

  • CRASH [expected OK] /webstorage/storage_local_setitem_quotaexceedederr.window.html
Stable unexpected results that are known to be intermittent (19)
  • TIMEOUT /FileAPI/url/url-in-tags-revoke.window.html (#19978)
    • TIMEOUT [expected PASS] subtest: Fetching a blob URL immediately before revoking it works in <script> tags.

      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 /beacon/beacon-basic.https.window.html (#41723)
    • FAIL [expected PASS] subtest: Payload size restriction should be accumulated: type = blob

      assert_false: expected false got true
      

  • TIMEOUT /content-security-policy/inheritance/auxiliary-blank-document.html (#44821)
    • FAIL [expected PASS] subtest: For transient doc, violation event occurs in auxiliary, not opener window.

      assert_equals: expected Document node with 1 child but got Document node with 1 child
      

  • OK /css/css-cascade/layer-font-face-override.html (#35935)
    • FAIL [expected PASS] subtest: @font-face override between layers

      assert_equals: expected "80px" but got "41.45px"
      

    • FAIL [expected PASS] subtest: @font-face override update with appended sheet 1

      assert_equals: expected "80px" but got "41.45px"
      

    • FAIL [expected PASS] subtest: @font-face override update with appended sheet 2

      assert_equals: expected "80px" but got "41.45px"
      

  • TIMEOUT /fetch/metadata/generated/css-images.sub.tentative.html (#29047)
    • TIMEOUT [expected PASS] subtest: background-image sec-fetch-site - Not sent to non-trustworthy same-origin destination

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: background-image sec-fetch-user - Not sent to non-trustworthy cross-site destination

      Test timed out
      

    • FAIL [expected PASS] subtest: content sec-fetch-site - HTTPS downgrade-upgrade

      assert_unreached: Reached unreachable code
      

  • TIMEOUT [expected OK] /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-requestsubmit.html (#44098)
    • TIMEOUT [expected FAIL] subtest: Replace before load, triggered by formElement.requestSubmit()

      Test timed out
      

  • TIMEOUT [expected OK] /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit.html (#44028)
  • TIMEOUT /html/browsers/history/the-history-interface/001.html (#12580)
    • PASS [expected FAIL] subtest: traversing history must also traverse hash changes
  • TIMEOUT [expected OK] /html/infrastructure/urls/base-url/document-base-url-window-initiator-is-not-opener.https.window.html (#30970)
  • TIMEOUT [expected OK] /html/interaction/focus/the-autofocus-attribute/supported-elements.html (#24145)
    • TIMEOUT [expected FAIL] subtest: Host element with delegatesFocus should support autofocus

      Test timed out
      

    • NOTRUN [expected FAIL] subtest: Host element with delegatesFocus including no focusable descendants should be skipped
    • NOTRUN [expected FAIL] subtest: Area element should support autofocus
  • OK [expected TIMEOUT] /html/syntax/parsing/html5lib_write.html?file=processing-instructions (#46205)
  • TIMEOUT [expected OK] /infrastructure/testdriver/click_nested.html (#43887)
    • NOTRUN [expected FAIL] subtest: TestDriver click method with multiple windows and nested iframe
  • OK /resource-timing/test_resource_timing.https.html (#25216)
    • PASS [expected FAIL] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (img)
    • FAIL [expected PASS] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (xmlhttprequest)

      assert_equals: expected 80.95 but got 80.94
      

  • OK [expected TIMEOUT] /trusted-types/trusted-types-navigation.html?11-15 (#44834)
    • PASS [expected TIMEOUT] subtest: Navigate a window via area with javascript:-urls in report-only mode.
  • TIMEOUT [expected OK] /trusted-types/trusted-types-navigation.html?16-20 (#44835)
    • FAIL [expected PASS] subtest: Navigate a frame via area with javascript:-urls in report-only mode.

      promise_test: Unhandled rejection with value: "Unexpected message received: \"No securitypolicyviolation reported!\""
      

    • TIMEOUT [expected PASS] subtest: Navigate a frame via area with javascript:-urls w/ default policy in report-only mode.

      Test timed out
      

  • TIMEOUT [expected OK] /trusted-types/trusted-types-navigation.html?26-30 (#38807)
    • TIMEOUT [expected PASS] subtest: Navigate a window via form-submission with javascript:-urls in report-only mode.

      Test timed out
      

    • NOTRUN [expected PASS] subtest: Navigate a window via form-submission with javascript:-urls w/ default policy in report-only mode.
    • NOTRUN [expected PASS] subtest: Navigate a frame via form-submission with javascript:-urls in enforcing mode.
    • NOTRUN [expected PASS] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in enforcing mode.
  • OK [expected TIMEOUT] /trusted-types/trusted-types-navigation.html?31-35 (#38034)
    • PASS [expected TIMEOUT] subtest: Navigate a frame via form-submission with javascript:-urls in report-only mode.
    • PASS [expected NOTRUN] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in report-only mode.
    • FAIL [expected NOTRUN] subtest: Navigate a window via form-submission with javascript:-urls w/ a default policy throwing an exception in enforcing mode.

      promise_test: Unhandled rejection with value: "Unexpected message received: \"No securitypolicyviolation reported!\""
      

    • FAIL [expected NOTRUN] subtest: Navigate a window via form-submission with javascript:-urls w/ a default policy throwing an exception in report-only mode.

      promise_test: Unhandled rejection with value: "Unexpected message received: \"No securitypolicyviolation reported!\""
      

    • FAIL [expected NOTRUN] subtest: Navigate a window via form-submission with javascript:-urls w/ a default policy making the URL invalid in enforcing mode.

      promise_test: Unhandled rejection with value: "Unexpected message received: \"No securitypolicyviolation reported!\""
      

  • OK [expected TIMEOUT] /webstorage/localstorage-about-blank-3P-iframe-opens-3P-window.partitioned.html (#29053)
    • FAIL [expected TIMEOUT] subtest: StorageKey: test 3P about:blank window opened from a 3P iframe

      assert_true: about:blank window opened by 3P iframe does not inherit 3P iframe's StorageKey expected true got false
      

@github-actions

Copy link
Copy Markdown

✨ Try run (#29647877790) succeeded.

@mrobinson
mrobinson marked this pull request as draft July 18, 2026 17:43
@mrobinson

Copy link
Copy Markdown
Member Author

Marking this one as a draft as it still needs a bit more work.

@mrobinson
mrobinson force-pushed the indexeddb-schema-operation-async-stream branch from 1093bb5 to 321ba67 Compare July 19, 2026 14:00
@mrobinson
mrobinson marked this pull request as ready for review July 19, 2026 14:01
@mrobinson

Copy link
Copy Markdown
Member Author

This is ready for review. it's 3 very-related changes (reviewable by commit) and one WPT expectations update. With this change the IndexedDB tests are very stable on my system (with a few unrelated flakes that I will continue to debug).

Previously schema changes were sent as synchronous operations, but those
happen on a different thread than asynchronous operations, which meant
they could race. This change routes schema changes to the asynchronous
IndexedDB path so that they can be handled in a consistent order with
other asynchronous operations.

Signed-off-by: Martin Robinson <martin@abandonedwig.info>
This allows properly handling errors that occur in the backend during
schema operations (now asynchronous). These are handled by aborting
the transaction.

Signed-off-by: Martin Robinson <martin@abandonedwig.info>
Abort should not happen when transactions unresolved. Instead they
should be queued (like the commit callbacks) and executed once the
transaction itself finishes. This eliminates flakiness that can happen
when the abort message arrives before or after the asynchronous
transaction happens.

Signed-off-by: Martin Robinson <martin@abandonedwig.info>
Signed-off-by: Martin Robinson <martin@abandonedwig.info>
@mrobinson

Copy link
Copy Markdown
Member Author

Remaining flakes are handled by #46636.

@jdm jdm left a comment

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.

Thank you!

@servo-highfive servo-highfive removed the S-awaiting-review There is new code that needs to be reviewed. label Jul 21, 2026
@jdm
jdm added this pull request to the merge queue Jul 21, 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 Jul 21, 2026
Merged via the queue into servo:main with commit 93ec8b4 Jul 21, 2026
31 checks passed
@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 Jul 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants