Commit c8db0041 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Add debugPrintTransientCallbackRegistrationStack (#5198)

Adds Scheduler.debugPrintTransientCallbackRegistrationStack so that you
can find out how your current callback got registered.
parent d7fb51a5
...@@ -277,6 +277,7 @@ class FlutterError extends AssertionError { ...@@ -277,6 +277,7 @@ class FlutterError extends AssertionError {
const List<String> filteredClasses = const <String>[ const List<String> filteredClasses = const <String>[
'_AssertionError', '_AssertionError',
'_FakeAsync', '_FakeAsync',
'_FrameCallbackEntry',
]; ];
final RegExp stackParser = new RegExp(r'^#[0-9]+ +([^.]+).* \(([^/]*)/[^:]+:[0-9]+(?::[0-9]+)?\)$'); final RegExp stackParser = new RegExp(r'^#[0-9]+ +([^.]+).* \(([^/]*)/[^:]+:[0-9]+(?::[0-9]+)?\)$');
final RegExp packageParser = new RegExp(r'^([^:]+):(.+)$'); final RegExp packageParser = new RegExp(r'^([^:]+):(.+)$');
......
...@@ -57,7 +57,7 @@ class _FrameCallbackEntry { ...@@ -57,7 +57,7 @@ class _FrameCallbackEntry {
assert(() { assert(() {
if (rescheduling) { if (rescheduling) {
assert(() { assert(() {
if (currentCallbackStack == null) { if (debugCurrentCallbackStack == null) {
throw new FlutterError( throw new FlutterError(
'addFrameCallback or scheduleFrameCallback called with rescheduling true, but no callback is in scope.\n' 'addFrameCallback or scheduleFrameCallback called with rescheduling true, but no callback is in scope.\n'
'The "rescheduling" argument should only be set to true if the ' 'The "rescheduling" argument should only be set to true if the '
...@@ -70,17 +70,20 @@ class _FrameCallbackEntry { ...@@ -70,17 +70,20 @@ class _FrameCallbackEntry {
} }
return true; return true;
}); });
stack = currentCallbackStack; debugStack = debugCurrentCallbackStack;
} else { } else {
// TODO(ianh): trim the frames from this library, so that the call to scheduleFrameCallback is the top one // TODO(ianh): trim the frames from this library, so that the call to scheduleFrameCallback is the top one
stack = StackTrace.current; debugStack = StackTrace.current;
} }
return true; return true;
}); });
} }
static StackTrace currentCallbackStack;
final FrameCallback callback; final FrameCallback callback;
StackTrace stack;
// debug-mode fields
static StackTrace debugCurrentCallbackStack;
StackTrace debugStack;
} }
/// Scheduler for running the following: /// Scheduler for running the following:
...@@ -195,7 +198,7 @@ abstract class SchedulerBinding extends BindingBase { ...@@ -195,7 +198,7 @@ abstract class SchedulerBinding extends BindingBase {
/// after a test's resources have been gracefully disposed. /// after a test's resources have been gracefully disposed.
int get transientCallbackCount => _transientCallbacks.length; int get transientCallbackCount => _transientCallbacks.length;
/// Schedules the given frame callback. /// Schedules the given transient frame callback.
/// ///
/// Adds the given callback to the list of frame callbacks and ensures that a /// Adds the given callback to the list of frame callbacks and ensures that a
/// frame is scheduled. /// frame is scheduled.
...@@ -219,7 +222,7 @@ abstract class SchedulerBinding extends BindingBase { ...@@ -219,7 +222,7 @@ abstract class SchedulerBinding extends BindingBase {
return addFrameCallback(callback, rescheduling: rescheduling); return addFrameCallback(callback, rescheduling: rescheduling);
} }
/// Adds a frame callback. /// Adds a transient frame callback.
/// ///
/// Frame callbacks are executed at the beginning of a frame (see /// Frame callbacks are executed at the beginning of a frame (see
/// [handleBeginFrame]). /// [handleBeginFrame]).
...@@ -251,13 +254,13 @@ abstract class SchedulerBinding extends BindingBase { ...@@ -251,13 +254,13 @@ abstract class SchedulerBinding extends BindingBase {
return _nextFrameCallbackId; return _nextFrameCallbackId;
} }
/// Cancels the callback of the given [id]. /// Cancels the transient frame callback with the given [id].
/// ///
/// Removes the given callback from the list of frame callbacks. If a frame /// Removes the given callback from the list of frame callbacks. If a frame
/// has been requested, this does not also cancel that request. /// has been requested, this does not also cancel that request.
/// ///
/// Frame callbacks are registered using [scheduleFrameCallback] or /// Transient frame callbacks are those registered using
/// [addFrameCallback]. /// [scheduleFrameCallback] or [addFrameCallback].
void cancelFrameCallbackWithId(int id) { void cancelFrameCallbackWithId(int id) {
assert(id > 0); assert(id > 0);
_transientCallbacks.remove(id); _transientCallbacks.remove(id);
...@@ -267,6 +270,9 @@ abstract class SchedulerBinding extends BindingBase { ...@@ -267,6 +270,9 @@ abstract class SchedulerBinding extends BindingBase {
/// Asserts that there are no registered transient callbacks; if /// Asserts that there are no registered transient callbacks; if
/// there are, prints their locations and throws an exception. /// there are, prints their locations and throws an exception.
/// ///
/// A transient frame callback is one that was registered with
/// [scheduleFrameCallback] or [addFrameCallback].
///
/// This is expected to be called at the end of tests (the /// This is expected to be called at the end of tests (the
/// flutter_test framework does it automatically in normal cases). /// flutter_test framework does it automatically in normal cases).
/// ///
...@@ -307,7 +313,7 @@ abstract class SchedulerBinding extends BindingBase { ...@@ -307,7 +313,7 @@ abstract class SchedulerBinding extends BindingBase {
for (int id in callbacks.keys) { for (int id in callbacks.keys) {
_FrameCallbackEntry entry = callbacks[id]; _FrameCallbackEntry entry = callbacks[id];
information.writeln('── callback $id ──'); information.writeln('── callback $id ──');
FlutterError.defaultStackFilter(entry.stack.toString().trimRight().split('\n')).forEach(information.writeln); FlutterError.defaultStackFilter(entry.debugStack.toString().trimRight().split('\n')).forEach(information.writeln);
} }
} }
)); ));
...@@ -317,6 +323,42 @@ abstract class SchedulerBinding extends BindingBase { ...@@ -317,6 +323,42 @@ abstract class SchedulerBinding extends BindingBase {
return true; return true;
} }
/// Prints the stack for where the current transient callback was registered.
///
/// A transient frame callback is one that was registered with
/// [scheduleFrameCallback] or [addFrameCallback].
///
/// When called in debug more and in the context of a transient callback, this
/// function prints the stack trace from where the current transient callback
/// was registered (i.e. where it first called addFrameCallback or
/// scheduleFrameCallback).
///
/// When called in debug mode in other contexts, it prints a message saying
/// that this function was not called in the context a transient callback.
///
/// In release mode, this function does nothing.
///
/// To call this function, use the following code:
///
/// ```dart
/// SchedulerBinding.debugPrintTransientCallbackRegistrationStack();
/// ```
static void debugPrintTransientCallbackRegistrationStack() {
assert(() {
if (_FrameCallbackEntry.debugCurrentCallbackStack != null) {
debugPrint('When the current transient callback was registered, this was the stack:');
debugPrint(
FlutterError.defaultStackFilter(
_FrameCallbackEntry.debugCurrentCallbackStack.toString().trimRight().split('\n')
).join('\n')
);
} else {
debugPrint('No transient callback is currently executing.');
}
return true;
});
}
final List<FrameCallback> _persistentCallbacks = new List<FrameCallback>(); final List<FrameCallback> _persistentCallbacks = new List<FrameCallback>();
/// Adds a persistent frame callback. /// Adds a persistent frame callback.
...@@ -328,6 +370,9 @@ abstract class SchedulerBinding extends BindingBase { ...@@ -328,6 +370,9 @@ abstract class SchedulerBinding extends BindingBase {
/// callbacks are observers of "begin frame" events. Since they are /// callbacks are observers of "begin frame" events. Since they are
/// executed after the transient frame callbacks they can drive the /// executed after the transient frame callbacks they can drive the
/// rendering pipeline. /// rendering pipeline.
///
/// Persistent frame callbacks cannot be unregistered. Once registered, they
/// are called for every frame for the lifetime of the application.
void addPersistentFrameCallback(FrameCallback callback) { void addPersistentFrameCallback(FrameCallback callback) {
_persistentCallbacks.add(callback); _persistentCallbacks.add(callback);
} }
...@@ -347,6 +392,8 @@ abstract class SchedulerBinding extends BindingBase { ...@@ -347,6 +392,8 @@ abstract class SchedulerBinding extends BindingBase {
/// ///
/// The callbacks are executed in the order in which they have been /// The callbacks are executed in the order in which they have been
/// added. /// added.
///
/// Post-frame callbacks cannot be unregistered. They are called exactly once.
void addPostFrameCallback(FrameCallback callback) { void addPostFrameCallback(FrameCallback callback) {
_postFrameCallbacks.add(callback); _postFrameCallbacks.add(callback);
} }
...@@ -435,7 +482,7 @@ abstract class SchedulerBinding extends BindingBase { ...@@ -435,7 +482,7 @@ abstract class SchedulerBinding extends BindingBase {
Duration timeStamp = _adjustForEpoch(rawTimeStamp); Duration timeStamp = _adjustForEpoch(rawTimeStamp);
assert(() { assert(() {
if (debugPrintBeginFrameBanner) if (debugPrintBeginFrameBanner)
print('━━━━━━━┫ Begin Frame ($timeStamp) ┣━━━━━━━'); debugPrint('━━━━━━━┫ Begin Frame ($timeStamp) ┣━━━━━━━');
return true; return true;
}); });
_lastRawTimeStamp = rawTimeStamp; _lastRawTimeStamp = rawTimeStamp;
...@@ -468,7 +515,7 @@ abstract class SchedulerBinding extends BindingBase { ...@@ -468,7 +515,7 @@ abstract class SchedulerBinding extends BindingBase {
_transientCallbacks = new Map<int, _FrameCallbackEntry>(); _transientCallbacks = new Map<int, _FrameCallbackEntry>();
callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) { callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
if (!_removedIds.contains(id)) if (!_removedIds.contains(id))
_invokeFrameCallback(callbackEntry.callback, timeStamp, callbackEntry.stack); _invokeFrameCallback(callbackEntry.callback, timeStamp, callbackEntry.debugStack);
}); });
_removedIds.clear(); _removedIds.clear();
Timeline.finishSync(); Timeline.finishSync();
...@@ -481,9 +528,9 @@ abstract class SchedulerBinding extends BindingBase { ...@@ -481,9 +528,9 @@ abstract class SchedulerBinding extends BindingBase {
// the error. // the error.
void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace callbackStack ]) { void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace callbackStack ]) {
assert(callback != null); assert(callback != null);
assert(_FrameCallbackEntry.currentCallbackStack == null); assert(_FrameCallbackEntry.debugCurrentCallbackStack == null);
// TODO(ianh): Consider using a Zone instead to track the current callback registration stack // TODO(ianh): Consider using a Zone instead to track the current callback registration stack
assert(() { _FrameCallbackEntry.currentCallbackStack = callbackStack; return true; }); assert(() { _FrameCallbackEntry.debugCurrentCallbackStack = callbackStack; return true; });
try { try {
callback(timeStamp); callback(timeStamp);
} catch (exception, exceptionStack) { } catch (exception, exceptionStack) {
...@@ -502,7 +549,7 @@ abstract class SchedulerBinding extends BindingBase { ...@@ -502,7 +549,7 @@ abstract class SchedulerBinding extends BindingBase {
} }
)); ));
} }
assert(() { _FrameCallbackEntry.currentCallbackStack = null; return true; }); assert(() { _FrameCallbackEntry.debugCurrentCallbackStack = null; return true; });
} }
} }
......
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