PageRenderTime 76ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/_stream_writable.js

https://github.com/bigeasy/node
JavaScript | 453 lines | 286 code | 81 blank | 86 comment | 51 complexity | 77f8d5543e5f187b52147e4f8145c2a3 MD5 | raw file
Possible License(s): WTFPL, BSD-3-Clause, ISC, 0BSD, Apache-2.0, MIT, AGPL-3.0
  1. // Copyright Joyent, Inc. and other Node contributors.
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a
  4. // copy of this software and associated documentation files (the
  5. // "Software"), to deal in the Software without restriction, including
  6. // without limitation the rights to use, copy, modify, merge, publish,
  7. // distribute, sublicense, and/or sell copies of the Software, and to permit
  8. // persons to whom the Software is furnished to do so, subject to the
  9. // following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included
  12. // in all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  15. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  17. // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  18. // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  19. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  20. // USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. // A bit simpler than readable streams.
  22. // Implement an async ._write(chunk, cb), and it'll handle all
  23. // the drain event emission and buffering.
  24. module.exports = Writable;
  25. Writable.WritableState = WritableState;
  26. var util = require('util');
  27. var Stream = require('stream');
  28. util.inherits(Writable, Stream);
  29. function WriteReq(chunk, encoding, cb) {
  30. this.chunk = chunk;
  31. this.encoding = encoding;
  32. this.callback = cb;
  33. }
  34. function WritableState(options, stream) {
  35. options = options || {};
  36. // the point at which write() starts returning false
  37. // Note: 0 is a valid value, means that we always return false if
  38. // the entire buffer is not flushed immediately on write()
  39. var hwm = options.highWaterMark;
  40. this.highWaterMark = (hwm || hwm === 0) ? hwm : 16 * 1024;
  41. // object stream flag to indicate whether or not this stream
  42. // contains buffers or objects.
  43. this.objectMode = !!options.objectMode;
  44. // cast to ints.
  45. this.highWaterMark = ~~this.highWaterMark;
  46. this.needDrain = false;
  47. // at the start of calling end()
  48. this.ending = false;
  49. // when end() has been called, and returned
  50. this.ended = false;
  51. // when 'finish' is emitted
  52. this.finished = false;
  53. // should we decode strings into buffers before passing to _write?
  54. // this is here so that some node-core streams can optimize string
  55. // handling at a lower level.
  56. var noDecode = options.decodeStrings === false;
  57. this.decodeStrings = !noDecode;
  58. // Crypto is kind of old and crusty. Historically, its default string
  59. // encoding is 'binary' so we have to make this configurable.
  60. // Everything else in the universe uses 'utf8', though.
  61. this.defaultEncoding = options.defaultEncoding || 'utf8';
  62. // not an actual buffer we keep track of, but a measurement
  63. // of how much we're waiting to get pushed to some underlying
  64. // socket or file.
  65. this.length = 0;
  66. // a flag to see when we're in the middle of a write.
  67. this.writing = false;
  68. // when true all writes will be buffered until .uncork() call
  69. this.corked = 0;
  70. // a flag to be able to tell if the onwrite cb is called immediately,
  71. // or on a later tick. We set this to true at first, becuase any
  72. // actions that shouldn't happen until "later" should generally also
  73. // not happen before the first write call.
  74. this.sync = true;
  75. // a flag to know if we're processing previously buffered items, which
  76. // may call the _write() callback in the same tick, so that we don't
  77. // end up in an overlapped onwrite situation.
  78. this.bufferProcessing = false;
  79. // the callback that's passed to _write(chunk,cb)
  80. this.onwrite = function(er) {
  81. onwrite(stream, er);
  82. };
  83. // the callback that the user supplies to write(chunk,encoding,cb)
  84. this.writecb = null;
  85. // the amount that is being written when _write is called.
  86. this.writelen = 0;
  87. this.buffer = [];
  88. // number of pending user-supplied write callbacks
  89. // this must be 0 before 'finish' can be emitted
  90. this.pendingcb = 0;
  91. // emit prefinish if the only thing we're waiting for is _write cbs
  92. // This is relevant for synchronous Transform streams
  93. this.prefinished = false;
  94. }
  95. function Writable(options) {
  96. // Writable ctor is applied to Duplexes, though they're not
  97. // instanceof Writable, they're instanceof Readable.
  98. if (!(this instanceof Writable) && !(this instanceof Stream.Duplex))
  99. return new Writable(options);
  100. this._writableState = new WritableState(options, this);
  101. // legacy.
  102. this.writable = true;
  103. Stream.call(this);
  104. }
  105. // Otherwise people can pipe Writable streams, which is just wrong.
  106. Writable.prototype.pipe = function() {
  107. this.emit('error', new Error('Cannot pipe. Not readable.'));
  108. };
  109. function writeAfterEnd(stream, state, cb) {
  110. var er = new Error('write after end');
  111. // TODO: defer error events consistently everywhere, not just the cb
  112. stream.emit('error', er);
  113. process.nextTick(function() {
  114. cb(er);
  115. });
  116. }
  117. // If we get something that is not a buffer, string, null, or undefined,
  118. // and we're not in objectMode, then that's an error.
  119. // Otherwise stream chunks are all considered to be of length=1, and the
  120. // watermarks determine how many objects to keep in the buffer, rather than
  121. // how many bytes or characters.
  122. function validChunk(stream, state, chunk, cb) {
  123. var valid = true;
  124. if (!util.isBuffer(chunk) &&
  125. !util.isString(chunk) &&
  126. !util.isNullOrUndefined(chunk) &&
  127. !state.objectMode) {
  128. var er = new TypeError('Invalid non-string/buffer chunk');
  129. stream.emit('error', er);
  130. process.nextTick(function() {
  131. cb(er);
  132. });
  133. valid = false;
  134. }
  135. return valid;
  136. }
  137. Writable.prototype.write = function(chunk, encoding, cb) {
  138. var state = this._writableState;
  139. var ret = false;
  140. if (util.isFunction(encoding)) {
  141. cb = encoding;
  142. encoding = null;
  143. }
  144. if (util.isBuffer(chunk))
  145. encoding = 'buffer';
  146. else if (!encoding)
  147. encoding = state.defaultEncoding;
  148. if (!util.isFunction(cb))
  149. cb = function() {};
  150. if (state.ended)
  151. writeAfterEnd(this, state, cb);
  152. else if (validChunk(this, state, chunk, cb)) {
  153. state.pendingcb++;
  154. ret = writeOrBuffer(this, state, chunk, encoding, cb);
  155. }
  156. return ret;
  157. };
  158. Writable.prototype.cork = function() {
  159. var state = this._writableState;
  160. state.corked++;
  161. };
  162. Writable.prototype.uncork = function() {
  163. var state = this._writableState;
  164. if (state.corked) {
  165. state.corked--;
  166. if (!state.writing &&
  167. !state.corked &&
  168. !state.finished &&
  169. !state.bufferProcessing &&
  170. state.buffer.length)
  171. clearBuffer(this, state);
  172. }
  173. };
  174. function decodeChunk(state, chunk, encoding) {
  175. if (!state.objectMode &&
  176. state.decodeStrings !== false &&
  177. util.isString(chunk)) {
  178. chunk = new Buffer(chunk, encoding);
  179. }
  180. return chunk;
  181. }
  182. // if we're already writing something, then just put this
  183. // in the queue, and wait our turn. Otherwise, call _write
  184. // If we return false, then we need a drain event, so set that flag.
  185. function writeOrBuffer(stream, state, chunk, encoding, cb) {
  186. chunk = decodeChunk(state, chunk, encoding);
  187. if (util.isBuffer(chunk))
  188. encoding = 'buffer';
  189. var len = state.objectMode ? 1 : chunk.length;
  190. state.length += len;
  191. var ret = state.length < state.highWaterMark;
  192. state.needDrain = !ret;
  193. if (state.writing || state.corked)
  194. state.buffer.push(new WriteReq(chunk, encoding, cb));
  195. else
  196. doWrite(stream, state, false, len, chunk, encoding, cb);
  197. return ret;
  198. }
  199. function doWrite(stream, state, writev, len, chunk, encoding, cb) {
  200. state.writelen = len;
  201. state.writecb = cb;
  202. state.writing = true;
  203. state.sync = true;
  204. if (writev)
  205. stream._writev(chunk, state.onwrite);
  206. else
  207. stream._write(chunk, encoding, state.onwrite);
  208. state.sync = false;
  209. }
  210. function onwriteError(stream, state, sync, er, cb) {
  211. if (sync)
  212. process.nextTick(function() {
  213. state.pendingcb--;
  214. cb(er);
  215. });
  216. else {
  217. state.pendingcb--;
  218. cb(er);
  219. }
  220. stream.emit('error', er);
  221. }
  222. function onwriteStateUpdate(state) {
  223. state.writing = false;
  224. state.writecb = null;
  225. state.length -= state.writelen;
  226. state.writelen = 0;
  227. }
  228. function onwrite(stream, er) {
  229. var state = stream._writableState;
  230. var sync = state.sync;
  231. var cb = state.writecb;
  232. onwriteStateUpdate(state);
  233. if (er)
  234. onwriteError(stream, state, sync, er, cb);
  235. else {
  236. // Check if we're actually ready to finish, but don't emit yet
  237. var finished = needFinish(stream, state);
  238. if (!finished &&
  239. !state.corked &&
  240. !state.bufferProcessing &&
  241. state.buffer.length) {
  242. clearBuffer(stream, state);
  243. }
  244. if (sync) {
  245. process.nextTick(function() {
  246. afterWrite(stream, state, finished, cb);
  247. });
  248. } else {
  249. afterWrite(stream, state, finished, cb);
  250. }
  251. }
  252. }
  253. function afterWrite(stream, state, finished, cb) {
  254. if (!finished)
  255. onwriteDrain(stream, state);
  256. state.pendingcb--;
  257. cb();
  258. finishMaybe(stream, state);
  259. }
  260. // Must force callback to be called on nextTick, so that we don't
  261. // emit 'drain' before the write() consumer gets the 'false' return
  262. // value, and has a chance to attach a 'drain' listener.
  263. function onwriteDrain(stream, state) {
  264. if (state.length === 0 && state.needDrain) {
  265. state.needDrain = false;
  266. stream.emit('drain');
  267. }
  268. }
  269. // if there's something in the buffer waiting, then process it
  270. function clearBuffer(stream, state) {
  271. state.bufferProcessing = true;
  272. if (stream._writev && state.buffer.length > 1) {
  273. // Fast case, write everything using _writev()
  274. var cbs = [];
  275. for (var c = 0; c < state.buffer.length; c++)
  276. cbs.push(state.buffer[c].callback);
  277. // count the one we are adding, as well.
  278. // TODO(isaacs) clean this up
  279. state.pendingcb++;
  280. doWrite(stream, state, true, state.length, state.buffer, '', function(err) {
  281. for (var i = 0; i < cbs.length; i++) {
  282. state.pendingcb--;
  283. cbs[i](err);
  284. }
  285. });
  286. // Clear buffer
  287. state.buffer = [];
  288. } else {
  289. // Slow case, write chunks one-by-one
  290. for (var c = 0; c < state.buffer.length; c++) {
  291. var entry = state.buffer[c];
  292. var chunk = entry.chunk;
  293. var encoding = entry.encoding;
  294. var cb = entry.callback;
  295. var len = state.objectMode ? 1 : chunk.length;
  296. doWrite(stream, state, false, len, chunk, encoding, cb);
  297. // if we didn't call the onwrite immediately, then
  298. // it means that we need to wait until it does.
  299. // also, that means that the chunk and cb are currently
  300. // being processed, so move the buffer counter past them.
  301. if (state.writing) {
  302. c++;
  303. break;
  304. }
  305. }
  306. if (c < state.buffer.length)
  307. state.buffer = state.buffer.slice(c);
  308. else
  309. state.buffer.length = 0;
  310. }
  311. state.bufferProcessing = false;
  312. }
  313. Writable.prototype._write = function(chunk, encoding, cb) {
  314. cb(new Error('not implemented'));
  315. };
  316. Writable.prototype._writev = null;
  317. Writable.prototype.end = function(chunk, encoding, cb) {
  318. var state = this._writableState;
  319. if (util.isFunction(chunk)) {
  320. cb = chunk;
  321. chunk = null;
  322. encoding = null;
  323. } else if (util.isFunction(encoding)) {
  324. cb = encoding;
  325. encoding = null;
  326. }
  327. if (!util.isNullOrUndefined(chunk))
  328. this.write(chunk, encoding);
  329. // .end() fully uncorks
  330. if (state.corked) {
  331. state.corked = 1;
  332. this.uncork();
  333. }
  334. // ignore unnecessary end() calls.
  335. if (!state.ending && !state.finished)
  336. endWritable(this, state, cb);
  337. };
  338. function needFinish(stream, state) {
  339. return (state.ending &&
  340. state.length === 0 &&
  341. !state.finished &&
  342. !state.writing);
  343. }
  344. function prefinish(stream, state) {
  345. if (!state.prefinished) {
  346. state.prefinished = true;
  347. stream.emit('prefinish');
  348. }
  349. }
  350. function finishMaybe(stream, state) {
  351. var need = needFinish(stream, state);
  352. if (need) {
  353. if (state.pendingcb === 0) {
  354. prefinish(stream, state);
  355. state.finished = true;
  356. stream.emit('finish');
  357. } else
  358. prefinish(stream, state);
  359. }
  360. return need;
  361. }
  362. function endWritable(stream, state, cb) {
  363. state.ending = true;
  364. finishMaybe(stream, state);
  365. if (cb) {
  366. if (state.finished)
  367. process.nextTick(cb);
  368. else
  369. stream.once('finish', cb);
  370. }
  371. state.ended = true;
  372. }