-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
Problem
flutter/packages/flutter_tools/lib/src/base/context.dart
Lines 74 to 79 in 7d1ceb4
| try { | |
| _zone = Zone.current; | |
| return await method(); | |
| } finally { | |
| _zone = previousZone; | |
| } |
That code is designed to say "while the callback is running in this context, set up the Zone pointer such that it can properly traverse the context hierarchy." However, there's a flaw in that logic, which is that code triggered by that callback may still be run after the await method() completes.
- There could have been an unintentional unawaited future within
method(). - There could have been a
Future.then()call withinmethod(), where we intentionally don't wait for the body of thethen()to complete. - We could have created a stream listener that's still responding to events after
method()completes.
In these types of cases, we'll have already mutated the AppContext._zone pointer after await method() returns, and when the async code in question runs after that point, its zone pointer will be invalid (causing it to skip the parent zone), and it'll be unable to lookup proper AppContext values.
Practically speaking, this is likely the cause of issues such as #14636
Proposed Solution
We should make AppContext immutable. Proposed API:
/// Generates an [AppContext] value.
typedef dynamic Generator();
/// The current [AppContext], as determined by the [Zone] hierarchy.
///
/// This will be the first context found as we scan up the zone hierarchy, or
/// the "root" context if a context cannot be found in the hierarchy. The root
/// context will not have any values associated with it.
AppContext get context;
/// A lookup table (mapping types to values) and an implied scope, in which
/// code is run.
///
/// [AppContext] is used to define a singleton injection context for code that
/// is run within it. Each time you call [run], a child context (and a new
/// scope) is created.
///
/// Child contexts are created and run using zones. To read more about how
/// zones work, see https://www.dartlang.org/articles/libraries/zones.
class AppContext {
/// Gets the value associated with the specified [type], or `null` if no
/// such value has been associated.
dynamic operator[](Type type);
/// Runs [method] in a child context and returns the value.
///
/// If [overrides] is specified, the child context will include the associated
/// type mappings, overriding any conflicting mappings in this (the parent)
/// context.
///
/// If [fallbacks] is specified, the child context will include the associated
/// type mappings only for those types not already contained in this (the
/// parent) context.
///
/// If [name] is specified, the child context will be assigned the given name.
/// This is useful for debugging purposes and is analogous to naming a thread
/// in Java.
///
/// If [onError] is specified, the zone is considered an error zone. All
/// uncaught errors (synchronous or asynchronous) in the zone are caught and
/// handled by the callback.
FutureOr<dynamic> run({
@required FutureOr<dynamic> method(),
Map<Type, dynamic> overrides,
Map<Type, Generator> fallbacks,
String name,
ZoneBinaryCallback<dynamic, dynamic, StackTrace> onError,
});
}