Skip to content

Commit

Permalink
Add a RestartableTimer class.
Browse files Browse the repository at this point in the history
This is useful for heartbeat-style timeouts where a timeout is reset
when certain actions occur.

R=lrn@google.com

Review URL: https://codereview.chromium.org//1417373004 .
  • Loading branch information
nex3 committed Oct 28, 2015
1 parent 59f5b4c commit d23c21e
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -3,6 +3,9 @@
- Added `AsyncMemoizer.future`, which allows the result to be accessed before
`runOnce()` is called.

- Added `RestartableTimer`, a non-periodic timer that can be reset over and
over.

## 1.3.0

- Added `StreamCompleter` class for creating a stream now and providing its
Expand Down
1 change: 1 addition & 0 deletions lib/async.dart
Expand Up @@ -14,6 +14,7 @@ export "src/delegate/stream_consumer.dart";
export "src/delegate/stream_sink.dart";
export "src/delegate/stream_subscription.dart";
export "src/future_group.dart";
export "src/restartable_timer.dart";
export "src/result_future.dart";
export "src/stream_completer.dart";
export "src/stream_group.dart";
Expand Down
48 changes: 48 additions & 0 deletions lib/src/restartable_timer.dart
@@ -0,0 +1,48 @@
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library async.restartable_timer;

import 'dart:async';

/// A non-periodic timer that can be restarted any number of times.
///
/// Once restarted (via [reset]), the timer counts down from its original
/// duration again.
class RestartableTimer implements Timer {
/// The duration of the timer.
final Duration _duration;

/// The callback to call when the timer fires.
final ZoneCallback _callback;

/// The timer for the current or most recent countdown.
///
/// This timer is canceled and overwritten every time this [RestartableTimer]
/// is reset.
Timer _timer;

/// Creates a new timer.
///
/// The [callback] function is invoked after the given [duration]. Unlike a
/// normal non-periodic [Timer], [callback] may be called more than once.
RestartableTimer(this._duration, this._callback) {
_timer = new Timer(_duration, _callback);
}

bool get isActive => _timer.isActive;

/// Restarts the timer so that it counts down from its original duration
/// again.
///
/// This restarts the timer even if it has already fired or has been canceled.
void reset() {
_timer.cancel();
_timer = new Timer(_duration, _callback);
}

void cancel() {
_timer.cancel();
}
}
3 changes: 2 additions & 1 deletion pubspec.yaml
@@ -1,9 +1,10 @@
name: async
version: 1.4.0-dev
version: 1.4.0
author: Dart Team <misc@dartlang.org>
description: Utility functions and classes related to the 'dart:async' library.
homepage: https://www.github.com/dart-lang/async
dev_dependencies:
fake_async: "^0.1.2"
stack_trace: "^1.0.0"
test: "^0.12.0"
environment:
Expand Down
110 changes: 110 additions & 0 deletions test/restartable_timer_test.dart
@@ -0,0 +1,110 @@
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';

import 'package:async/async.dart';
import 'package:fake_async/fake_async.dart';
import 'package:test/test.dart';

main() {
test("runs the callback once the duration has elapsed", () {
new FakeAsync().run((async) {
var fired = false;
var timer = new RestartableTimer(new Duration(seconds: 5), () {
fired = true;
});

async.elapse(new Duration(seconds: 4));
expect(fired, isFalse);

async.elapse(new Duration(seconds: 1));
expect(fired, isTrue);
});
});

test("doesn't run the callback if the timer is canceled", () {
new FakeAsync().run((async) {
var fired = false;
var timer = new RestartableTimer(new Duration(seconds: 5), () {
fired = true;
});

async.elapse(new Duration(seconds: 4));
expect(fired, isFalse);
timer.cancel();

async.elapse(new Duration(seconds: 4));
expect(fired, isFalse);
});
});

test("resets the duration if the timer is reset before it fires", () {
new FakeAsync().run((async) {
var fired = false;
var timer = new RestartableTimer(new Duration(seconds: 5), () {
fired = true;
});

async.elapse(new Duration(seconds: 4));
expect(fired, isFalse);
timer.reset();

async.elapse(new Duration(seconds: 4));
expect(fired, isFalse);

async.elapse(new Duration(seconds: 1));
expect(fired, isTrue);
});
});

test("re-runs the callback if the timer is reset after firing", () {
new FakeAsync().run((async) {
var fired = 0;
var timer = new RestartableTimer(new Duration(seconds: 5), () {
fired++;
});

async.elapse(new Duration(seconds: 5));
expect(fired, equals(1));
timer.reset();

async.elapse(new Duration(seconds: 5));
expect(fired, equals(2));
timer.reset();

async.elapse(new Duration(seconds: 5));
expect(fired, equals(3));
});
});

test("runs the callback if the timer is reset after being canceled", () {
new FakeAsync().run((async) {
var fired = false;
var timer = new RestartableTimer(new Duration(seconds: 5), () {
fired = true;
});

async.elapse(new Duration(seconds: 4));
expect(fired, isFalse);
timer.cancel();

async.elapse(new Duration(seconds: 4));
expect(fired, isFalse);
timer.reset();

async.elapse(new Duration(seconds: 5));
expect(fired, isTrue);
});
});

test("only runs the callback once if the timer isn't reset", () {
new FakeAsync().run((async) {
var timer = new RestartableTimer(
new Duration(seconds: 5),
expectAsync(() {}, count: 1));
async.elapse(new Duration(seconds: 10));
});
});
}

0 comments on commit d23c21e

Please sign in to comment.