Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 } | |
| OLD | NEW |