[vm/shared] Implement Isolate runEventLoopSync.

TEST=ffi/threading_runeventloop_test

Change-Id: I2061d10d14bcdd589a4a9893f122620f8d0a4e7e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/497100
Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
Alexander Aprelev
2026-05-28 11:38:29 -07:00
parent a898adf8b7
commit 527d529493
8 changed files with 323 additions and 59 deletions
+165
View File
@@ -0,0 +1,165 @@
// Copyright (c) 2026, 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.
//
// Tests Isolate threading API.
//
// VMOptions=--experimental-shared-data
import 'dart:async';
import 'dart:concurrent';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:dart_internal/isolate_group.dart' show IsolateGroup;
import "package:expect/async_helper.dart";
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
import 'threading_utils.dart';
@pragma('vm:shared')
int counter = 0;
int foo = 42;
@pragma('vm:shared')
late Mutex mutexCondvar;
@pragma('vm:shared')
late ConditionVariable condVar;
@pragma('vm:shared')
int greetingsReceived = 0;
int threadMain(Pointer<Void> data) {
final pthreadSelf = DynamicLibrary.process()
.lookupFunction<PthreadSelfNFT, PthreadSelfFT>('pthread_self');
final self = pthreadSelf();
final i = data.cast<Uint8>()[0];
print('threadMain started with $data i:$i pthreadid $self');
final new_isolate = Isolate.create(debugName: "helper");
Expect.isNotNull(new_isolate);
final SendPort sp = new_isolate.runSync(() {
late RawReceivePort rp;
rp = RawReceivePort((e) {
print('running RawReceivePort handler $e');
final pthreadSelf = DynamicLibrary.process()
.lookupFunction<PthreadSelfNFT, PthreadSelfFT>('pthread_self');
final isolate_self = pthreadSelf();
print('=== receivePort handler received $e on pthreadid $self');
Expect.equals(self, isolate_self);
Expect.equals("greetings!", e);
mutexCondvar.runLocked(() {
greetingsReceived |= (1 << i);
condVar.notify();
});
rp.close();
});
return rp.sendPort;
});
Expect.isNotNull(sp);
sp.send('greetings!');
// No response is expected until we start running event loop.
mutexCondvar.runLocked(() => condVar.wait(mutexCondvar, /*timeout_ms=*/ 100));
Expect.isFalse(((1 << i) & greetingsReceived) != 0);
print('=== running event loop for $new_isolate');
new_isolate.runEventLoopSync();
mutexCondvar.runLocked(() {
while (((1 << i) & greetingsReceived) == 0) {
condVar.wait(mutexCondvar);
}
});
Expect.isTrue(((1 << i) & greetingsReceived) != 0);
print('=== running runSync again');
new_isolate.runSync(() {
print('=== hi, kuka ${++foo}!');
Expect.equals(43, foo);
});
print('=== shutting down');
new_isolate.shutdownSync();
return 0;
}
ThreadInfo testRunOnNewIsolateOnNewThread(
int i,
int Function(Pointer<Void>) threadMain,
) {
final threadInfo = ThreadInfo();
Expect.equals(0, pthreadAttrInit(threadInfo.ptr_attr));
threadInfo.ptr_data.cast<Uint8>()[0] = i;
print(
'=== ptr_data: ${threadInfo.ptr_data.address.toRadixString(16)}, i: $i',
);
final callback =
NativeCallable<IntPtr Function(Pointer<Void>)>.isolateGroupBound(
threadMain,
exceptionalReturn: -1,
);
callback.keepIsolateAlive = false;
pthreadCreate(
threadInfo.ptr_tid,
threadInfo.ptr_attr,
callback.nativeFunction,
threadInfo.ptr_data.cast<Void>(),
);
return threadInfo;
}
Future<void> testRunEventLoopManyThreads({int numThreads = 63}) async {
if (Platform.isWindows) {
// pthread library loading doesn't work on Windows.
return;
}
mutexCondvar = Mutex();
condVar = ConditionVariable();
final threadInfos = <ThreadInfo>[];
final repliedMask = (1 << numThreads) - 1;
print('repliedMask: ${repliedMask.toRadixString(16)}');
for (int i = 0; i < numThreads; i++) {
threadInfos.add(testRunOnNewIsolateOnNewThread(i, threadMain));
}
mutexCondvar.runLocked(() {
while (greetingsReceived < repliedMask) {
condVar.wait(mutexCondvar);
print('main received ${greetingsReceived.toRadixString(16)}');
}
});
print('main is happy received ${greetingsReceived.toRadixString(16)}');
for (ThreadInfo threadInfo in threadInfos) {
threadInfo.join();
}
}
Future<void> testFailRunEventLoopFromIsolate() async {
Expect.throws(
() {
Isolate.current.runEventLoopSync();
},
(e) =>
e is StateError &&
e.message.contains("Should be invoked outside of an isolate"),
);
}
main(List<String> args, SendPort? message) async {
if (message != null) {
Expect.equals(1, args.length);
Expect.equals("worker", args[0]);
await ReceivePort().first;
return;
}
asyncStart();
await testRunEventLoopManyThreads(numThreads: 63);
await testFailRunEventLoopFromIsolate();
asyncEnd();
}
+1 -42
View File
@@ -18,30 +18,7 @@ import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
import 'dylib_utils.dart';
typedef PthreadAttrInitFT = int Function(Pointer<Char>);
typedef PthreadAttrInitNFT = IntPtr Function(Pointer<Char>);
final pthreadAttrInit = DynamicLibrary.process()
.lookupFunction<PthreadAttrInitNFT, PthreadAttrInitFT>('pthread_attr_init');
typedef PthreadAttrDestroyFT = int Function(Pointer<Char>);
typedef PthreadAttrDestroyNFT = IntPtr Function(Pointer<Char>);
final pthreadAttrDestroy = DynamicLibrary.process()
.lookupFunction<PthreadAttrDestroyNFT, PthreadAttrDestroyFT>(
'pthread_attr_destroy',
);
typedef PthreadCreateFT =
int Function(Pointer<IntPtr>, Pointer<Char>, Pointer, Pointer<Void>);
typedef PthreadCreateNFT =
IntPtr Function(Pointer<IntPtr>, Pointer<Char>, Pointer, Pointer<Void>);
final pthreadCreate = DynamicLibrary.process()
.lookupFunction<PthreadCreateNFT, PthreadCreateFT>('pthread_create');
typedef PthreadJoinFT = int Function(int, Pointer<Void>);
typedef PthreadJoinNFT = IntPtr Function(IntPtr, Pointer<Void>);
final pthreadJoin = DynamicLibrary.process()
.lookupFunction<PthreadJoinNFT, PthreadJoinFT>('pthread_join');
import 'threading_utils.dart';
@pragma('vm:shared')
int counter = 0;
@@ -163,24 +140,6 @@ int threadMain(Pointer<Void> data) {
return 0;
}
class ThreadInfo {
final ptr_attr = calloc<Char>(64); // big enough to fit pthread_attr_t?
final ptr_tid = calloc<IntPtr>(1);
final ptr_data = calloc<Int32>(1024);
final ptr_retval = calloc<IntPtr>(1024);
void join() {
Expect.equals(0, pthreadJoin(ptr_tid.value, ptr_retval.cast<Void>()));
calloc.free(ptr_retval);
calloc.free(ptr_data);
calloc.free(ptr_tid);
Expect.equals(0, pthreadAttrDestroy(ptr_attr));
calloc.free(ptr_attr);
}
}
Future<void> testRunSyncOnPinnedToSelfIsolate() async {
if (Platform.isWindows) {
return; // pthread is not available on Windows.
+56
View File
@@ -0,0 +1,56 @@
// Copyright (c) 2026, 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.
//
import 'dart:ffi';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
typedef PthreadAttrInitFT = int Function(Pointer<Char>);
typedef PthreadAttrInitNFT = IntPtr Function(Pointer<Char>);
final pthreadAttrInit = DynamicLibrary.process()
.lookupFunction<PthreadAttrInitNFT, PthreadAttrInitFT>('pthread_attr_init');
typedef PthreadAttrDestroyFT = int Function(Pointer<Char>);
typedef PthreadAttrDestroyNFT = IntPtr Function(Pointer<Char>);
final pthreadAttrDestroy = DynamicLibrary.process()
.lookupFunction<PthreadAttrDestroyNFT, PthreadAttrDestroyFT>(
'pthread_attr_destroy',
);
typedef PthreadCreateFT =
int Function(Pointer<IntPtr>, Pointer<Char>, Pointer, Pointer<Void>);
typedef PthreadCreateNFT =
IntPtr Function(Pointer<IntPtr>, Pointer<Char>, Pointer, Pointer<Void>);
final pthreadCreate = DynamicLibrary.process()
.lookupFunction<PthreadCreateNFT, PthreadCreateFT>('pthread_create');
typedef PthreadJoinFT = int Function(int, Pointer<Void>);
typedef PthreadJoinNFT = IntPtr Function(IntPtr, Pointer<Void>);
final pthreadJoin = DynamicLibrary.process()
.lookupFunction<PthreadJoinNFT, PthreadJoinFT>('pthread_join');
typedef PthreadSelfFT = int Function();
typedef PthreadSelfNFT = IntPtr Function();
final pthreadSelf = DynamicLibrary.process()
.lookupFunction<PthreadSelfNFT, PthreadSelfFT>('pthread_self');
class ThreadInfo {
final ptr_attr = calloc<Char>(64); // big enough to fit pthread_attr_t?
final ptr_tid = calloc<IntPtr>(1);
final ptr_data = calloc<Int32>(1024);
final ptr_retval = calloc<IntPtr>(1024);
void join() {
Expect.equals(0, pthreadJoin(ptr_tid.value, ptr_retval.cast<Void>()));
calloc.free(ptr_retval);
calloc.free(ptr_data);
calloc.free(ptr_tid);
Expect.equals(0, pthreadAttrDestroy(ptr_attr));
calloc.free(ptr_attr);
}
}