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; | |
|
Jennifer Messerly
2013/10/14 21:00:08
barback.src.file_pool?
Or did you mean to make th
Siggi Cherem (dart-lang)
2013/10/14 21:14:28
Good point. Making it visible would simplify what
Bob Nystrom
2013/10/14 21:26:13
We don't use ".src" in other library names in Barb
Bob Nystrom
2013/10/14 21:26:13
I'm trying to avoid exposing this functionality as
Jennifer Messerly
2013/10/14 21:30:42
trivial detail but: regarding library name -- shou
Bob Nystrom
2013/10/14 22:06:58
Hmm, good point. I'm up for changing all of these
| |
| 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; | |
|
Siggi Cherem (dart-lang)
2013/10/14 21:14:28
if we make it public, can we also add openWrite?
Bob Nystrom
2013/10/14 21:26:13
See above. Trying to not make it public. :)
| |
| 28 | |
| 29 void _retryPendingListen() { | |
| 30 if (_pendingListens.isEmpty) return; | |
| 31 | |
| 32 var pending = _pendingListens.removeFirst(); | |
| 33 pending._listen(); | |
| 34 } | |
| 35 } | |
| 36 | |
| 37 /// Wraps a raw file reading stream in a stream that handles "too many files" | |
| 38 /// errors. | |
| 39 /// | |
| 40 /// This also notifies the pool when the underlying file stream is closed so | |
| 41 /// that it can try to open a waiting file. | |
| 42 class _FileReader { | |
| 43 final FilePool _pool; | |
| 44 final File _file; | |
| 45 | |
| 46 /// The underyling file stream. | |
| 47 Stream<List<int>> _fileStream; | |
| 48 | |
| 49 /// The controller for the wrapped stream. | |
| 50 StreamController<List<int>> _controller; | |
| 51 | |
| 52 /// The current subscription to the underlying file stream. | |
| 53 /// | |
| 54 /// This will only be non-null while the wrapped stream is being listened to. | |
| 55 StreamSubscription _subscription; | |
| 56 Timer _timer; | |
| 57 | |
| 58 /// When a [listen] call has thrown a "too many files" error, this will be | |
| 59 /// the exception object. | |
| 60 Object _exception; | |
| 61 | |
| 62 /// When a [listen] call has thrown a "too many files" error, this will be | |
| 63 /// the captured stack trace. | |
| 64 Object _stackTrace; | |
| 65 | |
| 66 /// The wrapped stream that the file can be read from. | |
| 67 Stream<List<int>> get stream { | |
| 68 if (_controller != null) return _controller.stream; | |
| 69 | |
| 70 _controller = new StreamController<List<int>>(onListen: _listen, | |
| 71 onPause: () { | |
| 72 _subscription.pause(); | |
| 73 }, onResume: () { | |
| 74 _subscription.resume(); | |
| 75 }, onCancel: () { | |
| 76 if (_subscription != null) _subscription.cancel(); | |
| 77 _subscription = null; | |
| 78 }, sync: true); | |
| 79 | |
| 80 return _controller.stream; | |
| 81 } | |
| 82 | |
| 83 _FileReader(this._pool, this._file); | |
| 84 | |
| 85 /// Starts listening to the underlying file stream. | |
| 86 void _listen() { | |
| 87 if (_timer != null) { | |
| 88 _timer.cancel(); | |
| 89 _timer = null; | |
| 90 } | |
| 91 | |
| 92 _fileStream = _file.openRead(); | |
| 93 _subscription = _fileStream.listen(_controller.add, | |
| 94 onError: _onError, onDone: _onDone, cancelOnError: true); | |
| 95 } | |
| 96 | |
| 97 /// Handles an error from the underlying file stream. | |
| 98 /// | |
| 99 /// "Too many file" errors are caught so that we can retry later. Other | |
| 100 /// errors are passed to the wrapped stream and the underlying stream | |
| 101 /// subscription is canceled. | |
| 102 void _onError(Object exception, Object stackTrace) { | |
| 103 assert(_subscription != null); | |
| 104 assert(_exception == null); | |
| 105 | |
| 106 // The subscription is canceled after an error. | |
| 107 _subscription = null; | |
| 108 | |
| 109 // We only handle "Too many open files errors". | |
| 110 if (exception is! FileException || exception.osError.errorCode != 24) { | |
| 111 // TODO(bob): stack trace. | |
| 112 _controller.addError(exception, stackTrace); | |
| 113 return; | |
| 114 } | |
| 115 | |
| 116 // TODO(bob): What if already deferred? | |
| 117 _exception = exception; | |
| 118 _stackTrace = stackTrace; | |
| 119 | |
| 120 // We'll try to defer the listen in the hopes that another file will close | |
| 121 // and we can try. If that doesn't happen after a while, give up and just | |
| 122 // throw the original error. | |
| 123 // TODO(bob): How long? | |
| 124 _timer = new Timer(new Duration(seconds: 5), _onTimeout); | |
| 125 | |
| 126 // Tell the pool that this file is waiting. | |
| 127 _pool._pendingListens.add(this); | |
| 128 } | |
| 129 | |
| 130 /// Handles the underlying file stream finishing. | |
| 131 void _onDone() { | |
| 132 _subscription = null; | |
| 133 | |
| 134 _controller.close(); | |
| 135 _pool._retryPendingListen(); | |
| 136 } | |
| 137 | |
| 138 /// If this file failed to be read because there were too many open files and | |
| 139 /// no file was closed in time to retry, this handles giving up. | |
| 140 void _onTimeout() { | |
| 141 assert(_subscription == null); | |
| 142 assert(_exception != null); | |
| 143 | |
| 144 // We failed to open in time, so just fail with the original error. | |
| 145 _pool._pendingListens.remove(this); | |
| 146 _controller.addError(_exception, _stackTrace); | |
| 147 _controller.close(); | |
| 148 | |
| 149 _timer = null; | |
| 150 _exception = null; | |
| 151 _stackTrace = null; | |
| 152 | |
| 153 } | |
| 154 } | |
| OLD | NEW |