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; |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 53 /// 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 |
| 54 /// after it's finished. | 54 /// after it's finished. |
| 55 Task get currentTask => _currentTask; | 55 Task get currentTask => _currentTask; |
| 56 Task _currentTask; | 56 Task _currentTask; |
| 57 | 57 |
| 58 /// The current state of the schedule. | 58 /// The current state of the schedule. |
| 59 ScheduleState get state => _state; | 59 ScheduleState get state => _state; |
| 60 ScheduleState _state = ScheduleState.SET_UP; | 60 ScheduleState _state = ScheduleState.SET_UP; |
| 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 | |
| 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 |
| 76 // TODO(nweiz): make this a read-only view once issue 8321 is fixed. | |
| 77 /// Additional debugging info registered via [addDebugInfo]. | |
| 78 final debugInfo = <String>[]; | |
| 79 | |
| 77 /// The task queue that's currently being run. One of [tasks], [onException], | 80 /// The task queue that's currently being run. One of [tasks], [onException], |
| 78 /// or [onComplete]. This starts as [tasks], and can only be `null` after the | 81 /// or [onComplete]. This starts as [tasks], and can only be `null` after the |
| 79 /// schedule is done. | 82 /// schedule is done. |
| 80 TaskQueue get currentQueue => | 83 TaskQueue get currentQueue => |
| 81 _state == ScheduleState.DONE ? null : _currentQueue; | 84 _state == ScheduleState.DONE ? null : _currentQueue; |
| 82 TaskQueue _currentQueue; | 85 TaskQueue _currentQueue; |
| 83 | 86 |
| 84 /// The time to wait before terminating a task queue for inactivity. Defaults | 87 /// The time to wait before terminating a task queue for inactivity. Defaults |
| 85 /// to 30 seconds. This can be set to `null` to disable timeouts entirely. | 88 /// to 30 seconds. This can be set to `null` to disable timeouts entirely. |
| 86 /// | 89 /// |
| (...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 178 "${errorString()}"); | 181 "${errorString()}"); |
| 179 } else if (state == ScheduleState.SET_UP) { | 182 } else if (state == ScheduleState.SET_UP) { |
| 180 // If we're setting up, throwing the error will pipe it into the main | 183 // If we're setting up, throwing the error will pipe it into the main |
| 181 // error-handling code. | 184 // error-handling code. |
| 182 throw scheduleError; | 185 throw scheduleError; |
| 183 } else { | 186 } else { |
| 184 _currentQueue._signalError(scheduleError); | 187 _currentQueue._signalError(scheduleError); |
| 185 } | 188 } |
| 186 } | 189 } |
| 187 | 190 |
| 191 /// Adds [info] to the debugging output that will be printed if the test | |
| 192 /// fails. Unlike [signalError], this won't cause the test to fail, nor will | |
| 193 /// it short-circuit the current [TaskQueue]; it's just useful for providing | |
| 194 /// additional information that may not fit cleanly into an existing error. | |
| 195 void addDebugInfo(String info) => debugInfo.add(info); | |
| 196 | |
| 188 /// Notifies the schedule of an error that occurred in a task or out-of-band | 197 /// Notifies the schedule of an error that occurred in a task or out-of-band |
| 189 /// callback after the appropriate queue has timed out. If this schedule is | 198 /// callback after the appropriate queue has timed out. If this schedule is |
| 190 /// still running, the error will be added to the errors list to be shown | 199 /// still running, the error will be added to the errors list to be shown |
| 191 /// along with the timeout error; otherwise, a top-level error will be thrown. | 200 /// along with the timeout error; otherwise, a top-level error will be thrown. |
| 192 void _signalPostTimeoutError(error, [stackTrace]) { | 201 void _signalPostTimeoutError(error, [stackTrace]) { |
| 193 var scheduleError = new ScheduleError.from(this, error, | 202 var scheduleError = new ScheduleError.from(this, error, |
| 194 stackTrace: stackTrace); | 203 stackTrace: stackTrace); |
| 195 _addError(scheduleError); | 204 _addError(scheduleError); |
| 196 if (_state == ScheduleState.DONE) { | 205 if (_state == ScheduleState.DONE) { |
| 197 throw new StateError( | 206 throw new StateError( |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 265 // Don't top-level the error, since it's already been signaled to the | 274 // Don't top-level the error, since it's already been signaled to the |
| 266 // schedule. | 275 // schedule. |
| 267 future.catchError((_) => null); | 276 future.catchError((_) => null); |
| 268 | 277 |
| 269 return future; | 278 return future; |
| 270 } | 279 } |
| 271 | 280 |
| 272 /// Returns a string representation of all errors registered on this schedule. | 281 /// Returns a string representation of all errors registered on this schedule. |
| 273 String errorString() { | 282 String errorString() { |
| 274 if (errors.isEmpty) return "The schedule had no errors."; | 283 if (errors.isEmpty) return "The schedule had no errors."; |
| 275 if (errors.length == 1) return errors.first.toString(); | 284 if (errors.length == 1 && debugInfo.isEmpty) return errors.first.toString(); |
| 276 var errorStrings = errors.map((e) => e.toString()).join("\n================" | 285 |
| 277 "================================================================\n"); | 286 var border = "\n===========================================================" |
| 278 return "The schedule had ${errors.length} errors:\n$errorStrings"; | 287 "=====================\n"; |
| 288 var errorStrings = errors.map((e) => e.toString()).join(border); | |
| 289 var message = "The schedule had ${errors.length} errors:\n$errorStrings"; | |
| 290 | |
| 291 if (!debugInfo.isEmpty) { | |
| 292 message = "$message$border\nDebug info:\n${debugInfo.join(border)}"; | |
| 293 } | |
| 294 | |
| 295 return message; | |
| 279 } | 296 } |
| 280 | 297 |
| 281 /// Notifies the schedule that progress is being made on an asynchronous task. | 298 /// Notifies the schedule that progress is being made on an asynchronous task. |
| 282 /// This resets the timeout timer, and can be used in long-running tasks to | 299 /// This resets the timeout timer, and can be used in long-running tasks to |
| 283 /// keep them from timing out. | 300 /// keep them from timing out. |
| 284 void heartbeat() { | 301 void heartbeat() { |
| 285 if (_timeoutTimer != null) _timeoutTimer.cancel(); | 302 if (_timeoutTimer != null) _timeoutTimer.cancel(); |
| 286 if (_timeout == null) { | 303 if (_timeout == null) { |
| 287 _timeoutTimer = null; | 304 _timeoutTimer = null; |
| 288 } else { | 305 } else { |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 364 | 381 |
| 365 /// An out-of-band error signaled by [_schedule]. If this is non-null, it | 382 /// An out-of-band error signaled by [_schedule]. If this is non-null, it |
| 366 /// indicates that the queue should stop as soon as possible and re-throw this | 383 /// indicates that the queue should stop as soon as possible and re-throw this |
| 367 /// error. | 384 /// error. |
| 368 ScheduleError _error; | 385 ScheduleError _error; |
| 369 | 386 |
| 370 /// The [SubstituteFuture] for the currently-running task in the queue, or | 387 /// The [SubstituteFuture] for the currently-running task in the queue, or |
| 371 /// null if no task is currently running. | 388 /// null if no task is currently running. |
| 372 SubstituteFuture _taskFuture; | 389 SubstituteFuture _taskFuture; |
| 373 | 390 |
| 374 TaskQueue._(this.name, this._schedule); | 391 /// A [Future] that completes when the tasks in [this] are all complete. If an |
| 392 /// error occurs while running this queue, the returned [Future] will complete | |
| 393 /// with that error. | |
| 394 /// | |
| 395 /// The returned [Future] can complete before outstanding out-of-band | |
| 396 /// callbacks have finished running. | |
| 397 Future get onTasksComplete => _onTasksCompleteCompleter.future; | |
| 398 final _onTasksCompleteCompleter = new Completer(); | |
| 399 | |
| 400 TaskQueue._(this.name, this._schedule) { | |
| 401 // Avoid top-leveling errors that are passed to onTasksComplete if there are no | |
|
Bob Nystrom
2013/03/04 23:52:00
Long line.
nweiz
2013/03/05 02:16:09
Done.
| |
| 402 // listeners. | |
| 403 onTasksComplete.catchError((_) {}); | |
| 404 } | |
| 375 | 405 |
| 376 /// Whether this queue is currently running. | 406 /// Whether this queue is currently running. |
| 377 bool get isRunning => _schedule.state == ScheduleState.RUNNING && | 407 bool get isRunning => _schedule.state == ScheduleState.RUNNING && |
| 378 _schedule.currentQueue == this; | 408 _schedule.currentQueue == this; |
| 379 | 409 |
| 380 /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks | 410 /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks |
| 381 /// will be run in the order they're scheduled. In [fn] returns a [Future], | 411 /// will be run in the order they're scheduled. In [fn] returns a [Future], |
| 382 /// tasks after it won't be run until that [Future] completes. | 412 /// tasks after it won't be run until that [Future] completes. |
| 383 /// | 413 /// |
| 384 /// The return value will be completed once the scheduled task has finished | 414 /// The return value will be completed once the scheduled task has finished |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 396 /// top-level tasks which run in sequence. | 426 /// top-level tasks which run in sequence. |
| 397 Future schedule(fn(), [String description]) { | 427 Future schedule(fn(), [String description]) { |
| 398 if (isRunning) { | 428 if (isRunning) { |
| 399 var task = _schedule.currentTask; | 429 var task = _schedule.currentTask; |
| 400 var wrappedFn = () => _schedule.wrapFuture( | 430 var wrappedFn = () => _schedule.wrapFuture( |
| 401 new Future.immediate(null).then((_) => fn())); | 431 new Future.immediate(null).then((_) => fn())); |
| 402 if (task == null) return wrappedFn(); | 432 if (task == null) return wrappedFn(); |
| 403 return task.runChild(wrappedFn, description); | 433 return task.runChild(wrappedFn, description); |
| 404 } | 434 } |
| 405 | 435 |
| 406 var task = new Task(fn, description, this); | 436 var task = new Task(() { |
| 437 return new Future.of(fn).catchError((e) { | |
| 438 throw new ScheduleError.from(_schedule, e); | |
| 439 }); | |
| 440 }, description, this); | |
| 407 _contents.add(task); | 441 _contents.add(task); |
| 408 return task.result; | 442 return task.result; |
| 409 } | 443 } |
| 410 | 444 |
| 411 /// Runs all the tasks in this queue in order. | 445 /// Runs all the tasks in this queue in order. |
| 412 Future _run() { | 446 Future _run() { |
| 413 _schedule._currentQueue = this; | 447 _schedule._currentQueue = this; |
| 414 _schedule.heartbeat(); | 448 _schedule.heartbeat(); |
| 415 return Future.forEach(_contents, (task) { | 449 return Future.forEach(_contents, (task) { |
| 416 _schedule._currentTask = task; | 450 _schedule._currentTask = task; |
| 417 if (_error != null) throw _error; | 451 if (_error != null) throw _error; |
| 418 | 452 |
| 419 _taskFuture = new SubstituteFuture(task.fn()); | 453 _taskFuture = new SubstituteFuture(task.fn()); |
| 420 return _taskFuture.whenComplete(() { | 454 return _taskFuture.whenComplete(() { |
| 421 _taskFuture = null; | 455 _taskFuture = null; |
| 422 _schedule.heartbeat(); | 456 _schedule.heartbeat(); |
| 423 }).catchError((e) { | 457 }).catchError((e) { |
| 424 if (_error != null) _schedule._addError(_error); | 458 var error = new ScheduleError.from(_schedule, e); |
| 425 throw new ScheduleError.from(_schedule, e); | 459 _signalError(error); |
| 460 throw _error; | |
| 461 }); | |
| 462 }).then((_) { | |
| 463 _onTasksCompleteCompleter.complete(); | |
| 464 }).catchError((e) { | |
| 465 _onTasksCompleteCompleter.completeError(e); | |
| 466 throw e; | |
| 467 }).whenComplete(() { | |
| 468 _schedule._currentTask = null; | |
| 469 return _schedule._awaitNoPendingCallbacks().catchError((e) { | |
| 470 // Signal the error rather than passing it through directly so that if a | |
| 471 // timeout happens after an in-task error, both are reported. | |
| 472 _signalError(new ScheduleError.from(_schedule, e)); | |
| 426 }); | 473 }); |
| 427 }).whenComplete(() { | 474 }).whenComplete(() { |
| 428 _schedule._currentTask = null; | |
| 429 return _schedule._awaitNoPendingCallbacks(); | |
| 430 }).then((_) { | |
| 431 _schedule.heartbeat(); | 475 _schedule.heartbeat(); |
| 476 // If the tasks were otherwise successful, make sure we throw any | |
| 477 // out-of-band errors. If a task failed, make sure we throw the most | |
| 478 // recent error. | |
| 432 if (_error != null) throw _error; | 479 if (_error != null) throw _error; |
| 433 }); | 480 }); |
| 434 } | 481 } |
| 435 | 482 |
| 436 /// Signals that an out-of-band error has been detected and the queue should | 483 /// Signals that an out-of-band error has been detected and the queue should |
| 437 /// stop running as soon as possible. | 484 /// stop running as soon as possible. |
| 438 void _signalError(ScheduleError error) { | 485 void _signalError(ScheduleError error) { |
| 439 // If multiple errors are detected while a task is running, make sure the | 486 // If multiple errors are detected while a task is running, make sure the |
| 440 // earlier ones are recorded in the schedule. | 487 // earlier ones are recorded in the schedule. |
| 441 if (_error != null) _schedule._addError(_error); | 488 if (_error != null) _schedule._addError(_error); |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 470 return _contents.map((task) { | 517 return _contents.map((task) { |
| 471 var lines = task.toString().split("\n"); | 518 var lines = task.toString().split("\n"); |
| 472 var firstLine = task == highlight ? | 519 var firstLine = task == highlight ? |
| 473 "> ${lines.first}" : "* ${lines.first}"; | 520 "> ${lines.first}" : "* ${lines.first}"; |
| 474 lines = new List.from(lines.skip(1).map((line) => "| $line")); | 521 lines = new List.from(lines.skip(1).map((line) => "| $line")); |
| 475 lines.insertRange(0, 1, firstLine); | 522 lines.insertRange(0, 1, firstLine); |
| 476 return lines.join("\n"); | 523 return lines.join("\n"); |
| 477 }).join("\n"); | 524 }).join("\n"); |
| 478 } | 525 } |
| 479 } | 526 } |
| OLD | NEW |