GeezerVison is showing github.com — stream: add destroy and _destroy methods. · nodejs/node@b6e1d22
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/stream.md +47-1Lines changed: 47 additions & 1 deletion - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -499,6 +499,15 @@ write('hello', () => { | 499 | 499 |
| 500 | 500 | A Writable stream in object mode will always ignore the `encoding` argument. | 501 | 501 |
| | 502 | +##### writable.destroy([error]) | | 503 | +<!-- YAML | | 504 | +added: REPLACEME | | 505 | +--> | | 506 | + | | 507 | +Destroy the stream, and emit the passed error. After this call, the | | 508 | +writible stream has ended. Implementors should not override this method, | | 509 | +but instead implement [`writable._destroy`][writable-_destroy]. | | 510 | + | 502 | 511 | ### Readable Streams | 503 | 512 |
| 504 | 513 | Readable streams are an abstraction for a *source* from which data is | @@ -1070,6 +1079,16 @@ myReader.on('readable', () => { | 1070 | 1079 | }); | 1071 | 1080 | ``` | 1072 | 1081 |
| | 1082 | +##### readable.destroy([error]) | | 1083 | +<!-- YAML | | 1084 | +added: REPLACEME | | 1085 | +--> | | 1086 | + | | 1087 | +Destroy the stream, and emit `'error'`. After this call, the | | 1088 | +readable stream will release any internal resources. | | 1089 | +Implementors should not override this method, but instead implement | | 1090 | +[`readable._destroy`][readable-_destroy]. | | 1091 | + | 1073 | 1092 | ### Duplex and Transform Streams | 1074 | 1093 |
| 1075 | 1094 | #### Class: stream.Duplex | @@ -1109,6 +1128,16 @@ Examples of Transform streams include: | 1109 | 1128 | * [zlib streams][zlib] | 1110 | 1129 | * [crypto streams][crypto] | 1111 | 1130 |
| | 1131 | +##### transform.destroy([error]) | | 1132 | +<!-- YAML | | 1133 | +added: REPLACEME | | 1134 | +--> | | 1135 | + | | 1136 | +Destroy the stream, and emit `'error'`. After this call, the | | 1137 | +transform stream would release any internal resources. | | 1138 | +implementors should not override this method, but instead implement | | 1139 | +[`readable._destroy`][readable-_destroy]. | | 1140 | +The default implementation of `_destroy` for `Transform` also emit `'close'`. | 1112 | 1141 |
| 1113 | 1142 | ## API for Stream Implementers | 1114 | 1143 |
| @@ -1248,6 +1277,8 @@ constructor and implement the `writable._write()` method. The | 1248 | 1277 | [`stream._write()`][stream-_write] method. | 1249 | 1278 | * `writev` {Function} Implementation for the | 1250 | 1279 | [`stream._writev()`][stream-_writev] method. | | 1280 | + * `destroy` {Function} Implementation for the | | 1281 | + [`stream._destroy()`][writable-_destroy] method. | 1251 | 1282 |
| 1252 | 1283 | For example: | 1253 | 1284 |
| @@ -1358,6 +1389,15 @@ The `writable._writev()` method is prefixed with an underscore because it is | 1358 | 1389 | internal to the class that defines it, and should never be called directly by | 1359 | 1390 | user programs. | 1360 | 1391 |
| | 1392 | +#### writable.\_destroy(err, callback) | | 1393 | +<!-- YAML | | 1394 | +added: REPLACEME | | 1395 | +--> | | 1396 | + | | 1397 | +* `err` {Error} An error. | | 1398 | +* `callback` {Function} A callback function that takes an optional error argument | | 1399 | + which is invoked when the writable is destroyed. | | 1400 | + | 1361 | 1401 | #### Errors While Writing | 1362 | 1402 |
| 1363 | 1403 | It is recommended that errors occurring during the processing of the | @@ -1428,6 +1468,8 @@ constructor and implement the `readable._read()` method. | 1428 | 1468 | a single value instead of a Buffer of size n. Defaults to `false` | 1429 | 1469 | * `read` {Function} Implementation for the [`stream._read()`][stream-_read] | 1430 | 1470 | method. | | 1471 | + * `destroy` {Function} Implementation for the [`stream._destroy()`][readable-_destroy] | | 1472 | + method. | 1431 | 1473 |
| 1432 | 1474 | For example: | 1433 | 1475 |
| @@ -2079,4 +2121,8 @@ readable buffer so there is nothing for a user to consume. | 2079 | 2121 | [stream-read]: #stream_readable_read_size | 2080 | 2122 | [stream-resume]: #stream_readable_resume | 2081 | 2123 | [stream-write]: #stream_writable_write_chunk_encoding_callback | 2082 | | -[zlib]: zlib.html | | 2124 | +[readable-_destroy]: #stream_readable_destroy_err_callback | | 2125 | +[writable-_destroy]: #stream_writable_destroy_err_callback | | 2126 | +[TCP sockets]: net.html#net_class_net_socket | | 2127 | +[Transform]: #stream_class_stream_transform | | 2128 | +[Writable]: #stream_class_stream_writable |
Collapse file Copy file name to clipboardExpand all lines: lib/_stream_duplex.js +30Lines changed: 30 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -76,3 +76,33 @@ function onend() { | 76 | 76 | function onEndNT(self) { | 77 | 77 | self.end(); | 78 | 78 | } | | 79 | + | | 80 | +Object.defineProperty(Duplex.prototype, 'destroyed', { | | 81 | + get() { | | 82 | + if (this._readableState === undefined || | | 83 | + this._writableState === undefined) { | | 84 | + return false; | | 85 | + } | | 86 | + return this._readableState.destroyed && this._writableState.destroyed; | | 87 | + }, | | 88 | + set(value) { | | 89 | + // we ignore the value if the stream | | 90 | + // has not been initialized yet | | 91 | + if (this._readableState === undefined || | | 92 | + this._writableState === undefined) { | | 93 | + return; | | 94 | + } | | 95 | + | | 96 | + // backward compatibility, the user is explicitly | | 97 | + // managing destroyed | | 98 | + this._readableState.destroyed = value; | | 99 | + this._writableState.destroyed = value; | | 100 | + } | | 101 | +}); | | 102 | + | | 103 | +Duplex.prototype._destroy = function(err, cb) { | | 104 | + this.push(null); | | 105 | + this.end(); | | 106 | + | | 107 | + process.nextTick(cb, err); | | 108 | +}; |
Collapse file Copy file name to clipboardExpand all lines: lib/_stream_readable.js +38-2Lines changed: 38 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -30,6 +30,7 @@ const Buffer = require('buffer').Buffer; | 30 | 30 | const util = require('util'); | 31 | 31 | const debug = util.debuglog('stream'); | 32 | 32 | const BufferList = require('internal/streams/BufferList'); | | 33 | +const destroyImpl = require('internal/streams/destroy'); | 33 | 34 | var StringDecoder; | 34 | 35 |
| 35 | 36 | util.inherits(Readable, Stream); | @@ -99,6 +100,9 @@ function ReadableState(options, stream) { | 99 | 100 | this.readableListening = false; | 100 | 101 | this.resumeScheduled = false; | 101 | 102 |
| | 103 | + // has it been destroyed | | 104 | + this.destroyed = false; | | 105 | + | 102 | 106 | // Crypto is kind of old and crusty. Historically, its default string | 103 | 107 | // encoding is 'binary' so we have to make this configurable. | 104 | 108 | // Everything else in the universe uses 'utf8', though. | @@ -129,12 +133,44 @@ function Readable(options) { | 129 | 133 | // legacy | 130 | 134 | this.readable = true; | 131 | 135 |
| 132 | | - if (options && typeof options.read === 'function') | 133 | | - this._read = options.read; | | 136 | + if (options) { | | 137 | + if (typeof options.read === 'function') | | 138 | + this._read = options.read; | | 139 | + | | 140 | + if (typeof options.destroy === 'function') | | 141 | + this._destroy = options.destroy; | | 142 | + } | 134 | 143 |
| 135 | 144 | Stream.call(this); | 136 | 145 | } | 137 | 146 |
| | 147 | +Object.defineProperty(Readable.prototype, 'destroyed', { | | 148 | + get() { | | 149 | + if (this._readableState === undefined) { | | 150 | + return false; | | 151 | + } | | 152 | + return this._readableState.destroyed; | | 153 | + }, | | 154 | + set(value) { | | 155 | + // we ignore the value if the stream | | 156 | + // has not been initialized yet | | 157 | + if (!this._readableState) { | | 158 | + return; | | 159 | + } | | 160 | + | | 161 | + // backward compatibility, the user is explicitly | | 162 | + // managing destroyed | | 163 | + this._readableState.destroyed = value; | | 164 | + } | | 165 | +}); | | 166 | + | | 167 | +Readable.prototype.destroy = destroyImpl.destroy; | | 168 | +Readable.prototype._undestroy = destroyImpl.undestroy; | | 169 | +Readable.prototype._destroy = function(err, cb) { | | 170 | + this.push(null); | | 171 | + cb(err); | | 172 | +}; | | 173 | + | 138 | 174 | // Manually shove something into the read() buffer. | 139 | 175 | // This returns true if the highWaterMark has not been hit yet, | 140 | 176 | // similar to how Writable.write() returns true if you should | |
Collapse file Copy file name to clipboardExpand all lines: lib/_stream_transform.js +8Lines changed: 8 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -196,6 +196,14 @@ Transform.prototype._read = function(n) { | 196 | 196 | }; | 197 | 197 |
| 198 | 198 |
| | 199 | +Transform.prototype._destroy = function(err, cb) { | | 200 | + Duplex.prototype._destroy.call(this, err, (err2) => { | | 201 | + cb(err2); | | 202 | + this.emit('close'); | | 203 | + }); | | 204 | +}; | | 205 | + | | 206 | + | 199 | 207 | function done(stream, er, data) { | 200 | 208 | if (er) | 201 | 209 | return stream.emit('error', er); | |
Collapse file Copy file name to clipboardExpand all lines: lib/_stream_writable.js +34Lines changed: 34 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -32,6 +32,7 @@ const util = require('util'); | 32 | 32 | const internalUtil = require('internal/util'); | 33 | 33 | const Stream = require('stream'); | 34 | 34 | const Buffer = require('buffer').Buffer; | | 35 | +const destroyImpl = require('internal/streams/destroy'); | 35 | 36 |
| 36 | 37 | util.inherits(Writable, Stream); | 37 | 38 |
| @@ -66,6 +67,9 @@ function WritableState(options, stream) { | 66 | 67 | // when 'finish' is emitted | 67 | 68 | this.finished = false; | 68 | 69 |
| | 70 | + // has it been destroyed | | 71 | + this.destroyed = false; | | 72 | + | 69 | 73 | // should we decode strings into buffers before passing to _write? | 70 | 74 | // this is here so that some node-core streams can optimize string | 71 | 75 | // handling at a lower level. | @@ -192,6 +196,9 @@ function Writable(options) { | 192 | 196 |
| 193 | 197 | if (typeof options.writev === 'function') | 194 | 198 | this._writev = options.writev; | | 199 | + | | 200 | + if (typeof options.destroy === 'function') | | 201 | + this._destroy = options.destroy; | 195 | 202 | } | 196 | 203 |
| 197 | 204 | Stream.call(this); | @@ -563,3 +570,30 @@ function onCorkedFinish(corkReq, state, err) { | 563 | 570 | state.corkedRequestsFree = corkReq; | 564 | 571 | } | 565 | 572 | } | | 573 | + | | 574 | +Object.defineProperty(Writable.prototype, 'destroyed', { | | 575 | + get() { | | 576 | + if (this._writableState === undefined) { | | 577 | + return false; | | 578 | + } | | 579 | + return this._writableState.destroyed; | | 580 | + }, | | 581 | + set(value) { | | 582 | + // we ignore the value if the stream | | 583 | + // has not been initialized yet | | 584 | + if (!this._writableState) { | | 585 | + return; | | 586 | + } | | 587 | + | | 588 | + // backward compatibility, the user is explicitly | | 589 | + // managing destroyed | | 590 | + this._writableState.destroyed = value; | | 591 | + } | | 592 | +}); | | 593 | + | | 594 | +Writable.prototype.destroy = destroyImpl.destroy; | | 595 | +Writable.prototype._undestroy = destroyImpl.undestroy; | | 596 | +Writable.prototype._destroy = function(err, cb) { | | 597 | + this.end(); | | 598 | + cb(err); | | 599 | +}; |
Collapse file Copy file name to clipboardExpand all lines: lib/fs.js +5-6Lines changed: 5 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1986,11 +1986,10 @@ ReadStream.prototype._read = function(n) { | 1986 | 1986 | }; | 1987 | 1987 |
| 1988 | 1988 |
| 1989 | | -ReadStream.prototype.destroy = function() { | 1990 | | - if (this.destroyed) | 1991 | | - return; | 1992 | | - this.destroyed = true; | 1993 | | - this.close(); | | 1989 | +ReadStream.prototype._destroy = function(err, cb) { | | 1990 | + this.close(function(err2) { | | 1991 | + cb(err || err2); | | 1992 | + }); | 1994 | 1993 | }; | 1995 | 1994 |
| 1996 | 1995 |
| @@ -2157,7 +2156,7 @@ WriteStream.prototype._writev = function(data, cb) { | 2157 | 2156 | }; | 2158 | 2157 |
| 2159 | 2158 |
| 2160 | | -WriteStream.prototype.destroy = ReadStream.prototype.destroy; | | 2159 | +WriteStream.prototype._destroy = ReadStream.prototype._destroy; | 2161 | 2160 | WriteStream.prototype.close = ReadStream.prototype.close; | 2162 | 2161 |
| 2163 | 2162 | // There is no shutdown() for files. | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/process/stdio.js +8-4Lines changed: 8 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -18,10 +18,12 @@ function setupStdio() { | 18 | 18 | function getStdout() { | 19 | 19 | if (stdout) return stdout; | 20 | 20 | stdout = createWritableStdioStream(1); | 21 | | - stdout.destroy = stdout.destroySoon = function(er) { | | 21 | + stdout.destroySoon = stdout.destroy; | | 22 | + stdout._destroy = function(er, cb) { | | 23 | + // avoid errors if we already emitted | 22 | 24 | const errors = lazyErrors(); | 23 | 25 | er = er || new errors.Error('ERR_STDOUT_CLOSE'); | 24 | | - stdout.emit('error', er); | | 26 | + cb(er); | 25 | 27 | }; | 26 | 28 | if (stdout.isTTY) { | 27 | 29 | process.on('SIGWINCH', () => stdout._refreshSize()); | @@ -32,10 +34,12 @@ function setupStdio() { | 32 | 34 | function getStderr() { | 33 | 35 | if (stderr) return stderr; | 34 | 36 | stderr = createWritableStdioStream(2); | 35 | | - stderr.destroy = stderr.destroySoon = function(er) { | | 37 | + stderr.destroySoon = stderr.destroy; | | 38 | + stderr._destroy = function(er, cb) { | | 39 | + // avoid errors if we already emitted | 36 | 40 | const errors = lazyErrors(); | 37 | 41 | er = er || new errors.Error('ERR_STDERR_CLOSE'); | 38 | | - stderr.emit('error', er); | | 42 | + cb(er); | 39 | 43 | }; | 40 | 44 | if (stderr.isTTY) { | 41 | 45 | process.on('SIGWINCH', () => stderr._refreshSize()); | |
Collapse file Copy file name to clipboard +65Lines changed: 65 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | + | | 3 | +// undocumented cb() API, needed for core, not for public API | | 4 | +function destroy(err, cb) { | | 5 | + const readableDestroyed = this._readableState && | | 6 | + this._readableState.destroyed; | | 7 | + const writableDestroyed = this._writableState && | | 8 | + this._writableState.destroyed; | | 9 | + | | 10 | + if (readableDestroyed || writableDestroyed) { | | 11 | + if (err && (!this._writableState || !this._writableState.errorEmitted)) { | | 12 | + process.nextTick(emitErrorNT, this, err); | | 13 | + } | | 14 | + return; | | 15 | + } | | 16 | + | | 17 | + // we set destroyed to true before firing error callbacks in order | | 18 | + // to make it re-entrance safe in case destroy() is called within callbacks | | 19 | + | | 20 | + if (this._readableState) { | | 21 | + this._readableState.destroyed = true; | | 22 | + } | | 23 | + | | 24 | + // if this is a duplex stream mark the writable part as destroyed as well | | 25 | + if (this._writableState) { | | 26 | + this._writableState.destroyed = true; | | 27 | + } | | 28 | + | | 29 | + this._destroy(err || null, (err) => { | | 30 | + if (!cb && err) { | | 31 | + process.nextTick(emitErrorNT, this, err); | | 32 | + if (this._writableState) { | | 33 | + this._writableState.errorEmitted = true; | | 34 | + } | | 35 | + } else if (cb) { | | 36 | + cb(err); | | 37 | + } | | 38 | + }); | | 39 | +} | | 40 | + | | 41 | +function undestroy() { | | 42 | + if (this._readableState) { | | 43 | + this._readableState.destroyed = false; | | 44 | + this._readableState.reading = false; | | 45 | + this._readableState.ended = false; | | 46 | + this._readableState.endEmitted = false; | | 47 | + } | | 48 | + | | 49 | + if (this._writableState) { | | 50 | + this._writableState.destroyed = false; | | 51 | + this._writableState.ended = false; | | 52 | + this._writableState.ending = false; | | 53 | + this._writableState.finished = false; | | 54 | + this._writableState.errorEmitted = false; | | 55 | + } | | 56 | +} | | 57 | + | | 58 | +function emitErrorNT(self, err) { | | 59 | + self.emit('error', err); | | 60 | +} | | 61 | + | | 62 | +module.exports = { | | 63 | + destroy, | | 64 | + undestroy | | 65 | +}; |
|