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 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:meta/meta.dart' show visibleForTesting;
/// Signature for callbacks passed to [LicenseRegistry.addLicense].
......@@ -70,8 +72,8 @@ enum _LicenseEntryWithLineBreaksParserState {
///
/// ```dart
/// void initMyLibrary() {
/// LicenseRegistry.addLicense(() async* {
/// yield const LicenseEntryWithLineBreaks(<String>['my_library'], '''
/// LicenseRegistry.addLicense(() => Stream<LicenseEntry>.value(
/// const LicenseEntryWithLineBreaks(<String>['my_library'], '''
/// Copyright 2016 The Sample Authors. All rights reserved.
///
/// Redistribution and use in source and binary forms, with or without
......@@ -98,8 +100,9 @@ enum _LicenseEntryWithLineBreaksParserState {
/// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
/// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
/// (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}
......@@ -309,14 +312,19 @@ class LicenseRegistry {
/// Returns the licenses that have been registered.
///
/// Generating the list of licenses is expensive.
// TODO(dnfield): Refactor the license logic.
// https://github.com/flutter/flutter/issues/95043
// flutter_ignore: no_sync_async_star
static Stream<LicenseEntry> get licenses async* {
static Stream<LicenseEntry> get licenses {
if (_collectors == null)
return;
for (final LicenseEntryCollector collector in _collectors!)
yield* collector();
return const Stream<LicenseEntry>.empty();
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
......
......@@ -6,6 +6,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
......@@ -144,45 +145,29 @@ mixin ServicesBinding on BindingBase, SchedulerBinding {
LicenseRegistry.addLicense(_addLicenses);
}
// TODO(dnfield): Refactor the license logic.
// https://github.com/flutter/flutter/issues/95043
// flutter_ignore: no_sync_async_star
Stream<LicenseEntry> _addLicenses() async* {
// Using _something_ here to break
// this into two parts is important because isolates take a while to copy
// data at the moment, and if we receive the data in the same event loop
// iteration as we send the data to the next isolate, we are definitely
// going to miss frames. Another solution would be to have the work all
// happen in one isolate, and we may go there eventually, but first we are
// going to see if isolate communication can be made cheaper.
// See: https://github.com/dart-lang/sdk/issues/31959
// https://github.com/dart-lang/sdk/issues/31960
// TODO(ianh): Remove this complexity once these bugs are fixed.
final Completer<String> rawLicenses = Completer<String>();
scheduleTask(() async {
rawLicenses.complete(
kIsWeb
// NOTICES for web isn't compressed since we don't have access to
// dart:io on the client side and it's already compressed between
// the server and client.
? rootBundle.loadString('NOTICES', cache: false)
: () async {
// 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);
Stream<LicenseEntry> _addLicenses() {
late final StreamController<LicenseEntry> controller;
controller = StreamController<LicenseEntry>(
onListen: () async {
late final String rawLicenses;
if (kIsWeb) {
// NOTICES for web isn't compressed since we don't have access to
// dart:io on the client side and it's already compressed between
// the server and client.
rawLicenses = await rootBundle.loadString('NOTICES', cache: false);
} else {
// 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');
final List<int> unzippedBytes = await compute<List<int>, List<int>>(gzip.decode, licenseBytes.buffer.asUint8List(), debugLabel: 'decompressLicenses');
rawLicenses = await compute<List<int>, String>(utf8.decode, unzippedBytes, debugLabel: 'utf8DecodeLicenses');
}
final List<LicenseEntry> licenses = await compute<String, List<LicenseEntry>>(_parseLicenses, rawLicenses, debugLabel: 'parseLicenses');
licenses.forEach(controller.add);
await controller.close();
},
);
return controller.stream;
}
// 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