| 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:io'; |
| 10 |
| 11 /// Manages a pool of files that are opened for reading to cope with maximum |
| 12 /// file descriptor limits. |
| 13 /// |
| 14 /// If a file cannot be opened because too many files are already open, this |
| 15 /// will defer the open until a previously opened file is closed and then try |
| 16 /// again. If this doesn't succeed after a certain amount of time, the open |
| 17 /// will fail and the original "too many files" exception will be thrown. |
| 18 class FilePool { |
| 19 // TODO(rnystrom): Should we cap this to some maximum size? |
| 20 final _pendingListens = new Queue<_FileReader>(); |
| 21 |
| 22 /// Opens [file] for reading. |
| 23 /// |
| 24 /// When the returned stream is listened to, if there are too many files |
| 25 /// open, this will wait for a previously opened to file to be closed and |
| 26 /// then try again. |
| 27 Stream<List<int>> openRead(File file) => new _FileReader(this, file).stream; |
| 28 |
| 29 Future<String> readAsString(File file, Encoding encoding) { |
| 30 return _readAsBytes(file).then(encoding.decode); |
| 31 } |
| 32 |
| 33 Future<List<int>> _readAsBytes(File file) { |
| 34 Completer<List<int>> completer = new Completer<List<int>>(); |
| 35 var builder = new BytesBuilder(); |
| 36 openRead(file).listen( |
| 37 (d) => builder.add(d), |
| 38 onDone: () { |
| 39 completer.complete(builder.takeBytes()); |
| 40 }, |
| 41 onError: (e, StackTrace stackTrace) { |
| 42 completer.completeError(e, stackTrace); |
| 43 }, |
| 44 cancelOnError: true); |
| 45 return completer.future; |
| 46 } |
| 47 |
| 48 void _retryPendingListen() { |
| 49 if (_pendingListens.isEmpty) return; |
| 50 |
| 51 var pending = _pendingListens.removeFirst(); |
| 52 pending._listen(); |
| 53 } |
| 54 } |
| 55 |
| 56 /// Wraps a raw file reading stream in a stream that handles "too many files" |
| 57 /// errors. |
| 58 /// |
| 59 /// This also notifies the pool when the underlying file stream is closed so |
| 60 /// that it can try to open a waiting file. |
| 61 class _FileReader { |
| 62 final FilePool _pool; |
| 63 final File _file; |
| 64 |
| 65 /// The underyling file stream. |
| 66 Stream<List<int>> _fileStream; |
| 67 |
| 68 /// The controller for the wrapped stream. |
| 69 StreamController<List<int>> _controller; |
| 70 |
| 71 /// The current subscription to the underlying file stream. |
| 72 /// |
| 73 /// This will only be non-null while the wrapped stream is being listened to. |
| 74 StreamSubscription _subscription; |
| 75 Timer _timer; |
| 76 |
| 77 /// When a [listen] call has thrown a "too many files" error, this will be |
| 78 /// the exception object. |
| 79 Object _exception; |
| 80 |
| 81 /// When a [listen] call has thrown a "too many files" error, this will be |
| 82 /// the captured stack trace. |
| 83 Object _stackTrace; |
| 84 |
| 85 /// The wrapped stream that the file can be read from. |
| 86 Stream<List<int>> get stream { |
| 87 if (_controller != null) return _controller.stream; |
| 88 |
| 89 _controller = new StreamController<List<int>>(onListen: _listen, |
| 90 onPause: () { |
| 91 _subscription.pause(); |
| 92 }, onResume: () { |
| 93 _subscription.resume(); |
| 94 }, onCancel: () { |
| 95 if (_subscription != null) _subscription.cancel(); |
| 96 _subscription = null; |
| 97 }, sync: true); |
| 98 |
| 99 return _controller.stream; |
| 100 } |
| 101 |
| 102 _FileReader(this._pool, this._file); |
| 103 |
| 104 /// Starts listening to the underlying file stream. |
| 105 void _listen() { |
| 106 if (_timer != null) { |
| 107 _timer.cancel(); |
| 108 _timer = null; |
| 109 } |
| 110 |
| 111 _fileStream = _file.openRead(); |
| 112 _subscription = _fileStream.listen(_controller.add, |
| 113 onError: _onError, onDone: _onDone, cancelOnError: true); |
| 114 } |
| 115 |
| 116 /// Handles an error from the underlying file stream. |
| 117 /// |
| 118 /// "Too many file" errors are caught so that we can retry later. Other |
| 119 /// errors are passed to the wrapped stream and the underlying stream |
| 120 /// subscription is canceled. |
| 121 void _onError(Object exception, Object stackTrace) { |
| 122 assert(_subscription != null); |
| 123 assert(_exception == null); |
| 124 |
| 125 // The subscription is canceled after an error. |
| 126 _subscription = null; |
| 127 |
| 128 // We only handle "Too many open files errors". |
| 129 if (exception is! FileException || exception.osError.errorCode != 24) { |
| 130 // TODO(bob): stack trace. |
| 131 _controller.addError(exception, stackTrace); |
| 132 return; |
| 133 } |
| 134 |
| 135 // TODO(bob): What if already deferred? |
| 136 _exception = exception; |
| 137 _stackTrace = stackTrace; |
| 138 |
| 139 // We'll try to defer the listen in the hopes that another file will close |
| 140 // and we can try. If that doesn't happen after a while, give up and just |
| 141 // throw the original error. |
| 142 // TODO(bob): How long? |
| 143 _timer = new Timer(new Duration(seconds: 5), _onTimeout); |
| 144 |
| 145 // Tell the pool that this file is waiting. |
| 146 _pool._pendingListens.add(this); |
| 147 } |
| 148 |
| 149 /// Handles the underlying file stream finishing. |
| 150 void _onDone() { |
| 151 _subscription = null; |
| 152 |
| 153 _controller.close(); |
| 154 _pool._retryPendingListen(); |
| 155 } |
| 156 |
| 157 /// If this file failed to be read because there were too many open files and |
| 158 /// no file was closed in time to retry, this handles giving up. |
| 159 void _onTimeout() { |
| 160 assert(_subscription == null); |
| 161 assert(_exception != null); |
| 162 |
| 163 // We failed to open in time, so just fail with the original error. |
| 164 _pool._pendingListens.remove(this); |
| 165 _controller.addError(_exception, _stackTrace); |
| 166 _controller.close(); |
| 167 |
| 168 _timer = null; |
| 169 _exception = null; |
| 170 _stackTrace = null; |
| 171 |
| 172 } |
| 173 } |
| OLD | NEW |