Unverified Commit fc1d17d3 authored by Dan Field's avatar Dan Field Committed by GitHub

Rewrite license code to avoid async* (#95130)

parent 126ee23a
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:meta/meta.dart' show visibleForTesting; import 'package:meta/meta.dart' show visibleForTesting;
/// Signature for callbacks passed to [LicenseRegistry.addLicense]. /// Signature for callbacks passed to [LicenseRegistry.addLicense].
...@@ -70,8 +72,8 @@ enum _LicenseEntryWithLineBreaksParserState { ...@@ -70,8 +72,8 @@ enum _LicenseEntryWithLineBreaksParserState {
/// ///
/// ```dart /// ```dart
/// void initMyLibrary() { /// void initMyLibrary() {
/// LicenseRegistry.addLicense(() async* { /// LicenseRegistry.addLicense(() => Stream<LicenseEntry>.value(
/// yield const LicenseEntryWithLineBreaks(<String>['my_library'], ''' /// const LicenseEntryWithLineBreaks(<String>['my_library'], '''
/// Copyright 2016 The Sample Authors. All rights reserved. /// Copyright 2016 The Sample Authors. All rights reserved.
/// ///
/// Redistribution and use in source and binary forms, with or without /// Redistribution and use in source and binary forms, with or without
...@@ -98,8 +100,9 @@ enum _LicenseEntryWithLineBreaksParserState { ...@@ -98,8 +100,9 @@ enum _LicenseEntryWithLineBreaksParserState {
/// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY /// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
/// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT /// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
/// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE /// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
/// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.'''); /// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''',
/// }); /// ),
/// ));
/// } /// }
/// ``` /// ```
/// {@end-tool} /// {@end-tool}
...@@ -309,14 +312,19 @@ class LicenseRegistry { ...@@ -309,14 +312,19 @@ class LicenseRegistry {
/// Returns the licenses that have been registered. /// Returns the licenses that have been registered.
/// ///
/// Generating the list of licenses is expensive. /// Generating the list of licenses is expensive.
// TODO(dnfield): Refactor the license logic. static Stream<LicenseEntry> get licenses {
// https://github.com/flutter/flutter/issues/95043
// flutter_ignore: no_sync_async_star
static Stream<LicenseEntry> get licenses async* {
if (_collectors == null) if (_collectors == null)
return; return const Stream<LicenseEntry>.empty();
for (final LicenseEntryCollector collector in _collectors!)
yield* collector(); late final StreamController<LicenseEntry> controller;
controller = StreamController<LicenseEntry>(
onListen: () async {
for (final LicenseEntryCollector collector in _collectors!)
await controller.addStream(collector());
await controller.close();
},
);
return controller.stream;
} }
/// Resets the internal state of [LicenseRegistry]. Intended for use in /// Resets the internal state of [LicenseRegistry]. Intended for use in
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -144,45 +145,29 @@ mixin ServicesBinding on BindingBase, SchedulerBinding { ...@@ -144,45 +145,29 @@ mixin ServicesBinding on BindingBase, SchedulerBinding {
LicenseRegistry.addLicense(_addLicenses); LicenseRegistry.addLicense(_addLicenses);
} }
// TODO(dnfield): Refactor the license logic. Stream<LicenseEntry> _addLicenses() {
// https://github.com/flutter/flutter/issues/95043 late final StreamController<LicenseEntry> controller;
// flutter_ignore: no_sync_async_star controller = StreamController<LicenseEntry>(
Stream<LicenseEntry> _addLicenses() async* { onListen: () async {
// Using _something_ here to break late final String rawLicenses;
// this into two parts is important because isolates take a while to copy if (kIsWeb) {
// data at the moment, and if we receive the data in the same event loop // NOTICES for web isn't compressed since we don't have access to
// iteration as we send the data to the next isolate, we are definitely // dart:io on the client side and it's already compressed between
// going to miss frames. Another solution would be to have the work all // the server and client.
// happen in one isolate, and we may go there eventually, but first we are rawLicenses = await rootBundle.loadString('NOTICES', cache: false);
// going to see if isolate communication can be made cheaper. } else {
// See: https://github.com/dart-lang/sdk/issues/31959 // The compressed version doesn't have a more common .gz extension
// https://github.com/dart-lang/sdk/issues/31960 // because gradle for Android non-transparently manipulates .gz files.
// TODO(ianh): Remove this complexity once these bugs are fixed. final ByteData licenseBytes = await rootBundle.load('NOTICES.Z');
final Completer<String> rawLicenses = Completer<String>(); final List<int> unzippedBytes = await compute<List<int>, List<int>>(gzip.decode, licenseBytes.buffer.asUint8List(), debugLabel: 'decompressLicenses');
scheduleTask(() async { rawLicenses = await compute<List<int>, String>(utf8.decode, unzippedBytes, debugLabel: 'utf8DecodeLicenses');
rawLicenses.complete( }
kIsWeb final List<LicenseEntry> licenses = await compute<String, List<LicenseEntry>>(_parseLicenses, rawLicenses, debugLabel: 'parseLicenses');
// NOTICES for web isn't compressed since we don't have access to licenses.forEach(controller.add);
// dart:io on the client side and it's already compressed between await controller.close();
// the server and client. },
? rootBundle.loadString('NOTICES', cache: false) );
: () async { return controller.stream;
// The compressed version doesn't have a more common .gz extension
// because gradle for Android non-transparently manipulates .gz files.
final ByteData licenseBytes = await rootBundle.load('NOTICES.Z');
List<int> bytes = licenseBytes.buffer.asUint8List();
bytes = gzip.decode(bytes);
return utf8.decode(bytes);
}(),
);
}, Priority.animation);
await rawLicenses.future;
final Completer<List<LicenseEntry>> parsedLicenses = Completer<List<LicenseEntry>>();
scheduleTask(() async {
parsedLicenses.complete(compute<String, List<LicenseEntry>>(_parseLicenses, await rawLicenses.future, debugLabel: 'parseLicenses'));
}, Priority.animation);
await parsedLicenses.future;
yield* Stream<LicenseEntry>.fromIterable(await parsedLicenses.future);
} }
// This is run in another isolate created by _addLicenses above. // This is run in another isolate created by _addLicenses above.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment