Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(152)

Unified Diff: pkg/barback/lib/src/file_pool.dart

Issue 27242002: Use file pool to handle running out of file descriptors. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Revise. Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/barback/lib/src/asset.dart ('k') | pkg/barback/test/too_many_open_files_test.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/barback/lib/src/file_pool.dart
diff --git a/pkg/barback/lib/src/file_pool.dart b/pkg/barback/lib/src/file_pool.dart
new file mode 100644
index 0000000000000000000000000000000000000000..06192e766ea4fc122dc6318637fbe0bd091448dd
--- /dev/null
+++ b/pkg/barback/lib/src/file_pool.dart
@@ -0,0 +1,185 @@
+// Copyright (c) 2013, 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 barback.file_pool;
+
+import 'dart:async';
+import 'dart:collection';
+import 'dart:convert';
+import 'dart:io';
+
+/// Manages a pool of files that are opened for reading to cope with maximum
+/// file descriptor limits.
+///
+/// If a file cannot be opened because too many files are already open, this
+/// will defer the open until a previously opened file is closed and then try
+/// again. If this doesn't succeed after a certain amount of time, the open
+/// will fail and the original "too many files" exception will be thrown.
+class FilePool {
+ /// [_FileReader]s whose last [listen] call failed and that are waiting for
+ /// another file to close so they can be retried.
+ final _pendingListens = new Queue<_FileReader>();
+
+ /// Opens [file] for reading.
+ ///
+ /// When the returned stream is listened to, if there are too many files
+ /// open, this will wait for a previously opened file to be closed and then
+ /// try again.
+ Stream<List<int>> openRead(File file) => new _FileReader(this, file).stream;
+
+ /// Reads [file] as a string using [encoding].
+ ///
+ /// If there are too many files open and the read fails, this will wait for
+ /// a previously opened file to be closed and then try again.
+ Future<String> readAsString(File file, Encoding encoding) {
+ return _readAsBytes(file).then(encoding.decode);
+ }
+
+ /// Reads [file] as a list of bytes, using [openRead] to retry if there are
+ /// failures.
+ Future<List<int>> _readAsBytes(File file) {
+ var completer = new Completer<List<int>>();
+ var builder = new BytesBuilder();
+
+ openRead(file).listen(builder.add, onDone: () {
+ completer.complete(builder.takeBytes());
+ }, onError: completer.completeError, cancelOnError: true);
+
+ return completer.future;
+ }
+
+ /// Tries to re-listen to the next pending file reader if there are any.
+ void _retryPendingListen() {
+ if (_pendingListens.isEmpty) return;
+
+ var pending = _pendingListens.removeFirst();
+ pending._listen();
+ }
+}
+
+/// Wraps a raw file reading stream in a stream that handles "too many files"
+/// errors.
+///
+/// This also notifies the pool when the underlying file stream is closed so
+/// that it can try to open a waiting file.
+class _FileReader {
+ final FilePool _pool;
+ final File _file;
+
+ /// The underyling file stream.
+ Stream<List<int>> _fileStream;
+
+ /// The controller for the stream wrapper.
+ StreamController<List<int>> _controller;
+
+ /// The current subscription to the underlying file stream.
+ ///
+ /// This will only be non-null while the wrapped stream is being listened to.
+ StreamSubscription _subscription;
+
+ /// The timeout timer.
+ ///
+ /// If this timer fires before the listen is retried, it gives up and throws
+ /// the original error.
+ Timer _timer;
+
+ /// When a [listen] call has thrown a "too many files" error, this will be
+ /// the exception object.
+ Object _exception;
+
+ /// When a [listen] call has thrown a "too many files" error, this will be
+ /// the captured stack trace.
+ Object _stackTrace;
+
+ /// The wrapped stream that the file can be read from.
+ Stream<List<int>> get stream => _controller.stream;
+
+ _FileReader(this._pool, this._file) {
+ _controller = new StreamController<List<int>>(onListen: _listen,
+ onPause: () {
+ _subscription.pause();
+ }, onResume: () {
+ _subscription.resume();
+ }, onCancel: () {
+ if (_subscription != null) _subscription.cancel();
+ _subscription = null;
+ }, sync: true);
+ }
+
+ /// Starts listening to the underlying file stream.
+ void _listen() {
+ if (_timer != null) {
+ _timer.cancel();
+ _timer = null;
+ }
+
+ _exception = null;
+ _stackTrace = null;
+
+ _fileStream = _file.openRead();
+ _subscription = _fileStream.listen(_controller.add,
+ onError: _onError, onDone: _onDone, cancelOnError: true);
+ }
+
+ /// Handles an error from the underlying file stream.
+ ///
+ /// "Too many file" errors are caught so that we can retry later. Other
+ /// errors are passed to the wrapped stream and the underlying stream
+ /// subscription is canceled.
+ void _onError(Object exception, Object stackTrace) {
+ assert(_subscription != null);
+ assert(_exception == null);
+
+ // The subscription is canceled after an error.
+ _subscription = null;
+
+ // We only handle "Too many open files errors".
+ if (exception is! FileException || exception.osError.errorCode != 24) {
+ _controller.addError(exception, stackTrace);
+ return;
+ }
+
+ _exception = exception;
+ _stackTrace = stackTrace;
+
+ // We'll try to defer the listen in the hopes that another file will close
+ // and we can try. If that doesn't happen after a while, give up and just
+ // throw the original error.
+ // TODO(rnystrom): The point of this timer is to not get stuck forever in
+ // a deadlock scenario. But this can also erroneously fire if there is a
+ // large number of slow reads that do incrementally finish. A file may not
+ // move to the front of the queue in time even though it is making
+ // progress. A better solution is to have a single deadlock timer on the
+ // FilePool itself that starts when a pending listen is enqueued and checks
+ // to see if progress has been made when it fires.
+ _timer = new Timer(new Duration(seconds: 60), _onTimeout);
+
+ // Tell the pool that this file is waiting.
+ _pool._pendingListens.add(this);
+ }
+
+ /// Handles the underlying file stream finishing.
+ void _onDone() {
+ _subscription = null;
+
+ _controller.close();
+ _pool._retryPendingListen();
+ }
+
+ /// If this file failed to be read because there were too many open files and
+ /// no file was closed in time to retry, this handles giving up.
+ void _onTimeout() {
+ assert(_subscription == null);
+ assert(_exception != null);
+
+ // We failed to open in time, so just fail with the original error.
+ _pool._pendingListens.remove(this);
+ _controller.addError(_exception, _stackTrace);
+ _controller.close();
+
+ _timer = null;
+ _exception = null;
+ _stackTrace = null;
+ }
+}
« no previous file with comments | « pkg/barback/lib/src/asset.dart ('k') | pkg/barback/test/too_many_open_files_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698