Files
sdk/runtime/vm/ffi_callback_metadata_test.cc
T
Liam Appelbe 4df2a9515d [vm] Simplify FfiCallbackMetadata locking
Remove the locks in DLRT_GetFfiCallbackMetadata, and switch from
safepoint locks to ordinary locks in FfiCallbackMetadata. This fixes the
deadlock bugs at the cost of reducing thread safety in error cases. Some
cases that would have failed gracefully will now have undefined
behavior.

Also, FATAL instead of no-op if a dead callback is invoked.

Fixes: https://github.com/dart-lang/sdk/issues/61372
Change-Id: Ie09fca3c629ad61b2ffbdd029269338f2706df4b
TEST=CI, particularly many_listener_callbacks_test on reload bot
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/449160
Commit-Queue: Liam Appelbe <liama@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
2025-09-17 18:16:01 -07:00

632 lines
22 KiB
C++

// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "vm/ffi_callback_metadata.h"
#include <memory>
#include <thread> // NOLINT(build/c++11)
#include <unordered_set>
#include <vector>
#include "include/dart_api.h"
#include "platform/assert.h"
#include "vm/class_finalizer.h"
#include "vm/compiler/ffi/callback.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/message_handler.h"
#include "vm/object.h"
#include "vm/port.h"
#include "vm/symbols.h"
#include "vm/unit_test.h"
namespace dart {
FunctionPtr CreateTestFunction(FfiCallbackKind kind) {
const auto& ffi_lib = Library::Handle(Library::FfiLibrary());
const auto& ffi_void = Class::Handle(ffi_lib.LookupClass(Symbols::FfiVoid()));
const auto& ffi_void_type =
Type::Handle(Type::NewNonParameterizedType(ffi_void));
auto* thread = Thread::Current();
const char* kScriptChars =
R"(
void testFunction() {
}
)";
Dart_Handle library;
{
TransitionVMToNative transition(thread);
library = TestCase::LoadTestScript(kScriptChars, nullptr);
EXPECT_VALID(library);
}
const auto& lib =
Library::Handle(Library::RawCast(Api::UnwrapHandle(library)));
EXPECT(ClassFinalizer::ProcessPendingClasses());
const auto& cls = Class::Handle(lib.toplevel_class());
EXPECT(!cls.IsNull());
const auto& error = cls.EnsureIsFinalized(thread);
EXPECT(error == Error::null());
auto& function_name = String::Handle(String::New("testFunction"));
const auto& func = Function::Handle(cls.LookupStaticFunction(function_name));
EXPECT(!func.IsNull());
FunctionType& signature = FunctionType::Handle(FunctionType::New());
signature.set_result_type(ffi_void_type);
signature.SetIsFinalized();
signature ^= signature.Canonicalize(thread);
const auto& callback = Function::Handle(compiler::ffi::NativeCallbackFunction(
signature, func, Instance::Handle(Instance::null()), kind));
const auto& result = Object::Handle(
thread->zone(), Compiler::CompileFunction(thread, callback));
EXPECT(!result.IsError());
return callback.ptr();
}
class FakeMessageHandler : public MessageHandler {
public:
MessageStatus HandleMessage(std::unique_ptr<Message> message) override {
return MessageHandler::kOK;
}
};
VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateSyncFfiCallback) {
auto* fcm = FfiCallbackMetadata::Instance();
FfiCallbackMetadata::Trampoline tramp1 = 0;
FfiCallbackMetadata::Trampoline tramp2 = 0;
{
TestIsolateScope isolate_scope;
Thread* thread = Thread::Current();
Isolate* isolate = thread->isolate();
ASSERT(isolate == isolate_scope.isolate());
TransitionNativeToVM transition(thread);
StackZone stack_zone(thread);
HandleScope handle_scope(thread);
auto* zone = thread->zone();
const auto& func = Function::Handle(
CreateTestFunction(FfiCallbackKind::kIsolateLocalStaticCallback));
const auto& code = Code::Handle(func.EnsureHasCode());
EXPECT(!code.IsNull());
tramp1 = isolate->CreateIsolateLocalFfiCallback(
zone, func, Closure::Handle(Closure::null()), false);
EXPECT_NE(tramp1, 0u);
{
FfiCallbackMetadata::Metadata m1 =
fcm->LookupMetadataForTrampolineUnlocked(tramp1);
EXPECT(m1.IsLive());
EXPECT_EQ(m1.target_isolate(), isolate);
EXPECT_EQ(m1.target_entry_point(), code.EntryPoint());
EXPECT_EQ(m1.closure_handle(), nullptr);
EXPECT_EQ(static_cast<int>(m1.trampoline_type()),
static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
// head -> tramp1
auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
EXPECT_EQ(e1->list_prev(), nullptr);
EXPECT_EQ(e1->list_next(), nullptr);
}
tramp2 = isolate->CreateIsolateLocalFfiCallback(
zone, func, Closure::Handle(Closure::null()), false);
EXPECT_NE(tramp2, 0u);
EXPECT_NE(tramp2, tramp1);
{
FfiCallbackMetadata::Metadata m2 =
fcm->LookupMetadataForTrampolineUnlocked(tramp2);
EXPECT(m2.IsLive());
EXPECT_EQ(m2.target_isolate(), isolate);
EXPECT_EQ(m2.target_entry_point(), code.EntryPoint());
EXPECT_EQ(m2.closure_handle(), nullptr);
EXPECT_EQ(static_cast<int>(m2.trampoline_type()),
static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
}
{
// head -> tramp2 -> tramp1
auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
auto* e2 = fcm->MetadataEntryOfTrampoline(tramp2);
EXPECT_EQ(isolate->ffi_callback_list_head(), e2);
EXPECT_EQ(e2->list_prev(), nullptr);
EXPECT_EQ(e2->list_next(), e1);
EXPECT_EQ(e1->list_prev(), e2);
EXPECT_EQ(e1->list_next(), nullptr);
}
{
isolate->DeleteFfiCallback(tramp1);
FfiCallbackMetadata::Metadata m1 =
fcm->LookupMetadataForTrampolineUnlocked(tramp1);
EXPECT(!m1.IsLive());
// head -> tramp2
auto* e2 = fcm->MetadataEntryOfTrampoline(tramp2);
EXPECT_EQ(isolate->ffi_callback_list_head(), e2);
EXPECT_EQ(e2->list_prev(), nullptr);
EXPECT_EQ(e2->list_next(), nullptr);
}
}
{
// Isolate has shut down, so all callbacks should be deleted.
FfiCallbackMetadata::Metadata m1 =
fcm->LookupMetadataForTrampolineUnlocked(tramp1);
EXPECT(!m1.IsLive());
FfiCallbackMetadata::Metadata m2 =
fcm->LookupMetadataForTrampolineUnlocked(tramp2);
EXPECT(!m2.IsLive());
}
}
VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateAsyncFfiCallback) {
auto* fcm = FfiCallbackMetadata::Instance();
FfiCallbackMetadata::Trampoline tramp1 = 0;
FfiCallbackMetadata::Trampoline tramp2 = 0;
{
TestIsolateScope isolate_scope;
Thread* thread = Thread::Current();
Isolate* isolate = thread->isolate();
ASSERT(thread->isolate() == isolate_scope.isolate());
TransitionNativeToVM transition(thread);
StackZone stack_zone(thread);
HandleScope handle_scope(thread);
auto* zone = thread->zone();
const Function& func =
Function::Handle(CreateTestFunction(FfiCallbackKind::kAsyncCallback));
const Code& code = Code::Handle(func.EnsureHasCode());
EXPECT(!code.IsNull());
EXPECT_EQ(isolate->ffi_callback_list_head(), nullptr);
auto port1 = PortMap::CreatePort(new FakeMessageHandler());
tramp1 = isolate->CreateAsyncFfiCallback(zone, func, port1);
EXPECT_NE(tramp1, 0u);
{
FfiCallbackMetadata::Metadata m1 =
fcm->LookupMetadataForTrampolineUnlocked(tramp1);
EXPECT(m1.IsLive());
EXPECT_EQ(m1.target_isolate(), isolate);
EXPECT_EQ(m1.target_entry_point(), code.EntryPoint());
EXPECT_EQ(m1.send_port(), port1);
EXPECT_EQ(static_cast<int>(m1.trampoline_type()),
static_cast<int>(FfiCallbackMetadata::TrampolineType::kAsync));
// head -> tramp1
auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
EXPECT_EQ(e1->list_prev(), nullptr);
EXPECT_EQ(e1->list_next(), nullptr);
}
auto port2 = PortMap::CreatePort(new FakeMessageHandler());
tramp2 = isolate->CreateAsyncFfiCallback(zone, func, port2);
EXPECT_NE(tramp2, 0u);
EXPECT_NE(tramp2, tramp1);
{
FfiCallbackMetadata::Metadata m2 =
fcm->LookupMetadataForTrampolineUnlocked(tramp2);
EXPECT(m2.IsLive());
EXPECT_EQ(m2.target_isolate(), isolate);
EXPECT_EQ(m2.target_entry_point(), code.EntryPoint());
EXPECT_EQ(m2.send_port(), port2);
EXPECT_EQ(static_cast<int>(m2.trampoline_type()),
static_cast<int>(FfiCallbackMetadata::TrampolineType::kAsync));
}
{
// head -> tramp2 -> tramp1
auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
auto* e2 = fcm->MetadataEntryOfTrampoline(tramp2);
EXPECT_EQ(isolate->ffi_callback_list_head(), e2);
EXPECT_EQ(e2->list_prev(), nullptr);
EXPECT_EQ(e2->list_next(), e1);
EXPECT_EQ(e1->list_prev(), e2);
EXPECT_EQ(e1->list_next(), nullptr);
}
{
isolate->DeleteFfiCallback(tramp2);
FfiCallbackMetadata::Metadata m2 =
fcm->LookupMetadataForTrampolineUnlocked(tramp2);
EXPECT(!m2.IsLive());
// head -> tramp1
auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
EXPECT_EQ(e1->list_prev(), nullptr);
EXPECT_EQ(e1->list_next(), nullptr);
}
}
{
// Isolate has shut down, so all callbacks should be deleted.
FfiCallbackMetadata::Metadata m1 =
fcm->LookupMetadataForTrampolineUnlocked(tramp1);
EXPECT(!m1.IsLive());
FfiCallbackMetadata::Metadata m2 =
fcm->LookupMetadataForTrampolineUnlocked(tramp2);
EXPECT(!m2.IsLive());
}
}
VM_UNIT_TEST_CASE(FfiCallbackMetadata_CreateIsolateLocalFfiCallback) {
auto* fcm = FfiCallbackMetadata::Instance();
FfiCallbackMetadata::Trampoline tramp1 = 0;
FfiCallbackMetadata::Trampoline tramp2 = 0;
{
TestIsolateScope isolate_scope;
Thread* thread = Thread::Current();
Isolate* isolate = thread->isolate();
ASSERT(thread->isolate() == isolate_scope.isolate());
TransitionNativeToVM transition(thread);
StackZone stack_zone(thread);
HandleScope handle_scope(thread);
auto* zone = thread->zone();
const Function& func = Function::Handle(
CreateTestFunction(FfiCallbackKind::kIsolateLocalClosureCallback));
const Code& code = Code::Handle(func.EnsureHasCode());
EXPECT(!code.IsNull());
// Using a FfiCallbackKind::kSync function as a dummy closure.
const Function& closure_func = Function::Handle(
CreateTestFunction(FfiCallbackKind::kIsolateLocalStaticCallback));
const Context& context = Context::Handle(Context::null());
const Closure& closure1 = Closure::Handle(
Closure::New(Object::null_type_arguments(),
Object::null_type_arguments(), closure_func, context));
EXPECT_EQ(isolate->ffi_callback_list_head(), nullptr);
tramp1 = isolate->CreateIsolateLocalFfiCallback(zone, func, closure1, true);
EXPECT_NE(tramp1, 0u);
{
FfiCallbackMetadata::Metadata m1 =
fcm->LookupMetadataForTrampolineUnlocked(tramp1);
EXPECT(m1.IsLive());
EXPECT_EQ(m1.target_isolate(), isolate);
EXPECT_EQ(m1.target_entry_point(), code.EntryPoint());
EXPECT_EQ(m1.closure_handle()->ptr(), closure1.ptr());
EXPECT_EQ(static_cast<int>(m1.trampoline_type()),
static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
// head -> tramp1
auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
EXPECT_EQ(e1->list_prev(), nullptr);
EXPECT_EQ(e1->list_next(), nullptr);
}
const Closure& closure2 = Closure::Handle(
Closure::New(Object::null_type_arguments(),
Object::null_type_arguments(), closure_func, context));
tramp2 = isolate->CreateIsolateLocalFfiCallback(zone, func, closure2, true);
EXPECT_NE(tramp2, 0u);
EXPECT_NE(tramp2, tramp1);
{
FfiCallbackMetadata::Metadata m2 =
fcm->LookupMetadataForTrampolineUnlocked(tramp2);
EXPECT(m2.IsLive());
EXPECT_EQ(m2.target_isolate(), isolate);
EXPECT_EQ(m2.target_entry_point(), code.EntryPoint());
EXPECT_EQ(m2.closure_handle()->ptr(), closure2.ptr());
EXPECT_EQ(static_cast<int>(m2.trampoline_type()),
static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
}
{
// head -> tramp2 -> tramp1
auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
auto* e2 = fcm->MetadataEntryOfTrampoline(tramp2);
EXPECT_EQ(isolate->ffi_callback_list_head(), e2);
EXPECT_EQ(e2->list_prev(), nullptr);
EXPECT_EQ(e2->list_next(), e1);
EXPECT_EQ(e1->list_prev(), e2);
EXPECT_EQ(e1->list_next(), nullptr);
}
{
isolate->DeleteFfiCallback(tramp2);
FfiCallbackMetadata::Metadata m2 =
fcm->LookupMetadataForTrampolineUnlocked(tramp2);
EXPECT(!m2.IsLive());
// head -> tramp1
auto* e1 = fcm->MetadataEntryOfTrampoline(tramp1);
EXPECT_EQ(isolate->ffi_callback_list_head(), e1);
EXPECT_EQ(e1->list_prev(), nullptr);
EXPECT_EQ(e1->list_next(), nullptr);
}
}
{
// Isolate has shut down, so all callbacks should be deleted.
FfiCallbackMetadata::Metadata m1 =
fcm->LookupMetadataForTrampolineUnlocked(tramp1);
EXPECT(!m1.IsLive());
FfiCallbackMetadata::Metadata m2 =
fcm->LookupMetadataForTrampolineUnlocked(tramp2);
EXPECT(!m2.IsLive());
}
}
ISOLATE_UNIT_TEST_CASE(FfiCallbackMetadata_TrampolineRecycling) {
Isolate* isolate = thread->isolate();
auto* zone = thread->zone();
auto* fcm = FfiCallbackMetadata::Instance();
const Function& func =
Function::Handle(CreateTestFunction(FfiCallbackKind::kAsyncCallback));
const Code& code = Code::Handle(func.EnsureHasCode());
EXPECT(!code.IsNull());
auto port = PortMap::CreatePort(new FakeMessageHandler());
FfiCallbackMetadata::MetadataEntry* list_head = nullptr;
// Allocate and free one callback at a time, and verify that we don't reuse
// them. Allocate enough that the whole page fills up with dead trampolines.
std::vector<FfiCallbackMetadata::Trampoline> allocation_order;
std::unordered_set<FfiCallbackMetadata::Trampoline> allocated;
const intptr_t trampolines_per_page =
FfiCallbackMetadata::NumCallbackTrampolinesPerPage();
for (intptr_t i = 0; i < trampolines_per_page; ++i) {
auto tramp =
fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head);
EXPECT_EQ(allocated.count(tramp), 0u);
allocation_order.push_back(tramp);
allocated.insert(tramp);
fcm->DeleteCallback(tramp, &list_head);
}
// Now as we continue allocating and freeing, we start reusing them, in the
// same allocation order as before.
for (intptr_t i = 0; i < trampolines_per_page; ++i) {
auto tramp =
fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head);
EXPECT_EQ(allocated.count(tramp), 1u);
EXPECT_EQ(allocation_order[i], tramp);
fcm->DeleteCallback(tramp, &list_head);
}
// Now allocate enough to fill the page without freeing them. Again they
// should come out in the same order.
for (intptr_t i = 0; i < trampolines_per_page; ++i) {
auto tramp =
fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head);
EXPECT_EQ(allocated.count(tramp), 1u);
EXPECT_EQ(allocation_order[i], tramp);
}
// Now that the page is full, we should allocate a new page and see new
// trampolines we haven't seen before.
for (intptr_t i = 0; i < 3 * trampolines_per_page; ++i) {
auto tramp =
fcm->CreateAsyncFfiCallback(isolate, zone, func, port, &list_head);
EXPECT_EQ(allocated.count(tramp), 0u);
}
}
VM_UNIT_TEST_CASE(FfiCallbackMetadata_DeleteTrampolines) {
static constexpr int kCreations = 1000;
static constexpr int kDeletions = 100;
TestIsolateScope isolate_scope;
Thread* thread = Thread::Current();
Isolate* isolate = thread->isolate();
ASSERT(isolate == isolate_scope.isolate());
TransitionNativeToVM transition(thread);
StackZone stack_zone(thread);
HandleScope handle_scope(thread);
auto* fcm = FfiCallbackMetadata::Instance();
std::unordered_set<FfiCallbackMetadata::Trampoline> tramps;
FfiCallbackMetadata::MetadataEntry* list_head = nullptr;
const auto& sync_func = Function::Handle(
CreateTestFunction(FfiCallbackKind::kIsolateLocalStaticCallback));
const auto& sync_code = Code::Handle(sync_func.EnsureHasCode());
EXPECT(!sync_code.IsNull());
// Create some callbacks.
for (int itr = 0; itr < kCreations; ++itr) {
tramps.insert(fcm->CreateLocalFfiCallback(
isolate, /*isolate_group=*/nullptr, thread->zone(), sync_func,
Closure::Handle(Closure::null()), &list_head));
}
// Delete some of the callbacks.
for (int itr = 0; itr < kDeletions; ++itr) {
auto tramp = *tramps.begin();
fcm->DeleteCallback(tramp, &list_head);
tramps.erase(tramp);
}
// Verify all the callbacks.
for (FfiCallbackMetadata::Trampoline tramp : tramps) {
auto metadata = fcm->LookupMetadataForTrampolineUnlocked(tramp);
EXPECT(metadata.IsLive());
EXPECT_EQ(metadata.target_isolate(), isolate);
EXPECT_EQ(static_cast<int>(metadata.trampoline_type()),
static_cast<int>(FfiCallbackMetadata::TrampolineType::kSync));
EXPECT_EQ(metadata.target_entry_point(), sync_code.EntryPoint());
}
// Verify the list of callbacks.
uword list_length = 0;
for (FfiCallbackMetadata::MetadataEntry* me = list_head; me != nullptr;) {
++list_length;
auto tramp = fcm->TrampolineOfMetadataEntry(me);
EXPECT(me->metadata()->IsLive());
EXPECT_EQ(me->metadata()->target_isolate(), isolate);
EXPECT_EQ(tramps.count(tramp), 1u);
auto* next = me->list_next();
auto* prev = me->list_prev();
if (prev != nullptr) {
EXPECT_EQ(prev->list_next(), me);
} else {
EXPECT_EQ(list_head, me);
}
if (next != nullptr) {
EXPECT_EQ(next->list_prev(), me);
}
me = me->list_next();
}
EXPECT_EQ(list_length, tramps.size());
// Delete all callbacks and verify they're destroyed.
fcm->DeleteAllCallbacks(&list_head);
EXPECT_EQ(list_head, nullptr);
for (FfiCallbackMetadata::Trampoline tramp : tramps) {
EXPECT(!fcm->LookupMetadataForTrampolineUnlocked(tramp).IsLive());
}
}
static void RunBigRandomMultithreadedTest(uint64_t seed) {
static constexpr int kIterations = 1000;
TestIsolateScope isolate_scope;
Thread* thread = Thread::Current();
Isolate* isolate = thread->isolate();
ASSERT(isolate == isolate_scope.isolate());
TransitionNativeToVM transition(thread);
StackZone stack_zone(thread);
HandleScope handle_scope(thread);
struct TrampolineWithPort {
FfiCallbackMetadata::Trampoline tramp;
Dart_Port port;
};
auto* fcm = FfiCallbackMetadata::Instance();
Random random(seed);
std::vector<TrampolineWithPort> tramps;
std::unordered_set<FfiCallbackMetadata::Trampoline> tramp_set;
FfiCallbackMetadata::MetadataEntry* list_head = nullptr;
const Function& async_func =
Function::Handle(CreateTestFunction(FfiCallbackKind::kAsyncCallback));
const Code& async_code = Code::Handle(async_func.EnsureHasCode());
EXPECT(!async_code.IsNull());
const Function& sync_func = Function::Handle(
CreateTestFunction(FfiCallbackKind::kIsolateLocalStaticCallback));
const auto& sync_code = Code::Handle(sync_func.EnsureHasCode());
EXPECT(!sync_code.IsNull());
for (int itr = 0; itr < kIterations; ++itr) {
// Do a random action:
// - Allocate a sync callback
// - Allocate an async callback
// - Delete a callback
// - Delete all the sync callbacks for an isolate
if ((random.NextUInt32() % 100) == 0) {
// 1% chance of deleting all the callbacks on the thread.
fcm->DeleteAllCallbacks(&list_head);
// It would be nice to verify that all the trampolines have been deleted,
// but this is flaky because other threads can recycle these trampolines
// before we finish checking all of them.
tramps.clear();
tramp_set.clear();
EXPECT_EQ(list_head, nullptr);
} else if (tramps.size() > 0 && (random.NextUInt32() % 4) == 0) {
// 25% chance of deleting a callback.
uint32_t r = random.NextUInt32() % tramps.size();
auto tramp = tramps[r].tramp;
fcm->DeleteCallback(tramp, &list_head);
tramps[r] = tramps[tramps.size() - 1];
tramps.pop_back();
tramp_set.erase(tramp);
} else {
TrampolineWithPort tramp;
if ((random.NextUInt32() % 2) == 0) {
// 50% chance of creating a sync callback.
tramp.port = ILLEGAL_PORT;
tramp.tramp = fcm->CreateLocalFfiCallback(
isolate, /*isolate_group=*/nullptr, thread->zone(), sync_func,
Closure::Handle(Closure::null()), &list_head);
} else {
// 50% chance of creating an async callback.
tramp.port = PortMap::CreatePort(new FakeMessageHandler());
tramp.tramp = fcm->CreateAsyncFfiCallback(
isolate, thread->zone(), async_func, tramp.port, &list_head);
}
tramps.push_back(tramp);
tramp_set.insert(tramp.tramp);
}
// Verify all the callbacks.
for (const auto& tramp : tramps) {
auto metadata = fcm->LookupMetadataForTrampolineUnlocked(tramp.tramp);
EXPECT(metadata.IsLive());
EXPECT_EQ(metadata.target_isolate(), isolate);
if (metadata.trampoline_type() ==
FfiCallbackMetadata::TrampolineType::kSync) {
EXPECT_EQ(metadata.closure_handle(), nullptr);
EXPECT_EQ(metadata.target_entry_point(), sync_code.EntryPoint());
} else {
EXPECT_EQ(metadata.send_port(), tramp.port);
EXPECT_EQ(metadata.target_entry_point(), async_code.EntryPoint());
}
}
// Verify the isolate's list of callbacks.
uword list_length = 0;
for (FfiCallbackMetadata::MetadataEntry* me = list_head; me != nullptr;) {
++list_length;
auto tramp = fcm->TrampolineOfMetadataEntry(me);
EXPECT(me->metadata()->IsLive());
EXPECT_EQ(me->metadata()->target_isolate(), isolate);
EXPECT_EQ(tramp_set.count(tramp), 1u);
me = me->list_next();
}
EXPECT_EQ(list_length, tramps.size());
EXPECT_EQ(list_length, tramp_set.size());
}
// Delete all remaining callbacks.
fcm->DeleteAllCallbacks(&list_head);
EXPECT_EQ(list_head, nullptr);
}
ISOLATE_UNIT_TEST_CASE(FfiCallbackMetadata_BigRandomMultithreadedTest) {
static constexpr int kThreads = 5;
std::vector<std::thread> threads;
Random random;
for (int i = 0; i < kThreads; ++i) {
threads.push_back(
std::thread(RunBigRandomMultithreadedTest, random.NextUInt64()));
}
for (auto& thread : threads) {
thread.join();
}
}
} // namespace dart