Skip to content

Commit d3a9dea

Browse files
[pigeon] Update task queue handling (#8627)
Updates the task queue implementation across all supporting generators to use one task queue for an entire host API, rather than one per method, so that multiple methods using task queues will have ordering guarantees amongst them. Also changes the Obj-C implementation to no-op on macOS instead of failing to compile, so that shared iOS/macOS code can use task queues on iOS without breaking macOS. (We could deliberately add a compile error with a clear error message in a macOS ifdef if we get reports of user confusion in the future, but my expectation is that no-op is the best option for most people.) Related changes: - Adds integration testing that task queues work, instead of just that the generator step succeeds. - For platforms that don't support task queues, they are still included in the integration test to ensure that - I removed the now-vestigial background_platform_channels.dart. - Adds Swift support, since it was easier to add than to skip it in the tests. Fixes flutter/flutter#162624 Fixes flutter/flutter#111512
1 parent dbe37dd commit d3a9dea

File tree

35 files changed

+1080
-81
lines changed

35 files changed

+1080
-81
lines changed

packages/pigeon/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 24.2.0
2+
3+
* Adjusts task queues to use a shared task queue for all methods in a single
4+
API instance, to give the same ordering guarantees as non-task-queue usage.
5+
* [swift] Adds task queue support to the Swift generator.
6+
17
## 24.1.1
28

39
* [swift, kotlin] Adds an error message when a ProxyAPI callback method that returns a non-null

packages/pigeon/lib/src/generator_tools.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import 'ast.dart';
1414
/// The current version of pigeon.
1515
///
1616
/// This must match the version in pubspec.yaml.
17-
const String pigeonVersion = '24.1.1';
17+
const String pigeonVersion = '24.2.0';
1818

1919
/// Read all the content from [stdin] to a String.
2020
String readStdin() {

packages/pigeon/lib/src/java/java_generator.dart

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import '../ast.dart';
66
import '../functional.dart';
77
import '../generator.dart';
88
import '../generator_tools.dart';
9-
import '../pigeon_lib.dart' show TaskQueueType;
9+
import '../types/task_queue.dart';
1010

1111
/// Documentation open symbol.
1212
const String _docCommentPrefix = '/**';
@@ -835,15 +835,21 @@ if (wrapped == null) {
835835
indent.addScoped('{', '}', () {
836836
indent.writeln(
837837
'messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix;');
838+
String? serialBackgroundQueue;
839+
if (api.methods.any((Method m) =>
840+
m.taskQueueType == TaskQueueType.serialBackgroundThread)) {
841+
serialBackgroundQueue = 'taskQueue';
842+
indent.writeln(
843+
'BinaryMessenger.TaskQueue $serialBackgroundQueue = binaryMessenger.makeBackgroundTaskQueue();');
844+
}
838845
for (final Method method in api.methods) {
839-
_writeMethodSetUp(
840-
generatorOptions,
841-
root,
842-
indent,
843-
api,
844-
method,
845-
dartPackageName: dartPackageName,
846-
);
846+
_writeHostMethodMessageHandler(
847+
generatorOptions, root, indent, api, method,
848+
dartPackageName: dartPackageName,
849+
serialBackgroundQueue:
850+
method.taskQueueType == TaskQueueType.serialBackgroundThread
851+
? serialBackgroundQueue
852+
: null);
847853
}
848854
});
849855
});
@@ -887,34 +893,27 @@ if (wrapped == null) {
887893
indent.writeln('$returnType ${method.name}(${argSignature.join(', ')});');
888894
}
889895

890-
/// Write a static setUp function in the interface.
891-
/// Example:
892-
/// static void setUp(BinaryMessenger binaryMessenger, Foo api) {...}
893-
void _writeMethodSetUp(
896+
/// Write a single method's handler for the setUp function.
897+
void _writeHostMethodMessageHandler(
894898
JavaOptions generatorOptions,
895899
Root root,
896900
Indent indent,
897901
Api api,
898902
final Method method, {
899903
required String dartPackageName,
904+
String? serialBackgroundQueue,
900905
}) {
901906
final String channelName = makeChannelName(api, method, dartPackageName);
902907
indent.write('');
903908
indent.addScoped('{', '}', () {
904-
String? taskQueue;
905-
if (method.taskQueueType != TaskQueueType.serial) {
906-
taskQueue = 'taskQueue';
907-
indent.writeln(
908-
'BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue();');
909-
}
910909
indent.writeln('BasicMessageChannel<Object> channel =');
911910
indent.nest(2, () {
912911
indent.writeln('new BasicMessageChannel<>(');
913912
indent.nest(2, () {
914913
indent.write(
915914
'binaryMessenger, "$channelName" + messageChannelSuffix, getCodec()');
916-
if (taskQueue != null) {
917-
indent.addln(', $taskQueue);');
915+
if (serialBackgroundQueue != null) {
916+
indent.addln(', $serialBackgroundQueue);');
918917
} else {
919918
indent.addln(');');
920919
}

packages/pigeon/lib/src/kotlin/kotlin_generator.dart

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import '../ast.dart';
88
import '../functional.dart';
99
import '../generator.dart';
1010
import '../generator_tools.dart';
11-
import '../pigeon_lib.dart' show TaskQueueType;
11+
import '../types/task_queue.dart';
1212
import 'templates.dart';
1313

1414
/// Documentation open symbol.
@@ -654,6 +654,13 @@ if (wrapped == null) {
654654
indent.addScoped('{', '}', () {
655655
indent.writeln(
656656
r'val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""');
657+
String? serialBackgroundQueue;
658+
if (api.methods.any((Method m) =>
659+
m.taskQueueType == TaskQueueType.serialBackgroundThread)) {
660+
serialBackgroundQueue = 'taskQueue';
661+
indent.writeln(
662+
'val $serialBackgroundQueue = binaryMessenger.makeBackgroundTaskQueue()');
663+
}
657664
for (final Method method in api.methods) {
658665
_writeHostMethodMessageHandler(
659666
indent,
@@ -664,6 +671,10 @@ if (wrapped == null) {
664671
parameters: method.parameters,
665672
returnType: method.returnType,
666673
isAsynchronous: method.isAsynchronous,
674+
serialBackgroundQueue:
675+
method.taskQueueType == TaskQueueType.serialBackgroundThread
676+
? serialBackgroundQueue
677+
: null,
667678
);
668679
}
669680
});
@@ -1071,8 +1082,8 @@ if (wrapped == null) {
10711082
fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
10721083
sink.error(errorCode, errorMessage, errorDetails)
10731084
}
1074-
1075-
fun endOfStream() {
1085+
1086+
fun endOfStream() {
10761087
sink.endOfStream()
10771088
}
10781089
}
@@ -1249,24 +1260,18 @@ if (wrapped == null) {
12491260
required TypeDeclaration returnType,
12501261
String setHandlerCondition = 'api != null',
12511262
bool isAsynchronous = false,
1263+
String? serialBackgroundQueue,
12521264
String Function(List<String> safeArgNames, {required String apiVarName})?
12531265
onCreateCall,
12541266
}) {
12551267
indent.write('run ');
12561268
indent.addScoped('{', '}', () {
1257-
String? taskQueue;
1258-
if (taskQueueType != TaskQueueType.serial) {
1259-
taskQueue = 'taskQueue';
1260-
indent.writeln(
1261-
'val $taskQueue = binaryMessenger.makeBackgroundTaskQueue()');
1262-
}
1263-
12641269
indent.write(
12651270
'val channel = BasicMessageChannel<Any?>(binaryMessenger, "$channelName", codec',
12661271
);
12671272

1268-
if (taskQueue != null) {
1269-
indent.addln(', $taskQueue)');
1273+
if (serialBackgroundQueue != null) {
1274+
indent.addln(', $serialBackgroundQueue)');
12701275
} else {
12711276
indent.addln(')');
12721277
}

packages/pigeon/lib/src/objc/objc_generator.dart

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import '../ast.dart';
66
import '../functional.dart';
77
import '../generator.dart';
88
import '../generator_tools.dart';
9-
import '../pigeon_lib.dart' show Error, TaskQueueType;
9+
import '../pigeon_lib.dart' show Error;
10+
import '../types/task_queue.dart';
1011

1112
/// Documentation comment open symbol.
1213
const String _docCommentPrefix = '///';
@@ -860,24 +861,33 @@ if (self.wrapped == nil) {
860861
indent.addScoped('{', '}', () {
861862
indent.writeln(
862863
'messageChannelSuffix = messageChannelSuffix.length > 0 ? [NSString stringWithFormat: @".%@", messageChannelSuffix] : @"";');
864+
String? serialBackgroundQueue;
865+
if (api.methods.any((Method m) =>
866+
m.taskQueueType == TaskQueueType.serialBackgroundThread)) {
867+
serialBackgroundQueue = 'taskQueue';
868+
// See https://github.com/flutter/flutter/issues/162613 for why this
869+
// is an ifdef instead of just a respondsToSelector: check.
870+
indent.format('''
871+
#if TARGET_OS_IOS
872+
NSObject<FlutterTaskQueue> *$serialBackgroundQueue = [binaryMessenger makeBackgroundTaskQueue];
873+
#else
874+
NSObject<FlutterTaskQueue> *$serialBackgroundQueue = nil;
875+
#endif''');
876+
}
863877
for (final Method func in api.methods) {
864878
addDocumentationComments(
865879
indent, func.documentationComments, _docCommentSpec);
866880

867881
indent.writeScoped('{', '}', () {
868-
String? taskQueue;
869-
if (func.taskQueueType != TaskQueueType.serial) {
870-
taskQueue = 'taskQueue';
871-
indent.writeln(
872-
'NSObject<FlutterTaskQueue> *$taskQueue = [binaryMessenger makeBackgroundTaskQueue];');
873-
}
874882
_writeChannelAllocation(
875883
generatorOptions,
876884
indent,
877885
api,
878886
func,
879887
channelName,
880-
taskQueue,
888+
func.taskQueueType == TaskQueueType.serialBackgroundThread
889+
? serialBackgroundQueue
890+
: null,
881891
dartPackageName: dartPackageName,
882892
);
883893
indent.write('if (api) ');
@@ -1112,7 +1122,15 @@ static FlutterError *createConnectionError(NSString *channelName) {
11121122

11131123
if (taskQueue != null) {
11141124
indent.newln();
1115-
indent.addln('taskQueue:$taskQueue];');
1125+
// See https://github.com/flutter/flutter/issues/162613 for why this
1126+
// is in an ifdef instead of just relying on the parameter being
1127+
// nullable.
1128+
indent.format('''
1129+
#ifdef TARGET_OS_IOS
1130+
taskQueue:$taskQueue
1131+
#endif
1132+
];
1133+
''');
11161134
} else {
11171135
indent.addln('];');
11181136
}

packages/pigeon/lib/src/pigeon_lib.dart

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ import 'java/java_generator.dart';
3636
import 'kotlin/kotlin_generator.dart';
3737
import 'objc/objc_generator.dart';
3838
import 'swift/swift_generator.dart';
39+
import 'types/task_queue.dart';
40+
41+
export 'types/task_queue.dart' show TaskQueueType;
3942

4043
class _Asynchronous {
4144
const _Asynchronous();
@@ -211,21 +214,6 @@ class SwiftClass {
211214
const SwiftClass();
212215
}
213216

214-
/// Type of TaskQueue which determines how handlers are dispatched for
215-
/// HostApi's.
216-
enum TaskQueueType {
217-
/// Handlers are invoked serially on the default thread. This is the value if
218-
/// unspecified.
219-
serial,
220-
221-
/// Handlers are invoked serially on a background thread.
222-
serialBackgroundThread,
223-
224-
// TODO(gaaclarke): Add support for concurrent task queues.
225-
// /// Handlers are invoked concurrently on a background thread.
226-
// concurrentBackgroundThread,
227-
}
228-
229217
/// Metadata annotation to control how handlers are dispatched for HostApi's.
230218
/// Note that the TaskQueue API might not be available on the target version of
231219
/// Flutter, see also:

packages/pigeon/lib/src/swift/swift_generator.dart

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import '../ast.dart';
99
import '../functional.dart';
1010
import '../generator.dart';
1111
import '../generator_tools.dart';
12+
import '../types/task_queue.dart';
1213
import 'templates.dart';
1314

1415
/// Documentation comment open symbol.
@@ -736,6 +737,21 @@ if (wrapped == nil) {
736737
indent.addScoped('{', '}', () {
737738
indent.writeln(
738739
r'let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""');
740+
String? serialBackgroundQueue;
741+
if (api.methods.any((Method m) =>
742+
m.taskQueueType == TaskQueueType.serialBackgroundThread)) {
743+
serialBackgroundQueue = 'taskQueue';
744+
// TODO(stuartmorgan): Remove the ? once macOS supports task queues
745+
// and this is no longer an optional protocol method.
746+
// See https://github.com/flutter/flutter/issues/162613 for why this
747+
// is an ifdef instead of just relying on the optionality check.
748+
indent.format('''
749+
#if os(iOS)
750+
let $serialBackgroundQueue = binaryMessenger.makeBackgroundTaskQueue?()
751+
#else
752+
let $serialBackgroundQueue: FlutterTaskQueue? = nil
753+
#endif''');
754+
}
739755
for (final Method method in api.methods) {
740756
_writeHostMethodMessageHandler(
741757
indent,
@@ -747,6 +763,10 @@ if (wrapped == nil) {
747763
isAsynchronous: method.isAsynchronous,
748764
swiftFunction: method.swiftFunction,
749765
documentationComments: method.documentationComments,
766+
serialBackgroundQueue:
767+
method.taskQueueType == TaskQueueType.serialBackgroundThread
768+
? serialBackgroundQueue
769+
: null,
750770
);
751771
}
752772
});
@@ -1404,7 +1424,7 @@ private func nilOrValue<T>(_ value: Any?) -> T? {
14041424
func endOfStream() {
14051425
sink(FlutterEndOfEventStream)
14061426
}
1407-
1427+
14081428
}
14091429
''');
14101430
}
@@ -1413,7 +1433,7 @@ private func nilOrValue<T>(_ value: Any?) -> T? {
14131433
for (final Method func in api.methods) {
14141434
indent.format('''
14151435
class ${toUpperCamelCase(func.name)}StreamHandler: PigeonEventChannelWrapper<${_swiftTypeForDartType(func.returnType)}> {
1416-
static func register(with messenger: FlutterBinaryMessenger,
1436+
static func register(with messenger: FlutterBinaryMessenger,
14171437
instanceName: String = "",
14181438
streamHandler: ${toUpperCamelCase(func.name)}StreamHandler) {
14191439
var channelName = "${makeChannelName(api, func, dartPackageName)}"
@@ -1535,6 +1555,7 @@ private func nilOrValue<T>(_ value: Any?) -> T? {
15351555
required TypeDeclaration returnType,
15361556
required bool isAsynchronous,
15371557
required String? swiftFunction,
1558+
String? serialBackgroundQueue,
15381559
String setHandlerCondition = 'let api = api',
15391560
List<String> documentationComments = const <String>[],
15401561
String Function(List<String> safeArgNames, {required String apiVarName})?
@@ -1549,8 +1570,30 @@ private func nilOrValue<T>(_ value: Any?) -> T? {
15491570

15501571
final String varChannelName = '${name}Channel';
15511572
addDocumentationComments(indent, documentationComments, _docCommentSpec);
1552-
indent.writeln(
1553-
'let $varChannelName = FlutterBasicMessageChannel(name: "$channelName", binaryMessenger: binaryMessenger, codec: codec)');
1573+
final String baseArgs = 'name: "$channelName", '
1574+
'binaryMessenger: binaryMessenger, codec: codec';
1575+
// The version with taskQueue: is an optional protocol method that isn't
1576+
// implemented on macOS yet, so the call has to be conditionalized even
1577+
// though the taskQueue argument is nullable. The runtime branching can be
1578+
// removed once macOS supports task queues. The condition is on the task
1579+
// queue variable not being nil because the earlier code to set it will
1580+
// return nil on macOS where the optional parts of the protocol are not
1581+
// implemented.
1582+
final String channelCreationWithoutTaskQueue =
1583+
'FlutterBasicMessageChannel($baseArgs)';
1584+
if (serialBackgroundQueue == null) {
1585+
indent.writeln('let $varChannelName = $channelCreationWithoutTaskQueue');
1586+
} else {
1587+
final String channelCreationWithTaskQueue =
1588+
'FlutterBasicMessageChannel($baseArgs, taskQueue: $serialBackgroundQueue)';
1589+
1590+
indent.write('let $varChannelName = $serialBackgroundQueue == nil');
1591+
indent.addScoped('', '', () {
1592+
indent.writeln('? $channelCreationWithoutTaskQueue');
1593+
indent.writeln(': $channelCreationWithTaskQueue');
1594+
});
1595+
}
1596+
15541597
indent.write('if $setHandlerCondition ');
15551598
indent.addScoped('{', '}', () {
15561599
indent.write('$varChannelName.setMessageHandler ');
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
/// Type of TaskQueue which determines how handlers are dispatched for
6+
/// HostApi's.
7+
enum TaskQueueType {
8+
/// Handlers are invoked serially on the default thread. This is the value if
9+
/// unspecified.
10+
serial,
11+
12+
/// Handlers are invoked serially on a background thread.
13+
serialBackgroundThread,
14+
15+
// TODO(gaaclarke): Add support for concurrent task queues.
16+
// /// Handlers are invoked concurrently on a background thread.
17+
// concurrentBackgroundThread,
18+
}

0 commit comments

Comments
 (0)