| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library schedule; | 5 library schedule; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 | 9 |
| 10 import 'package:unittest/unittest.dart' as unittest; | 10 import 'package:unittest/unittest.dart' as unittest; |
| 11 | 11 |
| 12 import 'mock_clock.dart' as mock_clock; |
| 12 import 'schedule_error.dart'; | 13 import 'schedule_error.dart'; |
| 14 import 'substitute_future.dart'; |
| 13 import 'task.dart'; | 15 import 'task.dart'; |
| 14 | 16 |
| 15 /// The schedule of tasks to run for a single test. This has three separate task | 17 /// The schedule of tasks to run for a single test. This has three separate task |
| 16 /// queues: [tasks], [onComplete], and [onException]. It also provides | 18 /// queues: [tasks], [onComplete], and [onException]. It also provides |
| 17 /// visibility into the current state of the schedule. | 19 /// visibility into the current state of the schedule. |
| 18 class Schedule { | 20 class Schedule { |
| 19 /// The main task queue for the schedule. These tasks are run before the other | 21 /// The main task queue for the schedule. These tasks are run before the other |
| 20 /// queues and generally constitute the main test body. | 22 /// queues and generally constitute the main test body. |
| 21 TaskQueue get tasks => _tasks; | 23 TaskQueue get tasks => _tasks; |
| 22 TaskQueue _tasks; | 24 TaskQueue _tasks; |
| (...skipping 23 matching lines...) Expand all Loading... |
| 46 /// skipped. | 48 /// skipped. |
| 47 TaskQueue get onComplete => _onComplete; | 49 TaskQueue get onComplete => _onComplete; |
| 48 TaskQueue _onComplete; | 50 TaskQueue _onComplete; |
| 49 | 51 |
| 50 /// Returns the [Task] that's currently executing, or `null` if there is no | 52 /// Returns the [Task] that's currently executing, or `null` if there is no |
| 51 /// such task. This will be `null` both before the schedule starts running and | 53 /// such task. This will be `null` both before the schedule starts running and |
| 52 /// after it's finished. | 54 /// after it's finished. |
| 53 Task get currentTask => _currentTask; | 55 Task get currentTask => _currentTask; |
| 54 Task _currentTask; | 56 Task _currentTask; |
| 55 | 57 |
| 56 /// Whether the schedule has finished running. This is only set once | 58 /// The current state of the schedule. |
| 57 /// [onComplete] has finished running. It will be set whether or not an | 59 ScheduleState get state => _state; |
| 58 /// exception has occurred. | 60 ScheduleState _state = ScheduleState.SET_UP; |
| 59 bool get done => _done; | |
| 60 bool _done = false; | |
| 61 | 61 |
| 62 // TODO(nweiz): make this a read-only view once issue 8321 is fixed. | 62 // TODO(nweiz): make this a read-only view once issue 8321 is fixed. |
| 63 | 63 |
| 64 /// Errors thrown by the task queues. | 64 /// Errors thrown by the task queues. |
| 65 /// | 65 /// |
| 66 /// When running tasks in [tasks], this will always be empty. If an error | 66 /// When running tasks in [tasks], this will always be empty. If an error |
| 67 /// occurs in [tasks], it will be added to this list and then [onException] | 67 /// occurs in [tasks], it will be added to this list and then [onException] |
| 68 /// will be run. If an error occurs there as well, it will be added to this | 68 /// will be run. If an error occurs there as well, it will be added to this |
| 69 /// list and [onComplete] will be run. Errors thrown during [onComplete] will | 69 /// list and [onComplete] will be run. Errors thrown during [onComplete] will |
| 70 /// also be added to this list, although no scheduled tasks will be run | 70 /// also be added to this list, although no scheduled tasks will be run |
| 71 /// afterwards. | 71 /// afterwards. |
| 72 /// | 72 /// |
| 73 /// Any out-of-band callbacks that throw errors will also have those errors | 73 /// Any out-of-band callbacks that throw errors will also have those errors |
| 74 /// added to this list. | 74 /// added to this list. |
| 75 final errors = <ScheduleError>[]; | 75 final errors = <ScheduleError>[]; |
| 76 | 76 |
| 77 /// The task queue that's currently being run, or `null` if there is no such | 77 /// The task queue that's currently being run. One of [tasks], [onException], |
| 78 /// queue. One of [tasks], [onException], or [onComplete]. This will be `null` | 78 /// or [onComplete]. This starts as [tasks], and can only be `null` after the |
| 79 /// before the schedule starts running. | 79 /// schedule is done. |
| 80 TaskQueue get currentQueue => _done ? null : _currentQueue; | 80 TaskQueue get currentQueue => |
| 81 _state == ScheduleState.DONE ? null : _currentQueue; |
| 81 TaskQueue _currentQueue; | 82 TaskQueue _currentQueue; |
| 82 | 83 |
| 84 /// The time, in milliseconds, to wait before terminating a task queue for |
| 85 /// inactivity. Defaults to 30 seconds. This can be set to `null` to disable |
| 86 /// timeouts entirely. |
| 87 /// |
| 88 /// If a task queue times out, an error will be raised that can be handled as |
| 89 /// usual in the [onException] and [onComplete] queues. If [onException] times |
| 90 /// out, that can only be handled in [onComplete]; if [onComplete] times out, |
| 91 /// that cannot be handled. |
| 92 /// |
| 93 /// If a task times out and then later completes with an error, that error |
| 94 /// cannot be handled. The user will still be notified of it. |
| 95 int get timeoutMs => _timeoutMs; |
| 96 int _timeoutMs = 30 * 1000; |
| 97 set timeoutMs(int value) { |
| 98 _timeoutMs = value; |
| 99 heartbeat(); |
| 100 } |
| 101 |
| 83 /// The number of out-of-band callbacks that have been registered with | 102 /// The number of out-of-band callbacks that have been registered with |
| 84 /// [wrapAsync] but have yet to be called. | 103 /// [wrapAsync] but have yet to be called. |
| 85 int _pendingCallbacks = 0; | 104 int _pendingCallbacks = 0; |
| 86 | 105 |
| 87 /// A completer that will be completed once [_pendingCallbacks] reaches zero. | 106 /// A completer that will be completed once [_pendingCallbacks] reaches zero. |
| 88 /// This will only be non-`null` if [_awaitPendingCallbacks] has been called | 107 /// This will only be non-`null` if [_awaitPendingCallbacks] has been called |
| 89 /// while [_pendingCallbacks] is non-zero. | 108 /// while [_pendingCallbacks] is non-zero. |
| 90 Completer _noPendingCallbacks; | 109 Completer _noPendingCallbacks; |
| 91 | 110 |
| 111 /// The timer for keeping track of task timeouts. This may be null. |
| 112 Timer _timeoutTimer; |
| 113 |
| 92 /// Creates a new schedule with empty task queues. | 114 /// Creates a new schedule with empty task queues. |
| 93 Schedule() { | 115 Schedule() { |
| 94 _tasks = new TaskQueue._("tasks", this); | 116 _tasks = new TaskQueue._("tasks", this); |
| 95 _onComplete = new TaskQueue._("onComplete", this); | 117 _onComplete = new TaskQueue._("onComplete", this); |
| 96 _onException = new TaskQueue._("onException", this); | 118 _onException = new TaskQueue._("onException", this); |
| 119 _currentQueue = _tasks; |
| 120 |
| 121 heartbeat(); |
| 97 } | 122 } |
| 98 | 123 |
| 99 /// Sets up this schedule by running [setUp], then runs all the task queues in | 124 /// Sets up this schedule by running [setUp], then runs all the task queues in |
| 100 /// order. Any errors in [setUp] will cause [onException] to run. | 125 /// order. Any errors in [setUp] will cause [onException] to run. |
| 101 Future run(void setUp()) { | 126 Future run(void setUp()) { |
| 102 return new Future.immediate(null).then((_) { | 127 return new Future.immediate(null).then((_) { |
| 103 try { | 128 try { |
| 104 setUp(); | 129 setUp(); |
| 105 } catch (e, stackTrace) { | 130 } catch (e, stackTrace) { |
| 106 throw new ScheduleError.from(this, e, stackTrace: stackTrace); | 131 throw new ScheduleError.from(this, e, stackTrace: stackTrace); |
| 107 } | 132 } |
| 108 | 133 |
| 134 _state = ScheduleState.RUNNING; |
| 109 return tasks._run(); | 135 return tasks._run(); |
| 110 }).catchError((e) { | 136 }).catchError((e) { |
| 111 errors.add(e); | 137 errors.add(e); |
| 112 return onException._run().catchError((innerError) { | 138 return onException._run().catchError((innerError) { |
| 113 // If an error occurs in a task in the onException queue, make sure it's | 139 // If an error occurs in a task in the onException queue, make sure it's |
| 114 // registered in the error list and re-throw it. We could also re-throw | 140 // registered in the error list and re-throw it. We could also re-throw |
| 115 // `e`; ultimately, all the errors will be shown to the user if any | 141 // `e`; ultimately, all the errors will be shown to the user if any |
| 116 // ScheduleError is thrown. | 142 // ScheduleError is thrown. |
| 117 errors.add(innerError); | 143 errors.add(innerError); |
| 118 throw innerError; | 144 throw innerError; |
| 119 }).then((_) { | 145 }).then((_) { |
| 120 // If there are no errors in the onException queue, re-throw the | 146 // If there are no errors in the onException queue, re-throw the |
| 121 // original error that caused it to run. | 147 // original error that caused it to run. |
| 122 throw e; | 148 throw e; |
| 123 }); | 149 }); |
| 124 }).whenComplete(() { | 150 }).whenComplete(() { |
| 125 return onComplete._run().catchError((e) { | 151 return onComplete._run().catchError((e) { |
| 126 // If an error occurs in a task in the onComplete queue, make sure it's | 152 // If an error occurs in a task in the onComplete queue, make sure it's |
| 127 // registered in the error list and re-throw it. | 153 // registered in the error list and re-throw it. |
| 128 errors.add(e); | 154 errors.add(e); |
| 129 throw e; | 155 throw e; |
| 130 }); | 156 }); |
| 131 }).whenComplete(() { | 157 }).whenComplete(() { |
| 132 _done = true; | 158 if (_timeoutTimer != null) _timeoutTimer.cancel(); |
| 159 _state = ScheduleState.DONE; |
| 133 }); | 160 }); |
| 134 } | 161 } |
| 135 | 162 |
| 136 /// Signals that an out-of-band error has occurred. Using [wrapAsync] along | 163 /// Signals that an out-of-band error has occurred. Using [wrapAsync] along |
| 137 /// with `throw` is usually preferable to calling this directly. | 164 /// with `throw` is usually preferable to calling this directly. |
| 138 /// | 165 /// |
| 139 /// The metadata in [AsyncError]s and [ScheduleError]s will be preserved. | 166 /// The metadata in [AsyncError]s and [ScheduleError]s will be preserved. |
| 140 void signalError(error, [stackTrace]) { | 167 void signalError(error, [stackTrace]) { |
| 168 heartbeat(); |
| 169 |
| 141 var scheduleError = new ScheduleError.from(this, error, | 170 var scheduleError = new ScheduleError.from(this, error, |
| 142 stackTrace: stackTrace, task: currentTask); | 171 stackTrace: stackTrace); |
| 143 if (_done) { | 172 if (_state == ScheduleState.DONE) { |
| 144 errors.add(scheduleError); | |
| 145 throw new StateError( | 173 throw new StateError( |
| 146 "An out-of-band error was signaled outside of wrapAsync after the " | 174 "An out-of-band error was signaled outside of wrapAsync after the " |
| 147 "schedule finished running.\n" | 175 "schedule finished running.\n" |
| 148 "${errorString()}"); | 176 "${errorString()}"); |
| 149 } else if (currentQueue == null) { | 177 } else if (state == ScheduleState.SET_UP) { |
| 150 // If we're not done but there's no current queue, that means we haven't | 178 // If we're setting up, throwing the error will pipe it into the main |
| 151 // started yet and thus we're in setUp or the synchronous body of the | |
| 152 // function. Throwing the error will thus pipe it into the main | |
| 153 // error-handling code. | 179 // error-handling code. |
| 154 throw scheduleError; | 180 throw scheduleError; |
| 155 } else { | 181 } else { |
| 156 _currentQueue._signalError(scheduleError); | 182 _currentQueue._signalError(scheduleError); |
| 157 } | 183 } |
| 158 } | 184 } |
| 159 | 185 |
| 186 /// Notifies the schedule of an error that occurred in a task or out-of-band |
| 187 /// callback after the appropriate queue has timed out. If this schedule is |
| 188 /// still running, the error will be added to the errors list to be shown |
| 189 /// along with the timeout error; otherwise, a top-level error will be thrown. |
| 190 void _signalPostTimeoutError(error, [stackTrace]) { |
| 191 var scheduleError = new ScheduleError.from(this, error, |
| 192 stackTrace: stackTrace); |
| 193 errors.add(scheduleError); |
| 194 if (_state == ScheduleState.DONE) { |
| 195 throw new StateError( |
| 196 "An out-of-band error was caught after the test timed out.\n" |
| 197 "${errorString()}"); |
| 198 } |
| 199 } |
| 200 |
| 160 /// Returns a function wrapping [fn] that pipes any errors into the schedule | 201 /// Returns a function wrapping [fn] that pipes any errors into the schedule |
| 161 /// chain. This will also block the current task queue from completing until | 202 /// chain. This will also block the current task queue from completing until |
| 162 /// the returned function has been called. It's used to ensure that | 203 /// the returned function has been called. It's used to ensure that |
| 163 /// out-of-band callbacks are properly handled by the scheduled test. | 204 /// out-of-band callbacks are properly handled by the scheduled test. |
| 164 /// | 205 /// |
| 165 /// The top-level `wrapAsync` function should usually be used in preference to | 206 /// The top-level `wrapAsync` function should usually be used in preference to |
| 166 /// this. | 207 /// this. |
| 167 Function wrapAsync(fn(arg)) { | 208 Function wrapAsync(fn(arg)) { |
| 168 if (_done) { | 209 if (_state == ScheduleState.DONE) { |
| 169 throw new StateError("wrapAsync called after the schedule has finished " | 210 throw new StateError("wrapAsync called after the schedule has finished " |
| 170 "running."); | 211 "running."); |
| 171 } | 212 } |
| 213 heartbeat(); |
| 214 |
| 215 var queue = currentQueue; |
| 216 // It's possible that the queue timed out before this. |
| 217 bool _timedOut() => queue != currentQueue || _pendingCallbacks == 0; |
| 172 | 218 |
| 173 _pendingCallbacks++; | 219 _pendingCallbacks++; |
| 174 return (arg) { | 220 return (arg) { |
| 175 try { | 221 try { |
| 176 return fn(arg); | 222 return fn(arg); |
| 177 } catch (e, stackTrace) { | 223 } catch (e, stackTrace) { |
| 178 signalError(e, stackTrace); | 224 if (_timedOut()) { |
| 225 _signalPostTimeoutError(e, stackTrace); |
| 226 } else { |
| 227 signalError(e, stackTrace); |
| 228 } |
| 179 } finally { | 229 } finally { |
| 230 if (_timedOut()) return; |
| 231 |
| 180 _pendingCallbacks--; | 232 _pendingCallbacks--; |
| 181 if (_pendingCallbacks == 0 && _noPendingCallbacks != null) { | 233 if (_pendingCallbacks == 0 && _noPendingCallbacks != null) { |
| 182 _noPendingCallbacks.complete(); | 234 _noPendingCallbacks.complete(); |
| 183 _noPendingCallbacks = null; | 235 _noPendingCallbacks = null; |
| 184 } | 236 } |
| 185 } | 237 } |
| 186 }; | 238 }; |
| 187 } | 239 } |
| 188 | 240 |
| 189 /// Returns a string representation of all errors registered on this schedule. | 241 /// Returns a string representation of all errors registered on this schedule. |
| 190 String errorString() { | 242 String errorString() { |
| 191 if (errors.isEmpty) return "The schedule had no errors."; | 243 if (errors.isEmpty) return "The schedule had no errors."; |
| 192 if (errors.length == 1) return errors.first.toString(); | 244 if (errors.length == 1) return errors.first.toString(); |
| 193 var errorStrings = errors.map((e) => e.toString()).join("\n================" | 245 var errorStrings = errors.map((e) => e.toString()).join("\n================" |
| 194 "================================================================\n"); | 246 "================================================================\n"); |
| 195 return "The schedule had ${errors.length} errors:\n$errorStrings"; | 247 return "The schedule had ${errors.length} errors:\n$errorStrings"; |
| 196 } | 248 } |
| 197 | 249 |
| 250 /// Notifies the schedule that progress is being made on an asynchronous task. |
| 251 /// This resets the timeout timer, and can be used in long-running tasks to |
| 252 /// keep them from timing out. |
| 253 void heartbeat() { |
| 254 if (_timeoutTimer != null) _timeoutTimer.cancel(); |
| 255 if (_timeoutMs == null) { |
| 256 _timeoutTimer = null; |
| 257 } else { |
| 258 _timeoutTimer = mock_clock.getTimer(_timeoutMs, _signalTimeout); |
| 259 } |
| 260 } |
| 261 |
| 262 /// The callback to run when the timeout timer fires. Notifies the current |
| 263 /// queue that a timeout has occurred. |
| 264 void _signalTimeout(_) { |
| 265 // Reset the timer so that we can detect timeouts in the onException and |
| 266 // onComplete queues. |
| 267 _timeoutTimer = null; |
| 268 |
| 269 var error = new ScheduleError.from(this, "The schedule timed out after " |
| 270 "${_timeoutMs}ms of inactivity."); |
| 271 |
| 272 _pendingCallbacks = 0; |
| 273 if (_noPendingCallbacks != null) { |
| 274 var noPendingCallbacks = _noPendingCallbacks; |
| 275 _noPendingCallbacks = null; |
| 276 noPendingCallbacks.completeError(error); |
| 277 } else { |
| 278 currentQueue._signalTimeout(error); |
| 279 } |
| 280 } |
| 281 |
| 198 /// Returns a [Future] that will complete once there are no pending | 282 /// Returns a [Future] that will complete once there are no pending |
| 199 /// out-of-band callbacks. | 283 /// out-of-band callbacks. |
| 200 Future _awaitNoPendingCallbacks() { | 284 Future _awaitNoPendingCallbacks() { |
| 201 if (_pendingCallbacks == 0) return new Future.immediate(null); | 285 if (_pendingCallbacks == 0) return new Future.immediate(null); |
| 202 if (_noPendingCallbacks == null) _noPendingCallbacks = new Completer(); | 286 if (_noPendingCallbacks == null) _noPendingCallbacks = new Completer(); |
| 203 return _noPendingCallbacks.future; | 287 return _noPendingCallbacks.future; |
| 204 } | 288 } |
| 205 } | 289 } |
| 206 | 290 |
| 291 /// An enum of states for a [Schedule]. |
| 292 class ScheduleState { |
| 293 /// The schedule can have tasks added to its queue, but is not yet running |
| 294 /// them. |
| 295 static const SET_UP = const ScheduleState._("SET_UP"); |
| 296 |
| 297 /// The schedule is actively running tasks. This includes running tasks in |
| 298 /// [Schedule.onException] and [Schedule.onComplete]. |
| 299 static const RUNNING = const ScheduleState._("RUNNING"); |
| 300 |
| 301 /// The schedule has finished running all its tasks, either successfully or |
| 302 /// with an error. |
| 303 static const DONE = const ScheduleState._("DONE"); |
| 304 |
| 305 /// The name of the state. |
| 306 final String name; |
| 307 |
| 308 const ScheduleState._(this.name); |
| 309 |
| 310 String toString() => name; |
| 311 } |
| 312 |
| 207 /// A queue of asynchronous tasks to execute in order. | 313 /// A queue of asynchronous tasks to execute in order. |
| 208 class TaskQueue { | 314 class TaskQueue { |
| 209 // TODO(nweiz): make this a read-only view when issue 8321 is fixed. | 315 // TODO(nweiz): make this a read-only view when issue 8321 is fixed. |
| 210 /// The tasks in the queue. | 316 /// The tasks in the queue. |
| 211 Collection<Task> get contents => _contents; | 317 Collection<Task> get contents => _contents; |
| 212 final _contents = new Queue<Task>(); | 318 final _contents = new Queue<Task>(); |
| 213 | 319 |
| 214 /// The name of the queue, for debugging purposes. | 320 /// The name of the queue, for debugging purposes. |
| 215 final String name; | 321 final String name; |
| 216 | 322 |
| 217 /// The [Schedule] that created this queue. | 323 /// The [Schedule] that created this queue. |
| 218 final Schedule _schedule; | 324 final Schedule _schedule; |
| 219 | 325 |
| 220 /// An out-of-band error signaled by [_schedule]. If this is non-null, it | 326 /// An out-of-band error signaled by [_schedule]. If this is non-null, it |
| 221 /// indicates that the queue should stop as soon as possible and re-throw this | 327 /// indicates that the queue should stop as soon as possible and re-throw this |
| 222 /// error. | 328 /// error. |
| 223 ScheduleError _error; | 329 ScheduleError _error; |
| 224 | 330 |
| 331 /// The [SubstituteFuture] for the currently-running task in the queue, or |
| 332 /// null if no task is currently running. |
| 333 SubstituteFuture _taskFuture; |
| 334 |
| 225 TaskQueue._(this.name, this._schedule); | 335 TaskQueue._(this.name, this._schedule); |
| 226 | 336 |
| 227 /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks | 337 /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks |
| 228 /// will be run in the order they're scheduled. In [fn] returns a [Future], | 338 /// will be run in the order they're scheduled. In [fn] returns a [Future], |
| 229 /// tasks after it won't be run until that [Future] completes. | 339 /// tasks after it won't be run until that [Future] completes. |
| 230 /// | 340 /// |
| 231 /// The return value will be completed once the scheduled task has finished | 341 /// The return value will be completed once the scheduled task has finished |
| 232 /// running. Its return value is the same as the return value of [fn], or the | 342 /// running. Its return value is the same as the return value of [fn], or the |
| 233 /// value it completes to if it's a [Future]. | 343 /// value it completes to if it's a [Future]. |
| 234 /// | 344 /// |
| 235 /// If [description] is passed, it's used to describe the task for debugging | 345 /// If [description] is passed, it's used to describe the task for debugging |
| 236 /// purposes when an error occurs. | 346 /// purposes when an error occurs. |
| 237 Future schedule(fn(), [String description]) { | 347 Future schedule(fn(), [String description]) { |
| 238 var task = new Task(fn, this, description); | 348 var task = new Task(fn, this, description); |
| 239 _contents.add(task); | 349 _contents.add(task); |
| 240 return task.result; | 350 return task.result; |
| 241 } | 351 } |
| 242 | 352 |
| 243 /// Runs all the tasks in this queue in order. | 353 /// Runs all the tasks in this queue in order. |
| 244 Future _run() { | 354 Future _run() { |
| 245 _schedule._currentQueue = this; | 355 _schedule._currentQueue = this; |
| 356 _schedule.heartbeat(); |
| 246 return Future.forEach(_contents, (task) { | 357 return Future.forEach(_contents, (task) { |
| 247 _schedule._currentTask = task; | 358 _schedule._currentTask = task; |
| 248 if (_error != null) throw _error; | 359 if (_error != null) throw _error; |
| 249 return task.fn().catchError((e) { | 360 |
| 361 _taskFuture = new SubstituteFuture(task.fn()); |
| 362 return _taskFuture.whenComplete(() { |
| 363 _taskFuture = null; |
| 364 _schedule.heartbeat(); |
| 365 }).catchError((e) { |
| 250 if (_error != null) _schedule.errors.add(_error); | 366 if (_error != null) _schedule.errors.add(_error); |
| 251 throw new ScheduleError.from(_schedule, e, task: task); | 367 throw new ScheduleError.from(_schedule, e); |
| 252 }); | 368 }); |
| 253 }).whenComplete(() { | 369 }).whenComplete(() { |
| 254 _schedule._currentTask = null; | 370 _schedule._currentTask = null; |
| 255 return _schedule._awaitNoPendingCallbacks(); | 371 return _schedule._awaitNoPendingCallbacks(); |
| 256 }).then((_) { | 372 }).then((_) { |
| 373 _schedule.heartbeat(); |
| 257 if (_error != null) throw _error; | 374 if (_error != null) throw _error; |
| 258 }); | 375 }); |
| 259 } | 376 } |
| 260 | 377 |
| 261 /// Signals that an out-of-band error has been detected and the queue should | 378 /// Signals that an out-of-band error has been detected and the queue should |
| 262 /// stop running as soon as possible. | 379 /// stop running as soon as possible. |
| 263 void _signalError(ScheduleError error) { | 380 void _signalError(ScheduleError error) { |
| 264 // If multiple errors are detected while a task is running, make sure the | 381 // If multiple errors are detected while a task is running, make sure the |
| 265 // earlier ones are recorded in the schedule. | 382 // earlier ones are recorded in the schedule. |
| 266 if (_error != null) _schedule.errors.add(_error); | 383 if (_error != null) _schedule.errors.add(_error); |
| 267 _error = error; | 384 _error = error; |
| 268 } | 385 } |
| 269 | 386 |
| 387 /// Notifies the queue that it has timed out and it needs to terminate |
| 388 /// immediately with a timeout error. |
| 389 void _signalTimeout(ScheduleError error) { |
| 390 if (_taskFuture != null) { |
| 391 // Catch errors coming off the old task future, in case it completes after |
| 392 // timing out. |
| 393 _taskFuture.substitute(new Future.immediateError(error)).catchError((e) { |
| 394 _schedule._signalPostTimeoutError(e); |
| 395 }); |
| 396 } else { |
| 397 // This branch probably won't be reached, but it's conceivable that the |
| 398 // event loop might get pumped when _taskFuture is null but we haven't yet |
| 399 // called _awaitNoPendingCallbacks. |
| 400 _signalError(error); |
| 401 } |
| 402 } |
| 403 |
| 270 String toString() => name; | 404 String toString() => name; |
| 271 | 405 |
| 272 /// Returns a detailed representation of the queue as a tree of tasks. If | 406 /// Returns a detailed representation of the queue as a tree of tasks. If |
| 273 /// [highlight] is passed, that task is specially highlighted. | 407 /// [highlight] is passed, that task is specially highlighted. |
| 274 /// | 408 /// |
| 275 /// [highlight] must be a task in this queue. | 409 /// [highlight] must be a task in this queue. |
| 276 String generateTree([Task highlight]) { | 410 String generateTree([Task highlight]) { |
| 277 assert(highlight == null || highlight.queue == this); | 411 assert(highlight == null || highlight.queue == this); |
| 278 return _contents.map((task) { | 412 return _contents.map((task) { |
| 279 var lines = task.toString().split("\n"); | 413 var lines = task.toString().split("\n"); |
| 280 var firstLine = task == highlight ? | 414 var firstLine = task == highlight ? |
| 281 "> ${lines.first}" : "* ${lines.first}"; | 415 "> ${lines.first}" : "* ${lines.first}"; |
| 282 lines = new List.from(lines.skip(1).map((line) => "| $line")); | 416 lines = new List.from(lines.skip(1).map((line) => "| $line")); |
| 283 lines.insertRange(0, 1, firstLine); | 417 lines.insertRange(0, 1, firstLine); |
| 284 return lines.join("\n"); | 418 return lines.join("\n"); |
| 285 }).join("\n"); | 419 }).join("\n"); |
| 286 } | 420 } |
| 287 } | 421 } |
| OLD | NEW |