a6593d32bc
It's currently too difficult to include a third party package for it to be worthwhile for this use case. R=alanknight@google.com Review URL: https://codereview.chromium.org//419683002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@38564 260f80e4-7a28-3924-810f-c04153c831b5
140 lines
4.5 KiB
Dart
140 lines
4.5 KiB
Dart
// Copyright (c) 2014, 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 pool;
|
|
|
|
import 'dart:async';
|
|
import 'dart:collection';
|
|
|
|
import 'package:stack_trace/stack_trace.dart';
|
|
|
|
/// Manages an abstract pool of resources with a limit on how many may be in use
|
|
/// at once.
|
|
///
|
|
/// When a resource is needed, the user should call [request]. When the returned
|
|
/// future completes with a [PoolResource], the resource may be allocated. Once
|
|
/// the resource has been released, the user should call [PoolResource.release].
|
|
/// The pool will ensure that only a certain number of [PoolResource]s may be
|
|
/// allocated at once.
|
|
class Pool {
|
|
/// Completers for requests beyond the first [_maxAllocatedResources].
|
|
///
|
|
/// When an item is released, the next element of [_requestedResources] will
|
|
/// be completed.
|
|
final _requestedResources = new Queue<Completer<PoolResource>>();
|
|
|
|
/// The maximum number of resources that may be allocated at once.
|
|
final int _maxAllocatedResources;
|
|
|
|
/// The number of resources that are currently allocated.
|
|
int _allocatedResources = 0;
|
|
|
|
/// The timeout timer.
|
|
///
|
|
/// If [_timeout] isn't null, this timer is set as soon as the resource limit
|
|
/// is reached and is reset every time an resource is released or a new
|
|
/// resource is requested. If it fires, that indicates that the caller became
|
|
/// deadlocked, likely due to files waiting for additional files to be read
|
|
/// before they could be closed.
|
|
Timer _timer;
|
|
|
|
/// The amount of time to wait before timing out the pending resources.
|
|
Duration _timeout;
|
|
|
|
/// Creates a new pool with the given limit on how many resources may be
|
|
/// allocated at once.
|
|
///
|
|
/// If [timeout] is passed, then if that much time passes without any activity
|
|
/// all pending [request] futures will throw an exception. This is indented
|
|
/// to avoid deadlocks.
|
|
Pool(this._maxAllocatedResources, {Duration timeout})
|
|
: _timeout = timeout;
|
|
|
|
/// Request a [PoolResource].
|
|
///
|
|
/// If the maximum number of resources is already allocated, this will delay
|
|
/// until one of them is released.
|
|
Future<PoolResource> request() {
|
|
if (_allocatedResources < _maxAllocatedResources) {
|
|
_allocatedResources++;
|
|
return new Future.value(new PoolResource._(this));
|
|
} else {
|
|
var completer = new Completer<PoolResource>();
|
|
_requestedResources.add(completer);
|
|
_resetTimer();
|
|
return completer.future;
|
|
}
|
|
}
|
|
|
|
/// Requests a resource for the duration of [callback], which may return a
|
|
/// Future.
|
|
///
|
|
/// The return value of [callback] is piped to the returned Future.
|
|
Future withResource(callback()) {
|
|
return request().then((resource) =>
|
|
Chain.track(new Future.sync(callback)).whenComplete(resource.release));
|
|
}
|
|
|
|
/// If there are any pending requests, this will fire the oldest one.
|
|
void _onResourceReleased() {
|
|
if (_requestedResources.isEmpty) {
|
|
_allocatedResources--;
|
|
if (_timer != null) {
|
|
_timer.cancel();
|
|
_timer = null;
|
|
}
|
|
return;
|
|
}
|
|
|
|
_resetTimer();
|
|
var pending = _requestedResources.removeFirst();
|
|
pending.complete(new PoolResource._(this));
|
|
}
|
|
|
|
/// A resource has been requested, allocated, or released.
|
|
void _resetTimer() {
|
|
if (_timer != null) _timer.cancel();
|
|
if (_timeout == null) {
|
|
_timer = null;
|
|
} else {
|
|
_timer = new Timer(_timeout, _onTimeout);
|
|
}
|
|
}
|
|
|
|
/// Handles [_timer] timing out by causing all pending resource completers to
|
|
/// emit exceptions.
|
|
void _onTimeout() {
|
|
for (var completer in _requestedResources) {
|
|
completer.completeError("Pool deadlock: all resources have been "
|
|
"allocated for too long.", new Chain.current());
|
|
}
|
|
_requestedResources.clear();
|
|
_timer = null;
|
|
}
|
|
}
|
|
|
|
/// A member of a [Pool].
|
|
///
|
|
/// A [PoolResource] is a token that indicates that a resource is allocated.
|
|
/// When the associated resource is released, the user should call [release].
|
|
class PoolResource {
|
|
final Pool _pool;
|
|
|
|
/// Whether [this] has been released yet.
|
|
bool _released = false;
|
|
|
|
PoolResource._(this._pool);
|
|
|
|
/// Tells the parent [Pool] that the resource associated with this resource is
|
|
/// no longer allocated, and that a new [PoolResource] may be allocated.
|
|
void release() {
|
|
if (_released) {
|
|
throw new StateError("A PoolResource may only be released once.");
|
|
}
|
|
_released = true;
|
|
_pool._onResourceReleased();
|
|
}
|
|
}
|
|
|