Unverified Commit 08d5d51a authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

[flutter_tools] Instantiate shutdown hooks before localfilesystem (#110693)

parent 1560d0ce
...@@ -127,6 +127,7 @@ Future<void> main(List<String> args) async { ...@@ -127,6 +127,7 @@ Future<void> main(List<String> args) async {
}, },
PreRunValidator: () => PreRunValidator(fileSystem: globals.fs), PreRunValidator: () => PreRunValidator(fileSystem: globals.fs),
}, },
shutdownHooks: globals.shutdownHooks,
); );
} }
......
...@@ -34,6 +34,7 @@ Future<int> run( ...@@ -34,6 +34,7 @@ Future<int> run(
bool? reportCrashes, bool? reportCrashes,
String? flutterVersion, String? flutterVersion,
Map<Type, Generator>? overrides, Map<Type, Generator>? overrides,
required ShutdownHooks shutdownHooks,
}) async { }) async {
if (muteCommandLogging) { if (muteCommandLogging) {
// Remove the verbose option; for help and doctor, users don't need to see // Remove the verbose option; for help and doctor, users don't need to see
...@@ -64,7 +65,7 @@ Future<int> run( ...@@ -64,7 +65,7 @@ Future<int> run(
// Triggering [runZoned]'s error callback does not necessarily mean that // Triggering [runZoned]'s error callback does not necessarily mean that
// we stopped executing the body. See https://github.com/dart-lang/sdk/issues/42150. // we stopped executing the body. See https://github.com/dart-lang/sdk/issues/42150.
if (firstError == null) { if (firstError == null) {
return await _exit(0); return await _exit(0, shutdownHooks: shutdownHooks);
} }
// We already hit some error, so don't return success. The error path // We already hit some error, so don't return success. The error path
...@@ -74,7 +75,7 @@ Future<int> run( ...@@ -74,7 +75,7 @@ Future<int> run(
// This catches all exceptions to send to crash logging, etc. // This catches all exceptions to send to crash logging, etc.
firstError = error; firstError = error;
firstStackTrace = stackTrace; firstStackTrace = stackTrace;
return _handleToolError(error, stackTrace, verbose, args, reportCrashes!, getVersion); return _handleToolError(error, stackTrace, verbose, args, reportCrashes!, getVersion, shutdownHooks);
} }
}, onError: (Object error, StackTrace stackTrace) async { // ignore: deprecated_member_use }, onError: (Object error, StackTrace stackTrace) async { // ignore: deprecated_member_use
// If sending a crash report throws an error into the zone, we don't want // If sending a crash report throws an error into the zone, we don't want
...@@ -82,7 +83,7 @@ Future<int> run( ...@@ -82,7 +83,7 @@ Future<int> run(
// to send the original error that triggered the crash report. // to send the original error that triggered the crash report.
firstError ??= error; firstError ??= error;
firstStackTrace ??= stackTrace; firstStackTrace ??= stackTrace;
await _handleToolError(firstError!, firstStackTrace, verbose, args, reportCrashes!, getVersion); await _handleToolError(firstError!, firstStackTrace, verbose, args, reportCrashes!, getVersion, shutdownHooks);
}); });
}, overrides: overrides); }, overrides: overrides);
} }
...@@ -94,12 +95,13 @@ Future<int> _handleToolError( ...@@ -94,12 +95,13 @@ Future<int> _handleToolError(
List<String> args, List<String> args,
bool reportCrashes, bool reportCrashes,
String Function() getFlutterVersion, String Function() getFlutterVersion,
ShutdownHooks shutdownHooks,
) async { ) async {
if (error is UsageException) { if (error is UsageException) {
globals.printError('${error.message}\n'); globals.printError('${error.message}\n');
globals.printError("Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and options."); globals.printError("Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and options.");
// Argument error exit code. // Argument error exit code.
return _exit(64); return _exit(64, shutdownHooks: shutdownHooks);
} else if (error is ToolExit) { } else if (error is ToolExit) {
if (error.message != null) { if (error.message != null) {
globals.printError(error.message!); globals.printError(error.message!);
...@@ -107,14 +109,14 @@ Future<int> _handleToolError( ...@@ -107,14 +109,14 @@ Future<int> _handleToolError(
if (verbose) { if (verbose) {
globals.printError('\n$stackTrace\n'); globals.printError('\n$stackTrace\n');
} }
return _exit(error.exitCode ?? 1); return _exit(error.exitCode ?? 1, shutdownHooks: shutdownHooks);
} else if (error is ProcessExit) { } else if (error is ProcessExit) {
// We've caught an exit code. // We've caught an exit code.
if (error.immediate) { if (error.immediate) {
exit(error.exitCode); exit(error.exitCode);
return error.exitCode; return error.exitCode;
} else { } else {
return _exit(error.exitCode); return _exit(error.exitCode, shutdownHooks: shutdownHooks);
} }
} else { } else {
// We've crashed; emit a log report. // We've crashed; emit a log report.
...@@ -124,7 +126,7 @@ Future<int> _handleToolError( ...@@ -124,7 +126,7 @@ Future<int> _handleToolError(
// Print the stack trace on the bots - don't write a crash report. // Print the stack trace on the bots - don't write a crash report.
globals.stdio.stderrWrite('$error\n'); globals.stdio.stderrWrite('$error\n');
globals.stdio.stderrWrite('$stackTrace\n'); globals.stdio.stderrWrite('$stackTrace\n');
return _exit(1); return _exit(1, shutdownHooks: shutdownHooks);
} }
// Report to both [Usage] and [CrashReportSender]. // Report to both [Usage] and [CrashReportSender].
...@@ -165,7 +167,7 @@ Future<int> _handleToolError( ...@@ -165,7 +167,7 @@ Future<int> _handleToolError(
final File file = await _createLocalCrashReport(details); final File file = await _createLocalCrashReport(details);
await globals.crashReporter!.informUser(details, file); await globals.crashReporter!.informUser(details, file);
return _exit(1); return _exit(1, shutdownHooks: shutdownHooks);
// This catch catches all exceptions to ensure the message below is printed. // This catch catches all exceptions to ensure the message below is printed.
} catch (error, st) { // ignore: avoid_catches_without_on_clauses } catch (error, st) { // ignore: avoid_catches_without_on_clauses
globals.stdio.stderrWrite( globals.stdio.stderrWrite(
...@@ -228,7 +230,7 @@ Future<File> _createLocalCrashReport(CrashDetails details) async { ...@@ -228,7 +230,7 @@ Future<File> _createLocalCrashReport(CrashDetails details) async {
return crashFile; return crashFile;
} }
Future<int> _exit(int code) async { Future<int> _exit(int code, {required ShutdownHooks shutdownHooks}) async {
// Prints the welcome message if needed. // Prints the welcome message if needed.
globals.flutterUsage.printWelcome(); globals.flutterUsage.printWelcome();
...@@ -241,7 +243,7 @@ Future<int> _exit(int code) async { ...@@ -241,7 +243,7 @@ Future<int> _exit(int code) async {
} }
// Run shutdown hooks before flushing logs // Run shutdown hooks before flushing logs
await globals.shutdownHooks!.runShutdownHooks(); await shutdownHooks.runShutdownHooks(globals.logger);
final Completer<void> completer = Completer<void>(); final Completer<void> completer = Completer<void>();
......
...@@ -177,17 +177,18 @@ File getUniqueFile(Directory dir, String baseName, String ext) { ...@@ -177,17 +177,18 @@ File getUniqueFile(Directory dir, String baseName, String ext) {
/// directories and files that the tool creates under the system temporary /// directories and files that the tool creates under the system temporary
/// directory when the tool exits either normally or when killed by a signal. /// directory when the tool exits either normally or when killed by a signal.
class LocalFileSystem extends local_fs.LocalFileSystem { class LocalFileSystem extends local_fs.LocalFileSystem {
LocalFileSystem(this._signals, this._fatalSignals, this._shutdownHooks); LocalFileSystem(this._signals, this._fatalSignals, this.shutdownHooks);
@visibleForTesting @visibleForTesting
LocalFileSystem.test({ LocalFileSystem.test({
required Signals signals, required Signals signals,
List<ProcessSignal> fatalSignals = Signals.defaultExitSignals, List<ProcessSignal> fatalSignals = Signals.defaultExitSignals,
}) : this(signals, fatalSignals, null); }) : this(signals, fatalSignals, ShutdownHooks());
Directory? _systemTemp; Directory? _systemTemp;
final Map<ProcessSignal, Object> _signalTokens = <ProcessSignal, Object>{}; final Map<ProcessSignal, Object> _signalTokens = <ProcessSignal, Object>{};
final ShutdownHooks? _shutdownHooks;
final ShutdownHooks shutdownHooks;
Future<void> dispose() async { Future<void> dispose() async {
_tryToDeleteTemp(); _tryToDeleteTemp();
...@@ -206,7 +207,7 @@ class LocalFileSystem extends local_fs.LocalFileSystem { ...@@ -206,7 +207,7 @@ class LocalFileSystem extends local_fs.LocalFileSystem {
_systemTemp?.deleteSync(recursive: true); _systemTemp?.deleteSync(recursive: true);
} }
} on FileSystemException { } on FileSystemException {
// ignore. // ignore
} }
_systemTemp = null; _systemTemp = null;
} }
...@@ -239,7 +240,7 @@ class LocalFileSystem extends local_fs.LocalFileSystem { ...@@ -239,7 +240,7 @@ class LocalFileSystem extends local_fs.LocalFileSystem {
} }
// Make sure that the temporary directory is cleaned up when the tool // Make sure that the temporary directory is cleaned up when the tool
// exits normally. // exits normally.
_shutdownHooks?.addShutdownHook( shutdownHooks.addShutdownHook(
_tryToDeleteTemp, _tryToDeleteTemp,
); );
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'package:meta/meta.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import '../convert.dart'; import '../convert.dart';
...@@ -13,7 +14,7 @@ import 'logger.dart'; ...@@ -13,7 +14,7 @@ import 'logger.dart';
typedef StringConverter = String? Function(String string); typedef StringConverter = String? Function(String string);
/// A function that will be run before the VM exits. /// A function that will be run before the VM exits.
typedef ShutdownHook = FutureOr<dynamic> Function(); typedef ShutdownHook = FutureOr<void> Function();
// TODO(ianh): We have way too many ways to run subprocesses in this project. // TODO(ianh): We have way too many ways to run subprocesses in this project.
// Convert most of these into one or more lightweight wrappers around the // Convert most of these into one or more lightweight wrappers around the
...@@ -22,17 +23,16 @@ typedef ShutdownHook = FutureOr<dynamic> Function(); ...@@ -22,17 +23,16 @@ typedef ShutdownHook = FutureOr<dynamic> Function();
// for more details. // for more details.
abstract class ShutdownHooks { abstract class ShutdownHooks {
factory ShutdownHooks({ factory ShutdownHooks() => _DefaultShutdownHooks();
required Logger logger,
}) => _DefaultShutdownHooks(
logger: logger,
);
/// Registers a [ShutdownHook] to be executed before the VM exits. /// Registers a [ShutdownHook] to be executed before the VM exits.
void addShutdownHook( void addShutdownHook(
ShutdownHook shutdownHook ShutdownHook shutdownHook
); );
@visibleForTesting
List<ShutdownHook> get registeredHooks;
/// Runs all registered shutdown hooks and returns a future that completes when /// Runs all registered shutdown hooks and returns a future that completes when
/// all such hooks have finished. /// all such hooks have finished.
/// ///
...@@ -40,16 +40,17 @@ abstract class ShutdownHooks { ...@@ -40,16 +40,17 @@ abstract class ShutdownHooks {
/// hooks within a given stage will be started in parallel and will be /// hooks within a given stage will be started in parallel and will be
/// guaranteed to run to completion before shutdown hooks in the next stage are /// guaranteed to run to completion before shutdown hooks in the next stage are
/// started. /// started.
Future<void> runShutdownHooks(); ///
/// This class is constructed before the [Logger], so it cannot be direct
/// injected in the constructor.
Future<void> runShutdownHooks(Logger logger);
} }
class _DefaultShutdownHooks implements ShutdownHooks { class _DefaultShutdownHooks implements ShutdownHooks {
_DefaultShutdownHooks({ _DefaultShutdownHooks();
required Logger logger,
}) : _logger = logger;
final Logger _logger; @override
final List<ShutdownHook> _shutdownHooks = <ShutdownHook>[]; final List<ShutdownHook> registeredHooks = <ShutdownHook>[];
bool _shutdownHooksRunning = false; bool _shutdownHooksRunning = false;
...@@ -58,16 +59,18 @@ class _DefaultShutdownHooks implements ShutdownHooks { ...@@ -58,16 +59,18 @@ class _DefaultShutdownHooks implements ShutdownHooks {
ShutdownHook shutdownHook ShutdownHook shutdownHook
) { ) {
assert(!_shutdownHooksRunning); assert(!_shutdownHooksRunning);
_shutdownHooks.add(shutdownHook); registeredHooks.add(shutdownHook);
} }
@override @override
Future<void> runShutdownHooks() async { Future<void> runShutdownHooks(Logger logger) async {
_logger.printTrace('Running shutdown hooks'); logger.printTrace(
'Running ${registeredHooks.length} shutdown hook${registeredHooks.length == 1 ? '' : 's'}',
);
_shutdownHooksRunning = true; _shutdownHooksRunning = true;
try { try {
final List<Future<dynamic>> futures = <Future<dynamic>>[]; final List<Future<dynamic>> futures = <Future<dynamic>>[];
for (final ShutdownHook shutdownHook in _shutdownHooks) { for (final ShutdownHook shutdownHook in registeredHooks) {
final FutureOr<dynamic> result = shutdownHook(); final FutureOr<dynamic> result = shutdownHook();
if (result is Future<dynamic>) { if (result is Future<dynamic>) {
futures.add(result); futures.add(result);
...@@ -77,7 +80,7 @@ class _DefaultShutdownHooks implements ShutdownHooks { ...@@ -77,7 +80,7 @@ class _DefaultShutdownHooks implements ShutdownHooks {
} finally { } finally {
_shutdownHooksRunning = false; _shutdownHooksRunning = false;
} }
_logger.printTrace('Shutdown hooks complete'); logger.printTrace('Shutdown hooks complete');
} }
} }
......
...@@ -313,7 +313,6 @@ Future<T> runInContext<T>( ...@@ -313,7 +313,6 @@ Future<T> runInContext<T>(
platform: globals.platform, platform: globals.platform,
usage: globals.flutterUsage, usage: globals.flutterUsage,
), ),
ShutdownHooks: () => ShutdownHooks(logger: globals.logger),
Stdio: () => Stdio(), Stdio: () => Stdio(),
SystemClock: () => const SystemClock(), SystemClock: () => const SystemClock(),
Usage: () => Usage( Usage: () => Usage(
......
...@@ -248,7 +248,11 @@ PlistParser? _plistInstance; ...@@ -248,7 +248,11 @@ PlistParser? _plistInstance;
/// The global template renderer. /// The global template renderer.
TemplateRenderer get templateRenderer => context.get<TemplateRenderer>()!; TemplateRenderer get templateRenderer => context.get<TemplateRenderer>()!;
ShutdownHooks? get shutdownHooks => context.get<ShutdownHooks>(); /// Global [ShutdownHooks] that should be run before the tool process exits.
///
/// This is depended on by [localFileSystem] which is called before any
/// [Context] is set up, and thus this cannot be a Context getter.
final ShutdownHooks shutdownHooks = ShutdownHooks();
// Unless we're in a test of this class's signal handling features, we must // Unless we're in a test of this class's signal handling features, we must
// have only one instance created with the singleton LocalSignals instance // have only one instance created with the singleton LocalSignals instance
......
...@@ -10,6 +10,7 @@ import 'package:file_testing/file_testing.dart'; ...@@ -10,6 +10,7 @@ import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/signals.dart'; import 'package:flutter_tools/src/base/signals.dart';
import 'package:test/fake.dart'; import 'package:test/fake.dart';
...@@ -162,6 +163,21 @@ void main() { ...@@ -162,6 +163,21 @@ void main() {
signalUnderTest = ProcessSignal(fakeSignal); signalUnderTest = ProcessSignal(fakeSignal);
}); });
testWithoutContext('runs shutdown hooks', () async {
final Signals signals = Signals.test();
final LocalFileSystem localFileSystem = LocalFileSystem.test(
signals: signals,
);
final Directory temp = localFileSystem.systemTempDirectory;
expect(temp.existsSync(), isTrue);
expect(localFileSystem.shutdownHooks.registeredHooks, hasLength(1));
final BufferLogger logger = BufferLogger.test();
await localFileSystem.shutdownHooks.runShutdownHooks(logger);
expect(temp.existsSync(), isFalse);
expect(logger.traceText, contains('Running 1 shutdown hook'));
});
testWithoutContext('deletes system temp entry on a fatal signal', () async { testWithoutContext('deletes system temp entry on a fatal signal', () async {
final Completer<void> completer = Completer<void>(); final Completer<void> completer = Completer<void>();
final Signals signals = Signals.test(); final Signals signals = Signals.test();
......
...@@ -42,13 +42,13 @@ void main() { ...@@ -42,13 +42,13 @@ void main() {
int i = 1; int i = 1;
int? cleanup; int? cleanup;
final ShutdownHooks shutdownHooks = ShutdownHooks(logger: BufferLogger.test()); final ShutdownHooks shutdownHooks = ShutdownHooks();
shutdownHooks.addShutdownHook(() async { shutdownHooks.addShutdownHook(() async {
cleanup = i++; cleanup = i++;
}); });
await shutdownHooks.runShutdownHooks(); await shutdownHooks.runShutdownHooks(BufferLogger.test());
expect(cleanup, 1); expect(cleanup, 1);
}); });
......
...@@ -13,6 +13,7 @@ import 'package:flutter_tools/src/base/file_system.dart'; ...@@ -13,6 +13,7 @@ import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart' as io; import 'package:flutter_tools/src/base/io.dart' as io;
import 'package:flutter_tools/src/base/net.dart'; import 'package:flutter_tools/src/base/net.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/base/user_messages.dart'; import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
...@@ -68,6 +69,7 @@ void main() { ...@@ -68,6 +69,7 @@ void main() {
// This flutterVersion disables crash reporting. // This flutterVersion disables crash reporting.
flutterVersion: '[user-branch]/', flutterVersion: '[user-branch]/',
reportCrashes: true, reportCrashes: true,
shutdownHooks: ShutdownHooks(),
)); ));
return null; return null;
}, },
...@@ -120,6 +122,7 @@ void main() { ...@@ -120,6 +122,7 @@ void main() {
// This flutterVersion disables crash reporting. // This flutterVersion disables crash reporting.
flutterVersion: '[user-branch]/', flutterVersion: '[user-branch]/',
reportCrashes: true, reportCrashes: true,
shutdownHooks: ShutdownHooks(),
)); ));
return null; return null;
}, },
...@@ -159,16 +162,17 @@ void main() { ...@@ -159,16 +162,17 @@ void main() {
// catch it in a zone. // catch it in a zone.
unawaited(runZoned<Future<void>>( unawaited(runZoned<Future<void>>(
() { () {
unawaited(runner.run( unawaited(runner.run(
<String>['crash'], <String>['crash'],
() => <FlutterCommand>[ () => <FlutterCommand>[
CrashingFlutterCommand(), CrashingFlutterCommand(),
], ],
// This flutterVersion disables crash reporting. // This flutterVersion disables crash reporting.
flutterVersion: '[user-branch]/', flutterVersion: '[user-branch]/',
reportCrashes: true, reportCrashes: true,
)); shutdownHooks: ShutdownHooks(),
return null; ));
return null;
}, },
onError: (Object error, StackTrace stack) { // ignore: deprecated_member_use onError: (Object error, StackTrace stack) { // ignore: deprecated_member_use
expect(firstExitCode, isNotNull); expect(firstExitCode, isNotNull);
......
...@@ -68,7 +68,7 @@ void main() { ...@@ -68,7 +68,7 @@ void main() {
]); ]);
// Check for message only printed in verbose mode. // Check for message only printed in verbose mode.
expect(result.stdout, contains('Running shutdown hooks')); expect(result.stdout, contains('Shutdown hooks complete'));
}); });
testWithoutContext('flutter config contains all features', () async { testWithoutContext('flutter config contains all features', () async {
......
...@@ -384,7 +384,8 @@ void main() { ...@@ -384,7 +384,8 @@ void main() {
return null; return null;
}), }),
Barrier('Performing hot restart...'.padRight(progressMessageWidth)), Barrier('Performing hot restart...'.padRight(progressMessageWidth)),
Multiple(<Pattern>[RegExp(r'^Restarted application in [0-9]+ms.$'), 'called main', 'called paint'], handler: (String line) { // This could look like 'Restarted application in 1,237ms.'
Multiple(<Pattern>[RegExp(r'^Restarted application in .+m?s.$'), 'called main', 'called paint'], handler: (String line) {
return 'q'; return 'q';
}), }),
const Barrier('Application finished.'), const Barrier('Application finished.'),
......
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