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

Side by Side 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 to work with readAsString() too. (Thanks Kevin!) 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 library barback.file_pool;
6
7 import 'dart:async';
8 import 'dart:collection';
9 import 'dart:convert';
10 import 'dart:io';
11
12 /// Manages a pool of files that are opened for reading to cope with maximum
13 /// file descriptor limits.
14 ///
15 /// If a file cannot be opened because too many files are already open, this
16 /// will defer the open until a previously opened file is closed and then try
17 /// again. If this doesn't succeed after a certain amount of time, the open
18 /// will fail and the original "too many files" exception will be thrown.
19 class FilePool {
20 // TODO(rnystrom): Should we cap this to some maximum size?
nweiz 2013/10/16 00:16:01 I don't think so. Let the "too many files" error b
Bob Nystrom 2013/10/16 00:51:46 Done.
21 final _pendingListens = new Queue<_FileReader>();
nweiz 2013/10/16 00:16:01 Document this.
Bob Nystrom 2013/10/16 00:51:46 Done.
22
23 /// Opens [file] for reading.
24 ///
25 /// When the returned stream is listened to, if there are too many files
26 /// open, this will wait for a previously opened to file to be closed and
nweiz 2013/10/16 00:16:01 "opened to file" -> "opened file"
Bob Nystrom 2013/10/16 00:51:46 Done.
27 /// then try again.
28 Stream<List<int>> openRead(File file) => new _FileReader(this, file).stream;
29
30 /// Reads [file] as a string using [encoding].
31 ///
32 /// If there are too many files open and the read fails, this will wait for
33 /// a previously opened file to be closed and then try again.
34 Future<String> readAsString(File file, Encoding encoding) {
35 return _readAsBytes(file).then(encoding.decode);
36 }
37
38 /// Reads [file] as a list of bytes, using [openRead] to retry if there are
39 /// failures.
40 Future<List<int>> _readAsBytes(File file) {
41 var completer = new Completer<List<int>>();
42 var builder = new BytesBuilder();
43
44 openRead(file).listen(builder.add, onDone: () {
45 completer.complete(builder.takeBytes());
46 }, onError: completer.completeError, cancelOnError: true);
47
48 return completer.future;
49 }
50
51 /// Tries to reopen the next pending open if there are any.
nweiz 2013/10/16 00:16:01 "reopen" -> "restart" Also a little confusing tha
Bob Nystrom 2013/10/16 00:51:46 Fixed. The original implementation retried on open
52 void _retryPendingListen() {
53 if (_pendingListens.isEmpty) return;
54
55 var pending = _pendingListens.removeFirst();
56 pending._listen();
57 }
58 }
59
60 /// Wraps a raw file reading stream in a stream that handles "too many files"
61 /// errors.
62 ///
63 /// This also notifies the pool when the underlying file stream is closed so
64 /// that it can try to open a waiting file.
65 class _FileReader {
66 final FilePool _pool;
67 final File _file;
68
69 /// The underyling file stream.
70 Stream<List<int>> _fileStream;
71
72 /// The controller for the wrapped stream.
nweiz 2013/10/16 00:16:01 "wrapped" here isn't accurate; the wrapped stream
Bob Nystrom 2013/10/16 00:51:46 "stream wrapper".
73 StreamController<List<int>> _controller;
74
75 /// The current subscription to the underlying file stream.
76 ///
77 /// This will only be non-null while the wrapped stream is being listened to.
78 StreamSubscription _subscription;
79 Timer _timer;
nweiz 2013/10/16 00:16:01 Document this.
Bob Nystrom 2013/10/16 00:51:46 Done.
80
81 /// When a [listen] call has thrown a "too many files" error, this will be
82 /// the exception object.
83 Object _exception;
84
85 /// When a [listen] call has thrown a "too many files" error, this will be
86 /// the captured stack trace.
87 Object _stackTrace;
88
89 /// The wrapped stream that the file can be read from.
90 Stream<List<int>> get stream {
91 if (_controller != null) return _controller.stream;
92
93 _controller = new StreamController<List<int>>(onListen: _listen,
nweiz 2013/10/16 00:16:01 Why isn't this being initialized in the constructo
Bob Nystrom 2013/10/16 00:51:46 Done.
94 onPause: () {
95 _subscription.pause();
96 }, onResume: () {
97 _subscription.resume();
98 }, onCancel: () {
99 if (_subscription != null) _subscription.cancel();
100 _subscription = null;
101 }, sync: true);
102
103 return _controller.stream;
104 }
105
106 _FileReader(this._pool, this._file);
107
108 /// Starts listening to the underlying file stream.
109 void _listen() {
110 if (_timer != null) {
111 _timer.cancel();
112 _timer = null;
113 }
114
115 _fileStream = _file.openRead();
116 _subscription = _fileStream.listen(_controller.add,
117 onError: _onError, onDone: _onDone, cancelOnError: true);
118 }
119
120 /// Handles an error from the underlying file stream.
121 ///
122 /// "Too many file" errors are caught so that we can retry later. Other
123 /// errors are passed to the wrapped stream and the underlying stream
124 /// subscription is canceled.
125 void _onError(Object exception, Object stackTrace) {
126 assert(_subscription != null);
127 assert(_exception == null);
nweiz 2013/10/16 00:16:01 This doesn't seem right. It's definitely possible
Bob Nystrom 2013/10/16 00:51:46 Changed this to clear _exception in _listen(). Sin
128
129 // The subscription is canceled after an error.
130 _subscription = null;
131
132 // We only handle "Too many open files errors".
133 if (exception is! FileException || exception.osError.errorCode != 24) {
nweiz 2013/10/16 00:16:01 We should figure out what error code the exception
Bob Nystrom 2013/10/16 00:51:46 I did a little checking and I couldn't find much i
134 _controller.addError(exception, stackTrace);
135 return;
136 }
137
138 _exception = exception;
139 _stackTrace = stackTrace;
140
141 // We'll try to defer the listen in the hopes that another file will close
142 // and we can try. If that doesn't happen after a while, give up and just
143 // throw the original error.
144 // TODO(rnystrom): How long should this delay be?
nweiz 2013/10/16 00:16:01 The chance of a deadlock here is extremely small,
Bob Nystrom 2013/10/16 00:51:46 Good call. Increased the timeout and added a long
145 _timer = new Timer(new Duration(seconds: 10), _onTimeout);
146
147 // Tell the pool that this file is waiting.
148 _pool._pendingListens.add(this);
149 }
150
151 /// Handles the underlying file stream finishing.
152 void _onDone() {
153 _subscription = null;
154
155 _controller.close();
156 _pool._retryPendingListen();
157 }
158
159 /// If this file failed to be read because there were too many open files and
160 /// no file was closed in time to retry, this handles giving up.
161 void _onTimeout() {
162 assert(_subscription == null);
163 assert(_exception != null);
164
165 // We failed to open in time, so just fail with the original error.
166 _pool._pendingListens.remove(this);
167 _controller.addError(_exception, _stackTrace);
168 _controller.close();
169
170 _timer = null;
171 _exception = null;
172 _stackTrace = null;
nweiz 2013/10/16 00:16:01 I don't know how I feel about nulling out all the
Bob Nystrom 2013/10/16 00:51:46 Yeah, I'm mainly doing it as an ad-hoc state machi
173
nweiz 2013/10/16 00:16:01 Nit: extra newline.
Bob Nystrom 2013/10/16 00:51:46 Done.
174 }
175 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698