Add debugPrintTransientCallbackRegistrationStack (#5198)
Adds Scheduler.debugPrintTransientCallbackRegistrationStack so that you can find out how your current callback got registered.
This commit is contained in:
parent
d7fb51a551
commit
c8db0041d8
@ -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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
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 {
|
|||||||
/// 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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
/// 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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
/// 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 {
|
|||||||
///
|
///
|
||||||
/// 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 {
|
|||||||
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 {
|
|||||||
_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 {
|
|||||||
// 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 {
|
|||||||
}
|
}
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
assert(() { _FrameCallbackEntry.currentCallbackStack = null; return true; });
|
assert(() { _FrameCallbackEntry.debugCurrentCallbackStack = null; return true; });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user