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 {
const List<String> filteredClasses = const <String>[
'_AssertionError',
'_FakeAsync',
'_FrameCallbackEntry',
];
final RegExp stackParser = new RegExp(r'^#[0-9]+ +([^.]+).* \(([^/]*)/[^:]+:[0-9]+(?::[0-9]+)?\)$');
final RegExp packageParser = new RegExp(r'^([^:]+):(.+)$');
......
......@@ -57,7 +57,7 @@ class _FrameCallbackEntry {
assert(() {
if (rescheduling) {
assert(() {
if (currentCallbackStack == null) {
if (debugCurrentCallbackStack == null) {
throw new FlutterError(
'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 '
......@@ -70,17 +70,20 @@ class _FrameCallbackEntry {
}
return true;
});
stack = currentCallbackStack;
debugStack = debugCurrentCallbackStack;
} else {
// 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;
});
}
static StackTrace currentCallbackStack;
final FrameCallback callback;
StackTrace stack;
// debug-mode fields
static StackTrace debugCurrentCallbackStack;
StackTrace debugStack;
}
/// Scheduler for running the following:
......@@ -195,7 +198,7 @@ abstract class SchedulerBinding extends BindingBase {
/// after a test's resources have been gracefully disposed.
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
/// frame is scheduled.
......@@ -219,7 +222,7 @@ abstract class SchedulerBinding extends BindingBase {
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
/// [handleBeginFrame]).
......@@ -251,13 +254,13 @@ abstract class SchedulerBinding extends BindingBase {
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
/// has been requested, this does not also cancel that request.
///
/// Frame callbacks are registered using [scheduleFrameCallback] or
/// [addFrameCallback].
/// Transient frame callbacks are those registered using
/// [scheduleFrameCallback] or [addFrameCallback].
void cancelFrameCallbackWithId(int id) {
assert(id > 0);
_transientCallbacks.remove(id);
......@@ -267,6 +270,9 @@ abstract class SchedulerBinding extends BindingBase {
/// Asserts that there are no registered transient callbacks; if
/// 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
/// flutter_test framework does it automatically in normal cases).
///
......@@ -307,7 +313,7 @@ abstract class SchedulerBinding extends BindingBase {
for (int id in callbacks.keys) {
_FrameCallbackEntry entry = callbacks[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 {
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>();
/// Adds a persistent frame callback.
......@@ -328,6 +370,9 @@ abstract class SchedulerBinding extends BindingBase {
/// callbacks are observers of "begin frame" events. Since they are
/// executed after the transient frame callbacks they can drive the
/// 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) {
_persistentCallbacks.add(callback);
}
......@@ -347,6 +392,8 @@ abstract class SchedulerBinding extends BindingBase {
///
/// The callbacks are executed in the order in which they have been
/// added.
///
/// Post-frame callbacks cannot be unregistered. They are called exactly once.
void addPostFrameCallback(FrameCallback callback) {
_postFrameCallbacks.add(callback);
}
......@@ -435,7 +482,7 @@ abstract class SchedulerBinding extends BindingBase {
Duration timeStamp = _adjustForEpoch(rawTimeStamp);
assert(() {
if (debugPrintBeginFrameBanner)
print('━━━━━━━┫ Begin Frame ($timeStamp) ┣━━━━━━━');
debugPrint('━━━━━━━┫ Begin Frame ($timeStamp) ┣━━━━━━━');
return true;
});
_lastRawTimeStamp = rawTimeStamp;
......@@ -468,7 +515,7 @@ abstract class SchedulerBinding extends BindingBase {
_transientCallbacks = new Map<int, _FrameCallbackEntry>();
callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
if (!_removedIds.contains(id))
_invokeFrameCallback(callbackEntry.callback, timeStamp, callbackEntry.stack);
_invokeFrameCallback(callbackEntry.callback, timeStamp, callbackEntry.debugStack);
});
_removedIds.clear();
Timeline.finishSync();
......@@ -481,9 +528,9 @@ abstract class SchedulerBinding extends BindingBase {
// the error.
void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace callbackStack ]) {
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
assert(() { _FrameCallbackEntry.currentCallbackStack = callbackStack; return true; });
assert(() { _FrameCallbackEntry.debugCurrentCallbackStack = callbackStack; return true; });
try {
callback(timeStamp);
} catch (exception, exceptionStack) {
......@@ -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