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