LibWeb: Implement IndexedDB transaction dependency ordering

This fixes the regression in idbindex_reverse_cursor.any.html, which
was actually exposing the underlying issue of ignoring conflicting
read/write transactions. Now, if a read/write transaction is in the
queue, no transactions can coincide with its requests' execution.
This commit is contained in:
Zaggy1024
2026-03-05 02:27:40 -06:00
committed by Gregory Bertilson
parent 3c24a394c6
commit ea96072fee
Notes: github-actions[bot] 2026-03-05 23:42:22 +00:00
7 changed files with 129 additions and 5 deletions

View File

@@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/AnyOf.h>
#include <LibWeb/Bindings/IDBDatabasePrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Crypto/Crypto.h>
@@ -247,6 +248,8 @@ WebIDL::ExceptionOr<GC::Ref<IDBTransaction>> IDBDatabase::transaction(Variant<St
// 8. Set transactions cleanup event loop to the current event loop.
transaction->set_cleanup_event_loop(HTML::main_thread_event_loop());
block_on_conflicting_transactions(transaction);
// 9. Return an IDBTransaction object representing transaction.
return transaction;
}
@@ -299,4 +302,45 @@ void IDBDatabase::check_pending_transaction_waits()
}
}
// https://w3c.github.io/IndexedDB/#transaction-scheduling
void IDBDatabase::block_on_conflicting_transactions(GC::Ref<IDBTransaction> transaction)
{
// The following constraints define when a transaction can be started:
// - A read-only transactions tx can start when there are no read/write transactions which:
// - A read/write transaction tx can start when there are no transactions which:
Vector<GC::Ref<IDBTransaction>> blocking;
for (auto const& other : m_transactions) {
// - Were created before tx; and
if (other.ptr() == transaction.ptr())
break;
// NB: According to the above conditions, we only block on transactions if one is read/write.
if (transaction->is_readonly() && other->is_readonly())
continue;
// - have overlapping scopes with tx; and
bool have_overlapping_scopes = any_of(transaction->scope(), [&](auto const& store) {
return other->scope().contains_slow(store);
});
if (!have_overlapping_scopes)
continue;
// - are not finished.
if (other->is_finished())
continue;
blocking.append(other);
}
if (blocking.is_empty())
return;
transaction->request_list().block_execution();
wait_for_transactions_to_finish(blocking, GC::create_function(realm().heap(), [transaction] {
transaction->request_list().unblock_execution();
}));
}
}