Skip to content

Commit

Permalink
Remove support for async callbacks and fix the same-thread deadlock
Browse files Browse the repository at this point in the history
liamappelbe committed Dec 12, 2024
1 parent 004f57e commit 28c1f79
Showing 10 changed files with 301 additions and 146 deletions.
1 change: 0 additions & 1 deletion pkgs/ffigen/lib/src/code_generator.dart
Original file line number Diff line number Diff line change
@@ -11,7 +11,6 @@ export 'code_generator/constant.dart';
export 'code_generator/enum_class.dart';
export 'code_generator/func.dart';
export 'code_generator/func_type.dart';
export 'code_generator/future_type.dart';
export 'code_generator/global.dart';
export 'code_generator/handle.dart';
export 'code_generator/imports.dart';
48 changes: 0 additions & 48 deletions pkgs/ffigen/lib/src/code_generator/future_type.dart

This file was deleted.

45 changes: 28 additions & 17 deletions pkgs/ffigen/lib/src/code_generator/objc_block.dart
Original file line number Diff line number Diff line change
@@ -134,6 +134,8 @@ class ObjCBlock extends BindingType {
w.topLevelUniqueNamer.makeUnique('_${name}_blockingTrampoline');
final blockingCallable =
w.topLevelUniqueNamer.makeUnique('_${name}_blockingCallable');
final blockingListenerCallable =
w.topLevelUniqueNamer.makeUnique('_${name}_blockingListenerCallable');
final callExtension =
w.topLevelUniqueNamer.makeUnique('${name}_CallExtension');

@@ -180,14 +182,16 @@ $returnFfiDartType $listenerTrampoline(
}
${func.trampNatCallType} $listenerCallable = ${func.trampNatCallType}.listener(
$listenerTrampoline $exceptionalReturn)..keepIsolateAlive = false;
Future<$returnFfiDartType> $blockingTrampoline(
$blockCType block, ${blockingFunc.paramsFfiDartType}) async {
await ($getBlockClosure(block) as ${func.asyncFfiDartType})(
${func.paramsNameOnly});
$returnFfiDartType $blockingTrampoline(
$blockCType block, ${blockingFunc.paramsFfiDartType}) {
($getBlockClosure(block) as ${func.ffiDartType})(${func.paramsNameOnly});
$signalWaiterFn(waiter);
$releaseFn(block.cast());
}
${blockingFunc.trampNatCallType} $blockingCallable =
${blockingFunc.trampNatCallType}.isolateLocal(
$blockingTrampoline $exceptionalReturn)..keepIsolateAlive = false;
${blockingFunc.trampNatCallType} $blockingListenerCallable =
${blockingFunc.trampNatCallType}.listener(
$blockingTrampoline $exceptionalReturn)..keepIsolateAlive = false;
''');
@@ -282,8 +286,12 @@ abstract final class $name {
${func.dartType} fn, {Duration timeout = const Duration(seconds: 1)}) {
final raw = $newClosureBlock(
$blockingCallable.nativeFunction.cast(), $listenerConvFn);
final wrapper = $wrapBlockingBlockFn($wrapBlockingFn, raw, timeout);
final rawListener = $newClosureBlock(
$blockingListenerCallable.nativeFunction.cast(), $listenerConvFn);
final wrapper = $wrapBlockingBlockFn(
$wrapBlockingFn, raw, rawListener, timeout);
$releaseFn(raw.cast());
$releaseFn(rawListener.cast());
return $blockType(wrapper, retain: false, release: true);
}
''');
@@ -334,7 +342,8 @@ ref.pointer.ref.invoke.cast<${func.trampNatFnCType}>()
}
final waiterParam = Parameter(
name: 'waiter', type: PointerType(voidType), objCConsumed: false);
final blockingRetains = [waiterParam.name, ...retains];
final blockingRetains = ['nil', ...retains];
final blockingListenerRetains = [waiterParam.name, ...retains];

final argStr = argsReceived.join(', ');
final blockingArgStr = [
@@ -364,13 +373,20 @@ $listenerName $listenerWrapper($listenerName block) NS_RETURNS_RETAINED {
typedef ${returnType.getNativeType()} (^$blockingName)($blockingArgStr);
__attribute__((visibility("default"))) __attribute__((used))
$listenerName $blockingWrapper(
$blockingName block, double timeoutSeconds, void* (*newWaiter)(),
void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED {
$blockingName block, $blockingName listenerBlock, double timeoutSeconds,
void* (*newWaiter)(), void (*awaitWaiter)(void*, double))
NS_RETURNS_RETAINED {
NSThread *targetThread = [NSThread currentThread];
return ^void($argStr) {
void* waiter = newWaiter();
${generateRetain('block')};
block(${blockingRetains.join(', ')});
awaitWaiter(waiter, timeoutSeconds);
if ([NSThread currentThread] == targetThread) {
${generateRetain('block')};
block(${blockingRetains.join(', ')});
} else {
void* waiter = newWaiter();
${generateRetain('listenerBlock')};
listenerBlock(${blockingListenerRetains.join(', ')});
awaitWaiter(waiter, timeoutSeconds);
}
};
}
''');
@@ -456,7 +472,6 @@ class _FnHelper {
late final String natFnPtrCType;
late final String dartType;
late final String ffiDartType;
late final String asyncFfiDartType;
late final String trampCType;
late final String trampFfiDartType;
late final String trampNatCallType;
@@ -473,10 +488,6 @@ class _FnHelper {
dartType = fnType.getDartType(w, writeArgumentNames: false);
ffiDartType = fnType.getFfiDartType(w, writeArgumentNames: false);

final asyncFnType =
FunctionType(returnType: FutureOrType(returnType), parameters: params);
asyncFfiDartType = asyncFnType.getFfiDartType(w, writeArgumentNames: false);

final trampFnType = FunctionType(
returnType: returnType,
parameters: [
Original file line number Diff line number Diff line change
@@ -229,6 +229,10 @@ class ObjCBuiltInFunctions {
type: PointerType(objCBlockType),
objCConsumed: false),
if (blocking) ...[
Parameter(
name: 'listnerBlock',
type: PointerType(objCBlockType),
objCConsumed: false),
Parameter(
name: 'timeoutSeconds', type: doubleType, objCConsumed: false),
Parameter(
1 change: 1 addition & 0 deletions pkgs/ffigen/lib/src/code_generator/writer.dart
Original file line number Diff line number Diff line change
@@ -420,6 +420,7 @@ class Writer {
final s = StringBuffer();
s.write('''
#include <stdint.h>
#import <Foundation/Foundation.h>
''');

for (final entryPoint in nativeEntryPoints) {
34 changes: 21 additions & 13 deletions pkgs/ffigen/test/native_objc_test/block_test.dart
Original file line number Diff line number Diff line change
@@ -115,19 +115,27 @@ void main() {
expect(value, 123);
});*/

// test('Blocking block same thread', () {
// int value = 0;
// final block = VoidBlock.blocking(() async {
// await Future.delayed(Duration(milliseconds: 100));
// value = 123;
// });
// BlockTester.callOnSameThread_(block);
// expect(value, 123);
// });
void waitSync(Duration d) {
final t = Stopwatch();
t.start();
while (t.elapsed < d) {
// Waiting...
}
}

test('Blocking block same thread', () {
int value = 0;
final block = VoidBlock.blocking(() {
waitSync(Duration(milliseconds: 100));
value = 123;
});
BlockTester.callOnSameThread_(block);
expect(value, 123);
});

test('Blocking block new thread', () async {
final block = IntPtrBlock.blocking((Pointer<Int32> result) async {
await Future.delayed(Duration(milliseconds: 100));
final block = IntPtrBlock.blocking((Pointer<Int32> result) {
waitSync(Duration(milliseconds: 100));
result.value = 123456;
}, timeout: Duration(seconds: 60));
final resultCompleter = Completer<int>();
@@ -140,8 +148,8 @@ void main() {

test('Blocking block timeout', () async {
int value = 0;
final block = VoidBlock.blocking(() async {
await Future.delayed(Duration(milliseconds: 300));
final block = VoidBlock.blocking(() {
waitSync(Duration(milliseconds: 300));
value = 123456;
}, timeout: Duration(milliseconds: 100));
BlockTester.callOnNewThread_(block).start();
6 changes: 4 additions & 2 deletions pkgs/objective_c/lib/src/internal.dart
Original file line number Diff line number Diff line change
@@ -425,13 +425,15 @@ Function getBlockClosure(_BlkPtr block) {
typedef _NewWaiterFn = NativeFunction<_VoidPtr Function()>;
typedef _AwaitWaiterFn = NativeFunction<Void Function(_VoidPtr, Double)>;
typedef _NativeWrapperFn = _BlkPtr Function(
_BlkPtr, double, Pointer<_NewWaiterFn>, Pointer<_AwaitWaiterFn>);
_BlkPtr, _BlkPtr, double, Pointer<_NewWaiterFn>, Pointer<_AwaitWaiterFn>);

/// Only for use by ffigen bindings.
_BlkPtr wrapBlockingBlock(
_NativeWrapperFn nativeWrapper, _BlkPtr raw, Duration timeout) =>
_NativeWrapperFn nativeWrapper, _BlkPtr raw, _BlkPtr rawListener,
Duration timeout) =>
nativeWrapper(
raw,
rawListener,
timeout.inMicroseconds / Duration.microsecondsPerSecond,
Native.addressOf<_NewWaiterFn>(c.newWaiter),
Native.addressOf<_AwaitWaiterFn>(c.awaitWaiter),
200 changes: 171 additions & 29 deletions pkgs/objective_c/lib/src/objective_c_bindings_generated.dart

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions pkgs/objective_c/src/objective_c.m
Original file line number Diff line number Diff line change
@@ -59,20 +59,20 @@ -(void)wait: (double)timeoutSeconds {
@end

FFI_EXPORT void* DOBJC_newWaiter() {
DOBJCWaiter* wait = [[DOBJCWaiter alloc] init];
DOBJCWaiter* w = [[DOBJCWaiter alloc] init];
// __bridge_retained increments the ref count, __bridge_transfer decrements
// it, and __bridge doesn't change it. One of the __bridge_retained calls is
// balanced by the __bridge_transfer in signalWaiter, and the other is
// balanced by the one in awaitWaiter. In other words, this function returns
// an object with a +2 ref count, and signal and await each decrement the
// ref count.
return (__bridge_retained void*)(__bridge id)(__bridge_retained void*)(wait);
return (__bridge_retained void*)(__bridge id)(__bridge_retained void*)(w);
}

FFI_EXPORT void DOBJC_signalWaiter(void* wait) {
[(__bridge_transfer DOBJCWaiter*)wait signal];
FFI_EXPORT void DOBJC_signalWaiter(void* waiter) {
if (waiter) [(__bridge_transfer DOBJCWaiter*)waiter signal];
}

FFI_EXPORT void DOBJC_awaitWaiter(void* wait, double timeoutSeconds) {
[(__bridge_transfer DOBJCWaiter*)wait wait: timeoutSeconds];
FFI_EXPORT void DOBJC_awaitWaiter(void* waiter, double timeoutSeconds) {
[(__bridge_transfer DOBJCWaiter*)waiter wait: timeoutSeconds];
}
96 changes: 66 additions & 30 deletions pkgs/objective_c/src/objective_c_bindings_generated.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <stdint.h>
#import <Foundation/Foundation.h>
#import "foundation.h"
#import "input_stream_adapter.h"
#import "proxy.h"
@@ -24,13 +25,20 @@ _ListenerTrampoline _ObjectiveCBindings_wrapListenerBlock_1j2nt86(_ListenerTramp
typedef void (^_BlockingTrampoline)(void * waiter, id arg0, id arg1, id arg2);
__attribute__((visibility("default"))) __attribute__((used))
_ListenerTrampoline _ObjectiveCBindings_wrapBlockingBlock_1j2nt86(
_BlockingTrampoline block, double timeoutSeconds, void* (*newWaiter)(),
void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED {
_BlockingTrampoline block, _BlockingTrampoline listenerBlock, double timeoutSeconds,
void* (*newWaiter)(), void (*awaitWaiter)(void*, double))
NS_RETURNS_RETAINED {
NSThread *targetThread = [NSThread currentThread];
return ^void(id arg0, id arg1, id arg2) {
void* waiter = newWaiter();
objc_retainBlock(block);
block(waiter, objc_retainBlock(arg0), objc_retain(arg1), objc_retain(arg2));
awaitWaiter(waiter, timeoutSeconds);
if ([NSThread currentThread] == targetThread) {
objc_retainBlock(block);
block(nil, objc_retainBlock(arg0), objc_retain(arg1), objc_retain(arg2));
} else {
void* waiter = newWaiter();
objc_retainBlock(listenerBlock);
listenerBlock(waiter, objc_retainBlock(arg0), objc_retain(arg1), objc_retain(arg2));
awaitWaiter(waiter, timeoutSeconds);
}
};
}

@@ -46,13 +54,20 @@ _ListenerTrampoline1 _ObjectiveCBindings_wrapListenerBlock_ovsamd(_ListenerTramp
typedef void (^_BlockingTrampoline1)(void * waiter, void * arg0);
__attribute__((visibility("default"))) __attribute__((used))
_ListenerTrampoline1 _ObjectiveCBindings_wrapBlockingBlock_ovsamd(
_BlockingTrampoline1 block, double timeoutSeconds, void* (*newWaiter)(),
void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED {
_BlockingTrampoline1 block, _BlockingTrampoline1 listenerBlock, double timeoutSeconds,
void* (*newWaiter)(), void (*awaitWaiter)(void*, double))
NS_RETURNS_RETAINED {
NSThread *targetThread = [NSThread currentThread];
return ^void(void * arg0) {
void* waiter = newWaiter();
objc_retainBlock(block);
block(waiter, arg0);
awaitWaiter(waiter, timeoutSeconds);
if ([NSThread currentThread] == targetThread) {
objc_retainBlock(block);
block(nil, arg0);
} else {
void* waiter = newWaiter();
objc_retainBlock(listenerBlock);
listenerBlock(waiter, arg0);
awaitWaiter(waiter, timeoutSeconds);
}
};
}

@@ -68,13 +83,20 @@ _ListenerTrampoline2 _ObjectiveCBindings_wrapListenerBlock_wjovn7(_ListenerTramp
typedef void (^_BlockingTrampoline2)(void * waiter, void * arg0, id arg1);
__attribute__((visibility("default"))) __attribute__((used))
_ListenerTrampoline2 _ObjectiveCBindings_wrapBlockingBlock_wjovn7(
_BlockingTrampoline2 block, double timeoutSeconds, void* (*newWaiter)(),
void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED {
_BlockingTrampoline2 block, _BlockingTrampoline2 listenerBlock, double timeoutSeconds,
void* (*newWaiter)(), void (*awaitWaiter)(void*, double))
NS_RETURNS_RETAINED {
NSThread *targetThread = [NSThread currentThread];
return ^void(void * arg0, id arg1) {
void* waiter = newWaiter();
objc_retainBlock(block);
block(waiter, arg0, objc_retain(arg1));
awaitWaiter(waiter, timeoutSeconds);
if ([NSThread currentThread] == targetThread) {
objc_retainBlock(block);
block(nil, arg0, objc_retain(arg1));
} else {
void* waiter = newWaiter();
objc_retainBlock(listenerBlock);
listenerBlock(waiter, arg0, objc_retain(arg1));
awaitWaiter(waiter, timeoutSeconds);
}
};
}

@@ -90,13 +112,20 @@ _ListenerTrampoline3 _ObjectiveCBindings_wrapListenerBlock_18d6mda(_ListenerTram
typedef void (^_BlockingTrampoline3)(void * waiter, void * arg0, id arg1, NSStreamEvent arg2);
__attribute__((visibility("default"))) __attribute__((used))
_ListenerTrampoline3 _ObjectiveCBindings_wrapBlockingBlock_18d6mda(
_BlockingTrampoline3 block, double timeoutSeconds, void* (*newWaiter)(),
void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED {
_BlockingTrampoline3 block, _BlockingTrampoline3 listenerBlock, double timeoutSeconds,
void* (*newWaiter)(), void (*awaitWaiter)(void*, double))
NS_RETURNS_RETAINED {
NSThread *targetThread = [NSThread currentThread];
return ^void(void * arg0, id arg1, NSStreamEvent arg2) {
void* waiter = newWaiter();
objc_retainBlock(block);
block(waiter, arg0, objc_retain(arg1), arg2);
awaitWaiter(waiter, timeoutSeconds);
if ([NSThread currentThread] == targetThread) {
objc_retainBlock(block);
block(nil, arg0, objc_retain(arg1), arg2);
} else {
void* waiter = newWaiter();
objc_retainBlock(listenerBlock);
listenerBlock(waiter, arg0, objc_retain(arg1), arg2);
awaitWaiter(waiter, timeoutSeconds);
}
};
}

@@ -112,12 +141,19 @@ _ListenerTrampoline4 _ObjectiveCBindings_wrapListenerBlock_wjvic9(_ListenerTramp
typedef void (^_BlockingTrampoline4)(void * waiter, id arg0, id arg1);
__attribute__((visibility("default"))) __attribute__((used))
_ListenerTrampoline4 _ObjectiveCBindings_wrapBlockingBlock_wjvic9(
_BlockingTrampoline4 block, double timeoutSeconds, void* (*newWaiter)(),
void (*awaitWaiter)(void*, double)) NS_RETURNS_RETAINED {
_BlockingTrampoline4 block, _BlockingTrampoline4 listenerBlock, double timeoutSeconds,
void* (*newWaiter)(), void (*awaitWaiter)(void*, double))
NS_RETURNS_RETAINED {
NSThread *targetThread = [NSThread currentThread];
return ^void(id arg0, id arg1) {
void* waiter = newWaiter();
objc_retainBlock(block);
block(waiter, objc_retain(arg0), objc_retain(arg1));
awaitWaiter(waiter, timeoutSeconds);
if ([NSThread currentThread] == targetThread) {
objc_retainBlock(block);
block(nil, objc_retain(arg0), objc_retain(arg1));
} else {
void* waiter = newWaiter();
objc_retainBlock(listenerBlock);
listenerBlock(waiter, objc_retain(arg0), objc_retain(arg1));
awaitWaiter(waiter, timeoutSeconds);
}
};
}

0 comments on commit 28c1f79

Please sign in to comment.