diff --git a/benchmark/net-pipe.js b/benchmark/net-pipe.js index e678c2a9f2..0890e292eb 100644 --- a/benchmark/net-pipe.js +++ b/benchmark/net-pipe.js @@ -27,7 +27,7 @@ Writer.prototype.write = function(chunk, encoding, cb) { // doesn't matter, never emits anything. Writer.prototype.on = function() {}; - +Writer.prototype.once = function() {}; Writer.prototype.emit = function() {}; var statCounter = 0; diff --git a/doc/api/crypto.markdown b/doc/api/crypto.markdown index 4a5735d36d..de574bc37e 100644 --- a/doc/api/crypto.markdown +++ b/doc/api/crypto.markdown @@ -89,6 +89,11 @@ Example: this program that takes the sha1 sum of a file The class for creating hash digests of data. +It is a [stream](stream.html) that is both readable and writable. The +written data is used to compute the hash. Once the writable side of +the stream is ended, use the `read()` method to get the computed hash +digest. The legacy `update` and `digest` methods are also supported. + Returned by `crypto.createHash`. ### hash.update(data, [input_encoding]) @@ -114,6 +119,11 @@ called. Creates and returns a hmac object, a cryptographic hmac with the given algorithm and key. +It is a [stream](stream.html) that is both readable and writable. The +written data is used to compute the hmac. Once the writable side of +the stream is ended, use the `read()` method to get the computed +digest. The legacy `update` and `digest` methods are also supported. + `algorithm` is dependent on the available algorithms supported by OpenSSL - see createHash above. `key` is the hmac key to be used. @@ -148,6 +158,11 @@ recent releases, `openssl list-cipher-algorithms` will display the available cipher algorithms. `password` is used to derive key and IV, which must be a `'binary'` encoded string or a [buffer](buffer.html). +It is a [stream](stream.html) that is both readable and writable. The +written data is used to compute the hash. Once the writable side of +the stream is ended, use the `read()` method to get the computed hash +digest. The legacy `update` and `digest` methods are also supported. + ## crypto.createCipheriv(algorithm, key, iv) Creates and returns a cipher object, with the given algorithm, key and @@ -166,6 +181,11 @@ Class for encrypting data. Returned by `crypto.createCipher` and `crypto.createCipheriv`. +Cipher objects are [streams](stream.html) that are both readable and +writable. The written plain text data is used to produce the +encrypted data on the the readable side. The legacy `update` and +`final` methods are also supported. + ### cipher.update(data, [input_encoding], [output_encoding]) Updates the cipher with `data`, the encoding of which is given in @@ -213,6 +233,11 @@ Class for decrypting data. Returned by `crypto.createDecipher` and `crypto.createDecipheriv`. +Decipher objects are [streams](stream.html) that are both readable and +writable. The written enciphered data is used to produce the +plain-text data on the the readable side. The legacy `update` and +`final` methods are also supported. + ### decipher.update(data, [input_encoding], [output_encoding]) Updates the decipher with `data`, which is encoded in `'binary'`, @@ -246,28 +271,33 @@ Creates and returns a signing object, with the given algorithm. On recent OpenSSL releases, `openssl list-public-key-algorithms` will display the available signing algorithms. Examples are `'RSA-SHA256'`. -## Class: Signer +## Class: Sign Class for generating signatures. Returned by `crypto.createSign`. -### signer.update(data) +Sign objects are writable [streams](stream.html). The written data is +used to generate the signature. Once all of the data has been +written, the `sign` method will return the signature. The legacy +`update` method is also supported. + +### sign.update(data) -Updates the signer object with data. This can be called many times +Updates the sign object with data. This can be called many times with new data as it is streamed. -### signer.sign(private_key, [output_format]) +### sign.sign(private_key, [output_format]) Calculates the signature on all the updated data passed through the -signer. `private_key` is a string containing the PEM encoded private +sign. `private_key` is a string containing the PEM encoded private key for signing. Returns the signature in `output_format` which can be `'binary'`, `'hex'` or `'base64'`. If no encoding is provided, then a buffer is returned. -Note: `signer` object can not be used after `sign()` method been +Note: `sign` object can not be used after `sign()` method been called. ## crypto.createVerify(algorithm) @@ -281,6 +311,12 @@ Class for verifying signatures. Returned by `crypto.createVerify`. +Verify objects are writable [streams](stream.html). The written data +is used to validate against the supplied signature. Once all of the +data has been written, the `verify` method will return true if the +supplied signature is valid. The legacy `update` method is also +supported. + ### verifier.update(data) Updates the verifier object with data. This can be called many times @@ -469,9 +505,6 @@ default, set the `crypto.DEFAULT_ENCODING` field to 'binary'. Note that new programs will probably expect buffers, so only use this as a temporary measure. -Also, a Streaming API will be provided, but this will be done in such -a way as to preserve the legacy API surface. - [createCipher()]: #crypto_crypto_createcipher_algorithm_password [createCipheriv()]: #crypto_crypto_createcipheriv_algorithm_key_iv diff --git a/doc/api/stream.markdown b/doc/api/stream.markdown index 5e0c6f642d..974c5dd22c 100644 --- a/doc/api/stream.markdown +++ b/doc/api/stream.markdown @@ -7,186 +7,485 @@ Node. For example a request to an HTTP server is a stream, as is stdout. Streams are readable, writable, or both. All streams are instances of [EventEmitter][] -You can load up the Stream base class by doing `require('stream')`. +You can load the Stream base classes by doing `require('stream')`. +There are base classes provided for Readable streams, Writable +streams, Duplex streams, and Transform streams. -## Readable Stream +## Compatibility + +In earlier versions of Node, the Readable stream interface was +simpler, but also less powerful and less useful. + +* Rather than waiting for you to call the `read()` method, `'data'` + events would start emitting immediately. If you needed to do some + I/O to decide how to handle data, then you had to store the chunks + in some kind of buffer so that they would not be lost. +* The `pause()` method was advisory, rather than guaranteed. This + meant that you still had to be prepared to receive `'data'` events + even when the stream was in a paused state. + +In Node v0.10, the Readable class described below was added. For +backwards compatibility with older Node programs, Readable streams +switch into "old mode" when a `'data'` event handler is added, or when +the `pause()` or `resume()` methods are called. The effect is that, +even if you are not using the new `read()` method and `'readable'` +event, you no longer have to worry about losing `'data'` chunks. + +Most programs will continue to function normally. However, this +introduces an edge case in the following conditions: + +* No `'data'` event handler is added. +* The `pause()` and `resume()` methods are never called. + +For example, consider the following code: + +```javascript +// WARNING! BROKEN! +net.createServer(function(socket) { + + // we add an 'end' method, but never consume the data + socket.on('end', function() { + // It will never get here. + socket.end('I got your message (but didnt read it)\n'); + }); + +}).listen(1337); +``` + +In versions of node prior to v0.10, the incoming message data would be +simply discarded. However, in Node v0.10 and beyond, the socket will +remain paused forever. + +The workaround in this situation is to call the `resume()` method to +trigger "old mode" behavior: + +```javascript +// Workaround +net.createServer(function(socket) { + + socket.on('end', function() { + socket.end('I got your message (but didnt read it)\n'); + }); + + // start the flow of data, discarding it. + socket.resume(); + +}).listen(1337); +``` + +In addition to new Readable streams switching into old-mode, pre-v0.10 +style streams can be wrapped in a Readable class using the `wrap()` +method. + +## Class: stream.Readable A `Readable Stream` has the following methods, members, and events. -### Event: 'data' +Note that `stream.Readable` is an abstract class designed to be +extended with an underlying implementation of the `_read(size, cb)` +method. (See below.) -`function (data) { }` +### new stream.Readable([options]) -The `'data'` event emits either a `Buffer` (by default) or a string if -`setEncoding()` was used. +* `options` {Object} + * `bufferSize` {Number} The size of the chunks to consume from the + underlying resource. Default=16kb + * `lowWaterMark` {Number} The minimum number of bytes to store in + the internal buffer before emitting `readable`. Default=0 + * `highWaterMark` {Number} The maximum number of bytes to store in + the internal buffer before ceasing to read from the underlying + resource. Default=16kb + * `encoding` {String} If specified, then buffers will be decoded to + strings using the specified encoding. Default=null -Note that the __data will be lost__ if there is no listener when a -`Readable Stream` emits a `'data'` event. +In classes that extend the Readable class, make sure to call the +constructor so that the buffering settings can be properly +initialized. -### Event: 'end' +### readable.\_read(size, callback) + +* `size` {Number} Number of bytes to read asynchronously +* `callback` {Function} Called with an error or with data + +All Readable stream implementations must provide a `_read` method +to fetch data from the underlying resource. + +**This function MUST NOT be called directly.** It should be +implemented by child classes, and called by the internal Readable +class methods only. + +Call the callback using the standard `callback(error, data)` pattern. +When no more data can be fetched, call `callback(null, null)` to +signal the EOF. -`function () { }` +This method is prefixed with an underscore because it is internal to +the class that defines it, and should not be called directly by user +programs. However, you **are** expected to override this method in +your own extension classes. + + +### readable.wrap(stream) + +* `stream` {Stream} An "old style" readable stream + +If you are using an older Node library that emits `'data'` events and +has a `pause()` method that is advisory only, then you can use the +`wrap()` method to create a Readable stream that uses the old stream +as its data source. + +For example: + +```javascript +var OldReader = require('./old-api-module.js').OldReader; +var oreader = new OldReader; +var Readable = require('stream').Readable; +var myReader = new Readable().wrap(oreader); + +myReader.on('readable', function() { + myReader.read(); // etc. +}); +``` + +### Event: 'readable' + +When there is data ready to be consumed, this event will fire. The +number of bytes that are required to be considered "readable" depends +on the `lowWaterMark` option set in the constructor. + +When this event emits, call the `read()` method to consume the data. + +### Event: 'end' Emitted when the stream has received an EOF (FIN in TCP terminology). Indicates that no more `'data'` events will happen. If the stream is also writable, it may be possible to continue writing. -### Event: 'error' +### Event: 'data' + +The `'data'` event emits either a `Buffer` (by default) or a string if +`setEncoding()` was used. + +Note that adding a `'data'` event listener will switch the Readable +stream into "old mode", where data is emitted as soon as it is +available, rather than waiting for you to call `read()` to consume it. -`function (exception) { }` +### Event: 'error' Emitted if there was an error receiving data. ### Event: 'close' -`function () { }` - Emitted when the underlying resource (for example, the backing file descriptor) has been closed. Not all streams will emit this. -### stream.readable - -A boolean that is `true` by default, but turns `false` after an -`'error'` occurred, the stream came to an `'end'`, or `destroy()` was -called. - -### stream.setEncoding([encoding]) +### readable.setEncoding(encoding) Makes the `'data'` event emit a string instead of a `Buffer`. `encoding` -can be `'utf8'`, `'utf16le'` (`'ucs2'`), `'ascii'`, or `'hex'`. Defaults -to `'utf8'`. - -### stream.pause() +can be `'utf8'`, `'utf16le'` (`'ucs2'`), `'ascii'`, or `'hex'`. -Issues an advisory signal to the underlying communication layer, -requesting that no further data be sent until `resume()` is called. +The encoding can also be set by specifying an `encoding` field to the +constructor. -Note that, due to the advisory nature, certain streams will not be -paused immediately, and so `'data'` events may be emitted for some -indeterminate period of time even after `pause()` is called. You may -wish to buffer such `'data'` events. +### readable.read([size]) -### stream.resume() +* `size` {Number | null} Optional number of bytes to read. +* Return: {Buffer | String | null} -Resumes the incoming `'data'` events after a `pause()`. +Call this method to consume data once the `'readable'` event is +emitted. -### stream.destroy() +The `size` argument will set a minimum number of bytes that you are +interested in. If not set, then the entire content of the internal +buffer is returned. -Closes the underlying file descriptor. Stream is no longer `writable` -nor `readable`. The stream will not emit any more 'data', or 'end' -events. Any queued write data will not be sent. The stream should emit -'close' event once its resources have been disposed of. +If there is no data to consume, or if there are fewer bytes in the +internal buffer than the `size` argument, then `null` is returned, and +a future `'readable'` event will be emitted when more is available. +Note that calling `stream.read(0)` will always return `null`, and will +trigger a refresh of the internal buffer, but otherwise be a no-op. -### stream.pipe(destination, [options]) +### readable.pipe(destination, [options]) -This is a `Stream.prototype` method available on all `Stream`s. +* `destination` {Writable Stream} +* `options` {Object} Optional + * `end` {Boolean} Default=true -Connects this read stream to `destination` WriteStream. Incoming data on -this stream gets written to `destination`. The destination and source -streams are kept in sync by pausing and resuming as necessary. +Connects this readable stream to `destination` WriteStream. Incoming +data on this stream gets written to `destination`. Properly manages +back-pressure so that a slow destination will not be overwhelmed by a +fast readable stream. This function returns the `destination` stream. -Emulating the Unix `cat` command: - - process.stdin.resume(); process.stdin.pipe(process.stdout); +For example, emulating the Unix `cat` command: + process.stdin.pipe(process.stdout); By default `end()` is called on the destination when the source stream emits `end`, so that `destination` is no longer writable. Pass `{ end: false }` as `options` to keep the destination stream open. -This keeps `process.stdout` open so that "Goodbye" can be written at the +This keeps `writer` open so that "Goodbye" can be written at the end. - process.stdin.resume(); + reader.pipe(writer, { end: false }); + reader.on("end", function() { + writer.end("Goodbye\n"); + }); + +Note that `process.stderr` and `process.stdout` are never closed until +the process exits, regardless of the specified options. + +### readable.unpipe([destination]) + +* `destination` {Writable Stream} Optional + +Undo a previously established `pipe()`. If no destination is +provided, then all previously established pipes are removed. + +### readable.pause() - process.stdin.pipe(process.stdout, { end: false }); +Switches the readable stream into "old mode", where data is emitted +using a `'data'` event rather than being buffered for consumption via +the `read()` method. - process.stdin.on("end", function() { - process.stdout.write("Goodbye\n"); }); +Ceases the flow of data. No `'data'` events are emitted while the +stream is in a paused state. +### readable.resume() -## Writable Stream +Switches the readable stream into "old mode", where data is emitted +using a `'data'` event rather than being buffered for consumption via +the `read()` method. + +Resumes the incoming `'data'` events after a `pause()`. + + +## Class: stream.Writable -A `Writable Stream` has the following methods, members, and events. +A `Writable` Stream has the following methods, members, and events. -### Event: 'drain' +Note that `stream.Writable` is an abstract class designed to be +extended with an underlying implementation of the `_write(chunk, cb)` +method. (See below.) -`function () { }` +### new stream.Writable([options]) -Emitted when the stream's write queue empties and it's safe to write without -buffering again. Listen for it when `stream.write()` returns `false`. +* `options` {Object} + * `highWaterMark` {Number} Buffer level when `write()` starts + returning false. Default=16kb + * `lowWaterMark` {Number} The buffer level when `'drain'` is + emitted. Default=0 + * `decodeStrings` {Boolean} Whether or not to decode strings into + Buffers before passing them to `_write()`. Default=true -The `'drain'` event can happen at *any* time, regardless of whether or not -`stream.write()` has previously returned `false`. To avoid receiving unwanted -`'drain'` events, listen using `stream.once()`. +In classes that extend the Writable class, make sure to call the +constructor so that the buffering settings can be properly +initialized. -### Event: 'error' +### writable.\_write(chunk, callback) -`function (exception) { }` +* `chunk` {Buffer | Array} The data to be written +* `callback` {Function} Called with an error, or null when finished -Emitted on error with the exception `exception`. +All Writable stream implementations must provide a `_write` method to +send data to the underlying resource. -### Event: 'close' +**This function MUST NOT be called directly.** It should be +implemented by child classes, and called by the internal Writable +class methods only. + +Call the callback using the standard `callback(error)` pattern to +signal that the write completed successfully or with an error. + +If the `decodeStrings` flag is set in the constructor options, then +`chunk` will be an array rather than a Buffer. This is to support +implementations that have an optimized handling for certain string +data encodings. + +This method is prefixed with an underscore because it is internal to +the class that defines it, and should not be called directly by user +programs. However, you **are** expected to override this method in +your own extension classes. + + +### writable.write(chunk, [encoding], [callback]) + +* `chunk` {Buffer | String} Data to be written +* `encoding` {String} Optional. If `chunk` is a string, then encoding + defaults to `'utf8'` +* `callback` {Function} Optional. Called when this chunk is + successfully written. +* Returns {Boolean} + +Writes `chunk` to the stream. Returns `true` if the data has been +flushed to the underlying resource. Returns `false` to indicate that +the buffer is full, and the data will be sent out in the future. The +`'drain'` event will indicate when the buffer is empty again. + +The specifics of when `write()` will return false, and when a +subsequent `'drain'` event will be emitted, are determined by the +`highWaterMark` and `lowWaterMark` options provided to the +constructor. + +### writable.end([chunk], [encoding]) + +* `chunk` {Buffer | String} Optional final data to be written +* `encoding` {String} Optional. If `chunk` is a string, then encoding + defaults to `'utf8'` + +Call this method to signal the end of the data being written to the +stream. -`function () { }` +### Event: 'drain' + +Emitted when the stream's write queue empties and it's safe to write +without buffering again. Listen for it when `stream.write()` returns +`false`. + +### Event: 'close' -Emitted when the underlying file descriptor has been closed. +Emitted when the underlying resource (for example, the backing file +descriptor) has been closed. Not all streams will emit this. ### Event: 'pipe' -`function (src) { }` +* `source` {Readable Stream} Emitted when the stream is passed to a readable stream's pipe method. -### stream.writable +### Event 'unpipe' + +* `source` {Readable Stream} + +Emitted when a previously established `pipe()` is removed using the +source Readable stream's `unpipe()` method. + +## Class: stream.Duplex + + + +A "duplex" stream is one that is both Readable and Writable, such as a +TCP socket connection. + +Note that `stream.Duplex` is an abstract class designed to be +extended with an underlying implementation of the `_read(size, cb)` +and `_write(chunk, callback)` methods as you would with a Readable or +Writable stream class. + +Since JavaScript doesn't have multiple prototypal inheritance, this +class prototypally inherits from Readable, and then parasitically from +Writable. It is thus up to the user to implement both the lowlevel +`_read(n,cb)` method as well as the lowlevel `_write(chunk,cb)` method +on extension duplex classes. + +### new stream.Duplex(options) + +* `options` {Object} Passed to both Writable and Readable + constructors. Also has the following fields: + * `allowHalfOpen` {Boolean} Default=true. If set to `false`, then + the stream will automatically end the readable side when the + writable side ends and vice versa. + +In classes that extend the Duplex class, make sure to call the +constructor so that the buffering settings can be properly +initialized. + +## Class: stream.Transform + +A "transform" stream is a duplex stream where the output is causally +connected in some way to the input, such as a zlib stream or a crypto +stream. + +There is no requirement that the output be the same size as the input, +the same number of chunks, or arrive at the same time. For example, a +Hash stream will only ever have a single chunk of output which is +provided when the input is ended. A zlib stream will either produce +much smaller or much larger than its input. + +Rather than implement the `_read()` and `_write()` methods, Transform +classes must implement the `_transform()` method, and may optionally +also implement the `_flush()` method. (See below.) + +### new stream.Transform([options]) + +* `options` {Object} Passed to both Writable and Readable + constructors. + +In classes that extend the Transform class, make sure to call the +constructor so that the buffering settings can be properly +initialized. + +### transform.\_transform(chunk, outputFn, callback) + +* `chunk` {Buffer} The chunk to be transformed. +* `outputFn` {Function} Call this function with any output data to be + passed to the readable interface. +* `callback` {Function} Call this function (optionally with an error + argument) when you are done processing the supplied chunk. -A boolean that is `true` by default, but turns `false` after an -`'error'` occurred or `end()` / `destroy()` was called. +All Transform stream implementations must provide a `_transform` +method to accept input and produce output. -### stream.write(string, [encoding]) +**This function MUST NOT be called directly.** It should be +implemented by child classes, and called by the internal Transform +class methods only. -Writes `string` with the given `encoding` to the stream. Returns `true` -if the string has been flushed to the kernel buffer. Returns `false` to -indicate that the kernel buffer is full, and the data will be sent out -in the future. The `'drain'` event will indicate when the kernel buffer -is empty again. The `encoding` defaults to `'utf8'`. +`_transform` should do whatever has to be done in this specific +Transform class, to handle the bytes being written, and pass them off +to the readable portion of the interface. Do asynchronous I/O, +process things, and so on. -### stream.write(buffer) +Call the callback function only when the current chunk is completely +consumed. Note that this may mean that you call the `outputFn` zero +or more times, depending on how much data you want to output as a +result of this chunk. -Same as the above except with a raw buffer. +This method is prefixed with an underscore because it is internal to +the class that defines it, and should not be called directly by user +programs. However, you **are** expected to override this method in +your own extension classes. -### stream.end() +### transform.\_flush(outputFn, callback) -Terminates the stream with EOF or FIN. This call will allow queued -write data to be sent before closing the stream. +* `outputFn` {Function} Call this function with any output data to be + passed to the readable interface. +* `callback` {Function} Call this function (optionally with an error + argument) when you are done flushing any remaining data. -### stream.end(string, encoding) +**This function MUST NOT be called directly.** It MAY be implemented +by child classes, and if so, will be called by the internal Transform +class methods only. -Sends `string` with the given `encoding` and terminates the stream with -EOF or FIN. This is useful to reduce the number of packets sent. +In some cases, your transform operation may need to emit a bit more +data at the end of the stream. For example, a `Zlib` compression +stream will store up some internal state so that it can optimally +compress the output. At the end, however, it needs to do the best it +can with what is left, so that the data will be complete. -### stream.end(buffer) +In those cases, you can implement a `_flush` method, which will be +called at the very end, after all the written data is consumed, but +before emitting `end` to signal the end of the readable side. Just +like with `_transform`, call `outputFn` zero or more times, as +appropriate, and call `callback` when the flush operation is complete. -Same as above but with a `buffer`. +This method is prefixed with an underscore because it is internal to +the class that defines it, and should not be called directly by user +programs. However, you **are** expected to override this method in +your own extension classes. -### stream.destroy() -Closes the underlying file descriptor. Stream is no longer `writable` -nor `readable`. The stream will not emit any more 'data', or 'end' -events. Any queued write data will not be sent. The stream should emit -'close' event once its resources have been disposed of. +## Class: stream.PassThrough -### stream.destroySoon() +This is a trivial implementation of a `Transform` stream that simply +passes the input bytes across to the output. Its purpose is mainly +for examples and testing, but there are occasionally use cases where +it can come in handy. -After the write queue is drained, close the file descriptor. -`destroySoon()` can still destroy straight away, as long as there is no -data left in the queue for writes. [EventEmitter]: events.html#events_class_events_eventemitter diff --git a/lib/_debugger.js b/lib/_debugger.js index 19c26aa943..239220962d 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -36,7 +36,7 @@ exports.start = function(argv, stdin, stdout) { } // Setup input/output streams - stdin = stdin || process.openStdin(); + stdin = stdin || process.stdin; stdout = stdout || process.stdout; var args = ['--debug-brk'].concat(argv), diff --git a/lib/_stream_duplex.js b/lib/_stream_duplex.js new file mode 100644 index 0000000000..ef74c34c7e --- /dev/null +++ b/lib/_stream_duplex.js @@ -0,0 +1,63 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// a duplex stream is just a stream that is both readable and writable. +// Since JS doesn't have multiple prototypal inheritance, this class +// prototypally inherits from Readable, and then parasitically from +// Writable. + +module.exports = Duplex; +var util = require('util'); +var Readable = require('_stream_readable'); +var Writable = require('_stream_writable'); + +util.inherits(Duplex, Readable); + +Object.keys(Writable.prototype).forEach(function(method) { + if (!Duplex.prototype[method]) + Duplex.prototype[method] = Writable.prototype[method]; +}); + +function Duplex(options) { + if (!(this instanceof Duplex)) + return new Duplex(options); + + Readable.call(this, options); + Writable.call(this, options); + + this.allowHalfOpen = true; + if (options && options.allowHalfOpen === false) + this.allowHalfOpen = false; + + this.once('end', onend); +} + +// the no-half-open enforcer +function onend() { + // if we allow half-open state, or if the writable side ended, + // then we're ok. + if (this.allowHalfOpen || this._writableState.ended) + return; + + // no more data can be written. + // But allow more writes to happen in this tick. + process.nextTick(this.end.bind(this)); +} diff --git a/test/simple/test-zlib-destroy.js b/lib/_stream_passthrough.js similarity index 69% rename from test/simple/test-zlib-destroy.js rename to lib/_stream_passthrough.js index 7a1120e284..0f2fe14c78 100644 --- a/test/simple/test-zlib-destroy.js +++ b/lib/_stream_passthrough.js @@ -19,18 +19,23 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -var common = require('../common'); -var assert = require('assert'); -var zlib = require('zlib'); +// a passthrough stream. +// basically just the most minimal sort of Transform stream. +// Every written chunk gets output as-is. -['Deflate', 'Inflate', 'Gzip', 'Gunzip', 'DeflateRaw', 'InflateRaw', 'Unzip'] - .forEach(function (name) { - var a = false; - var zStream = new zlib[name](); - zStream.on('close', function () { - a = true; - }); - zStream.destroy(); +module.exports = PassThrough; - assert.equal(a, true, name+'#destroy() must emit \'close\''); - }); +var Transform = require('_stream_transform'); +var util = require('util'); +util.inherits(PassThrough, Transform); + +function PassThrough(options) { + if (!(this instanceof PassThrough)) + return new PassThrough(options); + + Transform.call(this, options); +} + +PassThrough.prototype._transform = function(chunk, output, cb) { + cb(null, chunk); +}; diff --git a/lib/_stream_readable.js b/lib/_stream_readable.js new file mode 100644 index 0000000000..9baa2e6bfa --- /dev/null +++ b/lib/_stream_readable.js @@ -0,0 +1,752 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +module.exports = Readable; +Readable.ReadableState = ReadableState; + +var Stream = require('stream'); +var util = require('util'); +var assert = require('assert'); +var StringDecoder; + +util.inherits(Readable, Stream); + +function ReadableState(options, stream) { + options = options || {}; + + // the argument passed to this._read(n,cb) + this.bufferSize = options.hasOwnProperty('bufferSize') ? + options.bufferSize : 16 * 1024; + + // the point at which it stops calling _read() to fill the buffer + this.highWaterMark = options.hasOwnProperty('highWaterMark') ? + options.highWaterMark : 16 * 1024; + + // the minimum number of bytes to buffer before emitting 'readable' + // default to pushing everything out as fast as possible. + this.lowWaterMark = options.hasOwnProperty('lowWaterMark') ? + options.lowWaterMark : 0; + + // cast to ints. + assert(typeof this.bufferSize === 'number'); + assert(typeof this.lowWaterMark === 'number'); + assert(typeof this.highWaterMark === 'number'); + this.bufferSize = ~~this.bufferSize; + this.lowWaterMark = ~~this.lowWaterMark; + this.highWaterMark = ~~this.highWaterMark; + assert(this.bufferSize >= 0); + assert(this.lowWaterMark >= 0); + assert(this.highWaterMark >= this.lowWaterMark, + this.highWaterMark + '>=' + this.lowWaterMark); + + this.buffer = []; + this.length = 0; + this.pipes = null; + this.pipesCount = 0; + this.flowing = false; + this.ended = false; + this.endEmitted = false; + this.reading = false; + this.sync = false; + this.onread = function(er, data) { + onread(stream, er, data); + }; + + // whenever we return null, then we set a flag to say + // that we're awaiting a 'readable' event emission. + this.needReadable = false; + this.emittedReadable = false; + + // when piping, we only care about 'readable' events that happen + // after read()ing all the bytes and not getting any pushback. + this.ranOut = false; + + // the number of writers that are awaiting a drain event in .pipe()s + this.awaitDrain = 0; + this.pipeChunkSize = null; + + this.decoder = null; + if (options.encoding) { + if (!StringDecoder) + StringDecoder = require('string_decoder').StringDecoder; + this.decoder = new StringDecoder(options.encoding); + } +} + +function Readable(options) { + if (!(this instanceof Readable)) + return new Readable(options); + + this._readableState = new ReadableState(options, this); + + // legacy + this.readable = true; + + Stream.apply(this); +} + +// backwards compatibility. +Readable.prototype.setEncoding = function(enc) { + if (!StringDecoder) + StringDecoder = require('string_decoder').StringDecoder; + this._readableState.decoder = new StringDecoder(enc); +}; + + +function howMuchToRead(n, state) { + if (state.length === 0 && state.ended) + return 0; + + if (isNaN(n) || n === null) + return state.length; + + if (n <= 0) + return 0; + + // don't have that much. return null, unless we've ended. + if (n > state.length) { + if (!state.ended) { + state.needReadable = true; + return 0; + } else + return state.length; + } + + return n; +} + +// you can override either this method, or _read(n, cb) below. +Readable.prototype.read = function(n) { + var state = this._readableState; + var nOrig = n; + + if (typeof n !== 'number' || n > 0) + state.emittedReadable = false; + + n = howMuchToRead(n, state); + + // if we've ended, and we're now clear, then finish it up. + if (n === 0 && state.ended) { + endReadable(this); + return null; + } + + // All the actual chunk generation logic needs to be + // *below* the call to _read. The reason is that in certain + // synthetic stream cases, such as passthrough streams, _read + // may be a completely synchronous operation which may change + // the state of the read buffer, providing enough data when + // before there was *not* enough. + // + // So, the steps are: + // 1. Figure out what the state of things will be after we do + // a read from the buffer. + // + // 2. If that resulting state will trigger a _read, then call _read. + // Note that this may be asynchronous, or synchronous. Yes, it is + // deeply ugly to write APIs this way, but that still doesn't mean + // that the Readable class should behave improperly, as streams are + // designed to be sync/async agnostic. + // Take note if the _read call is sync or async (ie, if the read call + // has returned yet), so that we know whether or not it's safe to emit + // 'readable' etc. + // + // 3. Actually pull the requested chunks out of the buffer and return. + + // if we need a readable event, then we need to do some reading. + var doRead = state.needReadable; + + // if we currently have less than the highWaterMark, then also read some + if (state.length - n <= state.highWaterMark) + doRead = true; + + // however, if we've ended, then there's no point, and if we're already + // reading, then it's unnecessary. + if (state.ended || state.reading) + doRead = false; + + if (doRead) { + state.reading = true; + state.sync = true; + // if the length is currently zero, then we *need* a readable event. + if (state.length === 0) + state.needReadable = true; + // call internal read method + this._read(state.bufferSize, state.onread); + state.sync = false; + } + + // If _read called its callback synchronously, then `reading` + // will be false, and we need to re-evaluate how much data we + // can return to the user. + if (doRead && !state.reading) + n = howMuchToRead(nOrig, state); + + var ret; + if (n > 0) + ret = fromList(n, state.buffer, state.length, !!state.decoder); + else + ret = null; + + if (ret === null || ret.length === 0) { + state.needReadable = true; + n = 0; + } + + state.length -= n; + + // If we have nothing in the buffer, then we want to know + // as soon as we *do* get something into the buffer. + if (state.length === 0 && !state.ended) + state.needReadable = true; + + return ret; +}; + +function onread(stream, er, chunk) { + var state = stream._readableState; + var sync = state.sync; + + state.reading = false; + if (er) + return stream.emit('error', er); + + if (!chunk || !chunk.length) { + // eof + state.ended = true; + if (state.decoder) { + chunk = state.decoder.end(); + if (chunk && chunk.length) { + state.buffer.push(chunk); + state.length += chunk.length; + } + } + // if we've ended and we have some data left, then emit + // 'readable' now to make sure it gets picked up. + if (!sync) { + if (state.length > 0) { + state.needReadable = false; + if (!state.emittedReadable) { + state.emittedReadable = true; + stream.emit('readable'); + } + } else + endReadable(stream); + } + return; + } + + if (state.decoder) + chunk = state.decoder.write(chunk); + + // update the buffer info. + if (chunk) { + state.length += chunk.length; + state.buffer.push(chunk); + } + + // if we haven't gotten enough to pass the lowWaterMark, + // and we haven't ended, then don't bother telling the user + // that it's time to read more data. Otherwise, emitting 'readable' + // probably will trigger another stream.read(), which can trigger + // another _read(n,cb) before this one returns! + if (state.length <= state.lowWaterMark) { + state.reading = true; + stream._read(state.bufferSize, state.onread); + return; + } + + if (state.needReadable && !sync) { + state.needReadable = false; + if (!state.emittedReadable) { + state.emittedReadable = true; + stream.emit('readable'); + } + } +} + +// abstract method. to be overridden in specific implementation classes. +// call cb(er, data) where data is <= n in length. +// for virtual (non-string, non-buffer) streams, "length" is somewhat +// arbitrary, and perhaps not very meaningful. +Readable.prototype._read = function(n, cb) { + process.nextTick(function() { + cb(new Error('not implemented')); + }); +}; + +Readable.prototype.pipe = function(dest, pipeOpts) { + var src = this; + var state = this._readableState; + + switch (state.pipesCount) { + case 0: + state.pipes = dest; + break; + case 1: + state.pipes = [state.pipes, dest]; + break; + default: + state.pipes.push(dest); + break; + } + state.pipesCount += 1; + + if ((!pipeOpts || pipeOpts.end !== false) && + dest !== process.stdout && + dest !== process.stderr) { + src.once('end', onend); + dest.on('unpipe', function(readable) { + if (readable === src) + src.removeListener('end', onend); + }); + } + + if (pipeOpts && pipeOpts.chunkSize) + state.pipeChunkSize = pipeOpts.chunkSize; + + function onend() { + dest.end(); + } + + // when the dest drains, it reduces the awaitDrain counter + // on the source. This would be more elegant with a .once() + // handler in flow(), but adding and removing repeatedly is + // too slow. + var ondrain = pipeOnDrain(src); + dest.on('drain', ondrain); + dest.on('unpipe', function(readable) { + if (readable === src) + dest.removeListener('drain', ondrain); + + // if the reader is waiting for a drain event from this + // specific writer, then it would cause it to never start + // flowing again. + // So, if this is awaiting a drain, then we just call it now. + // If we don't know, then assume that we are waiting for one. + if (!dest._writableState || dest._writableState.needDrain) + ondrain(); + }); + + // if the dest has an error, then stop piping into it. + // however, don't suppress the throwing behavior for this. + dest.once('error', function(er) { + unpipe(); + if (dest.listeners('error').length === 0) + dest.emit('error', er); + }); + + // if the dest emits close, then presumably there's no point writing + // to it any more. + dest.on('close', unpipe); + dest.on('finish', function() { + dest.removeListener('close', unpipe); + }); + + function unpipe() { + src.unpipe(dest); + } + + // tell the dest that it's being piped to + dest.emit('pipe', src); + + // start the flow if it hasn't been started already. + if (!state.flowing) { + // the handler that waits for readable events after all + // the data gets sucked out in flow. + // This would be easier to follow with a .once() handler + // in flow(), but that is too slow. + this.on('readable', pipeOnReadable); + + state.flowing = true; + process.nextTick(function() { + flow(src); + }); + } + + return dest; +}; + +function pipeOnDrain(src) { + return function() { + var dest = this; + var state = src._readableState; + state.awaitDrain--; + if (state.awaitDrain === 0) + flow(src); + }; +} + +function flow(src) { + var state = src._readableState; + var chunk; + state.awaitDrain = 0; + + function write(dest, i, list) { + var written = dest.write(chunk); + if (false === written) { + state.awaitDrain++; + } + } + + while (state.pipesCount && + null !== (chunk = src.read(state.pipeChunkSize))) { + + if (state.pipesCount === 1) + write(state.pipes, 0, null); + else + state.pipes.forEach(write); + + src.emit('data', chunk); + + // if anyone needs a drain, then we have to wait for that. + if (state.awaitDrain > 0) + return; + } + + // if every destination was unpiped, either before entering this + // function, or in the while loop, then stop flowing. + // + // NB: This is a pretty rare edge case. + if (state.pipesCount === 0) { + state.flowing = false; + + // if there were data event listeners added, then switch to old mode. + if (src.listeners('data').length) + emitDataEvents(src); + return; + } + + // at this point, no one needed a drain, so we just ran out of data + // on the next readable event, start it over again. + state.ranOut = true; +} + +function pipeOnReadable() { + if (this._readableState.ranOut) { + this._readableState.ranOut = false; + flow(this); + } +} + + +Readable.prototype.unpipe = function(dest) { + var state = this._readableState; + + // if we're not piping anywhere, then do nothing. + if (state.pipesCount === 0) + return this; + + // just one destination. most common case. + if (state.pipesCount === 1) { + // passed in one, but it's not the right one. + if (dest && dest !== state.pipes) + return this; + + if (!dest) + dest = state.pipes; + + // got a match. + state.pipes = null; + state.pipesCount = 0; + this.removeListener('readable', pipeOnReadable); + if (dest) + dest.emit('unpipe', this); + return this; + } + + // slow case. multiple pipe destinations. + + if (!dest) { + // remove all. + var dests = state.pipes; + var len = state.pipesCount; + state.pipes = null; + state.pipesCount = 0; + this.removeListener('readable', pipeOnReadable); + + for (var i = 0; i < len; i++) + dests[i].emit('unpipe', this); + return this; + } + + // try to find the right one. + var i = state.pipes.indexOf(dest); + if (i === -1) + return this; + + state.pipes.splice(i, 1); + state.pipesCount -= 1; + if (state.pipesCount === 1) + state.pipes = state.pipes[0]; + + dest.emit('unpipe', this); + + return this; +}; + +// kludge for on('data', fn) consumers. Sad. +// This is *not* part of the new readable stream interface. +// It is an ugly unfortunate mess of history. +Readable.prototype.on = function(ev, fn) { + // https://github.com/isaacs/readable-stream/issues/16 + // if we're already flowing, then no need to set up data events. + if (ev === 'data' && !this._readableState.flowing) + emitDataEvents(this); + + return Stream.prototype.on.call(this, ev, fn); +}; +Readable.prototype.addListener = Readable.prototype.on; + +// pause() and resume() are remnants of the legacy readable stream API +// If the user uses them, then switch into old mode. +Readable.prototype.resume = function() { + emitDataEvents(this); + this.read(0); + this.emit('resume'); +}; + +Readable.prototype.pause = function() { + emitDataEvents(this, true); + this.emit('pause'); +}; + +function emitDataEvents(stream, startPaused) { + var state = stream._readableState; + + if (state.flowing) { + // https://github.com/isaacs/readable-stream/issues/16 + throw new Error('Cannot switch to old mode now.'); + } + + var paused = startPaused || false; + var readable = false; + + // convert to an old-style stream. + stream.readable = true; + stream.pipe = Stream.prototype.pipe; + stream.on = stream.addEventListener = Stream.prototype.on; + + stream.on('readable', function() { + readable = true; + + var c; + while (!paused && (null !== (c = stream.read()))) + stream.emit('data', c); + + if (c === null) { + readable = false; + stream._readableState.needReadable = true; + } + }); + + stream.pause = function() { + paused = true; + this.emit('pause'); + }; + + stream.resume = function() { + paused = false; + if (readable) + process.nextTick(function() { + stream.emit('readable'); + }); + else + this.read(0); + this.emit('resume'); + }; + + // now make it start, just in case it hadn't already. + stream.emit('readable'); +} + +// wrap an old-style stream as the async data source. +// This is *not* part of the readable stream interface. +// It is an ugly unfortunate mess of history. +Readable.prototype.wrap = function(stream) { + var state = this._readableState; + var paused = false; + + var self = this; + stream.on('end', function() { + state.ended = true; + if (state.decoder) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) { + state.buffer.push(chunk); + state.length += chunk.length; + } + } + + if (state.length > 0) + self.emit('readable'); + else + endReadable(self); + }); + + stream.on('data', function(chunk) { + if (state.decoder) + chunk = state.decoder.write(chunk); + if (!chunk || !chunk.length) + return; + + state.buffer.push(chunk); + state.length += chunk.length; + self.emit('readable'); + + // if not consumed, then pause the stream. + if (state.length > state.lowWaterMark && !paused) { + paused = true; + stream.pause(); + } + }); + + // proxy all the other methods. + // important when wrapping filters and duplexes. + for (var i in stream) { + if (typeof stream[i] === 'function' && + typeof this[i] === 'undefined') { + this[i] = function(method) { return function() { + return stream[method].apply(stream, arguments); + }}(i); + } + } + + // proxy certain important events. + var events = ['error', 'close', 'destroy', 'pause', 'resume']; + events.forEach(function(ev) { + stream.on(ev, self.emit.bind(self, ev)); + }); + + // consume some bytes. if not all is consumed, then + // pause the underlying stream. + this.read = function(n) { + if (state.length === 0) { + state.needReadable = true; + return null; + } + + if (isNaN(n) || n <= 0) + n = state.length; + + if (n > state.length) { + if (!state.ended) { + state.needReadable = true; + return null; + } else + n = state.length; + } + + var ret = fromList(n, state.buffer, state.length, !!state.decoder); + state.length -= n; + + if (state.length === 0 && !state.ended) + state.needReadable = true; + + if (state.length <= state.lowWaterMark && paused) { + stream.resume(); + paused = false; + } + + if (state.length === 0 && state.ended) + endReadable(this); + + return ret; + }; +}; + + + +// exposed for testing purposes only. +Readable._fromList = fromList; + +// Pluck off n bytes from an array of buffers. +// Length is the combined lengths of all the buffers in the list. +function fromList(n, list, length, stringMode) { + var ret; + + // nothing in the list, definitely empty. + if (list.length === 0) { + return null; + } + + if (length === 0) + ret = null; + else if (!n || n >= length) { + // read it all, truncate the array. + if (stringMode) + ret = list.join(''); + else + ret = Buffer.concat(list, length); + list.length = 0; + } else { + // read just some of it. + if (n < list[0].length) { + // just take a part of the first list item. + // slice is the same for buffers and strings. + var buf = list[0]; + ret = buf.slice(0, n); + list[0] = buf.slice(n); + } else if (n === list[0].length) { + // first list is a perfect match + ret = list.shift(); + } else { + // complex case. + // we have enough to cover it, but it spans past the first buffer. + if (stringMode) + ret = ''; + else + ret = new Buffer(n); + + var c = 0; + for (var i = 0, l = list.length; i < l && c < n; i++) { + var buf = list[0]; + var cpy = Math.min(n - c, buf.length); + + if (stringMode) + ret += buf.slice(0, cpy); + else + buf.copy(ret, c, 0, cpy); + + if (cpy < buf.length) + list[0] = buf.slice(cpy); + else + list.shift(); + + c += cpy; + } + } + } + + return ret; +} + +function endReadable(stream) { + var state = stream._readableState; + if (state.endEmitted) + return; + state.ended = true; + state.endEmitted = true; + process.nextTick(function() { + stream.readable = false; + stream.emit('end'); + }); +} diff --git a/lib/_stream_transform.js b/lib/_stream_transform.js new file mode 100644 index 0000000000..b0819de29a --- /dev/null +++ b/lib/_stream_transform.js @@ -0,0 +1,231 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + + +// a transform stream is a readable/writable stream where you do +// something with the data. Sometimes it's called a "filter", +// but that's not a great name for it, since that implies a thing where +// some bits pass through, and others are simply ignored. (That would +// be a valid example of a transform, of course.) +// +// While the output is causally related to the input, it's not a +// necessarily symmetric or synchronous transformation. For example, +// a zlib stream might take multiple plain-text writes(), and then +// emit a single compressed chunk some time in the future. +// +// Here's how this works: +// +// The Transform stream has all the aspects of the readable and writable +// stream classes. When you write(chunk), that calls _write(chunk,cb) +// internally, and returns false if there's a lot of pending writes +// buffered up. When you call read(), that calls _read(n,cb) until +// there's enough pending readable data buffered up. +// +// In a transform stream, the written data is placed in a buffer. When +// _read(n,cb) is called, it transforms the queued up data, calling the +// buffered _write cb's as it consumes chunks. If consuming a single +// written chunk would result in multiple output chunks, then the first +// outputted bit calls the readcb, and subsequent chunks just go into +// the read buffer, and will cause it to emit 'readable' if necessary. +// +// This way, back-pressure is actually determined by the reading side, +// since _read has to be called to start processing a new chunk. However, +// a pathological inflate type of transform can cause excessive buffering +// here. For example, imagine a stream where every byte of input is +// interpreted as an integer from 0-255, and then results in that many +// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in +// 1kb of data being output. In this case, you could write a very small +// amount of input, and end up with a very large amount of output. In +// such a pathological inflating mechanism, there'd be no way to tell +// the system to stop doing the transform. A single 4MB write could +// cause the system to run out of memory. +// +// However, even in such a pathological case, only a single written chunk +// would be consumed, and then the rest would wait (un-transformed) until +// the results of the previous transformed chunk were consumed. Because +// the transform happens on-demand, it will only transform as much as is +// necessary to fill the readable buffer to the specified lowWaterMark. + +module.exports = Transform; + +var Duplex = require('_stream_duplex'); +var util = require('util'); +util.inherits(Transform, Duplex); + +function TransformState(stream) { + this.buffer = []; + this.transforming = false; + this.pendingReadCb = null; + this.output = function(chunk) { + stream._output(chunk); + }; +} + +function Transform(options) { + if (!(this instanceof Transform)) + return new Transform(options); + + Duplex.call(this, options); + + // bind output so that it can be passed around as a regular function. + var stream = this; + + // the queue of _write chunks that are pending being transformed + var ts = this._transformState = new TransformState(stream); + + // when the writable side finishes, then flush out anything remaining. + this.once('finish', function() { + if ('function' === typeof this._flush) + this._flush(ts.output, function(er) { + done(stream, er); + }); + else + done(stream); + }); +} + +// This is the part where you do stuff! +// override this function in implementation classes. +// 'chunk' is an input chunk. +// +// Call `output(newChunk)` to pass along transformed output +// to the readable side. You may call 'output' zero or more times. +// +// Call `cb(err)` when you are done with this chunk. If you pass +// an error, then that'll put the hurt on the whole operation. If you +// never call cb(), then you'll never get another chunk. +Transform.prototype._transform = function(chunk, output, cb) { + throw new Error('not implemented'); +}; + +Transform.prototype._write = function(chunk, cb) { + var ts = this._transformState; + var rs = this._readableState; + ts.buffer.push([chunk, cb]); + + // no need for auto-pull if already in the midst of one. + if (ts.transforming) + return; + + // now we have something to transform, if we were waiting for it. + // kick off a _read to pull it in. + if (ts.pendingReadCb) { + var readcb = ts.pendingReadCb; + ts.pendingReadCb = null; + this._read(0, readcb); + } + + // if we weren't waiting for it, but nothing is queued up, then + // still kick off a transform, just so it's there when the user asks. + var doRead = rs.needReadable || rs.length <= rs.highWaterMark; + if (doRead && !rs.reading) { + var ret = this.read(0); + if (ret !== null) + return cb(new Error('invalid stream transform state')); + } +}; + +Transform.prototype._read = function(n, readcb) { + var ws = this._writableState; + var rs = this._readableState; + var ts = this._transformState; + + if (ts.pendingReadCb) + throw new Error('_read while _read already in progress'); + + ts.pendingReadCb = readcb; + + // if there's nothing pending, then we just wait. + // if we're already transforming, then also just hold on a sec. + // we've already stashed the readcb, so we can come back later + // when we have something to transform + if (ts.buffer.length === 0 || ts.transforming) + return; + + // go ahead and transform that thing, now that someone wants it + var req = ts.buffer.shift(); + var chunk = req[0]; + var writecb = req[1]; + ts.transforming = true; + this._transform(chunk, ts.output, function(er, data) { + ts.transforming = false; + if (data) + ts.output(data); + writecb(er); + }); +}; + +Transform.prototype._output = function(chunk) { + if (!chunk || !chunk.length) + return; + + // if we've got a pending readcb, then just call that, + // and let Readable take care of it. If not, then we fill + // the readable buffer ourselves, and emit whatever's needed. + var ts = this._transformState; + var readcb = ts.pendingReadCb; + if (readcb) { + ts.pendingReadCb = null; + readcb(null, chunk); + return; + } + + // otherwise, it's up to us to fill the rs buffer. + var rs = this._readableState; + var len = rs.length; + rs.buffer.push(chunk); + rs.length += chunk.length; + if (rs.needReadable) { + rs.needReadable = false; + this.emit('readable'); + } +}; + +function done(stream, er) { + if (er) + return stream.emit('error', er); + + // if there's nothing in the write buffer, then that means + // that nothing more will ever be provided + var ws = stream._writableState; + var rs = stream._readableState; + var ts = stream._transformState; + + if (ws.length) + throw new Error('calling transform done when ws.length != 0'); + + if (ts.transforming) + throw new Error('calling transform done when still transforming'); + + // if we were waiting on a read, let them know that it isn't coming. + var readcb = ts.pendingReadCb; + if (readcb) + return readcb(); + + rs.ended = true; + // we may have gotten a 'null' read before, and since there is + // no more data coming from the writable side, we need to emit + // now so that the consumer knows to pick up the tail bits. + if (rs.length && rs.needReadable) + stream.emit('readable'); + else if (rs.length === 0) + stream.emit('end'); +} diff --git a/lib/_stream_writable.js b/lib/_stream_writable.js new file mode 100644 index 0000000000..619603fcad --- /dev/null +++ b/lib/_stream_writable.js @@ -0,0 +1,257 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// A bit simpler than readable streams. +// Implement an async ._write(chunk, cb), and it'll handle all +// the drain event emission and buffering. + +module.exports = Writable; +Writable.WritableState = WritableState; + +var util = require('util'); +var assert = require('assert'); +var Stream = require('stream'); + +util.inherits(Writable, Stream); + +function WritableState(options, stream) { + options = options || {}; + + // the point at which write() starts returning false + this.highWaterMark = options.hasOwnProperty('highWaterMark') ? + options.highWaterMark : 16 * 1024; + + // the point that it has to get to before we call _write(chunk,cb) + // default to pushing everything out as fast as possible. + this.lowWaterMark = options.hasOwnProperty('lowWaterMark') ? + options.lowWaterMark : 0; + + // cast to ints. + assert(typeof this.lowWaterMark === 'number'); + assert(typeof this.highWaterMark === 'number'); + this.lowWaterMark = ~~this.lowWaterMark; + this.highWaterMark = ~~this.highWaterMark; + assert(this.lowWaterMark >= 0); + assert(this.highWaterMark >= this.lowWaterMark, + this.highWaterMark + '>=' + this.lowWaterMark); + + this.needDrain = false; + // at the start of calling end() + this.ending = false; + // when end() has been called, and returned + this.ended = false; + // when 'finish' has emitted + this.finished = false; + // when 'finish' is being emitted + this.finishing = false; + + // should we decode strings into buffers before passing to _write? + // this is here so that some node-core streams can optimize string + // handling at a lower level. + this.decodeStrings = options.hasOwnProperty('decodeStrings') ? + options.decodeStrings : true; + + // not an actual buffer we keep track of, but a measurement + // of how much we're waiting to get pushed to some underlying + // socket or file. + this.length = 0; + + // a flag to see when we're in the middle of a write. + this.writing = false; + + // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. + this.sync = false; + + // the callback that's passed to _write(chunk,cb) + this.onwrite = function(er) { + onwrite(stream, er); + }; + + // the callback that the user supplies to write(chunk,encoding,cb) + this.writecb = null; + + // the amount that is being written when _write is called. + this.writelen = 0; + + this.buffer = []; +} + +function Writable(options) { + // Writable ctor is applied to Duplexes, though they're not + // instanceof Writable, they're instanceof Readable. + if (!(this instanceof Writable) && !(this instanceof Stream.Duplex)) + return new Writable(options); + + this._writableState = new WritableState(options, this); + + // legacy. + this.writable = true; + + Stream.call(this); +} + +// Override this method or _write(chunk, cb) +Writable.prototype.write = function(chunk, encoding, cb) { + var state = this._writableState; + + if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (state.ended) { + var er = new Error('write after end'); + if (typeof cb === 'function') + cb(er); + this.emit('error', er); + return; + } + + var l = chunk.length; + if (false === state.decodeStrings) + chunk = [chunk, encoding || 'utf8']; + else if (typeof chunk === 'string' || encoding) { + chunk = new Buffer(chunk + '', encoding); + l = chunk.length; + } + + state.length += l; + + var ret = state.length < state.highWaterMark; + if (ret === false) + state.needDrain = true; + + // if we're already writing something, then just put this + // in the queue, and wait our turn. + if (state.writing) { + state.buffer.push([chunk, cb]); + return ret; + } + + state.writing = true; + state.sync = true; + state.writelen = l; + state.writecb = cb; + this._write(chunk, state.onwrite); + state.sync = false; + + return ret; +}; + +function onwrite(stream, er) { + var state = stream._writableState; + var sync = state.sync; + var cb = state.writecb; + var l = state.writelen; + + state.writing = false; + state.writelen = null; + state.writecb = null; + + if (er) { + if (cb) { + if (sync) + process.nextTick(function() { + cb(er); + }); + else + cb(er); + } + + // backwards compatibility. still emit if there was a cb. + stream.emit('error', er); + return; + } + state.length -= l; + + if (cb) { + // don't call the cb until the next tick if we're in sync mode. + // also, defer if we're about to write some more right now. + if (sync || state.buffer.length) + process.nextTick(cb); + else + cb(); + } + + if (state.length === 0 && (state.ended || state.ending) && + !state.finished && !state.finishing) { + // emit 'finish' at the very end. + state.finishing = true; + stream.emit('finish'); + state.finished = true; + return; + } + + // if there's something in the buffer waiting, then do that, too. + if (state.buffer.length) { + var chunkCb = state.buffer.shift(); + var chunk = chunkCb[0]; + cb = chunkCb[1]; + + if (false === state.decodeStrings) + l = chunk[0].length; + else + l = chunk.length; + + state.writelen = l; + state.writecb = cb; + state.writechunk = chunk; + state.writing = true; + stream._write(chunk, state.onwrite); + } + + if (state.length <= state.lowWaterMark && state.needDrain) { + // Must force callback to be called on nextTick, so that we don't + // emit 'drain' before the write() consumer gets the 'false' return + // value, and has a chance to attach a 'drain' listener. + process.nextTick(function() { + if (!state.needDrain) + return; + state.needDrain = false; + stream.emit('drain'); + }); + } +} + +Writable.prototype._write = function(chunk, cb) { + process.nextTick(function() { + cb(new Error('not implemented')); + }); +}; + +Writable.prototype.end = function(chunk, encoding) { + var state = this._writableState; + + // ignore unnecessary end() calls. + if (state.ending || state.ended || state.finished) + return; + + state.ending = true; + if (chunk) + this.write(chunk, encoding); + else if (state.length === 0 && !state.finishing && !state.finished) { + state.finishing = true; + this.emit('finish'); + state.finished = true; + } + state.ended = true; +}; diff --git a/lib/child_process.js b/lib/child_process.js index 1f9ddd543a..8509d53024 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -110,7 +110,6 @@ var handleConversion = { 'net.Socket': { send: function(message, socket) { // pause socket so no data is lost, will be resumed later - socket.pause(); // if the socket wsa created by net.Server if (socket.server) { @@ -142,7 +141,6 @@ var handleConversion = { got: function(message, handle, emit) { var socket = new net.Socket({handle: handle}); socket.readable = socket.writable = true; - socket.pause(); // if the socket was created by net.Server we will track the socket if (message.key) { @@ -153,7 +151,6 @@ var handleConversion = { } emit(socket); - socket.resume(); } } }; diff --git a/lib/crypto.js b/lib/crypto.js index a787e09c34..0033267cee 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -37,6 +37,9 @@ try { var crypto = false; } +var stream = require('stream'); +var util = require('util'); + // This is here because many functions accepted binary strings without // any explicit encoding in older versions of node, and we don't want // to break them unnecessarily. @@ -148,12 +151,24 @@ exports.createCredentials = function(options, context) { exports.createHash = exports.Hash = Hash; -function Hash(algorithm) { +function Hash(algorithm, options) { if (!(this instanceof Hash)) return new Hash(algorithm); this._binding = new binding.Hash(algorithm); + stream.Transform.call(this, options); } +util.inherits(Hash, stream.Transform); + +Hash.prototype._transform = function(chunk, output, callback) { + this._binding.update(chunk); + callback(); +}; + +Hash.prototype._flush = function(output, callback) { + output(this._binding.digest()); + callback(); +}; Hash.prototype.update = function(data, encoding) { encoding = encoding || exports.DEFAULT_ENCODING; @@ -174,16 +189,20 @@ Hash.prototype.digest = function(outputEncoding) { exports.createHmac = exports.Hmac = Hmac; -function Hmac(hmac, key) { +function Hmac(hmac, key, options) { if (!(this instanceof Hmac)) return new Hmac(hmac, key); this._binding = new binding.Hmac(); this._binding.init(hmac, toBuf(key)); + stream.Transform.call(this, options); } +util.inherits(Hmac, stream.Transform); Hmac.prototype.update = Hash.prototype.update; Hmac.prototype.digest = Hash.prototype.digest; +Hmac.prototype._flush = Hash.prototype._flush; +Hmac.prototype._transform = Hash.prototype._transform; function getDecoder(decoder, encoding) { @@ -194,15 +213,28 @@ function getDecoder(decoder, encoding) { exports.createCipher = exports.Cipher = Cipher; -function Cipher(cipher, password) { +function Cipher(cipher, password, options) { if (!(this instanceof Cipher)) return new Cipher(cipher, password); this._binding = new binding.Cipher; this._binding.init(cipher, toBuf(password)); this._decoder = null; + + stream.Transform.call(this, options); } +util.inherits(Cipher, stream.Transform); + +Cipher.prototype._transform = function(chunk, output, callback) { + output(this._binding.update(chunk)); + callback(); +}; + +Cipher.prototype._flush = function(output, callback) { + output(this._binding.final()); + callback(); +}; Cipher.prototype.update = function(data, inputEncoding, outputEncoding) { inputEncoding = inputEncoding || exports.DEFAULT_ENCODING; @@ -241,15 +273,20 @@ Cipher.prototype.setAutoPadding = function(ap) { exports.createCipheriv = exports.Cipheriv = Cipheriv; -function Cipheriv(cipher, key, iv) { +function Cipheriv(cipher, key, iv, options) { if (!(this instanceof Cipheriv)) return new Cipheriv(cipher, key, iv); this._binding = new binding.Cipher(); this._binding.initiv(cipher, toBuf(key), toBuf(iv)); this._decoder = null; + + stream.Transform.call(this, options); } +util.inherits(Cipheriv, stream.Transform); +Cipheriv.prototype._transform = Cipher.prototype._transform; +Cipheriv.prototype._flush = Cipher.prototype._flush; Cipheriv.prototype.update = Cipher.prototype.update; Cipheriv.prototype.final = Cipher.prototype.final; Cipheriv.prototype.setAutoPadding = Cipher.prototype.setAutoPadding; @@ -257,16 +294,21 @@ Cipheriv.prototype.setAutoPadding = Cipher.prototype.setAutoPadding; exports.createDecipher = exports.Decipher = Decipher; -function Decipher(cipher, password) { +function Decipher(cipher, password, options) { if (!(this instanceof Decipher)) return new Decipher(cipher, password); this._binding = new binding.Decipher; this._binding.init(cipher, toBuf(password)); this._decoder = null; + + stream.Transform.call(this, options); } +util.inherits(Decipher, stream.Transform); +Decipher.prototype._transform = Cipher.prototype._transform; +Decipher.prototype._flush = Cipher.prototype._flush; Decipher.prototype.update = Cipher.prototype.update; Decipher.prototype.final = Cipher.prototype.final; Decipher.prototype.finaltol = Cipher.prototype.final; @@ -275,16 +317,21 @@ Decipher.prototype.setAutoPadding = Cipher.prototype.setAutoPadding; exports.createDecipheriv = exports.Decipheriv = Decipheriv; -function Decipheriv(cipher, key, iv) { +function Decipheriv(cipher, key, iv, options) { if (!(this instanceof Decipheriv)) return new Decipheriv(cipher, key, iv); this._binding = new binding.Decipher; this._binding.initiv(cipher, toBuf(key), toBuf(iv)); this._decoder = null; + + stream.Transform.call(this, options); } +util.inherits(Decipheriv, stream.Transform); +Decipheriv.prototype._transform = Cipher.prototype._transform; +Decipheriv.prototype._flush = Cipher.prototype._flush; Decipheriv.prototype.update = Cipher.prototype.update; Decipheriv.prototype.final = Cipher.prototype.final; Decipheriv.prototype.finaltol = Cipher.prototype.final; @@ -293,16 +340,23 @@ Decipheriv.prototype.setAutoPadding = Cipher.prototype.setAutoPadding; exports.createSign = exports.Sign = Sign; -function Sign(algorithm) { +function Sign(algorithm, options) { if (!(this instanceof Sign)) return new Sign(algorithm); this._binding = new binding.Sign(); this._binding.init(algorithm); + + stream.Writable.call(this, options); } +util.inherits(Sign, stream.Writable); -Sign.prototype.update = Hash.prototype.update; +Sign.prototype._write = function(chunk, callback) { + this._binding.update(chunk); + callback(); +}; +Sign.prototype.update = Hash.prototype.update; Sign.prototype.sign = function(key, encoding) { encoding = encoding || exports.DEFAULT_ENCODING; @@ -317,17 +371,20 @@ Sign.prototype.sign = function(key, encoding) { exports.createVerify = exports.Verify = Verify; -function Verify(algorithm) { +function Verify(algorithm, options) { if (!(this instanceof Verify)) return new Verify(algorithm); this._binding = new binding.Verify; this._binding.init(algorithm); -} + stream.Writable.call(this, options); +} -Verify.prototype.update = Hash.prototype.update; +util.inherits(Verify, stream.Writable); +Verify.prototype._write = Sign.prototype._write; +Verify.prototype.update = Sign.prototype.update; Verify.prototype.verify = function(object, signature, sigEncoding) { sigEncoding = sigEncoding || exports.DEFAULT_ENCODING; diff --git a/lib/fs.js b/lib/fs.js index 83bacc932f..96af63186c 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -34,6 +34,9 @@ var fs = exports; var Stream = require('stream').Stream; var EventEmitter = require('events').EventEmitter; +var Readable = Stream.Readable; +var Writable = Stream.Writable; + var kMinPoolSpace = 128; var kPoolSize = 40 * 1024; @@ -1386,34 +1389,30 @@ fs.createReadStream = function(path, options) { return new ReadStream(path, options); }; -var ReadStream = fs.ReadStream = function(path, options) { - if (!(this instanceof ReadStream)) return new ReadStream(path, options); - - Stream.call(this); - - var self = this; - - this.path = path; - this.fd = null; - this.readable = true; - this.paused = false; +util.inherits(ReadStream, Readable); +fs.ReadStream = ReadStream; - this.flags = 'r'; - this.mode = 438; /*=0666*/ - this.bufferSize = 64 * 1024; +function ReadStream(path, options) { + if (!(this instanceof ReadStream)) + return new ReadStream(path, options); - options = options || {}; + // a little bit bigger buffer and water marks by default + options = util._extend({ + bufferSize: 64 * 1024, + lowWaterMark: 16 * 1024, + highWaterMark: 64 * 1024 + }, options || {}); - // Mixin options into this - var keys = Object.keys(options); - for (var index = 0, length = keys.length; index < length; index++) { - var key = keys[index]; - this[key] = options[key]; - } + Readable.call(this, options); - assertEncoding(this.encoding); + this.path = path; + this.fd = options.hasOwnProperty('fd') ? options.fd : null; + this.flags = options.hasOwnProperty('flags') ? options.flags : 'r'; + this.mode = options.hasOwnProperty('mode') ? options.mode : 438; /*=0666*/ - if (this.encoding) this.setEncoding(this.encoding); + this.start = options.hasOwnProperty('start') ? options.start : undefined; + this.end = options.hasOwnProperty('start') ? options.end : undefined; + this.pos = undefined; if (this.start !== undefined) { if ('number' !== typeof this.start) { @@ -1432,41 +1431,40 @@ var ReadStream = fs.ReadStream = function(path, options) { this.pos = this.start; } - if (this.fd !== null) { - process.nextTick(function() { - self._read(); - }); - return; - } + if (typeof this.fd !== 'number') + this.open(); - fs.open(this.path, this.flags, this.mode, function(err, fd) { - if (err) { - self.emit('error', err); - self.readable = false; + this.on('end', function() { + this.destroy(); + }); +} + +fs.FileReadStream = fs.ReadStream; // support the legacy name + +ReadStream.prototype.open = function() { + var self = this; + fs.open(this.path, this.flags, this.mode, function(er, fd) { + if (er) { + self.destroy(); + self.emit('error', er); return; } self.fd = fd; self.emit('open', fd); - self._read(); + // start the flow of data. + self.read(); }); }; -util.inherits(ReadStream, Stream); - -fs.FileReadStream = fs.ReadStream; // support the legacy name - -ReadStream.prototype.setEncoding = function(encoding) { - assertEncoding(encoding); - var StringDecoder = require('string_decoder').StringDecoder; // lazy load - this._decoder = new StringDecoder(encoding); -}; +ReadStream.prototype._read = function(n, cb) { + if (typeof this.fd !== 'number') + return this.once('open', function() { + this._read(n, cb); + }); -ReadStream.prototype._read = function() { - var self = this; - if (!this.readable || this.paused || this.reading) return; - - this.reading = true; + if (this.destroyed) + return; if (!pool || pool.length - pool.used < kMinPoolSpace) { // discard the old pool. Can't add to the free list because @@ -1475,149 +1473,110 @@ ReadStream.prototype._read = function() { allocNewPool(); } - // Grab another reference to the pool in the case that while we're in the - // thread pool another read() finishes up the pool, and allocates a new - // one. + // Grab another reference to the pool in the case that while we're + // in the thread pool another read() finishes up the pool, and + // allocates a new one. var thisPool = pool; - var toRead = Math.min(pool.length - pool.used, ~~this.bufferSize); + var toRead = Math.min(pool.length - pool.used, n); var start = pool.used; - if (this.pos !== undefined) { + if (this.pos !== undefined) toRead = Math.min(this.end - this.pos + 1, toRead); - } - function afterRead(err, bytesRead) { - self.reading = false; - if (err) { - fs.close(self.fd, function() { - self.fd = null; - self.emit('error', err); - self.readable = false; - }); - return; - } + // already read everything we were supposed to read! + // treat as EOF. + if (toRead <= 0) + return cb(); - if (bytesRead === 0) { - if (this._decoder) { - var ret = this._decoder.end(); - if (ret) - this.emit('data', ret); - } - self.emit('end'); - self.destroy(); - return; - } - - var b = thisPool.slice(start, start + bytesRead); + // the actual read. + var self = this; + fs.read(this.fd, pool, pool.used, toRead, this.pos, onread); - // Possible optimizition here? - // Reclaim some bytes if bytesRead < toRead? - // Would need to ensure that pool === thisPool. + // move the pool positions, and internal position for reading. + if (this.pos !== undefined) + this.pos += toRead; + pool.used += toRead; - // do not emit events if the stream is paused - if (self.paused) { - self.buffer = b; - return; + function onread(er, bytesRead) { + if (er) { + self.destroy(); + return cb(er); } - // do not emit events anymore after we declared the stream unreadable - if (!self.readable) return; + var b = null; + if (bytesRead > 0) + b = thisPool.slice(start, start + bytesRead); - self._emitData(b); - self._read(); + cb(null, b); } - - fs.read(this.fd, pool, pool.used, toRead, this.pos, afterRead); - - if (this.pos !== undefined) { - this.pos += toRead; - } - pool.used += toRead; }; -ReadStream.prototype._emitData = function(d) { - if (this._decoder) { - var string = this._decoder.write(d); - if (string.length) this.emit('data', string); - } else { - this.emit('data', d); - } +ReadStream.prototype.destroy = function() { + if (this.destroyed) + return; + this.destroyed = true; + + if ('number' === typeof this.fd) + this.close(); }; -ReadStream.prototype.destroy = function() { +ReadStream.prototype.close = function(cb) { + if (cb) + this.once('close', cb); + if (this.closed || 'number' !== typeof this.fd) { + if ('number' !== typeof this.fd) + this.once('open', close); + return process.nextTick(this.emit.bind(this, 'close')); + } + this.closed = true; var self = this; - - if (!this.readable) return; - this.readable = false; + close(); function close() { - fs.close(self.fd, function(err) { - if (err) { - self.emit('error', err); - } else { + fs.close(self.fd, function(er) { + if (er) + self.emit('error', er); + else self.emit('close'); - } }); - } - - if (this.fd === null) { - this.addListener('open', close); - } else { - close(); + self.fd = null; } }; -ReadStream.prototype.pause = function() { - this.paused = true; -}; - - -ReadStream.prototype.resume = function() { - this.paused = false; - - if (this.buffer) { - var buffer = this.buffer; - this.buffer = null; - this._emitData(buffer); - } - - // hasn't opened yet. - if (null == this.fd) return; - - this._read(); -}; - fs.createWriteStream = function(path, options) { return new WriteStream(path, options); }; -var WriteStream = fs.WriteStream = function(path, options) { - if (!(this instanceof WriteStream)) return new WriteStream(path, options); +util.inherits(WriteStream, Writable); +fs.WriteStream = WriteStream; +function WriteStream(path, options) { + if (!(this instanceof WriteStream)) + return new WriteStream(path, options); - Stream.call(this); + // a little bit bigger buffer and water marks by default + options = util._extend({ + bufferSize: 64 * 1024, + lowWaterMark: 16 * 1024, + highWaterMark: 64 * 1024 + }, options || {}); + + Writable.call(this, options); this.path = path; this.fd = null; - this.writable = true; - - this.flags = 'w'; - this.encoding = 'binary'; - this.mode = 438; /*=0666*/ - this.bytesWritten = 0; - options = options || {}; + this.fd = options.hasOwnProperty('fd') ? options.fd : null; + this.flags = options.hasOwnProperty('flags') ? options.flags : 'w'; + this.mode = options.hasOwnProperty('mode') ? options.mode : 438; /*=0666*/ - // Mixin options into this - var keys = Object.keys(options); - for (var index = 0, length = keys.length; index < length; index++) { - var key = keys[index]; - this[key] = options[key]; - } + this.start = options.hasOwnProperty('start') ? options.start : undefined; + this.pos = undefined; + this.bytesWritten = 0; if (this.start !== undefined) { if ('number' !== typeof this.start) { @@ -1630,154 +1589,54 @@ var WriteStream = fs.WriteStream = function(path, options) { this.pos = this.start; } - this.busy = false; - this._queue = []; + if ('number' !== typeof this.fd) + this.open(); - if (this.fd === null) { - this._open = fs.open; - this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); - this.flush(); - } -}; -util.inherits(WriteStream, Stream); + // dispose on finish. + this.once('finish', this.close); +} fs.FileWriteStream = fs.WriteStream; // support the legacy name -WriteStream.prototype.flush = function() { - if (this.busy) return; - var self = this; - - var args = this._queue.shift(); - if (!args) { - if (this.drainable) { this.emit('drain'); } - return; - } - - this.busy = true; - - var method = args.shift(), - cb = args.pop(); - - args.push(function(err) { - self.busy = false; - - if (err) { - self.writable = false; - - function emit() { - self.fd = null; - if (cb) cb(err); - self.emit('error', err); - } - - if (self.fd === null) { - emit(); - } else { - fs.close(self.fd, emit); - } - - return; - } - if (method == fs.write) { - self.bytesWritten += arguments[1]; - if (cb) { - // write callback - cb(null, arguments[1]); - } - - } else if (method === self._open) { - // save reference for file pointer - self.fd = arguments[1]; - self.emit('open', self.fd); - - } else if (method === fs.close) { - // stop flushing after close - if (cb) { - cb(null); - } - self.emit('close'); +WriteStream.prototype.open = function() { + fs.open(this.path, this.flags, this.mode, function(er, fd) { + if (er) { + this.destroy(); + this.emit('error', er); return; } - self.flush(); - }); - - // Inject the file pointer - if (method !== self._open) { - args.unshift(this.fd); - } - - method.apply(this, args); + this.fd = fd; + this.emit('open', fd); + }.bind(this)); }; -WriteStream.prototype.write = function(data) { - if (!this.writable) { - this.emit('error', new Error('stream not writable')); - return false; - } - this.drainable = true; - - var cb; - if (typeof(arguments[arguments.length - 1]) == 'function') { - cb = arguments[arguments.length - 1]; - } +WriteStream.prototype._write = function(data, cb) { + if (!Buffer.isBuffer(data)) + return this.emit('error', new Error('Invalid data')); - if (!Buffer.isBuffer(data)) { - var encoding = 'utf8'; - if (typeof(arguments[1]) == 'string') encoding = arguments[1]; - assertEncoding(encoding); - data = new Buffer('' + data, encoding); - } + if (typeof this.fd !== 'number') + return this.once('open', this._write.bind(this, data, cb)); - this._queue.push([fs.write, data, 0, data.length, this.pos, cb]); + var self = this; + fs.write(this.fd, data, 0, data.length, this.pos, function(er, bytes) { + if (er) { + self.destroy(); + return cb(er); + } + self.bytesWritten += bytes; + cb(); + }); - if (this.pos !== undefined) { + if (this.pos !== undefined) this.pos += data.length; - } - - this.flush(); - - return false; -}; - -WriteStream.prototype.end = function(data, encoding, cb) { - if (typeof(data) === 'function') { - cb = data; - } else if (typeof(encoding) === 'function') { - cb = encoding; - this.write(data); - } else if (arguments.length > 0) { - this.write(data, encoding); - } - this.writable = false; - this._queue.push([fs.close, cb]); - this.flush(); }; -WriteStream.prototype.destroy = function() { - var self = this; - - if (!this.writable) return; - this.writable = false; - - function close() { - fs.close(self.fd, function(err) { - if (err) { - self.emit('error', err); - } else { - self.emit('close'); - } - }); - } - if (this.fd === null) { - this.addListener('open', close); - } else { - close(); - } -}; +WriteStream.prototype.destroy = ReadStream.prototype.destroy; +WriteStream.prototype.close = ReadStream.prototype.close; // There is no shutdown() for files. WriteStream.prototype.destroySoon = WriteStream.prototype.end; diff --git a/lib/http.js b/lib/http.js index 62b2ffe052..0555e699e2 100644 --- a/lib/http.js +++ b/lib/http.js @@ -114,19 +114,30 @@ function parserOnHeadersComplete(info) { return skipBody; } +// XXX This is a mess. +// TODO: http.Parser should be a Writable emits request/response events. function parserOnBody(b, start, len) { var parser = this; - var slice = b.slice(start, start + len); - if (parser.incoming._paused || parser.incoming._pendings.length) { - parser.incoming._pendings.push(slice); - } else { - parser.incoming._emitData(slice); + var stream = parser.incoming; + var rs = stream._readableState; + var socket = stream.socket; + + // pretend this was the result of a stream._read call. + if (len > 0) { + var slice = b.slice(start, start + len); + rs.onread(null, slice); } + + if (rs.length >= rs.highWaterMark) + socket.pause(); } function parserOnMessageComplete() { var parser = this; - parser.incoming.complete = true; + var stream = parser.incoming; + var socket = stream.socket; + + stream.complete = true; // Emit any trailing headers. var headers = parser._headers; @@ -140,19 +151,13 @@ function parserOnMessageComplete() { parser._url = ''; } - if (!parser.incoming.upgrade) { + if (!stream.upgrade) // For upgraded connections, also emit this after parser.execute - if (parser.incoming._paused || parser.incoming._pendings.length) { - parser.incoming._pendings.push(END_OF_FILE); - } else { - parser.incoming.readable = false; - parser.incoming._emitEnd(); - } - } + stream._readableState.onread(null, null); if (parser.socket.readable) { // force to read the next incoming message - parser.socket.resume(); + socket.resume(); } } @@ -263,9 +268,13 @@ function utcDate() { /* Abstract base class for ServerRequest and ClientResponse. */ function IncomingMessage(socket) { - Stream.call(this); + Stream.Readable.call(this); + + // XXX This implementation is kind of all over the place + // When the parser emits body chunks, they go in this list. + // _read() pulls them out, and when it finds EOF, it ends. + this._pendings = []; - // TODO Remove one of these eventually. this.socket = socket; this.connection = socket; @@ -276,77 +285,49 @@ function IncomingMessage(socket) { this.readable = true; - this._paused = false; this._pendings = []; - - this._endEmitted = false; + this._pendingIndex = 0; // request (server) only this.url = ''; - this.method = null; // response (client) only this.statusCode = null; this.client = this.socket; + + // flag for backwards compatibility grossness. + this._consuming = false; } -util.inherits(IncomingMessage, Stream); +util.inherits(IncomingMessage, Stream.Readable); exports.IncomingMessage = IncomingMessage; -IncomingMessage.prototype.destroy = function(error) { - this.socket.destroy(error); +IncomingMessage.prototype.read = function(n) { + this._consuming = true; + return Stream.Readable.prototype.read.call(this, n); }; -IncomingMessage.prototype.setEncoding = function(encoding) { - var StringDecoder = require('string_decoder').StringDecoder; // lazy load - this._decoder = new StringDecoder(encoding); -}; - - -IncomingMessage.prototype.pause = function() { - this._paused = true; - this.socket.pause(); +IncomingMessage.prototype._read = function(n, callback) { + // We actually do almost nothing here, because the parserOnBody + // function fills up our internal buffer directly. However, we + // do need to unpause the underlying socket so that it flows. + if (!this.socket.readable) + return callback(null, null); + else + this.socket.resume(); }; -IncomingMessage.prototype.resume = function() { - this._paused = false; - if (this.socket) { - this.socket.resume(); - } - - this._emitPending(); +IncomingMessage.prototype.destroy = function(error) { + this.socket.destroy(error); }; -IncomingMessage.prototype._emitPending = function(callback) { - if (this._pendings.length) { - var self = this; - process.nextTick(function() { - while (!self._paused && self._pendings.length) { - var chunk = self._pendings.shift(); - if (chunk !== END_OF_FILE) { - assert(Buffer.isBuffer(chunk)); - self._emitData(chunk); - } else { - assert(self._pendings.length === 0); - self.readable = false; - self._emitEnd(); - } - } - if (callback) { - callback(); - } - }); - } else if (callback) { - callback(); - } -}; IncomingMessage.prototype._emitData = function(d) { @@ -1016,7 +997,7 @@ ServerResponse.prototype.writeHead = function(statusCode) { // don't keep alive connections where the client expects 100 Continue // but we sent a final status; they may put extra bytes on the wire. - if (this._expect_continue && ! this._sent100) { + if (this._expect_continue && !this._sent100) { this.shouldKeepAlive = false; } @@ -1321,11 +1302,10 @@ function socketCloseListener() { // Socket closed before we emitted 'end' below. req.res.emit('aborted'); var res = req.res; - req.res._emitPending(function() { - res._emitEnd(); + res.on('end', function() { res.emit('close'); - res = null; }); + res._readableState.onread(null, null); } else if (!req.res && !req._hadError) { // This socket error fired before we started to // receive a response. The error needs to @@ -1428,11 +1408,13 @@ function socketOnData(d, start, end) { } +// client function parserOnIncomingClient(res, shouldKeepAlive) { var parser = this; var socket = this.socket; var req = socket._httpMessage; + // propogate "domain" setting... if (req.domain && !res.domain) { debug('setting "res.domain"'); @@ -1480,15 +1462,21 @@ function parserOnIncomingClient(res, shouldKeepAlive) { DTRACE_HTTP_CLIENT_RESPONSE(socket, req); COUNTER_HTTP_CLIENT_RESPONSE(); - req.emit('response', res); req.res = res; res.req = req; - + var handled = req.emit('response', res); res.on('end', responseOnEnd); + // If the user did not listen for the 'response' event, then they + // can't possibly read the data, so we .resume() it into the void + // so that the socket doesn't hang there in a paused state. + if (!handled) + res.resume(); + return isHeadResponse; } +// client function responseOnEnd() { var res = this; var req = res.req; @@ -1784,7 +1772,7 @@ function connectionListener(socket) { incoming.push(req); var res = new ServerResponse(req); - debug('server response shouldKeepAlive: ' + shouldKeepAlive); + res.shouldKeepAlive = shouldKeepAlive; DTRACE_HTTP_SERVER_REQUEST(req, socket); COUNTER_HTTP_SERVER_REQUEST(); @@ -1806,6 +1794,12 @@ function connectionListener(socket) { incoming.shift(); + // if the user never called req.read(), and didn't pipe() or + // .resume() or .on('data'), then we call req.resume() so that the + // bytes will be pulled off the wire. + if (!req._consuming) + req.resume(); + res.detachSocket(socket); if (res._last) { diff --git a/lib/net.js b/lib/net.js index 81d02a5a64..d0a2c5a627 100644 --- a/lib/net.js +++ b/lib/net.js @@ -20,7 +20,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. var events = require('events'); -var Stream = require('stream'); +var stream = require('stream'); var timers = require('timers'); var util = require('util'); var assert = require('assert'); @@ -42,16 +42,16 @@ function createTCP() { } -/* Bit flags for socket._flags */ -var FLAG_GOT_EOF = 1 << 0; -var FLAG_SHUTDOWN = 1 << 1; -var FLAG_DESTROY_SOON = 1 << 2; -var FLAG_SHUTDOWN_QUEUED = 1 << 3; - - var debug; if (process.env.NODE_DEBUG && /net/.test(process.env.NODE_DEBUG)) { - debug = function(x) { console.error('NET:', x); }; + var pid = process.pid; + debug = function(x) { + // if console is not set up yet, then skip this. + if (!console.error) + return; + console.error('NET: %d', pid, + util.format.apply(util, arguments).slice(0, 500)); + }; } else { debug = function() { }; } @@ -110,12 +110,8 @@ function normalizeConnectArgs(args) { exports._normalizeConnectArgs = normalizeConnectArgs; -/* called when creating new Socket, or when re-using a closed Socket */ +// called when creating new Socket, or when re-using a closed Socket function initSocketHandle(self) { - self._pendingWriteReqs = 0; - - self._flags = 0; - self._connectQueueSize = 0; self.destroyed = false; self.errorEmitted = false; self.bytesRead = 0; @@ -131,8 +127,6 @@ function initSocketHandle(self) { function Socket(options) { if (!(this instanceof Socket)) return new Socket(options); - Stream.call(this); - switch (typeof options) { case 'number': options = { fd: options }; // Legacy interface. @@ -142,7 +136,10 @@ function Socket(options) { break; } - if (typeof options.fd === 'undefined') { + this.readable = this.writable = false; + if (options.handle) { + this._handle = options.handle; // private + } else if (typeof options.fd === 'undefined') { this._handle = options && options.handle; // private } else { this._handle = createPipe(); @@ -150,17 +147,105 @@ function Socket(options) { this.readable = this.writable = true; } + this.onend = null; + + // shut down the socket when we're finished with it. + this.on('finish', onSocketFinish); + this.on('_socketEnd', onSocketEnd); + initSocketHandle(this); - this.allowHalfOpen = options && options.allowHalfOpen; + + this._pendingWrite = null; + + stream.Duplex.call(this, options); + + // handle strings directly + this._writableState.decodeStrings = false; + + // default to *not* allowing half open sockets + this.allowHalfOpen = options && options.allowHalfOpen || false; + + // if we have a handle, then start the flow of data into the + // buffer. if not, then this will happen when we connect + if (this._handle && (!options || options.readable !== false)) + this.read(0); +} +util.inherits(Socket, stream.Duplex); + +// the user has called .end(), and all the bytes have been +// sent out to the other side. +// If allowHalfOpen is false, or if the readable side has +// ended already, then destroy. +// If allowHalfOpen is true, then we need to do a shutdown, +// so that only the writable side will be cleaned up. +function onSocketFinish() { + debug('onSocketFinish'); + if (this._readableState.ended) { + debug('oSF: ended, destroy', this._readableState); + return this.destroy(); + } + + debug('oSF: not ended, call shutdown()'); + + // otherwise, just shutdown, or destroy() if not possible + if (!this._handle.shutdown) + return this.destroy(); + + var shutdownReq = this._handle.shutdown(); + + if (!shutdownReq) + return this._destroy(errnoException(errno, 'shutdown')); + + shutdownReq.oncomplete = afterShutdown; +} + + +function afterShutdown(status, handle, req) { + var self = handle.owner; + + debug('afterShutdown destroyed=%j', self.destroyed, + self._readableState); + + // callback may come after call to destroy. + if (self.destroyed) + return; + + if (self._readableState.ended) { + debug('readableState ended, destroying'); + self.destroy(); + } else { + self.once('_socketEnd', self.destroy); + } } -util.inherits(Socket, Stream); +// the EOF has been received, and no more bytes are coming. +// if the writable side has ended already, then clean everything +// up. +function onSocketEnd() { + // XXX Should not have to do as much crap in this function. + // ended should already be true, since this is called *after* + // the EOF errno and onread has returned null to the _read cb. + debug('onSocketEnd', this._readableState); + this._readableState.ended = true; + if (this._readableState.endEmitted) { + this.readable = false; + } else { + this.once('end', function() { + this.readable = false; + }); + this.read(0); + } + + if (!this.allowHalfOpen) + this.destroySoon(); +} exports.Socket = Socket; exports.Stream = Socket; // Legacy naming. Socket.prototype.listen = function() { + debug('socket.listen'); var self = this; self.on('connection', arguments[0]); listen(self, null, null, null); @@ -230,96 +315,62 @@ Object.defineProperty(Socket.prototype, 'readyState', { Object.defineProperty(Socket.prototype, 'bufferSize', { get: function() { if (this._handle) { - return this._handle.writeQueueSize + this._connectQueueSize; + return this._handle.writeQueueSize; } } }); -Socket.prototype.pause = function() { - this._paused = true; - if (this._handle && !this._connecting) { - this._handle.readStop(); +// Just call handle.readStart until we have enough in the buffer +Socket.prototype._read = function(n, callback) { + debug('_read'); + if (this._connecting || !this._handle) { + debug('_read wait for connection'); + this.once('connect', this._read.bind(this, n, callback)); + return; } -}; + assert(callback === this._readableState.onread); + assert(this._readableState.reading = true); -Socket.prototype.resume = function() { - this._paused = false; - if (this._handle && !this._connecting) { - this._handle.readStart(); + if (!this._handle.reading) { + debug('Socket._read readStart'); + this._handle.reading = true; + var r = this._handle.readStart(); + if (r) + this._destroy(errnoException(errno, 'read')); + } else { + debug('readStart already has been called.'); } }; Socket.prototype.end = function(data, encoding) { - if (this._connecting && ((this._flags & FLAG_SHUTDOWN_QUEUED) == 0)) { - // still connecting, add data to buffer - if (data) this.write(data, encoding); - this.writable = false; - this._flags |= FLAG_SHUTDOWN_QUEUED; - } - - if (!this.writable) return; + stream.Duplex.prototype.end.call(this, data, encoding); this.writable = false; - - if (data) this.write(data, encoding); DTRACE_NET_STREAM_END(this); - if (!this.readable) { - this.destroySoon(); - } else { - this._flags |= FLAG_SHUTDOWN; - var shutdownReq = this._handle.shutdown(); - - if (!shutdownReq) { - this._destroy(errnoException(errno, 'shutdown')); - return false; - } - - shutdownReq.oncomplete = afterShutdown; - } - - return true; + // just in case we're waiting for an EOF. + if (!this._readableState.endEmitted) + this.read(0); + return; }; -function afterShutdown(status, handle, req) { - var self = handle.owner; - - assert.ok(self._flags & FLAG_SHUTDOWN); - assert.ok(!self.writable); - - // callback may come after call to destroy. - if (self.destroyed) { - return; - } - - if (self._flags & FLAG_GOT_EOF || !self.readable) { - self._destroy(); - } else { - } -} - - Socket.prototype.destroySoon = function() { - this.writable = false; - this._flags |= FLAG_DESTROY_SOON; - - if (this._pendingWriteReqs == 0) { - this._destroy(); - } -}; - + if (this.writable) + this.end(); -Socket.prototype._connectQueueCleanUp = function(exception) { - this._connecting = false; - this._connectQueueSize = 0; - this._connectQueue = null; + if (this._writableState.finishing || this._writableState.finished) + this.destroy(); + else + this.once('finish', this.destroy); }; Socket.prototype._destroy = function(exception, cb) { + debug('destroy'); + var self = this; function fireErrorCallbacks() { @@ -333,13 +384,12 @@ Socket.prototype._destroy = function(exception, cb) { }; if (this.destroyed) { + debug('already destroyed, fire error callbacks'); fireErrorCallbacks(); return; } - self._connectQueueCleanUp(); - - debug('destroy'); + self._connecting = false; this.readable = this.writable = false; @@ -347,6 +397,8 @@ Socket.prototype._destroy = function(exception, cb) { debug('close'); if (this._handle) { + if (this !== process.stderr) + debug('close handle'); this._handle.close(); this._handle.onread = noop; this._handle = null; @@ -355,6 +407,7 @@ Socket.prototype._destroy = function(exception, cb) { fireErrorCallbacks(); process.nextTick(function() { + debug('emit close'); self.emit('close', exception ? true : false); }); @@ -362,6 +415,7 @@ Socket.prototype._destroy = function(exception, cb) { if (this.server) { COUNTER_NET_SERVER_CONNECTION_CLOSE(this); + debug('has server'); this.server._connections--; if (this.server._emitCloseIfDrained) { this.server._emitCloseIfDrained(); @@ -371,10 +425,13 @@ Socket.prototype._destroy = function(exception, cb) { Socket.prototype.destroy = function(exception) { + debug('destroy', exception); this._destroy(exception); }; +// This function is called whenever the handle gets a +// buffer, or when there's an error reading. function onread(buffer, offset, length) { var handle = this; var self = handle.owner; @@ -383,47 +440,56 @@ function onread(buffer, offset, length) { timers.active(self); var end = offset + length; + debug('onread', global.errno, offset, length, end); if (buffer) { - // Emit 'data' event. + debug('got data'); - if (self._decoder) { - // Emit a string. - var string = self._decoder.write(buffer.slice(offset, end)); - if (string.length) self.emit('data', string); - } else { - // Emit a slice. Attempt to avoid slicing the buffer if no one is - // listening for 'data'. - if (self._events && self._events['data']) { - self.emit('data', buffer.slice(offset, end)); - } + // read success. + // In theory (and in practice) calling readStop right now + // will prevent this from being called again until _read() gets + // called again. + + // if we didn't get any bytes, that doesn't necessarily mean EOF. + // wait for the next one. + if (offset === end) { + debug('not any data, keep waiting'); + return; } + // if it's not enough data, we'll just call handle.readStart() + // again right away. self.bytesRead += length; + self._readableState.onread(null, buffer.slice(offset, end)); + + if (handle.reading && !self._readableState.reading) { + handle.reading = false; + debug('readStop'); + var r = handle.readStop(); + if (r) + self._destroy(errnoException(errno, 'read')); + } // Optimization: emit the original buffer with end points if (self.ondata) self.ondata(buffer, offset, end); } else if (errno == 'EOF') { - // EOF - self.readable = false; + debug('EOF'); - assert.ok(!(self._flags & FLAG_GOT_EOF)); - self._flags |= FLAG_GOT_EOF; + if (self._readableState.length === 0) + self.readable = false; - // We call destroy() before end(). 'close' not emitted until nextTick so - // the 'end' event will come first as required. - if (!self.writable) self._destroy(); + if (self.onend) self.once('end', self.onend); - if (!self.allowHalfOpen) self.end(); - if (self._decoder) { - var ret = self._decoder.end(); - if (ret) - self.emit('data', ret); - } - if (self._events && self._events['end']) self.emit('end'); - if (self.onend) self.onend(); + // send a null to the _read cb to signal the end of data. + self._readableState.onread(null, null); + + // internal end event so that we know that the actual socket + // is no longer readable, and we can start the shutdown + // procedure. No need to wait for all the data to be consumed. + self.emit('_socketEnd'); } else { + debug('error', errno); // Error if (errno == 'ECONNRESET') { self._destroy(); @@ -434,12 +500,6 @@ function onread(buffer, offset, length) { } -Socket.prototype.setEncoding = function(encoding) { - var StringDecoder = require('string_decoder').StringDecoder; // lazy load - this._decoder = new StringDecoder(encoding); -}; - - Socket.prototype._getpeername = function() { if (!this._handle || !this._handle.getpeername) { return {}; @@ -465,63 +525,39 @@ Socket.prototype.__defineGetter__('remotePort', function() { }); -/* - * Arguments data, [encoding], [cb] - */ -Socket.prototype.write = function(data, arg1, arg2) { - var encoding, cb; +Socket.prototype.write = function(chunk, encoding, cb) { + if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk)) + throw new TypeError('invalid data'); + return stream.Duplex.prototype.write.apply(this, arguments); +}; - // parse arguments - if (arg1) { - if (typeof arg1 === 'string') { - encoding = arg1; - cb = arg2; - } else if (typeof arg1 === 'function') { - cb = arg1; - } else { - throw new Error('bad arg'); - } - } - if (typeof data === 'string') { - encoding = (encoding || 'utf8').toLowerCase(); - switch (encoding) { - case 'utf8': - case 'utf-8': - case 'ascii': - case 'ucs2': - case 'ucs-2': - case 'utf16le': - case 'utf-16le': - // This encoding can be handled in the binding layer. - break; +Socket.prototype._write = function(dataEncoding, cb) { + assert(Array.isArray(dataEncoding)); + var data = dataEncoding[0]; + var encoding = dataEncoding[1] || 'utf8'; - default: - data = new Buffer(data, encoding); - } - } else if (!Buffer.isBuffer(data)) { - throw new TypeError('First argument must be a buffer or a string.'); - } + if (this !== process.stderr && this !== process.stdout) + debug('Socket._write'); // If we are still connecting, then buffer this for later. + // The Writable logic will buffer up any more writes while + // waiting for this one to be done. if (this._connecting) { - this._connectQueueSize += data.length; - if (this._connectQueue) { - this._connectQueue.push([data, encoding, cb]); - } else { - this._connectQueue = [[data, encoding, cb]]; - } - return false; + debug('_write: waiting for connection'); + this._pendingWrite = dataEncoding; + this.once('connect', function() { + debug('_write: connected now, try again'); + this._write(dataEncoding, cb); + }); + return; } + this._pendingWrite = null; - return this._write(data, encoding, cb); -}; - - -Socket.prototype._write = function(data, encoding, cb) { timers.active(this); if (!this._handle) { + debug('already destroyed'); this._destroy(new Error('This socket is closed.'), cb); return false; } @@ -550,39 +586,32 @@ Socket.prototype._write = function(data, encoding, cb) { break; default: - assert(0); + writeReq = this._handle.writeBuffer(new Buffer(data, encoding)); + break; } } - if (!writeReq || typeof writeReq !== 'object') { - this._destroy(errnoException(errno, 'write'), cb); - return false; - } + if (!writeReq || typeof writeReq !== 'object') + return this._destroy(errnoException(errno, 'write'), cb); writeReq.oncomplete = afterWrite; writeReq.cb = cb; - this._pendingWriteReqs++; this._bytesDispatched += writeReq.bytes; - - return this._handle.writeQueueSize == 0; }; Socket.prototype.__defineGetter__('bytesWritten', function() { var bytes = this._bytesDispatched, - connectQueue = this._connectQueue; + state = this._writableState, + pending = this._pendingWrite; - if (connectQueue) { - connectQueue.forEach(function(el) { - var data = el[0]; - if (Buffer.isBuffer(data)) { - bytes += data.length; - } else { - bytes += Buffer.byteLength(data, el[1]); - } - }, this); - } + state.buffer.forEach(function(el) { + bytes += Buffer.byteLength(el[0], el[1]); + }); + + if (pending) + bytes += Buffer.byteLength(pending[0], pending[1]); return bytes; }); @@ -590,30 +619,28 @@ Socket.prototype.__defineGetter__('bytesWritten', function() { function afterWrite(status, handle, req) { var self = handle.owner; + var state = self._writableState; + if (self !== process.stderr && self !== process.stdout) + debug('afterWrite', status, req); // callback may come after call to destroy. if (self.destroyed) { + debug('afterWrite destroyed'); return; } if (status) { + debug('write failure', errnoException(errno, 'write')); self._destroy(errnoException(errno, 'write'), req.cb); return; } timers.active(self); - self._pendingWriteReqs--; - - if (self._pendingWriteReqs == 0) { - self.emit('drain'); - } + if (self !== process.stderr && self !== process.stdout) + debug('afterWrite call cb'); - if (req.cb) req.cb(); - - if (self._pendingWriteReqs == 0 && self._flags & FLAG_DESTROY_SOON) { - self._destroy(); - } + req.cb.call(self); } @@ -663,10 +690,21 @@ Socket.prototype.connect = function(options, cb) { return Socket.prototype.connect.apply(this, args); } + if (this.destroyed) { + this._readableState.reading = false; + this._readableState.ended = false; + this._writableState.ended = false; + this._writableState.ending = false; + this._writableState.finished = false; + this._writableState.finishing = false; + this.destroyed = false; + this._handle = null; + } + var self = this; var pipe = !!options.path; - if (this.destroyed || !this._handle) { + if (!this._handle) { this._handle = pipe ? createPipe() : createTCP(); initSocketHandle(this); } @@ -755,28 +793,15 @@ function afterConnect(status, handle, req, readable, writable) { self.writable = writable; timers.active(self); - if (self.readable && !self._paused) { - handle.readStart(); - } - - if (self._connectQueue) { - debug('Drain the connect queue'); - var connectQueue = self._connectQueue; - for (var i = 0; i < connectQueue.length; i++) { - self._write.apply(self, connectQueue[i]); - } - self._connectQueueCleanUp(); - } - self.emit('connect'); - if (self._flags & FLAG_SHUTDOWN_QUEUED) { - // end called before connected - call end now with no data - self._flags &= ~FLAG_SHUTDOWN_QUEUED; - self.end(); - } + // start the first read, or get an immediate EOF. + // this doesn't actually consume any bytes, because len=0. + if (readable) + self.read(0); + } else { - self._connectQueueCleanUp(); + self._connecting = false; self._destroy(errnoException(errno, 'connect')); } } @@ -831,9 +856,9 @@ function Server(/* [ options, ] listener */) { configurable: true, enumerable: true }); - this.allowHalfOpen = options.allowHalfOpen || false; - this._handle = null; + + this.allowHalfOpen = options.allowHalfOpen || false; } util.inherits(Server, events.EventEmitter); exports.Server = Server; @@ -901,12 +926,14 @@ var createServerHandle = exports._createServerHandle = Server.prototype._listen2 = function(address, port, addressType, backlog, fd) { + debug('listen2', address, port, addressType, backlog); var self = this; var r = 0; // If there is not yet a handle, we need to create one and bind. // In the case of a server sent via IPC, we don't need to do this. if (!self._handle) { + debug('_listen2: create a handle'); self._handle = createServerHandle(address, port, addressType, fd); if (!self._handle) { var error = errnoException(errno, 'listen'); @@ -915,6 +942,8 @@ Server.prototype._listen2 = function(address, port, addressType, backlog, fd) { }); return; } + } else { + debug('_listen2: have a handle already'); } self._handle.onconnection = onconnection; @@ -1049,7 +1078,6 @@ function onconnection(clientHandle) { }); socket.readable = socket.writable = true; - clientHandle.readStart(); self._connections++; socket.server = self; @@ -1086,11 +1114,17 @@ Server.prototype.close = function(cb) { }; Server.prototype._emitCloseIfDrained = function() { + debug('SERVER _emitCloseIfDrained'); var self = this; - if (self._handle || self._connections) return; + if (self._handle || self._connections) { + debug('SERVER handle? %j connections? %d', + !!self._handle, self._connections); + return; + } process.nextTick(function() { + debug('SERVER: emit close'); self.emit('close'); }); }; diff --git a/lib/repl.js b/lib/repl.js index 55df83f970..3438a45cc1 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -69,8 +69,8 @@ module.paths = require('module')._nodeModulePaths(module.filename); exports.writer = util.inspect; exports._builtinLibs = ['assert', 'buffer', 'child_process', 'cluster', - 'crypto', 'dgram', 'dns', 'events', 'fs', 'http', 'https', 'net', - 'os', 'path', 'punycode', 'querystring', 'readline', 'repl', + 'crypto', 'dgram', 'dns', 'events', 'fs', 'http', 'https', 'net', 'os', + 'path', 'punycode', 'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'tls', 'tty', 'url', 'util', 'vm', 'zlib']; diff --git a/lib/stream.js b/lib/stream.js index 16e2e0e723..481d7644e5 100644 --- a/lib/stream.js +++ b/lib/stream.js @@ -19,17 +19,30 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. +module.exports = Stream; + var events = require('events'); var util = require('util'); -function Stream() { - events.EventEmitter.call(this); -} util.inherits(Stream, events.EventEmitter); -module.exports = Stream; +Stream.Readable = require('_stream_readable'); +Stream.Writable = require('_stream_writable'); +Stream.Duplex = require('_stream_duplex'); +Stream.Transform = require('_stream_transform'); +Stream.PassThrough = require('_stream_passthrough'); + // Backwards-compat with node 0.4.x Stream.Stream = Stream; + + +// old-style streams. Note that the pipe method (the only relevant +// part of this class) is overridden in the Readable class. + +function Stream() { + events.EventEmitter.call(this); +} + Stream.prototype.pipe = function(dest, options) { var source = this; diff --git a/lib/string_decoder.js b/lib/string_decoder.js index 31d4b24702..6b1e30895a 100644 --- a/lib/string_decoder.js +++ b/lib/string_decoder.js @@ -19,8 +19,15 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. +function assertEncoding(encoding) { + if (encoding && !Buffer.isEncoding(encoding)) { + throw new Error('Unknown encoding: ' + encoding); + } +} + var StringDecoder = exports.StringDecoder = function(encoding) { this.encoding = (encoding || 'utf8').toLowerCase().replace(/[-_]/, ''); + assertEncoding(encoding); switch (this.encoding) { case 'utf8': // CESU-8 represents each of Surrogate Pair by 3-bytes diff --git a/lib/tty.js b/lib/tty.js index 9fa18fd38c..5b4036d0db 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -40,42 +40,47 @@ exports.setRawMode = util.deprecate(function(flag) { }, 'tty.setRawMode: Use `process.stdin.setRawMode()` instead.'); -function ReadStream(fd) { - if (!(this instanceof ReadStream)) return new ReadStream(fd); - net.Socket.call(this, { +function ReadStream(fd, options) { + if (!(this instanceof ReadStream)) + return new ReadStream(fd, options); + + options = util._extend({ + highWaterMark: 0, + lowWaterMark: 0, handle: new TTY(fd, true) - }); + }, options); + + net.Socket.call(this, options); this.readable = true; this.writable = false; this.isRaw = false; + this.isTTY = true; + + // this.read = function(orig) { return function(n) { + // var ret = orig.apply(this, arguments); + // console.trace('TTY read(' + n + ') -> ' + ret); + // return ret; + // } }(this.read); } inherits(ReadStream, net.Socket); exports.ReadStream = ReadStream; -ReadStream.prototype.pause = function() { - return net.Socket.prototype.pause.call(this); -}; - -ReadStream.prototype.resume = function() { - return net.Socket.prototype.resume.call(this); -}; - ReadStream.prototype.setRawMode = function(flag) { flag = !!flag; this._handle.setRawMode(flag); this.isRaw = flag; }; -ReadStream.prototype.isTTY = true; - function WriteStream(fd) { if (!(this instanceof WriteStream)) return new WriteStream(fd); net.Socket.call(this, { - handle: new TTY(fd, false) + handle: new TTY(fd, false), + readable: false, + writable: true }); this.readable = false; diff --git a/lib/zlib.js b/lib/zlib.js index 9b56241146..bc3e9330f2 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -19,9 +19,10 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. +var Transform = require('_stream_transform'); + var binding = process.binding('zlib'); var util = require('util'); -var Stream = require('stream'); var assert = require('assert').ok; // zlib doesn't provide these, so kludge them in following the same @@ -138,33 +139,35 @@ function zlibBuffer(engine, buffer, callback) { var buffers = []; var nread = 0; + engine.on('error', onError); + engine.on('end', onEnd); + + engine.end(buffer); + flow(); + + function flow() { + var chunk; + while (null !== (chunk = engine.read())) { + buffers.push(chunk); + nread += chunk.length; + } + engine.once('readable', flow); + } + function onError(err) { engine.removeListener('end', onEnd); - engine.removeListener('error', onError); + engine.removeListener('readable', flow); callback(err); } - function onData(chunk) { - buffers.push(chunk); - nread += chunk.length; - } - function onEnd() { var buf = Buffer.concat(buffers, nread); buffers = []; callback(null, buf); } - - engine.on('error', onError); - engine.on('data', onData); - engine.on('end', onEnd); - - engine.write(buffer); - engine.end(); } - // generic zlib // minimal 2-byte header function Deflate(opts) { @@ -217,15 +220,13 @@ function Unzip(opts) { // you call the .write() method. function Zlib(opts, mode) { - Stream.call(this); - this._opts = opts = opts || {}; - this._queue = []; - this._processing = false; - this._ended = false; - this.readable = true; - this.writable = true; - this._flush = binding.Z_NO_FLUSH; + this._chunkSize = opts.chunkSize || exports.Z_DEFAULT_CHUNK; + + Transform.call(this, opts); + + // means a different thing there. + this._readableState.chunkSize = null; if (opts.chunkSize) { if (opts.chunkSize < exports.Z_MIN_CHUNK || @@ -274,13 +275,12 @@ function Zlib(opts, mode) { this._binding = new binding.Zlib(mode); var self = this; + this._hadError = false; this._binding.onerror = function(message, errno) { // there is no way to cleanly recover. // continuing only obscures problems. self._binding = null; self._hadError = true; - self._queue.length = 0; - self._processing = false; var error = new Error(message); error.errno = errno; @@ -294,7 +294,6 @@ function Zlib(opts, mode) { opts.strategy || exports.Z_DEFAULT_STRATEGY, opts.dictionary); - this._chunkSize = opts.chunkSize || exports.Z_DEFAULT_CHUNK; this._buffer = new Buffer(this._chunkSize); this._offset = 0; this._closed = false; @@ -302,59 +301,47 @@ function Zlib(opts, mode) { this.once('end', this.close); } -util.inherits(Zlib, Stream); - -Zlib.prototype.write = function write(chunk, cb) { - if (this._hadError) return true; - - if (this._ended) { - return this.emit('error', new Error('Cannot write after end')); - } - - if (arguments.length === 1 && typeof chunk === 'function') { - cb = chunk; - chunk = null; - } - - if (!chunk) { - chunk = null; - } else if (typeof chunk === 'string') { - chunk = new Buffer(chunk); - } else if (!Buffer.isBuffer(chunk)) { - return this.emit('error', new Error('Invalid argument')); - } - - - var empty = this._queue.length === 0; - - this._queue.push([chunk, cb]); - this._process(); - if (!empty) { - this._needDrain = true; - } - return empty; -}; +util.inherits(Zlib, Transform); Zlib.prototype.reset = function reset() { return this._binding.reset(); }; -Zlib.prototype.flush = function flush(cb) { - this._flush = binding.Z_SYNC_FLUSH; - return this.write(cb); +Zlib.prototype._flush = function(output, callback) { + var rs = this._readableState; + var self = this; + this._transform(null, output, function(er) { + if (er) + return callback(er); + + // now a weird thing happens... it could be that you called flush + // but everything had already actually been consumed, but it wasn't + // enough to get over the Readable class's lowWaterMark. + // In that case, we emit 'readable' now to make sure it's consumed. + if (rs.length && + rs.length < rs.lowWaterMark && + !rs.ended && + rs.needReadable) + self.emit('readable'); + + callback(); + }); }; -Zlib.prototype.end = function end(chunk, cb) { - if (this._hadError) return true; +Zlib.prototype.flush = function(callback) { + var ws = this._writableState; + var ts = this._transformState; - var self = this; - this._ending = true; - var ret = this.write(chunk, function() { - self.emit('end'); - if (cb) cb(); - }); - this._ended = true; - return ret; + if (ws.writing) { + ws.needDrain = true; + var self = this; + this.once('drain', function() { + self._flush(ts.output, callback); + }); + return; + } + + this._flush(ts.output, callback || function() {}); }; Zlib.prototype.close = function(callback) { @@ -368,37 +355,37 @@ Zlib.prototype.close = function(callback) { this._binding.close(); - process.nextTick(this.emit.bind(this, 'close')); + var self = this; + process.nextTick(function() { + self.emit('close'); + }); }; -Zlib.prototype._process = function() { - if (this._hadError) return; - - if (this._processing || this._paused) return; - - if (this._queue.length === 0) { - if (this._needDrain) { - this._needDrain = false; - this.emit('drain'); - } - // nothing to do, waiting for more data at this point. - return; - } - - var req = this._queue.shift(); - var cb = req.pop(); - var chunk = req.pop(); - - if (this._ending && this._queue.length === 0) { - this._flush = binding.Z_FINISH; - } +Zlib.prototype._transform = function(chunk, output, cb) { + var flushFlag; + var ws = this._writableState; + var ending = ws.ending || ws.ended; + var last = ending && (!chunk || ws.length === chunk.length); + + if (chunk !== null && !Buffer.isBuffer(chunk)) + return cb(new Error('invalid input')); + + // If it's the last chunk, or a final flush, we use the Z_FINISH flush flag. + // If it's explicitly flushing at some other time, then we use + // Z_FULL_FLUSH. Otherwise, use Z_NO_FLUSH for maximum compression + // goodness. + if (last) + flushFlag = binding.Z_FINISH; + else if (chunk === null) + flushFlag = binding.Z_FULL_FLUSH; + else + flushFlag = binding.Z_NO_FLUSH; - var self = this; var availInBefore = chunk && chunk.length; var availOutBefore = this._chunkSize - this._offset; - var inOff = 0; - var req = this._binding.write(this._flush, + + var req = this._binding.write(flushFlag, chunk, // in inOff, // in_off availInBefore, // in_len @@ -408,23 +395,23 @@ Zlib.prototype._process = function() { req.buffer = chunk; req.callback = callback; - this._processing = req; + var self = this; function callback(availInAfter, availOutAfter, buffer) { - if (self._hadError) return; + if (self._hadError) + return; var have = availOutBefore - availOutAfter; - assert(have >= 0, 'have should not go down'); if (have > 0) { var out = self._buffer.slice(self._offset, self._offset + have); self._offset += have; - self.emit('data', out); + // serve some output to the consumer. + output(out); } - // XXX Maybe have a 'min buffer' size so we don't dip into the - // thread pool with only 1 byte available or something? + // exhausted the output buffer, or used all the input create a new one. if (availOutAfter === 0 || self._offset >= self._chunkSize) { availOutBefore = self._chunkSize; self._offset = 0; @@ -439,7 +426,7 @@ Zlib.prototype._process = function() { inOff += (availInBefore - availInAfter); availInBefore = availInAfter; - var newReq = self._binding.write(self._flush, + var newReq = self._binding.write(flushFlag, chunk, inOff, availInBefore, @@ -448,34 +435,14 @@ Zlib.prototype._process = function() { self._chunkSize); newReq.callback = callback; // this same function newReq.buffer = chunk; - self._processing = newReq; return; } // finished with the chunk. - self._processing = false; - if (cb) cb(); - self._process(); + cb(); } }; -Zlib.prototype.pause = function() { - this._paused = true; - this.emit('pause'); -}; - -Zlib.prototype.resume = function() { - this._paused = false; - this._process(); -}; - -Zlib.prototype.destroy = function() { - this.readable = false; - this.writable = false; - this._ended = true; - this.emit('close'); -}; - util.inherits(Deflate, Zlib); util.inherits(Inflate, Zlib); util.inherits(Gzip, Zlib); diff --git a/node.gyp b/node.gyp index f6651db83d..14058eb960 100644 --- a/node.gyp +++ b/node.gyp @@ -44,6 +44,11 @@ 'lib/readline.js', 'lib/repl.js', 'lib/stream.js', + 'lib/_stream_readable.js', + 'lib/_stream_writable.js', + 'lib/_stream_duplex.js', + 'lib/_stream_transform.js', + 'lib/_stream_passthrough.js', 'lib/string_decoder.js', 'lib/sys.js', 'lib/timers.js', diff --git a/src/node.js b/src/node.js index 7f762cd413..76c94b7592 100644 --- a/src/node.js +++ b/src/node.js @@ -140,7 +140,6 @@ } else { // Read all of stdin - execute it. - process.stdin.resume(); process.stdin.setEncoding('utf8'); var code = ''; @@ -497,17 +496,20 @@ switch (tty_wrap.guessHandleType(fd)) { case 'TTY': var tty = NativeModule.require('tty'); - stdin = new tty.ReadStream(fd); + stdin = new tty.ReadStream(fd, { + highWaterMark: 0, + lowWaterMark: 0 + }); break; case 'FILE': var fs = NativeModule.require('fs'); - stdin = new fs.ReadStream(null, {fd: fd}); + stdin = new fs.ReadStream(null, { fd: fd }); break; case 'PIPE': var net = NativeModule.require('net'); - stdin = new net.Stream(fd); + stdin = new net.Stream({ fd: fd }); stdin.readable = true; break; @@ -520,16 +522,23 @@ stdin.fd = fd; // stdin starts out life in a paused state, but node doesn't - // know yet. Call pause() explicitly to unref() it. - stdin.pause(); - - // when piping stdin to a destination stream, - // let the data begin to flow. - var pipe = stdin.pipe; - stdin.pipe = function(dest, opts) { - stdin.resume(); - return pipe.call(stdin, dest, opts); - }; + // know yet. Explicitly to readStop() it to put it in the + // not-reading state. + if (stdin._handle && stdin._handle.readStop) { + stdin._handle.reading = false; + stdin._readableState.reading = false; + stdin._handle.readStop(); + } + + // if the user calls stdin.pause(), then we need to stop reading + // immediately, so that the process can close down. + stdin.on('pause', function() { + if (!stdin._handle) + return; + stdin._readableState.reading = false; + stdin._handle.reading = false; + stdin._handle.readStop(); + }); return stdin; }); @@ -701,8 +710,8 @@ var nativeModule = new NativeModule(id); - nativeModule.compile(); nativeModule.cache(); + nativeModule.compile(); return nativeModule.exports; }; diff --git a/src/node_zlib.cc b/src/node_zlib.cc index 13f94e9020..881b20ce62 100644 --- a/src/node_zlib.cc +++ b/src/node_zlib.cc @@ -109,7 +109,19 @@ class ZCtx : public ObjectWrap { assert(!ctx->write_in_progress_ && "write already in progress"); ctx->write_in_progress_ = true; + assert(!args[0]->IsUndefined() && "must provide flush value"); + unsigned int flush = args[0]->Uint32Value(); + + if (flush != Z_NO_FLUSH && + flush != Z_PARTIAL_FLUSH && + flush != Z_SYNC_FLUSH && + flush != Z_FULL_FLUSH && + flush != Z_FINISH && + flush != Z_BLOCK) { + assert(0 && "Invalid flush value"); + } + Bytef *in; Bytef *out; size_t in_off, in_len, out_off, out_len; @@ -483,6 +495,7 @@ void InitZlib(Handle target) { callback_sym = NODE_PSYMBOL("callback"); onerror_sym = NODE_PSYMBOL("onerror"); + // valid flush values. NODE_DEFINE_CONSTANT(target, Z_NO_FLUSH); NODE_DEFINE_CONSTANT(target, Z_PARTIAL_FLUSH); NODE_DEFINE_CONSTANT(target, Z_SYNC_FLUSH); diff --git a/test/fixtures/x1024.txt b/test/fixtures/x1024.txt new file mode 100644 index 0000000000..c6a9d2f1a5 --- /dev/null +++ b/test/fixtures/x1024.txt @@ -0,0 +1 @@ +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ No newline at end of file diff --git a/test/message/max_tick_depth_trace.out b/test/message/max_tick_depth_trace.out index 17cb7a7296..0fab6cd2c7 100644 --- a/test/message/max_tick_depth_trace.out +++ b/test/message/max_tick_depth_trace.out @@ -8,10 +8,10 @@ tick 14 tick 13 tick 12 Trace: (node) warning: Recursive process.nextTick detected. This will break in the next version of node. Please use setImmediate for recursive deferral. - at maxTickWarn (node.js:289:17) - at process.nextTick (node.js:362:9) - at f (*test*message*max_tick_depth_trace.js:30:13) - at process._tickCallback (node.js:335:13) + at maxTickWarn (node.js:*:*) + at process.nextTick (node.js:*:* + at f (*test*message*max_tick_depth_trace.js:*:*) + at process._tickCallback (node.js:*:*) tick 11 tick 10 tick 9 @@ -23,9 +23,9 @@ tick 4 tick 3 tick 2 Trace: (node) warning: Recursive process.nextTick detected. This will break in the next version of node. Please use setImmediate for recursive deferral. - at maxTickWarn (node.js:289:17) - at process.nextTick (node.js:362:9) - at f (*test*message*max_tick_depth_trace.js:30:13) - at process._tickCallback (node.js:335:13) + at maxTickWarn (node.js:*:*) + at process.nextTick (node.js:*:* + at f (*test*message*max_tick_depth_trace.js:*:*) + at process._tickCallback (node.js:*:*) tick 1 tick 0 diff --git a/test/message/stdin_messages.out b/test/message/stdin_messages.out index e1a3790ca6..b0ad45bda4 100644 --- a/test/message/stdin_messages.out +++ b/test/message/stdin_messages.out @@ -9,7 +9,8 @@ SyntaxError: Strict mode code may not include a with statement at evalScript (node.js:*:*) at Socket. (node.js:*:*) at Socket.EventEmitter.emit (events.js:*:*) - at Pipe.onread (net.js:*:*) + at _stream_readable.js:*:* + at process._tickCallback (node.js:*:*) at process._makeCallback (node.js:*:*) 42 42 @@ -18,26 +19,28 @@ SyntaxError: Strict mode code may not include a with statement throw new Error("hello") ^ Error: hello - at [stdin]:1:7 + at [stdin]:1:* at Object. ([stdin]-wrapper:*:*) at Module._compile (module.js:*:*) at evalScript (node.js:*:*) at Socket. (node.js:*:*) at Socket.EventEmitter.emit (events.js:*:*) - at Pipe.onread (net.js:*:*) + at _stream_readable.js:*:* + at process._tickCallback (node.js:*:*) at process._makeCallback (node.js:*:*) [stdin]:1 throw new Error("hello") ^ Error: hello - at [stdin]:1:7 + at [stdin]:1:* at Object. ([stdin]-wrapper:*:*) at Module._compile (module.js:*:*) at evalScript (node.js:*:*) at Socket. (node.js:*:*) at Socket.EventEmitter.emit (events.js:*:*) - at Pipe.onread (net.js:*:*) + at _stream_readable.js:*:* + at process._tickCallback (node.js:*:*) at process._makeCallback (node.js:*:*) 100 @@ -51,7 +54,8 @@ ReferenceError: y is not defined at evalScript (node.js:*:*) at Socket. (node.js:*:*) at Socket.EventEmitter.emit (events.js:*:*) - at Pipe.onread (net.js:*:*) + at _stream_readable.js:*:* + at process._tickCallback (node.js:*:*) at process._makeCallback (node.js:*:*) [stdin]:1 diff --git a/test/simple/test-child-process-disconnect.js b/test/simple/test-child-process-disconnect.js index 2136aaf3e6..162e7dde8b 100644 --- a/test/simple/test-child-process-disconnect.js +++ b/test/simple/test-child-process-disconnect.js @@ -31,6 +31,8 @@ if (process.argv[2] === 'child') { server.on('connection', function(socket) { + socket.resume(); + process.on('disconnect', function() { socket.end((process.connected).toString()); }); diff --git a/test/simple/test-child-process-fork-net2.js b/test/simple/test-child-process-fork-net2.js index 48713566a6..be749fe319 100644 --- a/test/simple/test-child-process-fork-net2.js +++ b/test/simple/test-child-process-fork-net2.js @@ -23,31 +23,59 @@ var assert = require('assert'); var common = require('../common'); var fork = require('child_process').fork; var net = require('net'); +var count = 12; if (process.argv[2] === 'child') { - var endMe = null; + var needEnd = []; process.on('message', function(m, socket) { if (!socket) return; + console.error('got socket', m); + // will call .end('end') or .write('write'); socket[m](m); + socket.resume(); + + socket.on('data', function() { + console.error('%d socket.data', process.pid, m); + }); + + socket.on('end', function() { + console.error('%d socket.end', process.pid, m); + }); + // store the unfinished socket if (m === 'write') { - endMe = socket; + needEnd.push(socket); } + + socket.on('close', function() { + console.error('%d socket.close', process.pid, m); + }); + + socket.on('finish', function() { + console.error('%d socket finished', process.pid, m); + }); }); process.on('message', function(m) { if (m !== 'close') return; - endMe.end('end'); - endMe = null; + console.error('got close message'); + needEnd.forEach(function(endMe, i) { + console.error('%d ending %d', process.pid, i); + endMe.end('end'); + }); }); process.on('disconnect', function() { - endMe.end('end'); + console.error('%d process disconnect, ending', process.pid); + needEnd.forEach(function(endMe, i) { + console.error('%d ending %d', process.pid, i); + endMe.end('end'); + }); endMe = null; }); @@ -61,7 +89,7 @@ if (process.argv[2] === 'child') { var connected = 0; server.on('connection', function(socket) { - switch (connected) { + switch (connected % 6) { case 0: child1.send('end', socket); break; case 1: @@ -77,7 +105,7 @@ if (process.argv[2] === 'child') { } connected += 1; - if (connected === 6) { + if (connected === count) { closeServer(); } }); @@ -85,17 +113,23 @@ if (process.argv[2] === 'child') { var disconnected = 0; server.on('listening', function() { - var j = 6, client; + var j = count, client; while (j--) { client = net.connect(common.PORT, '127.0.0.1'); client.on('close', function() { + console.error('CLIENT: close event in master'); disconnected += 1; }); + // XXX This resume() should be unnecessary. + // a stream high water mark should be enough to keep + // consuming the input. + client.resume(); } }); var closeEmitted = false; server.on('close', function() { + console.error('server close'); closeEmitted = true; child1.kill(); @@ -107,14 +141,18 @@ if (process.argv[2] === 'child') { var timeElasped = 0; var closeServer = function() { + console.error('closeServer'); var startTime = Date.now(); server.on('close', function() { + console.error('emit(close)'); timeElasped = Date.now() - startTime; }); + console.error('calling server.close'); server.close(); setTimeout(function() { + console.error('sending close to children'); child1.send('close'); child2.send('close'); child3.disconnect(); @@ -122,8 +160,8 @@ if (process.argv[2] === 'child') { }; process.on('exit', function() { - assert.equal(disconnected, 6); - assert.equal(connected, 6); + assert.equal(disconnected, count); + assert.equal(connected, count); assert.ok(closeEmitted); assert.ok(timeElasped >= 190 && timeElasped <= 1000, 'timeElasped was not between 190 and 1000 ms'); diff --git a/test/simple/test-child-process-ipc.js b/test/simple/test-child-process-ipc.js index 8ccc505a82..e8144e439d 100644 --- a/test/simple/test-child-process-ipc.js +++ b/test/simple/test-child-process-ipc.js @@ -42,10 +42,13 @@ child.stdout.setEncoding('utf8'); child.stdout.on('data', function(data) { console.log('child said: ' + JSON.stringify(data)); if (!gotHelloWorld) { + console.error('testing for hello world'); assert.equal('hello world\r\n', data); gotHelloWorld = true; + console.error('writing echo me'); child.stdin.write('echo me\r\n'); } else { + console.error('testing for echo me'); assert.equal('echo me\r\n', data); gotEcho = true; child.stdin.end(); diff --git a/test/simple/test-cluster-http-pipe.js b/test/simple/test-cluster-http-pipe.js index 46d429ad6d..7123bf6275 100644 --- a/test/simple/test-cluster-http-pipe.js +++ b/test/simple/test-cluster-http-pipe.js @@ -53,6 +53,7 @@ http.createServer(function(req, res) { }).listen(common.PIPE, function() { var self = this; http.get({ socketPath: common.PIPE, path: '/' }, function(res) { + res.resume(); res.on('end', function(err) { if (err) throw err; process.send('DONE'); diff --git a/test/simple/test-cluster-message.js b/test/simple/test-cluster-message.js index 313355f7f6..3a76dbc740 100644 --- a/test/simple/test-cluster-message.js +++ b/test/simple/test-cluster-message.js @@ -81,6 +81,7 @@ else if (cluster.isMaster) { var check = function(type, result) { checks[type].receive = true; checks[type].correct = result; + console.error('check', checks); var missing = false; forEach(checks, function(type) { @@ -88,6 +89,7 @@ else if (cluster.isMaster) { }); if (missing === false) { + console.error('end client'); client.end(); } }; diff --git a/test/simple/test-crypto.js b/test/simple/test-crypto.js index 6012671231..8f5043ef73 100644 --- a/test/simple/test-crypto.js +++ b/test/simple/test-crypto.js @@ -230,15 +230,20 @@ var rfc4231 = [ for (var i = 0, l = rfc4231.length; i < l; i++) { for (var hash in rfc4231[i]['hmac']) { + var str = crypto.createHmac(hash, rfc4231[i].key); + str.end(rfc4231[i].data); + var strRes = str.read().toString('hex'); var result = crypto.createHmac(hash, rfc4231[i]['key']) .update(rfc4231[i]['data']) .digest('hex'); if (rfc4231[i]['truncate']) { result = result.substr(0, 32); // first 128 bits == 32 hex chars + strRes = strRes.substr(0, 32); } assert.equal(rfc4231[i]['hmac'][hash], result, 'Test HMAC-' + hash + ': Test case ' + (i + 1) + ' rfc 4231'); + assert.equal(strRes, result, 'Should get same result from stream'); } } @@ -373,6 +378,18 @@ var a2 = crypto.createHash('sha256').update('Test123').digest('base64'); var a3 = crypto.createHash('sha512').update('Test123').digest(); // binary var a4 = crypto.createHash('sha1').update('Test123').digest('buffer'); +// stream interface +var a5 = crypto.createHash('sha512'); +a5.end('Test123'); +a5 = a5.read(); + +var a6 = crypto.createHash('sha512'); +a6.write('Te'); +a6.write('st'); +a6.write('123'); +a6.end(); +a6 = a6.read(); + assert.equal(a0, '8308651804facb7b9af8ffc53a33a22d6a1c8ac2', 'Test SHA1'); assert.equal(a1, 'h\u00ea\u00cb\u0097\u00d8o\fF!\u00fa+\u000e\u0017\u00ca' + '\u00bd\u008c', 'Test MD5 as binary'); @@ -392,6 +409,10 @@ assert.deepEqual(a4, new Buffer('8308651804facb7b9af8ffc53a33a22d6a1c8ac2', 'hex'), 'Test SHA1'); +// stream interface should produce the same result. +assert.deepEqual(a5, a3, 'stream interface is consistent'); +assert.deepEqual(a6, a3, 'stream interface is consistent'); + // Test multiple updates to same hash var h1 = crypto.createHash('sha1').update('Test123').digest('hex'); var h2 = crypto.createHash('sha1').update('Test').update('123').digest('hex'); @@ -419,6 +440,11 @@ assert.throws(function() { var s1 = crypto.createSign('RSA-SHA1') .update('Test123') .sign(keyPem, 'base64'); +var s1stream = crypto.createSign('RSA-SHA1'); +s1stream.end('Test123'); +s1stream = s1stream.sign(keyPem, 'base64'); +assert.equal(s1, s1stream, 'Stream produces same output'); + var verified = crypto.createVerify('RSA-SHA1') .update('Test') .update('123') @@ -427,13 +453,25 @@ assert.strictEqual(verified, true, 'sign and verify (base 64)'); var s2 = crypto.createSign('RSA-SHA256') .update('Test123') - .sign(keyPem); // binary + .sign(keyPem, 'binary'); +var s2stream = crypto.createSign('RSA-SHA256'); +s2stream.end('Test123'); +s2stream = s2stream.sign(keyPem, 'binary'); +assert.equal(s2, s2stream, 'Stream produces same output'); + var verified = crypto.createVerify('RSA-SHA256') .update('Test') .update('123') - .verify(certPem, s2); // binary + .verify(certPem, s2, 'binary'); assert.strictEqual(verified, true, 'sign and verify (binary)'); +var verStream = crypto.createVerify('RSA-SHA256'); +verStream.write('Tes'); +verStream.write('t12'); +verStream.end('3'); +verified = verStream.verify(certPem, s2, 'binary'); +assert.strictEqual(verified, true, 'sign and verify (stream)'); + var s3 = crypto.createSign('RSA-SHA1') .update('Test123') .sign(keyPem, 'buffer'); @@ -443,6 +481,13 @@ var verified = crypto.createVerify('RSA-SHA1') .verify(certPem, s3); assert.strictEqual(verified, true, 'sign and verify (buffer)'); +var verStream = crypto.createVerify('RSA-SHA1'); +verStream.write('Tes'); +verStream.write('t12'); +verStream.end('3'); +verified = verStream.verify(certPem, s3); +assert.strictEqual(verified, true, 'sign and verify (stream)'); + function testCipher1(key) { // Test encryption and decryption @@ -460,6 +505,20 @@ function testCipher1(key) { txt += decipher.final('utf8'); assert.equal(txt, plaintext, 'encryption and decryption'); + + // streaming cipher interface + // NB: In real life, it's not guaranteed that you can get all of it + // in a single read() like this. But in this case, we know it's + // quite small, so there's no harm. + var cStream = crypto.createCipher('aes192', key); + cStream.end(plaintext); + ciph = cStream.read(); + + var dStream = crypto.createDecipher('aes192', key); + dStream.end(ciph); + txt = dStream.read().toString('utf8'); + + assert.equal(txt, plaintext, 'encryption and decryption with streams'); } @@ -500,6 +559,20 @@ function testCipher3(key, iv) { txt += decipher.final('utf8'); assert.equal(txt, plaintext, 'encryption and decryption with key and iv'); + + // streaming cipher interface + // NB: In real life, it's not guaranteed that you can get all of it + // in a single read() like this. But in this case, we know it's + // quite small, so there's no harm. + var cStream = crypto.createCipheriv('des-ede3-cbc', key, iv); + cStream.end(plaintext); + ciph = cStream.read(); + + var dStream = crypto.createDecipheriv('des-ede3-cbc', key, iv); + dStream.end(ciph); + txt = dStream.read().toString('utf8'); + + assert.equal(txt, plaintext, 'streaming cipher iv'); } diff --git a/test/simple/test-domain-http-server.js b/test/simple/test-domain-http-server.js index f9962d3b8d..666f5d190a 100644 --- a/test/simple/test-domain-http-server.js +++ b/test/simple/test-domain-http-server.js @@ -33,12 +33,13 @@ var disposeEmit = 0; var server = http.createServer(function(req, res) { var dom = domain.create(); + req.resume(); dom.add(req); dom.add(res); dom.on('error', function(er) { serverCaught++; - console.log('server error', er); + console.log('horray! got a server error', er); // try to send a 500. If that fails, oh well. res.writeHead(500, {'content-type':'text/plain'}); res.end(er.stack || er.message || 'Unknown error'); @@ -81,12 +82,7 @@ function next() { dom.on('error', function(er) { clientCaught++; console.log('client error', er); - // kill everything. - dom.dispose(); - }); - - dom.on('dispose', function() { - disposeEmit += 1; + req.socket.destroy(); }); var req = http.get({ host: 'localhost', port: common.PORT, path: p }); @@ -106,6 +102,7 @@ function next() { d += c; }); res.on('end', function() { + console.error('trying to parse json', d); d = JSON.parse(d); console.log('json!', d); }); @@ -116,6 +113,5 @@ function next() { process.on('exit', function() { assert.equal(serverCaught, 2); assert.equal(clientCaught, 2); - assert.equal(disposeEmit, 2); console.log('ok'); }); diff --git a/test/simple/test-file-write-stream.js b/test/simple/test-file-write-stream.js index 5d2286cbd7..afedc5b521 100644 --- a/test/simple/test-file-write-stream.js +++ b/test/simple/test-file-write-stream.js @@ -22,46 +22,50 @@ var common = require('../common'); var assert = require('assert'); -var path = require('path'), - fs = require('fs'), - fn = path.join(common.tmpDir, 'write.txt'), - file = fs.createWriteStream(fn), +var path = require('path'); +var fs = require('fs'); +var fn = path.join(common.tmpDir, 'write.txt'); +var file = fs.createWriteStream(fn, { + lowWaterMark: 0, + highWaterMark: 10 + }); - EXPECTED = '012345678910', +var EXPECTED = '012345678910'; - callbacks = { +var callbacks = { open: -1, drain: -2, - close: -1, - endCb: -1 + close: -1 }; file .on('open', function(fd) { + console.error('open!'); callbacks.open++; assert.equal('number', typeof fd); }) .on('error', function(err) { throw err; + console.error('error!', err.stack); }) .on('drain', function() { + console.error('drain!', callbacks.drain); callbacks.drain++; if (callbacks.drain == -1) { - assert.equal(EXPECTED, fs.readFileSync(fn)); + assert.equal(EXPECTED, fs.readFileSync(fn, 'utf8')); file.write(EXPECTED); } else if (callbacks.drain == 0) { - assert.equal(EXPECTED + EXPECTED, fs.readFileSync(fn)); - file.end(function(err) { - assert.ok(!err); - callbacks.endCb++; - }); + assert.equal(EXPECTED + EXPECTED, fs.readFileSync(fn, 'utf8')); + file.end(); } }) .on('close', function() { + console.error('close!'); assert.strictEqual(file.bytesWritten, EXPECTED.length * 2); callbacks.close++; assert.throws(function() { + console.error('write after end should not be allowed'); file.write('should not work anymore'); }); @@ -70,7 +74,7 @@ file for (var i = 0; i < 11; i++) { (function(i) { - assert.strictEqual(false, file.write(i)); + file.write('' + i); })(i); } @@ -78,4 +82,5 @@ process.on('exit', function() { for (var k in callbacks) { assert.equal(0, callbacks[k], k + ' count off by ' + callbacks[k]); } + console.log('ok'); }); diff --git a/test/simple/test-file-write-stream2.js b/test/simple/test-file-write-stream2.js index 9b5d7ffef0..4f2e73ce82 100644 --- a/test/simple/test-file-write-stream2.js +++ b/test/simple/test-file-write-stream2.js @@ -22,18 +22,18 @@ var common = require('../common'); var assert = require('assert'); -var path = require('path'), - fs = require('fs'), - util = require('util'); +var path = require('path'); +var fs = require('fs'); +var util = require('util'); -var filepath = path.join(common.tmpDir, 'write.txt'), - file; +var filepath = path.join(common.tmpDir, 'write.txt'); +var file; var EXPECTED = '012345678910'; -var cb_expected = 'write open drain write drain close error ', - cb_occurred = ''; +var cb_expected = 'write open drain write drain close error '; +var cb_occurred = ''; var countDrains = 0; @@ -47,6 +47,8 @@ process.on('exit', function() { assert.strictEqual(cb_occurred, cb_expected, 'events missing or out of order: "' + cb_occurred + '" !== "' + cb_expected + '"'); + } else { + console.log('ok'); } }); @@ -59,22 +61,30 @@ function removeTestFile() { removeTestFile(); -file = fs.createWriteStream(filepath); +// drain at 0, return false at 10. +file = fs.createWriteStream(filepath, { + lowWaterMark: 0, + highWaterMark: 11 +}); file.on('open', function(fd) { + console.error('open'); cb_occurred += 'open '; assert.equal(typeof fd, 'number'); }); file.on('drain', function() { + console.error('drain'); cb_occurred += 'drain '; ++countDrains; if (countDrains === 1) { - assert.equal(fs.readFileSync(filepath), EXPECTED); - file.write(EXPECTED); + console.error('drain=1, write again'); + assert.equal(fs.readFileSync(filepath, 'utf8'), EXPECTED); + console.error('ondrain write ret=%j', file.write(EXPECTED)); cb_occurred += 'write '; } else if (countDrains == 2) { - assert.equal(fs.readFileSync(filepath), EXPECTED + EXPECTED); + console.error('second drain, end'); + assert.equal(fs.readFileSync(filepath, 'utf8'), EXPECTED + EXPECTED); file.end(); } }); @@ -88,11 +98,15 @@ file.on('close', function() { file.on('error', function(err) { cb_occurred += 'error '; - assert.ok(err.message.indexOf('not writable') >= 0); + assert.ok(err.message.indexOf('write after end') >= 0); }); for (var i = 0; i < 11; i++) { - assert.strictEqual(file.write(i), false); + var ret = file.write(i + ''); + console.error('%d %j', i, ret); + + // return false when i hits 10 + assert(ret === (i != 10)); } cb_occurred += 'write '; diff --git a/test/simple/test-fs-empty-readStream.js b/test/simple/test-fs-empty-readStream.js index a9b378fea1..d181c21982 100644 --- a/test/simple/test-fs-empty-readStream.js +++ b/test/simple/test-fs-empty-readStream.js @@ -32,12 +32,13 @@ fs.open(emptyFile, 'r', function (error, fd) { var read = fs.createReadStream(emptyFile, { 'fd': fd }); read.once('data', function () { - throw new Error("data event should not emit"); + throw new Error('data event should not emit'); }); var readEmit = false; read.once('end', function () { readEmit = true; + console.error('end event 1'); }); setTimeout(function () { @@ -52,12 +53,13 @@ fs.open(emptyFile, 'r', function (error, fd) { read.pause(); read.once('data', function () { - throw new Error("data event should not emit"); + throw new Error('data event should not emit'); }); var readEmit = false; read.once('end', function () { readEmit = true; + console.error('end event 2'); }); setTimeout(function () { diff --git a/test/simple/test-fs-read-stream-err.js b/test/simple/test-fs-read-stream-err.js index 2c285f183c..77960f06e0 100644 --- a/test/simple/test-fs-read-stream-err.js +++ b/test/simple/test-fs-read-stream-err.js @@ -23,28 +23,42 @@ var common = require('../common'); var assert = require('assert'); var fs = require('fs'); -var stream = fs.createReadStream(__filename, { bufferSize: 64 }); +var stream = fs.createReadStream(__filename, { + bufferSize: 64, + lowWaterMark: 0 +}); var err = new Error('BAM'); -stream.on('data', function(buf) { - var fd = stream.fd; +stream.on('error', common.mustCall(function errorHandler(err_) { + console.error('error event'); + process.nextTick(function() { + assert.equal(stream.fd, null); + assert.equal(err_, err); + }); +})); + +fs.close = common.mustCall(function(fd_, cb) { + assert.equal(fd_, stream.fd); + process.nextTick(cb); +}); +var read = fs.read; +fs.read = function() { + // first time is ok. + read.apply(fs, arguments); + // then it breaks fs.read = function() { var cb = arguments[arguments.length - 1]; process.nextTick(function() { cb(err); }); + // and should not be called again! + fs.read = function() { + throw new Error('BOOM!'); + }; }; +}; - fs.close = common.mustCall(function(fd_, cb) { - assert.equal(fd_, fd); - process.nextTick(cb); - }); - - stream.on('error', common.mustCall(function(err_) { - assert.equal(stream.fd, null); - assert.equal(err_, err); - })); - +stream.on('data', function(buf) { stream.on('data', assert.fail); // no more 'data' events should follow }); diff --git a/test/simple/test-fs-read-stream.js b/test/simple/test-fs-read-stream.js index 71cff2c059..a88802b654 100644 --- a/test/simple/test-fs-read-stream.js +++ b/test/simple/test-fs-read-stream.js @@ -60,12 +60,10 @@ file.on('data', function(data) { paused = true; file.pause(); - assert.ok(file.paused); setTimeout(function() { paused = false; file.resume(); - assert.ok(!file.paused); }, 10); }); @@ -77,7 +75,6 @@ file.on('end', function(chunk) { file.on('close', function() { callbacks.close++; - assert.ok(!file.readable); //assert.equal(fs.readFileSync(fn), fileContent); }); @@ -104,6 +101,7 @@ process.on('exit', function() { assert.equal(2, callbacks.close); assert.equal(30000, file.length); assert.equal(10000, file3.length); + console.error('ok'); }); var file4 = fs.createReadStream(rangeFile, {bufferSize: 1, start: 1, end: 2}); diff --git a/test/simple/test-fs-write-stream-end.js b/test/simple/test-fs-write-stream-end.js index 6c0f29c344..2a85ac3eea 100644 --- a/test/simple/test-fs-write-stream-end.js +++ b/test/simple/test-fs-write-stream-end.js @@ -31,7 +31,8 @@ var writeEndOk = false; var file = path.join(common.tmpDir, 'write-end-test.txt'); var stream = fs.createWriteStream(file); - stream.end('a\n', 'utf8', function() { + stream.end('a\n', 'utf8'); + stream.on('close', function() { var content = fs.readFileSync(file, 'utf8'); assert.equal(content, 'a\n'); writeEndOk = true; diff --git a/test/simple/test-fs-write-stream-err.js b/test/simple/test-fs-write-stream-err.js index 94d249f896..a4d20200e5 100644 --- a/test/simple/test-fs-write-stream-err.js +++ b/test/simple/test-fs-write-stream-err.js @@ -23,30 +23,50 @@ var common = require('../common'); var assert = require('assert'); var fs = require('fs'); -var stream = fs.createWriteStream(common.tmpDir + '/out'); +var stream = fs.createWriteStream(common.tmpDir + '/out', { + lowWaterMark: 0, + highWaterMark: 10 +}); var err = new Error('BAM'); -stream.write(new Buffer(256), function() { - var fd = stream.fd; +var write = fs.write; +var writeCalls = 0; +fs.write = function() { + switch (writeCalls++) { + case 0: + console.error('first write'); + // first time is ok. + return write.apply(fs, arguments); + case 1: + // then it breaks + console.error('second write'); + var cb = arguments[arguments.length - 1]; + return process.nextTick(function() { + cb(err); + }); + default: + // and should not be called again! + throw new Error('BOOM!'); + } +}; - fs.write = function() { - var cb = arguments[arguments.length - 1]; - process.nextTick(function() { - cb(err); - }); - }; +fs.close = common.mustCall(function(fd_, cb) { + console.error('fs.close', fd_, stream.fd); + assert.equal(fd_, stream.fd); + process.nextTick(cb); +}); - fs.close = function(fd_, cb) { - assert.equal(fd_, fd); - process.nextTick(cb); - }; +stream.on('error', common.mustCall(function(err_) { + console.error('error handler'); + assert.equal(stream.fd, null); + assert.equal(err_, err); +})); - stream.write(new Buffer(256), common.mustCall(function(err_) { - assert.equal(err_, err); - })); - stream.on('error', common.mustCall(function(err_) { - assert.equal(stream.fd, null); +stream.write(new Buffer(256), function() { + console.error('first cb'); + stream.write(new Buffer(256), common.mustCall(function(err_) { + console.error('second cb'); assert.equal(err_, err); })); }); diff --git a/test/simple/test-http-1.0-keep-alive.js b/test/simple/test-http-1.0-keep-alive.js index 623facb173..851409d28b 100644 --- a/test/simple/test-http-1.0-keep-alive.js +++ b/test/simple/test-http-1.0-keep-alive.js @@ -115,6 +115,7 @@ function check(tests) { function server(req, res) { if (current + 1 === test.responses.length) this.close(); var ctx = test.responses[current]; + console.error('< SERVER SENDING RESPONSE', ctx); res.writeHead(200, ctx.headers); ctx.chunks.slice(0, -1).forEach(function(chunk) { res.write(chunk) }); res.end(ctx.chunks[ctx.chunks.length - 1]); @@ -126,16 +127,19 @@ function check(tests) { function connected() { var ctx = test.requests[current]; + console.error(' > CLIENT SENDING REQUEST', ctx); conn.setEncoding('utf8'); conn.write(ctx.data); function onclose() { + console.error(' > CLIENT CLOSE'); if (!ctx.expectClose) throw new Error('unexpected close'); client(); } conn.on('close', onclose); function ondata(s) { + console.error(' > CLIENT ONDATA %j %j', s.length, s.toString()); current++; if (ctx.expectClose) return; conn.removeListener('close', onclose); diff --git a/test/simple/test-http-abort-client.js b/test/simple/test-http-abort-client.js index 6acdd6f404..f15238af16 100644 --- a/test/simple/test-http-abort-client.js +++ b/test/simple/test-http-abort-client.js @@ -46,13 +46,21 @@ server.listen(common.PORT, function() { res.on('data', function(chunk) { console.log('Read ' + chunk.length + ' bytes'); - console.log(chunk.toString()); + console.log(' chunk=%j', chunk.toString()); }); res.on('end', function() { console.log('Response ended.'); }); + res.on('aborted', function() { + console.log('Response aborted.'); + }); + + res.socket.on('close', function() { + console.log('socket closed, but not res'); + }) + // it would be nice if this worked: res.on('close', function() { console.log('Response aborted'); diff --git a/test/simple/test-http-agent.js b/test/simple/test-http-agent.js index a1ce456e5f..fc66dc49f8 100644 --- a/test/simple/test-http-agent.js +++ b/test/simple/test-http-agent.js @@ -40,10 +40,14 @@ server.listen(common.PORT, function() { setTimeout(function() { for (var j = 0; j < M; j++) { http.get({ port: common.PORT, path: '/' }, function(res) { - console.log(res.statusCode); - if (++responses == N * M) server.close(); + console.log('%d %d', responses, res.statusCode); + if (++responses == N * M) { + console.error('Received all responses, closing server'); + server.close(); + } + res.resume(); }).on('error', function(e) { - console.log(e.message); + console.log('Error!', e); process.exit(1); }); } diff --git a/test/simple/test-http-client-agent.js b/test/simple/test-http-client-agent.js index f7112e3bfe..aedf64ba70 100644 --- a/test/simple/test-http-client-agent.js +++ b/test/simple/test-http-client-agent.js @@ -61,6 +61,7 @@ function request(i) { server.close(); } }); + res.resume(); }); } diff --git a/test/simple/test-http-client-pipe-end.js b/test/simple/test-http-client-pipe-end.js index 7cb592e4c5..51edebbe1a 100644 --- a/test/simple/test-http-client-pipe-end.js +++ b/test/simple/test-http-client-pipe-end.js @@ -26,6 +26,7 @@ var assert = require('assert'); var http = require('http'); var server = http.createServer(function(req, res) { + req.resume(); req.once('end', function() { res.writeHead(200); res.end(); @@ -50,9 +51,9 @@ server.listen(common.PIPE, function() { function sched(cb, ticks) { function fn() { if (--ticks) - process.nextTick(fn); + setImmediate(fn); else cb(); } - process.nextTick(fn); + setImmediate(fn); } diff --git a/test/simple/test-http-connect.js b/test/simple/test-http-connect.js index 668dda7963..3643cec18e 100644 --- a/test/simple/test-http-connect.js +++ b/test/simple/test-http-connect.js @@ -73,7 +73,11 @@ server.listen(common.PORT, function() { assert(!socket.onend); assert.equal(socket.listeners('connect').length, 0); assert.equal(socket.listeners('data').length, 0); - assert.equal(socket.listeners('end').length, 0); + + // the stream.Duplex onend listener + // allow 0 here, so that i can run the same test on streams1 impl + assert(socket.listeners('end').length <= 1); + assert.equal(socket.listeners('free').length, 0); assert.equal(socket.listeners('close').length, 0); assert.equal(socket.listeners('error').length, 0); diff --git a/test/simple/test-http-date-header.js b/test/simple/test-http-date-header.js index 8ed5281712..b11507c017 100644 --- a/test/simple/test-http-date-header.js +++ b/test/simple/test-http-date-header.js @@ -49,6 +49,7 @@ server.addListener('listening', function() { server.close(); process.exit(); }); + res.resume(); }); req.end(); }); diff --git a/test/simple/test-http-default-encoding.js b/test/simple/test-http-default-encoding.js index 4e4741a061..b06f7c83a2 100644 --- a/test/simple/test-http-default-encoding.js +++ b/test/simple/test-http-default-encoding.js @@ -50,6 +50,7 @@ server.listen(common.PORT, function() { method: 'POST' }, function(res) { console.log(res.statusCode); + res.resume(); }).on('error', function(e) { console.log(e.message); process.exit(1); diff --git a/test/simple/test-http-header-read.js b/test/simple/test-http-header-read.js index e844deafcf..3383775922 100644 --- a/test/simple/test-http-header-read.js +++ b/test/simple/test-http-header-read.js @@ -50,5 +50,6 @@ function runTest() { response.on('end', function() { s.close(); }); + response.resume(); }); } diff --git a/test/simple/test-http-header-response-splitting.js b/test/simple/test-http-header-response-splitting.js index 044618436c..a54af12ccd 100644 --- a/test/simple/test-http-header-response-splitting.js +++ b/test/simple/test-http-header-response-splitting.js @@ -27,6 +27,7 @@ var testIndex = 0, responses = 0; var server = http.createServer(function(req, res) { + console.error('request', testIndex); switch (testIndex++) { case 0: res.writeHead(200, { test: 'foo \r\ninvalid: bar' }); @@ -41,6 +42,7 @@ var server = http.createServer(function(req, res) { res.writeHead(200, { test: 'foo \n\n\ninvalid: bar' }); break; case 4: + console.error('send request, then close'); res.writeHead(200, { test: 'foo \r\n \r\n \r\ninvalid: bar' }); server.close(); break; @@ -49,15 +51,16 @@ var server = http.createServer(function(req, res) { } res.end('Hi mars!'); }); -server.listen(common.PORT); - -for (var i = 0; i < 5; i++) { - var req = http.get({ port: common.PORT, path: '/' }, function(res) { - assert.strictEqual(res.headers.test, 'foo invalid: bar'); - assert.strictEqual(res.headers.invalid, undefined); - responses++; - }); -} +server.listen(common.PORT, function() { + for (var i = 0; i < 5; i++) { + var req = http.get({ port: common.PORT, path: '/' }, function(res) { + assert.strictEqual(res.headers.test, 'foo invalid: bar'); + assert.strictEqual(res.headers.invalid, undefined); + responses++; + res.resume(); + }); + } +}); process.on('exit', function() { assert.strictEqual(responses, 5); diff --git a/test/simple/test-http-host-headers.js b/test/simple/test-http-host-headers.js index 85f07a5685..ca7f70947f 100644 --- a/test/simple/test-http-host-headers.js +++ b/test/simple/test-http-host-headers.js @@ -57,13 +57,14 @@ function testHttp() { var counter = 0; - function cb() { + function cb(res) { counter--; console.log('back from http request. counter = ' + counter); if (counter === 0) { httpServer.close(); testHttps(); } + res.resume(); } httpServer.listen(common.PORT, function(er) { @@ -124,13 +125,14 @@ function testHttps() { var counter = 0; - function cb() { + function cb(res) { counter--; console.log('back from https request. counter = ' + counter); if (counter === 0) { httpsServer.close(); console.log('ok'); } + res.resume(); } httpsServer.listen(common.PORT, function(er) { diff --git a/test/simple/test-http-keep-alive-close-on-header.js b/test/simple/test-http-keep-alive-close-on-header.js index 8fd7348dd5..53c73ae461 100644 --- a/test/simple/test-http-keep-alive-close-on-header.js +++ b/test/simple/test-http-keep-alive-close-on-header.js @@ -44,8 +44,9 @@ server.listen(common.PORT, function() { headers: headers, port: common.PORT, agent: agent - }, function() { + }, function(res) { assert.equal(1, agent.sockets['localhost:' + common.PORT].length); + res.resume(); }); request.on('socket', function(s) { s.on('connect', function() { @@ -60,8 +61,9 @@ server.listen(common.PORT, function() { headers: headers, port: common.PORT, agent: agent - }, function() { + }, function(res) { assert.equal(1, agent.sockets['localhost:' + common.PORT].length); + res.resume(); }); request.on('socket', function(s) { s.on('connect', function() { @@ -80,6 +82,7 @@ server.listen(common.PORT, function() { assert.equal(1, agent.sockets['localhost:' + common.PORT].length); server.close(); }); + response.resume(); }); request.on('socket', function(s) { s.on('connect', function() { diff --git a/test/simple/test-http-keep-alive.js b/test/simple/test-http-keep-alive.js index aa03639deb..4e8a6e816f 100644 --- a/test/simple/test-http-keep-alive.js +++ b/test/simple/test-http-keep-alive.js @@ -42,6 +42,7 @@ server.listen(common.PORT, function() { }, function(response) { assert.equal(agent.sockets[name].length, 1); assert.equal(agent.requests[name].length, 2); + response.resume(); }); http.get({ @@ -49,6 +50,7 @@ server.listen(common.PORT, function() { }, function(response) { assert.equal(agent.sockets[name].length, 1); assert.equal(agent.requests[name].length, 1); + response.resume(); }); http.get({ @@ -59,6 +61,7 @@ server.listen(common.PORT, function() { assert(!agent.requests.hasOwnProperty(name)); server.close(); }); + response.resume(); }); }); diff --git a/test/simple/test-http-many-keep-alive-connections.js b/test/simple/test-http-many-keep-alive-connections.js index 4714cd523c..adbebbdcc4 100644 --- a/test/simple/test-http-many-keep-alive-connections.js +++ b/test/simple/test-http-many-keep-alive-connections.js @@ -55,6 +55,7 @@ server.listen(common.PORT, function() { server.close(); } }); + res.resume(); }).on('error', function(e) { console.log(e.message); process.exit(1); diff --git a/test/simple/test-http-parser-free.js b/test/simple/test-http-parser-free.js index bbf4a50274..7b35781f4d 100644 --- a/test/simple/test-http-parser-free.js +++ b/test/simple/test-http-parser-free.js @@ -44,6 +44,7 @@ server.listen(common.PORT, function() { if (++responses === N) { server.close(); } + res.resume(); }); })(i); } diff --git a/test/simple/test-http-request-end-twice.js b/test/simple/test-http-request-end-twice.js index f33cc6df3d..aa58772226 100644 --- a/test/simple/test-http-request-end-twice.js +++ b/test/simple/test-http-request-end-twice.js @@ -36,6 +36,7 @@ server.listen(common.PORT, function() { assert.ok(!req.end()); server.close(); }); + res.resume(); }); }); diff --git a/test/simple/test-http-request-end.js b/test/simple/test-http-request-end.js index f5da0faf2e..f64dcc305a 100644 --- a/test/simple/test-http-request-end.js +++ b/test/simple/test-http-request-end.js @@ -47,6 +47,7 @@ server.listen(common.PORT, function() { method: 'POST' }, function(res) { console.log(res.statusCode); + res.resume(); }).on('error', function(e) { console.log(e.message); process.exit(1); diff --git a/test/simple/test-http-res-write-end-dont-take-array.js b/test/simple/test-http-res-write-end-dont-take-array.js index f4b3f8ccee..0d68afc3f8 100644 --- a/test/simple/test-http-res-write-end-dont-take-array.js +++ b/test/simple/test-http-res-write-end-dont-take-array.js @@ -53,11 +53,13 @@ var server = http.createServer(function(req, res) { server.listen(common.PORT, function() { // just make a request, other tests handle responses - http.get({port: common.PORT}, function() { + http.get({port: common.PORT}, function(res) { + res.resume(); // lazy serial test, becuase we can only call end once per request test += 1; // do it again to test .end(Buffer); - http.get({port: common.PORT}, function() { + http.get({port: common.PORT}, function(res) { + res.resume(); server.close(); }); }); diff --git a/test/simple/test-http-response-readable.js b/test/simple/test-http-response-readable.js index b31fcc3d12..b48c06fb40 100644 --- a/test/simple/test-http-response-readable.js +++ b/test/simple/test-http-response-readable.js @@ -35,6 +35,7 @@ testServer.listen(common.PORT, function() { assert.equal(res.readable, false, 'res.readable set to false after end'); testServer.close(); }); + res.resume(); }); }); diff --git a/test/simple/test-http-set-trailers.js b/test/simple/test-http-set-trailers.js index a0896df5ac..445a3eeaac 100644 --- a/test/simple/test-http-set-trailers.js +++ b/test/simple/test-http-set-trailers.js @@ -106,6 +106,7 @@ server.on('listening', function() { process.exit(); } }); + res.resume(); }); outstanding_reqs++; }); diff --git a/test/simple/test-http-status-code.js b/test/simple/test-http-status-code.js index 395623869f..ca56230d26 100644 --- a/test/simple/test-http-status-code.js +++ b/test/simple/test-http-status-code.js @@ -59,6 +59,7 @@ function nextTest() { testIdx += 1; nextTest(); }); + response.resume(); }); } diff --git a/test/simple/test-http-timeout.js b/test/simple/test-http-timeout.js index 5170d795aa..c68a465f33 100644 --- a/test/simple/test-http-timeout.js +++ b/test/simple/test-http-timeout.js @@ -55,6 +55,8 @@ server.listen(port, function() { server.close(); } }) + + res.resume(); }); req.setTimeout(1000, callback); diff --git a/test/simple/test-https-agent.js b/test/simple/test-https-agent.js index b54d5c38ae..34fa15c737 100644 --- a/test/simple/test-https-agent.js +++ b/test/simple/test-https-agent.js @@ -54,6 +54,7 @@ server.listen(common.PORT, function() { port: common.PORT, rejectUnauthorized: false }, function(res) { + res.resume(); console.log(res.statusCode); if (++responses == N * M) server.close(); }).on('error', function(e) { diff --git a/test/simple/test-https-socket-options.js b/test/simple/test-https-socket-options.js index 4487cf8fa4..21b1118f7d 100644 --- a/test/simple/test-https-socket-options.js +++ b/test/simple/test-https-socket-options.js @@ -55,6 +55,7 @@ server_http.listen(common.PORT, function() { rejectUnauthorized: false }, function(res) { server_http.close(); + res.resume(); }); // These methods should exist on the request and get passed down to the socket req.setNoDelay(true); @@ -77,6 +78,7 @@ server_https.listen(common.PORT+1, function() { rejectUnauthorized: false }, function(res) { server_https.close(); + res.resume(); }); // These methods should exist on the request and get passed down to the socket req.setNoDelay(true); diff --git a/test/simple/test-https-strict.js b/test/simple/test-https-strict.js index 43febc8e13..720b0a6db7 100644 --- a/test/simple/test-https-strict.js +++ b/test/simple/test-https-strict.js @@ -170,6 +170,7 @@ function makeReq(path, port, error, host, ca) { server2.close(); server3.close(); } + res.resume(); }) } diff --git a/test/simple/test-net-after-close.js b/test/simple/test-net-after-close.js index 65fda21900..2f3d4c3797 100644 --- a/test/simple/test-net-after-close.js +++ b/test/simple/test-net-after-close.js @@ -25,12 +25,14 @@ var net = require('net'); var closed = false; var server = net.createServer(function(s) { + console.error('SERVER: got connection'); s.end(); }); server.listen(common.PORT, function() { var c = net.createConnection(common.PORT); c.on('close', function() { + console.error('connection closed'); assert.strictEqual(c._handle, null); closed = true; assert.doesNotThrow(function() { diff --git a/test/simple/test-net-binary.js b/test/simple/test-net-binary.js index 349e2da000..6b41d72d7b 100644 --- a/test/simple/test-net-binary.js +++ b/test/simple/test-net-binary.js @@ -41,12 +41,15 @@ for (var i = 255; i >= 0; i--) { // safe constructor var echoServer = net.Server(function(connection) { + // connection._readableState.lowWaterMark = 0; + console.error('SERVER got connection'); connection.setEncoding('binary'); connection.on('data', function(chunk) { - common.error('recved: ' + JSON.stringify(chunk)); + common.error('SERVER recved: ' + JSON.stringify(chunk)); connection.write(chunk, 'binary'); }); connection.on('end', function() { + console.error('SERVER ending'); connection.end(); }); }); @@ -55,29 +58,44 @@ echoServer.listen(common.PORT); var recv = ''; echoServer.on('listening', function() { + console.error('SERVER listening'); var j = 0; - var c = net.createConnection(common.PORT); + var c = net.createConnection({ + port: common.PORT + }); + + // c._readableState.lowWaterMark = 0; c.setEncoding('binary'); c.on('data', function(chunk) { - if (j < 256) { - common.error('write ' + j); + console.error('CLIENT data %j', chunk); + var n = j + chunk.length; + while (j < n && j < 256) { + common.error('CLIENT write ' + j); c.write(String.fromCharCode(j), 'binary'); j++; - } else { + } + if (j === 256) { + console.error('CLIENT ending'); c.end(); } recv += chunk; }); c.on('connect', function() { + console.error('CLIENT connected, writing'); c.write(binaryString, 'binary'); }); c.on('close', function() { + console.error('CLIENT closed'); console.dir(recv); echoServer.close(); }); + + c.on('finish', function() { + console.error('CLIENT finished'); + }); }); process.on('exit', function() { diff --git a/test/simple/test-net-bytes-stats.js b/test/simple/test-net-bytes-stats.js index a406e991c8..0cb08009ee 100644 --- a/test/simple/test-net-bytes-stats.js +++ b/test/simple/test-net-bytes-stats.js @@ -34,33 +34,40 @@ var count = 0; var tcp = net.Server(function(s) { console.log('tcp server connection'); + // trigger old mode. + s.resume(); + s.on('end', function() { bytesRead += s.bytesRead; console.log('tcp socket disconnect #' + count); }); }); -tcp.listen(common.PORT, function() { +tcp.listen(common.PORT, function doTest() { + console.error('listening'); var socket = net.createConnection(tcpPort); socket.on('connect', function() { count++; - console.log('tcp client connection #' + count); + console.error('CLIENT connect #%d', count); socket.write('foo', function() { + console.error('CLIENT: write cb'); socket.end('bar'); }); }); - socket.on('end', function() { + socket.on('finish', function() { bytesWritten += socket.bytesWritten; - console.log('tcp client disconnect #' + count); + console.error('CLIENT end event #%d', count); }); socket.on('close', function() { + console.error('CLIENT close event #%d', count); console.log('Bytes read: ' + bytesRead); console.log('Bytes written: ' + bytesWritten); if (count < 2) { + console.error('RECONNECTING'); socket.connect(tcpPort); } else { tcp.close(); diff --git a/test/simple/test-net-can-reset-timeout.js b/test/simple/test-net-can-reset-timeout.js index bb9b071427..b9ea97efe8 100644 --- a/test/simple/test-net-can-reset-timeout.js +++ b/test/simple/test-net-can-reset-timeout.js @@ -28,6 +28,8 @@ var timeoutCount = 0; var server = net.createServer(function(stream) { stream.setTimeout(100); + stream.resume(); + stream.on('timeout', function() { console.log('timeout'); // try to reset the timeout. diff --git a/test/simple/test-net-connect-buffer.js b/test/simple/test-net-connect-buffer.js index 0950593626..679e18e907 100644 --- a/test/simple/test-net-connect-buffer.js +++ b/test/simple/test-net-connect-buffer.js @@ -38,6 +38,7 @@ var tcp = net.Server(function(s) { }); s.on('end', function() { + console.error('SERVER: end', buf.toString()); assert.equal(buf, "L'État, c'est moi"); console.log('tcp socket disconnect'); s.end(); @@ -50,7 +51,7 @@ var tcp = net.Server(function(s) { }); tcp.listen(common.PORT, function() { - var socket = net.Stream(); + var socket = net.Stream({ highWaterMark: 0 }); console.log('Connecting to socket '); @@ -77,6 +78,7 @@ tcp.listen(common.PORT, function() { {} ].forEach(function(v) { function f() { + console.error('write', v); socket.write(v); } assert.throws(f, TypeError); @@ -90,12 +92,17 @@ tcp.listen(common.PORT, function() { // We're still connecting at this point so the datagram is first pushed onto // the connect queue. Make sure that it's not added to `bytesWritten` again // when the actual write happens. - var r = socket.write(a, function() { + var r = socket.write(a, function(er) { + console.error('write cb'); dataWritten = true; assert.ok(connectHappened); - assert.equal(socket.bytesWritten, Buffer(a + b).length); + console.error('socket.bytesWritten', socket.bytesWritten); + //assert.equal(socket.bytesWritten, Buffer(a + b).length); console.error('data written'); }); + console.error('socket.bytesWritten', socket.bytesWritten); + console.error('write returned', r); + assert.equal(socket.bytesWritten, Buffer(a).length); assert.equal(false, r); diff --git a/test/simple/test-net-connect-options.js b/test/simple/test-net-connect-options.js index 8df692ef7e..6be3696dae 100644 --- a/test/simple/test-net-connect-options.js +++ b/test/simple/test-net-connect-options.js @@ -27,6 +27,7 @@ var serverGotEnd = false; var clientGotEnd = false; var server = net.createServer({allowHalfOpen: true}, function(socket) { + socket.resume(); socket.on('end', function() { serverGotEnd = true; }); @@ -39,6 +40,8 @@ server.listen(common.PORT, function() { port: common.PORT, allowHalfOpen: true }, function() { + console.error('client connect cb'); + client.resume(); client.on('end', function() { clientGotEnd = true; setTimeout(function() { @@ -53,6 +56,7 @@ server.listen(common.PORT, function() { }); process.on('exit', function() { + console.error('exit', serverGotEnd, clientGotEnd); assert(serverGotEnd); assert(clientGotEnd); }); diff --git a/test/simple/test-net-pingpong.js b/test/simple/test-net-pingpong.js index a81f0dc80d..1e7b6681ab 100644 --- a/test/simple/test-net-pingpong.js +++ b/test/simple/test-net-pingpong.js @@ -60,6 +60,8 @@ function pingPongTest(port, host) { }); socket.on('end', function() { + console.error(socket); + assert.equal(true, socket.allowHalfOpen); assert.equal(true, socket.writable); // because allowHalfOpen assert.equal(false, socket.readable); socket.end(); @@ -129,10 +131,11 @@ function pingPongTest(port, host) { } /* All are run at once, so run on different ports */ +console.log(common.PIPE); pingPongTest(common.PIPE); -pingPongTest(20988); -pingPongTest(20989, 'localhost'); -pingPongTest(20997, '::1'); +pingPongTest(common.PORT); +pingPongTest(common.PORT + 1, 'localhost'); +pingPongTest(common.PORT + 2, '::1'); process.on('exit', function() { assert.equal(4, tests_run); diff --git a/test/simple/test-net-reconnect.js b/test/simple/test-net-reconnect.js index f7fcb8b29b..58e0fef627 100644 --- a/test/simple/test-net-reconnect.js +++ b/test/simple/test-net-reconnect.js @@ -30,39 +30,49 @@ var client_recv_count = 0; var disconnect_count = 0; var server = net.createServer(function(socket) { + console.error('SERVER: got socket connection'); + socket.resume(); + socket.on('connect', function() { + console.error('SERVER connect, writing'); socket.write('hello\r\n'); }); socket.on('end', function() { + console.error('SERVER socket end, calling end()'); socket.end(); }); socket.on('close', function(had_error) { - //console.log('server had_error: ' + JSON.stringify(had_error)); + console.log('SERVER had_error: ' + JSON.stringify(had_error)); assert.equal(false, had_error); }); }); server.listen(common.PORT, function() { - console.log('listening'); + console.log('SERVER listening'); var client = net.createConnection(common.PORT); client.setEncoding('UTF8'); client.on('connect', function() { - console.log('client connected.'); + console.error('CLIENT connected', client._writableState); }); client.on('data', function(chunk) { client_recv_count += 1; console.log('client_recv_count ' + client_recv_count); assert.equal('hello\r\n', chunk); + console.error('CLIENT: calling end', client._writableState); client.end(); }); + client.on('end', function() { + console.error('CLIENT end'); + }); + client.on('close', function(had_error) { - console.log('disconnect'); + console.log('CLIENT disconnect'); assert.equal(false, had_error); if (disconnect_count++ < N) client.connect(common.PORT); // reconnect diff --git a/test/simple/test-net-remote-address-port.js b/test/simple/test-net-remote-address-port.js index 9b585fce9e..5d1ae3c8eb 100644 --- a/test/simple/test-net-remote-address-port.js +++ b/test/simple/test-net-remote-address-port.js @@ -34,6 +34,7 @@ var server = net.createServer(function(socket) { socket.on('end', function() { if (++conns_closed == 2) server.close(); }); + socket.resume(); }); server.listen(common.PORT, 'localhost', function() { diff --git a/test/simple/test-net-write-after-close.js b/test/simple/test-net-write-after-close.js index b77e9af724..3b98bbc42e 100644 --- a/test/simple/test-net-write-after-close.js +++ b/test/simple/test-net-write-after-close.js @@ -32,12 +32,16 @@ process.on('exit', function() { }); var server = net.createServer(function(socket) { + socket.resume(); + socket.on('error', function(error) { + console.error('got error, closing server', error); server.close(); gotError = true; }); setTimeout(function() { + console.error('about to try to write'); socket.write('test', function(e) { gotWriteCB = true; }); diff --git a/test/simple/test-pipe-file-to-http.js b/test/simple/test-pipe-file-to-http.js index 99fad6ebb6..1b3ba7089b 100644 --- a/test/simple/test-pipe-file-to-http.js +++ b/test/simple/test-pipe-file-to-http.js @@ -31,6 +31,7 @@ var clientReqComplete = false; var count = 0; var server = http.createServer(function(req, res) { + console.error('SERVER request'); var timeoutId; assert.equal('POST', req.method); req.pause(); @@ -63,6 +64,8 @@ server.on('listening', function() { cp.exec(cmd, function(err, stdout, stderr) { if (err) throw err; + console.error('EXEC returned successfully stdout=%d stderr=%d', + stdout.length, stderr.length); makeRequest(); }); }); @@ -75,8 +78,15 @@ function makeRequest() { }); common.error('pipe!'); + var s = fs.ReadStream(filename); s.pipe(req); + s.on('data', function(chunk) { + console.error('FS data chunk=%d', chunk.length); + }); + s.on('end', function() { + console.error('FS end'); + }); s.on('close', function(err) { if (err) throw err; clientReqComplete = true; @@ -84,7 +94,10 @@ function makeRequest() { }); req.on('response', function(res) { + console.error('RESPONSE', res.statusCode, res.headers); + res.resume(); res.on('end', function() { + console.error('RESPONSE end'); server.close(); }); }); diff --git a/test/simple/test-pipe.js b/test/simple/test-pipe.js index 3dd243725d..9f1dae885b 100644 --- a/test/simple/test-pipe.js +++ b/test/simple/test-pipe.js @@ -125,6 +125,7 @@ function startClient() { }); req.write(buffer); req.end(); + console.error('ended request', req); } process.on('exit', function() { diff --git a/test/simple/test-regress-GH-877.js b/test/simple/test-regress-GH-877.js index d431118fdf..1cdca9f7db 100644 --- a/test/simple/test-regress-GH-877.js +++ b/test/simple/test-regress-GH-877.js @@ -48,6 +48,7 @@ server.listen(common.PORT, '127.0.0.1', function() { if (++responses == N) { server.close(); } + res.resume(); }); assert.equal(req.agent, agent); diff --git a/test/simple/test-repl-autolibs.js b/test/simple/test-repl-autolibs.js index 0f4ae8b387..a8ee68ccc3 100644 --- a/test/simple/test-repl-autolibs.js +++ b/test/simple/test-repl-autolibs.js @@ -48,8 +48,9 @@ function test1(){ putIn.write = function (data) { gotWrite = true; if (data.length) { + // inspect output matches repl output - assert.equal(data, util.inspect(require('fs'), null, null, false) + '\n'); + assert.equal(data, util.inspect(require('fs'), null, 2, false) + '\n'); // globally added lib matches required lib assert.equal(global.fs, require('fs')); test2(); diff --git a/test/simple/test-stream2-basic.js b/test/simple/test-stream2-basic.js new file mode 100644 index 0000000000..0b4f4cf2b5 --- /dev/null +++ b/test/simple/test-stream2-basic.js @@ -0,0 +1,320 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + + +var common = require('../common.js'); +var R = require('_stream_readable'); +var assert = require('assert'); + +var util = require('util'); +var EE = require('events').EventEmitter; + +function TestReader(n) { + R.apply(this); + this._buffer = new Buffer(n || 100); + this._buffer.fill('x'); + this._pos = 0; + this._bufs = 10; +} + +util.inherits(TestReader, R); + +TestReader.prototype.read = function(n) { + var max = this._buffer.length - this._pos; + n = n || max; + n = Math.max(n, 0); + var toRead = Math.min(n, max); + if (toRead === 0) { + // simulate the read buffer filling up with some more bytes some time + // in the future. + setTimeout(function() { + this._pos = 0; + this._bufs -= 1; + if (this._bufs <= 0) { + // read them all! + if (!this.ended) { + this.emit('end'); + this.ended = true; + } + } else { + this.emit('readable'); + } + }.bind(this), 10); + return null; + } + + var ret = this._buffer.slice(this._pos, this._pos + toRead); + this._pos += toRead; + return ret; +}; + +///// + +function TestWriter() { + EE.apply(this); + this.received = []; + this.flush = false; +} + +util.inherits(TestWriter, EE); + +TestWriter.prototype.write = function(c) { + this.received.push(c.toString()); + this.emit('write', c); + return true; + + // flip back and forth between immediate acceptance and not. + this.flush = !this.flush; + if (!this.flush) setTimeout(this.emit.bind(this, 'drain'), 10); + return this.flush; +}; + +TestWriter.prototype.end = function(c) { + if (c) this.write(c); + this.emit('end', this.received); +}; + +//////// + +// tiny node-tap lookalike. +var tests = []; +function test(name, fn) { + tests.push([name, fn]); +} + +function run() { + var next = tests.shift(); + if (!next) + return console.error('ok'); + + var name = next[0]; + var fn = next[1]; + console.log('# %s', name); + fn({ + same: assert.deepEqual, + equal: assert.equal, + end: run + }); +} + +process.nextTick(run); + + +test('a most basic test', function(t) { + var r = new TestReader(20); + + var reads = []; + var expect = [ 'x', + 'xx', + 'xxx', + 'xxxx', + 'xxxxx', + 'xxxxx', + 'xxxxxxxx', + 'xxxxxxxxx', + 'xxx', + 'xxxxxxxxxxxx', + 'xxxxxxxx', + 'xxxxxxxxxxxxxxx', + 'xxxxx', + 'xxxxxxxxxxxxxxxxxx', + 'xx', + 'xxxxxxxxxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxxxxxxx' ]; + + r.on('end', function() { + t.same(reads, expect); + t.end(); + }); + + var readSize = 1; + function flow() { + var res; + while (null !== (res = r.read(readSize++))) { + reads.push(res.toString()); + } + r.once('readable', flow); + } + + flow(); +}); + +test('pipe', function(t) { + var r = new TestReader(5); + + var expect = [ 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx' ] + + var w = new TestWriter; + var flush = true; + w.on('end', function(received) { + t.same(received, expect); + t.end(); + }); + + r.pipe(w); +}); + + + +[1,2,3,4,5,6,7,8,9].forEach(function(SPLIT) { + test('unpipe', function(t) { + var r = new TestReader(5); + + // unpipe after 3 writes, then write to another stream instead. + var expect = [ 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx' ]; + expect = [ expect.slice(0, SPLIT), expect.slice(SPLIT) ]; + + var w = [ new TestWriter(), new TestWriter() ]; + + var writes = SPLIT; + w[0].on('write', function() { + if (--writes === 0) { + r.unpipe(); + t.equal(r._readableState.pipes, null); + w[0].end(); + r.pipe(w[1]); + t.equal(r._readableState.pipes, w[1]); + } + }); + + var ended = 0; + + var ended0 = false; + var ended1 = false; + w[0].on('end', function(results) { + t.equal(ended0, false); + ended0 = true; + ended++; + t.same(results, expect[0]); + }); + + w[1].on('end', function(results) { + t.equal(ended1, false); + ended1 = true; + ended++; + t.equal(ended, 2); + t.same(results, expect[1]); + t.end(); + }); + + r.pipe(w[0]); + }); +}); + + +// both writers should get the same exact data. +test('multipipe', function(t) { + var r = new TestReader(5); + var w = [ new TestWriter, new TestWriter ]; + + var expect = [ 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx' ]; + + var c = 2; + w[0].on('end', function(received) { + t.same(received, expect, 'first'); + if (--c === 0) t.end(); + }); + w[1].on('end', function(received) { + t.same(received, expect, 'second'); + if (--c === 0) t.end(); + }); + + r.pipe(w[0]); + r.pipe(w[1]); +}); + + +[1,2,3,4,5,6,7,8,9].forEach(function(SPLIT) { + test('multi-unpipe', function(t) { + var r = new TestReader(5); + + // unpipe after 3 writes, then write to another stream instead. + var expect = [ 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx', + 'xxxxx' ]; + expect = [ expect.slice(0, SPLIT), expect.slice(SPLIT) ]; + + var w = [ new TestWriter(), new TestWriter(), new TestWriter() ]; + + var writes = SPLIT; + w[0].on('write', function() { + if (--writes === 0) { + r.unpipe(); + w[0].end(); + r.pipe(w[1]); + } + }); + + var ended = 0; + + w[0].on('end', function(results) { + ended++; + t.same(results, expect[0]); + }); + + w[1].on('end', function(results) { + ended++; + t.equal(ended, 2); + t.same(results, expect[1]); + t.end(); + }); + + r.pipe(w[0]); + r.pipe(w[2]); + }); +}); diff --git a/test/simple/test-stream2-fs.js b/test/simple/test-stream2-fs.js new file mode 100644 index 0000000000..70cd8ba4de --- /dev/null +++ b/test/simple/test-stream2-fs.js @@ -0,0 +1,76 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + + +var common = require('../common.js'); +var R = require('_stream_readable'); +var assert = require('assert'); + +var fs = require('fs'); +var FSReadable = fs.ReadStream; + +var path = require('path'); +var file = path.resolve(common.fixturesDir, 'x1024.txt'); + +var size = fs.statSync(file).size; + +// expect to see chunks no more than 10 bytes each. +var expectLengths = []; +for (var i = size; i > 0; i -= 10) { + expectLengths.push(Math.min(i, 10)); +} + +var util = require('util'); +var Stream = require('stream'); + +util.inherits(TestWriter, Stream); + +function TestWriter() { + Stream.apply(this); + this.buffer = []; + this.length = 0; +} + +TestWriter.prototype.write = function(c) { + this.buffer.push(c.toString()); + this.length += c.length; + return true; +}; + +TestWriter.prototype.end = function(c) { + if (c) this.buffer.push(c.toString()); + this.emit('results', this.buffer); +} + +var r = new FSReadable(file, { bufferSize: 10 }); +var w = new TestWriter(); + +w.on('results', function(res) { + console.error(res, w.length); + assert.equal(w.length, size); + var l = 0; + assert.deepEqual(res.map(function (c) { + return c.length; + }), expectLengths); + console.log('ok'); +}); + +r.pipe(w, { chunkSize: 10 }); diff --git a/test/simple/test-stream2-pipe-error-handling.js b/test/simple/test-stream2-pipe-error-handling.js new file mode 100644 index 0000000000..c17139f5d3 --- /dev/null +++ b/test/simple/test-stream2-pipe-error-handling.js @@ -0,0 +1,105 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'); +var assert = require('assert'); +var stream = require('stream'); + +(function testErrorListenerCatches() { + var count = 1000; + + var source = new stream.Readable(); + source._read = function(n, cb) { + n = Math.min(count, n); + count -= n; + cb(null, new Buffer(n)); + }; + + var unpipedDest; + source.unpipe = function(dest) { + unpipedDest = dest; + stream.Readable.prototype.unpipe.call(this, dest); + }; + + var dest = new stream.Writable(); + dest._write = function(chunk, cb) { + cb(); + }; + + source.pipe(dest); + + var gotErr = null; + dest.on('error', function(err) { + gotErr = err; + }); + + var unpipedSource; + dest.on('unpipe', function(src) { + unpipedSource = src; + }); + + var err = new Error('This stream turned into bacon.'); + dest.emit('error', err); + assert.strictEqual(gotErr, err); + assert.strictEqual(unpipedSource, source); + assert.strictEqual(unpipedDest, dest); +})(); + +(function testErrorWithoutListenerThrows() { + var count = 1000; + + var source = new stream.Readable(); + source._read = function(n, cb) { + n = Math.min(count, n); + count -= n; + cb(null, new Buffer(n)); + }; + + var unpipedDest; + source.unpipe = function(dest) { + unpipedDest = dest; + stream.Readable.prototype.unpipe.call(this, dest); + }; + + var dest = new stream.Writable(); + dest._write = function(chunk, cb) { + cb(); + }; + + source.pipe(dest); + + var unpipedSource; + dest.on('unpipe', function(src) { + unpipedSource = src; + }); + + var err = new Error('This stream turned into bacon.'); + + var gotErr = null; + try { + dest.emit('error', err); + } catch (e) { + gotErr = e; + } + assert.strictEqual(gotErr, err); + assert.strictEqual(unpipedSource, source); + assert.strictEqual(unpipedDest, dest); +})(); diff --git a/test/simple/test-stream2-readable-from-list.js b/test/simple/test-stream2-readable-from-list.js new file mode 100644 index 0000000000..a28fe343ee --- /dev/null +++ b/test/simple/test-stream2-readable-from-list.js @@ -0,0 +1,109 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var assert = require('assert'); +var common = require('../common.js'); +var fromList = require('_stream_readable')._fromList; + +// tiny node-tap lookalike. +var tests = []; +function test(name, fn) { + tests.push([name, fn]); +} + +function run() { + var next = tests.shift(); + if (!next) + return console.error('ok'); + + var name = next[0]; + var fn = next[1]; + console.log('# %s', name); + fn({ + same: assert.deepEqual, + equal: assert.equal, + end: run + }); +} + +process.nextTick(run); + + + +test('buffers', function(t) { + // have a length + var len = 16; + var list = [ new Buffer('foog'), + new Buffer('bark'), + new Buffer('bazy'), + new Buffer('kuel') ]; + + // read more than the first element. + var ret = fromList(6, list, 16); + t.equal(ret.toString(), 'foogba'); + + // read exactly the first element. + ret = fromList(2, list, 10); + t.equal(ret.toString(), 'rk'); + + // read less than the first element. + ret = fromList(2, list, 8); + t.equal(ret.toString(), 'ba'); + + // read more than we have. + ret = fromList(100, list, 6); + t.equal(ret.toString(), 'zykuel'); + + // all consumed. + t.same(list, []); + + t.end(); +}); + +test('strings', function(t) { + // have a length + var len = 16; + var list = [ 'foog', + 'bark', + 'bazy', + 'kuel' ]; + + // read more than the first element. + var ret = fromList(6, list, 16, true); + t.equal(ret, 'foogba'); + + // read exactly the first element. + ret = fromList(2, list, 10, true); + t.equal(ret, 'rk'); + + // read less than the first element. + ret = fromList(2, list, 8, true); + t.equal(ret, 'ba'); + + // read more than we have. + ret = fromList(100, list, 6, true); + t.equal(ret, 'zykuel'); + + // all consumed. + t.same(list, []); + + t.end(); +}); diff --git a/test/simple/test-stream2-set-encoding.js b/test/simple/test-stream2-set-encoding.js new file mode 100644 index 0000000000..a1883fbbb8 --- /dev/null +++ b/test/simple/test-stream2-set-encoding.js @@ -0,0 +1,299 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + + +var common = require('../common.js'); +var assert = require('assert'); +var R = require('_stream_readable'); +var util = require('util'); + +// tiny node-tap lookalike. +var tests = []; +function test(name, fn) { + tests.push([name, fn]); +} + +function run() { + var next = tests.shift(); + if (!next) + return console.error('ok'); + + var name = next[0]; + var fn = next[1]; + console.log('# %s', name); + fn({ + same: assert.deepEqual, + equal: assert.equal, + end: run + }); +} + +process.nextTick(run); + +///// + +util.inherits(TestReader, R); + +function TestReader(n, opts) { + R.call(this, util._extend({ + bufferSize: 5 + }, opts)); + + this.pos = 0; + this.len = n || 100; +} + +TestReader.prototype._read = function(n, cb) { + setTimeout(function() { + + if (this.pos >= this.len) { + return cb(); + } + + n = Math.min(n, this.len - this.pos); + if (n <= 0) { + return cb(); + } + + this.pos += n; + var ret = new Buffer(n); + ret.fill('a'); + + return cb(null, ret); + }.bind(this), 1); +}; + +test('setEncoding utf8', function(t) { + var tr = new TestReader(100); + tr.setEncoding('utf8'); + var out = []; + var expect = + [ 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa' ]; + + tr.on('readable', function flow() { + var chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', function() { + t.same(out, expect); + t.end(); + }); + + // just kick it off. + tr.emit('readable'); +}); + + +test('setEncoding hex', function(t) { + var tr = new TestReader(100); + tr.setEncoding('hex'); + var out = []; + var expect = + [ '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161' ]; + + tr.on('readable', function flow() { + var chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', function() { + t.same(out, expect); + t.end(); + }); + + // just kick it off. + tr.emit('readable'); +}); + +test('setEncoding hex with read(13)', function(t) { + var tr = new TestReader(100); + tr.setEncoding('hex'); + var out = []; + var expect = + [ "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "16161" ]; + + tr.on('readable', function flow() { + var chunk; + while (null !== (chunk = tr.read(13))) + out.push(chunk); + }); + + tr.on('end', function() { + t.same(out, expect); + t.end(); + }); + + // just kick it off. + tr.emit('readable'); +}); + +test('encoding: utf8', function(t) { + var tr = new TestReader(100, { encoding: 'utf8' }); + var out = []; + var expect = + [ 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa', + 'aaaaaaaaaa' ]; + + tr.on('readable', function flow() { + var chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', function() { + t.same(out, expect); + t.end(); + }); + + // just kick it off. + tr.emit('readable'); +}); + + +test('encoding: hex', function(t) { + var tr = new TestReader(100, { encoding: 'hex' }); + var out = []; + var expect = + [ '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161', + '6161616161' ]; + + tr.on('readable', function flow() { + var chunk; + while (null !== (chunk = tr.read(10))) + out.push(chunk); + }); + + tr.on('end', function() { + t.same(out, expect); + t.end(); + }); + + // just kick it off. + tr.emit('readable'); +}); + +test('encoding: hex with read(13)', function(t) { + var tr = new TestReader(100, { encoding: 'hex' }); + var out = []; + var expect = + [ "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "1616161616161", + "6161616161616", + "16161" ]; + + tr.on('readable', function flow() { + var chunk; + while (null !== (chunk = tr.read(13))) + out.push(chunk); + }); + + tr.on('end', function() { + t.same(out, expect); + t.end(); + }); + + // just kick it off. + tr.emit('readable'); +}); diff --git a/test/simple/test-stream2-transform.js b/test/simple/test-stream2-transform.js new file mode 100644 index 0000000000..2bc008517c --- /dev/null +++ b/test/simple/test-stream2-transform.js @@ -0,0 +1,314 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var assert = require('assert'); +var common = require('../common.js'); +var PassThrough = require('_stream_passthrough'); +var Transform = require('_stream_transform'); + +// tiny node-tap lookalike. +var tests = []; +function test(name, fn) { + tests.push([name, fn]); +} + +function run() { + var next = tests.shift(); + if (!next) + return console.error('ok'); + + var name = next[0]; + var fn = next[1]; + console.log('# %s', name); + fn({ + same: assert.deepEqual, + equal: assert.equal, + end: run + }); +} + +process.nextTick(run); + +///// + +test('passthrough', function(t) { + var pt = new PassThrough(); + + pt.write(new Buffer('foog')); + pt.write(new Buffer('bark')); + pt.write(new Buffer('bazy')); + pt.write(new Buffer('kuel')); + pt.end(); + + t.equal(pt.read(5).toString(), 'foogb'); + t.equal(pt.read(5).toString(), 'arkba'); + t.equal(pt.read(5).toString(), 'zykue'); + t.equal(pt.read(5).toString(), 'l'); + t.end(); +}); + +test('simple transform', function(t) { + var pt = new Transform; + pt._transform = function(c, output, cb) { + var ret = new Buffer(c.length); + ret.fill('x'); + output(ret); + cb(); + }; + + pt.write(new Buffer('foog')); + pt.write(new Buffer('bark')); + pt.write(new Buffer('bazy')); + pt.write(new Buffer('kuel')); + pt.end(); + + t.equal(pt.read(5).toString(), 'xxxxx'); + t.equal(pt.read(5).toString(), 'xxxxx'); + t.equal(pt.read(5).toString(), 'xxxxx'); + t.equal(pt.read(5).toString(), 'x'); + t.end(); +}); + +test('async passthrough', function(t) { + var pt = new Transform; + pt._transform = function(chunk, output, cb) { + setTimeout(function() { + output(chunk); + cb(); + }, 10); + }; + + pt.write(new Buffer('foog')); + pt.write(new Buffer('bark')); + pt.write(new Buffer('bazy')); + pt.write(new Buffer('kuel')); + pt.end(); + + setTimeout(function() { + t.equal(pt.read(5).toString(), 'foogb'); + t.equal(pt.read(5).toString(), 'arkba'); + t.equal(pt.read(5).toString(), 'zykue'); + t.equal(pt.read(5).toString(), 'l'); + t.end(); + }, 100); +}); + +test('assymetric transform (expand)', function(t) { + var pt = new Transform; + + // emit each chunk 2 times. + pt._transform = function(chunk, output, cb) { + setTimeout(function() { + output(chunk); + setTimeout(function() { + output(chunk); + cb(); + }, 10) + }, 10); + }; + + pt.write(new Buffer('foog')); + pt.write(new Buffer('bark')); + pt.write(new Buffer('bazy')); + pt.write(new Buffer('kuel')); + pt.end(); + + setTimeout(function() { + t.equal(pt.read(5).toString(), 'foogf'); + t.equal(pt.read(5).toString(), 'oogba'); + t.equal(pt.read(5).toString(), 'rkbar'); + t.equal(pt.read(5).toString(), 'kbazy'); + t.equal(pt.read(5).toString(), 'bazyk'); + t.equal(pt.read(5).toString(), 'uelku'); + t.equal(pt.read(5).toString(), 'el'); + t.end(); + }, 200); +}); + +test('assymetric transform (compress)', function(t) { + var pt = new Transform; + + // each output is the first char of 3 consecutive chunks, + // or whatever's left. + pt.state = ''; + + pt._transform = function(chunk, output, cb) { + if (!chunk) + chunk = ''; + var s = chunk.toString(); + setTimeout(function() { + this.state += s.charAt(0); + if (this.state.length === 3) { + output(new Buffer(this.state)); + this.state = ''; + } + cb(); + }.bind(this), 10); + }; + + pt._flush = function(output, cb) { + // just output whatever we have. + setTimeout(function() { + output(new Buffer(this.state)); + this.state = ''; + cb(); + }.bind(this), 10); + }; + + pt._writableState.lowWaterMark = 3; + + pt.write(new Buffer('aaaa')); + pt.write(new Buffer('bbbb')); + pt.write(new Buffer('cccc')); + pt.write(new Buffer('dddd')); + pt.write(new Buffer('eeee')); + pt.write(new Buffer('aaaa')); + pt.write(new Buffer('bbbb')); + pt.write(new Buffer('cccc')); + pt.write(new Buffer('dddd')); + pt.write(new Buffer('eeee')); + pt.write(new Buffer('aaaa')); + pt.write(new Buffer('bbbb')); + pt.write(new Buffer('cccc')); + pt.write(new Buffer('dddd')); + pt.end(); + + // 'abcdeabcdeabcd' + setTimeout(function() { + t.equal(pt.read(5).toString(), 'abcde'); + t.equal(pt.read(5).toString(), 'abcde'); + t.equal(pt.read(5).toString(), 'abcd'); + t.end(); + }, 200); +}); + + +test('passthrough event emission', function(t) { + var pt = new PassThrough({ + lowWaterMark: 0 + }); + var emits = 0; + pt.on('readable', function() { + var state = pt._readableState; + console.error('>>> emit readable %d', emits); + emits++; + }); + + var i = 0; + + pt.write(new Buffer('foog')); + pt.write(new Buffer('bark')); + + t.equal(pt.read(5).toString(), 'foogb'); + t.equal(pt.read(5) + '', 'null'); + + console.error('need emit 0'); + + pt.write(new Buffer('bazy')); + console.error('should have emitted, but not again'); + pt.write(new Buffer('kuel')); + + console.error('should have emitted readable now 1 === %d', emits); + t.equal(emits, 1); + + t.equal(pt.read(5).toString(), 'arkba'); + t.equal(pt.read(5).toString(), 'zykue'); + t.equal(pt.read(5), null); + + console.error('need emit 1'); + + pt.end(); + + t.equal(emits, 2); + + t.equal(pt.read(5).toString(), 'l'); + t.equal(pt.read(5), null); + + console.error('should not have emitted again'); + t.equal(emits, 2); + t.end(); +}); + +test('passthrough event emission reordered', function(t) { + var pt = new PassThrough; + var emits = 0; + pt.on('readable', function() { + console.error('emit readable', emits) + emits++; + }); + + pt.write(new Buffer('foog')); + pt.write(new Buffer('bark')); + + t.equal(pt.read(5).toString(), 'foogb'); + t.equal(pt.read(5), null); + + console.error('need emit 0'); + pt.once('readable', function() { + t.equal(pt.read(5).toString(), 'arkba'); + + t.equal(pt.read(5), null); + + console.error('need emit 1'); + pt.once('readable', function() { + t.equal(pt.read(5).toString(), 'zykue'); + t.equal(pt.read(5), null); + pt.once('readable', function() { + t.equal(pt.read(5).toString(), 'l'); + t.equal(pt.read(5), null); + t.equal(emits, 3); + t.end(); + }); + pt.end(); + }); + pt.write(new Buffer('kuel')); + }); + + pt.write(new Buffer('bazy')); +}); + +test('passthrough facaded', function(t) { + console.error('passthrough facaded'); + var pt = new PassThrough; + var datas = []; + pt.on('data', function(chunk) { + datas.push(chunk.toString()); + }); + + pt.on('end', function() { + t.same(datas, ['foog', 'bark', 'bazy', 'kuel']); + t.end(); + }); + + pt.write(new Buffer('foog')); + setTimeout(function() { + pt.write(new Buffer('bark')); + setTimeout(function() { + pt.write(new Buffer('bazy')); + setTimeout(function() { + pt.write(new Buffer('kuel')); + setTimeout(function() { + pt.end(); + }, 10); + }, 10); + }, 10); + }, 10); +}); diff --git a/test/simple/test-stream2-writable.js b/test/simple/test-stream2-writable.js new file mode 100644 index 0000000000..be40664edf --- /dev/null +++ b/test/simple/test-stream2-writable.js @@ -0,0 +1,246 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common.js'); +var W = require('_stream_writable'); +var assert = require('assert'); + +var util = require('util'); +util.inherits(TestWriter, W); + +function TestWriter() { + W.apply(this, arguments); + this.buffer = []; + this.written = 0; +} + +TestWriter.prototype._write = function(chunk, cb) { + // simulate a small unpredictable latency + setTimeout(function() { + this.buffer.push(chunk.toString()); + this.written += chunk.length; + cb(); + }.bind(this), Math.floor(Math.random() * 10)); +}; + +var chunks = new Array(50); +for (var i = 0; i < chunks.length; i++) { + chunks[i] = new Array(i + 1).join('x'); +} + +// tiny node-tap lookalike. +var tests = []; +function test(name, fn) { + tests.push([name, fn]); +} + +function run() { + var next = tests.shift(); + if (!next) + return console.log('ok'); + + var name = next[0]; + var fn = next[1]; + + if (!fn) + return run(); + + console.log('# %s', name); + fn({ + same: assert.deepEqual, + equal: assert.equal, + end: run + }); +} + +process.nextTick(run); + +test('write fast', function(t) { + var tw = new TestWriter({ + lowWaterMark: 5, + highWaterMark: 100 + }); + + tw.on('finish', function() { + t.same(tw.buffer, chunks, 'got chunks in the right order'); + t.end(); + }); + + chunks.forEach(function(chunk) { + // screw backpressure. Just buffer it all up. + tw.write(chunk); + }); + tw.end(); +}); + +test('write slow', function(t) { + var tw = new TestWriter({ + lowWaterMark: 5, + highWaterMark: 100 + }); + + tw.on('finish', function() { + t.same(tw.buffer, chunks, 'got chunks in the right order'); + t.end(); + }); + + var i = 0; + (function W() { + tw.write(chunks[i++]); + if (i < chunks.length) + setTimeout(W, 10); + else + tw.end(); + })(); +}); + +test('write backpressure', function(t) { + var tw = new TestWriter({ + lowWaterMark: 5, + highWaterMark: 50 + }); + + var drains = 0; + + tw.on('finish', function() { + t.same(tw.buffer, chunks, 'got chunks in the right order'); + t.equal(drains, 17); + t.end(); + }); + + tw.on('drain', function() { + drains++; + }); + + var i = 0; + (function W() { + do { + var ret = tw.write(chunks[i++]); + } while (ret !== false && i < chunks.length); + + if (i < chunks.length) { + assert(tw._writableState.length >= 50); + tw.once('drain', W); + } else { + tw.end(); + } + })(); +}); + +test('write bufferize', function(t) { + var tw = new TestWriter({ + lowWaterMark: 5, + highWaterMark: 100 + }); + + var encodings = + [ 'hex', + 'utf8', + 'utf-8', + 'ascii', + 'binary', + 'base64', + 'ucs2', + 'ucs-2', + 'utf16le', + 'utf-16le', + undefined ]; + + tw.on('finish', function() { + t.same(tw.buffer, chunks, 'got the expected chunks'); + }); + + chunks.forEach(function(chunk, i) { + var enc = encodings[ i % encodings.length ]; + chunk = new Buffer(chunk); + tw.write(chunk.toString(enc), enc); + }); + t.end(); +}); + +test('write no bufferize', function(t) { + var tw = new TestWriter({ + lowWaterMark: 5, + highWaterMark: 100, + decodeStrings: false + }); + + tw._write = function(chunk, cb) { + assert(Array.isArray(chunk)); + assert(typeof chunk[0] === 'string'); + chunk = new Buffer(chunk[0], chunk[1]); + return TestWriter.prototype._write.call(this, chunk, cb); + }; + + var encodings = + [ 'hex', + 'utf8', + 'utf-8', + 'ascii', + 'binary', + 'base64', + 'ucs2', + 'ucs-2', + 'utf16le', + 'utf-16le', + undefined ]; + + tw.on('finish', function() { + t.same(tw.buffer, chunks, 'got the expected chunks'); + }); + + chunks.forEach(function(chunk, i) { + var enc = encodings[ i % encodings.length ]; + chunk = new Buffer(chunk); + tw.write(chunk.toString(enc), enc); + }); + t.end(); +}); + +test('write callbacks', function (t) { + var callbacks = chunks.map(function(chunk, i) { + return [i, function(er) { + callbacks._called[i] = chunk; + }]; + }).reduce(function(set, x) { + set['callback-' + x[0]] = x[1]; + return set; + }, {}); + callbacks._called = []; + + var tw = new TestWriter({ + lowWaterMark: 5, + highWaterMark: 100 + }); + + tw.on('finish', function() { + process.nextTick(function() { + t.same(tw.buffer, chunks, 'got chunks in the right order'); + t.same(callbacks._called, chunks, 'called all callbacks'); + t.end(); + }); + }); + + chunks.forEach(function(chunk, i) { + tw.write(chunk, callbacks['callback-' + i]); + }); + tw.end(); +}); diff --git a/test/simple/test-tcp-wrap-connect.js b/test/simple/test-tcp-wrap-connect.js index b8ac6aae34..3da8d932e1 100644 --- a/test/simple/test-tcp-wrap-connect.js +++ b/test/simple/test-tcp-wrap-connect.js @@ -54,6 +54,7 @@ var shutdownCount = 0; var server = require('net').Server(function(s) { console.log('got connection'); connectCount++; + s.resume(); s.on('end', function() { console.log('got eof'); endCount++; diff --git a/test/simple/test-tls-pause.js b/test/simple/test-tls-pause.js index 0b29ae20d3..5eaac8da19 100644 --- a/test/simple/test-tls-pause.js +++ b/test/simple/test-tls-pause.js @@ -41,6 +41,9 @@ var received = 0; var server = tls.Server(options, function(socket) { socket.pipe(socket); + socket.on('data', function(c) { + console.error('data', c.length); + }); }); server.listen(common.PORT, function() { @@ -49,11 +52,16 @@ server.listen(common.PORT, function() { port: common.PORT, rejectUnauthorized: false }, function() { + console.error('connected'); client.pause(); common.debug('paused'); send(); function send() { - if (client.write(new Buffer(bufSize))) { + console.error('sending'); + var ret = client.write(new Buffer(bufSize)); + console.error('write => %j', ret); + if (false !== ret) { + console.error('write again'); sent += bufSize; assert.ok(sent < 100 * 1024 * 1024); // max 100MB return process.nextTick(send); @@ -62,12 +70,15 @@ server.listen(common.PORT, function() { common.debug('sent: ' + sent); resumed = true; client.resume(); - common.debug('resumed'); + console.error('resumed', client); } }); client.on('data', function(data) { + console.error('data'); assert.ok(resumed); received += data.length; + console.error('received', received); + console.error('sent', sent); if (received >= sent) { common.debug('received: ' + received); client.end(); diff --git a/test/simple/test-zlib-invalid-input.js b/test/simple/test-zlib-invalid-input.js index f97c5831ad..c3d8b5b47a 100644 --- a/test/simple/test-zlib-invalid-input.js +++ b/test/simple/test-zlib-invalid-input.js @@ -50,13 +50,6 @@ unzips.forEach(function (uz, i) { uz.on('error', function(er) { console.error('Error event', er); hadError[i] = true; - - // to be friendly to the Stream API, zlib objects just return true and - // ignore data on the floor after an error. It's up to the user to - // catch the 'error' event and do something intelligent. They do not - // emit any more data, however. - assert.equal(uz.write('also invalid'), true); - assert.equal(uz.end(), true); }); uz.on('end', function(er) { diff --git a/test/simple/test-zlib-random-byte-pipes.js b/test/simple/test-zlib-random-byte-pipes.js index f9723cc40d..fc1db1cbba 100644 --- a/test/simple/test-zlib-random-byte-pipes.js +++ b/test/simple/test-zlib-random-byte-pipes.js @@ -150,8 +150,25 @@ var inp = new RandomReadStream({ total: 1024, block: 256, jitter: 16 }); var out = new HashStream(); var gzip = zlib.createGzip(); var gunz = zlib.createGunzip(); + inp.pipe(gzip).pipe(gunz).pipe(out); +inp.on('data', function(c) { + console.error('inp data', c.length); +}); + +gzip.on('data', function(c) { + console.error('gzip data', c.length); +}); + +gunz.on('data', function(c) { + console.error('gunz data', c.length); +}); + +out.on('data', function(c) { + console.error('out data', c.length); +}); + var didSomething = false; out.on('data', function(c) { didSomething = true;