| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a2881b7 commit 5f406a3
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -665,7 +665,7 @@ function parserOnIncomingClient(res, shouldKeepAlive) { | |||
| 665 | 665 | ||
| 666 | 666 | // Add our listener first, so that we guarantee socket cleanup | |
| 667 | 667 | res.on('end', responseOnEnd); | |
| 668 | - req.on('prefinish', requestOnPrefinish); | ||
| 668 | + req.on('finish', requestOnFinish); | ||
| 669 | 669 | socket.on('timeout', responseOnTimeout); | |
| 670 | 670 | ||
| 671 | 671 | // If the user did not listen for the 'response' event, then they | |
@@ -737,12 +737,16 @@ function responseOnEnd() { | |||
| 737 | 737 | socket.end(); | |
| 738 | 738 | } | |
| 739 | 739 | assert(!socket.writable); | |
| 740 | - } else if (req.finished && !this.aborted) { | ||
| 740 | + } else if (req.writableFinished && !this.aborted) { | ||
| 741 | + assert(req.finished); | ||
| 741 | 742 | // We can assume `req.finished` means all data has been written since: | |
| 742 | 743 | // - `'responseOnEnd'` means we have been assigned a socket. | |
| 743 | 744 | // - when we have a socket we write directly to it without buffering. | |
| 744 | 745 | // - `req.finished` means `end()` has been called and no further data. | |
| 745 | 746 | // can be written | |
| 747 | + // In addition, `req.writableFinished` means all data written has been | ||
| 748 | + // accepted by the kernel. (i.e. the `req.socket` is drained).Without | ||
| 749 | + // this constraint, we may assign a non drained socket to a request. | ||
| 746 | 750 | responseKeepAlive(req); | |
| 747 | 751 | } | |
| 748 | 752 | } | |
@@ -755,7 +759,9 @@ function responseOnTimeout() { | |||
| 755 | 759 | res.emit('timeout'); | |
| 756 | 760 | } | |
| 757 | 761 | ||
| 758 | - function requestOnPrefinish() { | ||
| 762 | + // This function is necessary in the case where we receive the entire reponse | ||
| 763 | + // from server before we finish sending out the request | ||
| 764 | + function requestOnFinish() { | ||
| 759 | 765 | const req = this; | |
| 760 | 766 | ||
| 761 | 767 | if (req.shouldKeepAlive && req._ended) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -985,6 +985,8 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { | |||
| 985 | 985 | }; | |
| 986 | 986 | ||
| 987 | 987 | ||
| 988 | + // This function is called once all user data are flushed to the socket. | ||
| 989 | + // Note that it has a chance that the socket is not drained. | ||
| 988 | 990 | OutgoingMessage.prototype._finish = function _finish() { | |
| 989 | 991 | assert(this.socket); | |
| 990 | 992 | this.emit('prefinish'); | |
@@ -1008,7 +1010,7 @@ OutgoingMessage.prototype._finish = function _finish() { | |||
| 1008 | 1010 | // the socket yet. Thus the outgoing messages need to be prepared to queue | |
| 1009 | 1011 | // up data internally before sending it on further to the socket's queue. | |
| 1010 | 1012 | // | |
| 1011 | - // This function, outgoingFlush(), is called by both the Server and Client | ||
| 1013 | + // This function, _flush(), is called by both the Server and Client | ||
| 1012 | 1014 | // to attempt to flush any pending messages out to the socket. | |
| 1013 | 1015 | OutgoingMessage.prototype._flush = function _flush() { | |
| 1014 | 1016 | const socket = this.socket; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,122 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const assert = require('assert'); | ||
| 4 | + const http = require('http'); | ||
| 5 | + const net = require('net'); | ||
| 6 | + | ||
| 7 | + const agent = new http.Agent({ | ||
| 8 | + keepAlive: true, | ||
| 9 | + maxFreeSockets: Infinity, | ||
| 10 | + maxSockets: Infinity, | ||
| 11 | + maxTotalSockets: Infinity, | ||
| 12 | + }); | ||
| 13 | + | ||
| 14 | + const server = net.createServer({ | ||
| 15 | + pauseOnConnect: true, | ||
| 16 | + }, (sock) => { | ||
| 17 | + // Do not read anything from `sock` | ||
| 18 | + sock.pause(); | ||
| 19 | + sock.write('HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: Keep-Alive\r\n\r\n'); | ||
| 20 | + }); | ||
| 21 | + | ||
| 22 | + server.listen(0, common.mustCall(() => { | ||
| 23 | + sendFstReq(server.address().port); | ||
| 24 | + })); | ||
| 25 | + | ||
| 26 | + function sendFstReq(serverPort) { | ||
| 27 | + const req = http.request({ | ||
| 28 | + agent, | ||
| 29 | + host: '127.0.0.1', | ||
| 30 | + port: serverPort, | ||
| 31 | + }, (res) => { | ||
| 32 | + res.on('data', noop); | ||
| 33 | + res.on('end', common.mustCall(() => { | ||
| 34 | + // Agent's socket reusing code is registered to process.nextTick(), | ||
| 35 | + // and will be run after this function, make sure it take effect. | ||
| 36 | + setImmediate(sendSecReq, serverPort, req.socket.localPort); | ||
| 37 | + })); | ||
| 38 | + }); | ||
| 39 | + | ||
| 40 | + // Make the `req.socket` non drained, i.e. has some data queued to write to | ||
| 41 | + // and accept by the kernel. In Linux and Mac, we only need to call `req.end(aLargeBuffer)`. | ||
| 42 | + // However, in Windows, the mechanism of acceptance is loose, the following code is a workaround | ||
| 43 | + // for Windows. | ||
| 44 | + | ||
| 45 | + /** | ||
| 46 | + * https://docs.microsoft.com/en-US/troubleshoot/windows/win32/data-segment-tcp-winsock says | ||
| 47 | + * | ||
| 48 | + * Winsock uses the following rules to indicate a send completion to the application | ||
| 49 | + * (depending on how the send is invoked, the completion notification could be the | ||
| 50 | + * function returning from a blocking call, signaling an event, or calling a notification | ||
| 51 | + * function, and so forth): | ||
| 52 | + * - If the socket is still within SO_SNDBUF quota, Winsock copies the data from the application | ||
| 53 | + * send and indicates the send completion to the application. | ||
| 54 | + * - If the socket is beyond SO_SNDBUF quota and there's only one previously buffered send still | ||
| 55 | + * in the stack kernel buffer, Winsock copies the data from the application send and indicates | ||
| 56 | + * the send completion to the application. | ||
| 57 | + * - If the socket is beyond SO_SNDBUF quota and there's more than one previously buffered send | ||
| 58 | + * in the stack kernel buffer, Winsock copies the data from the application send. Winsock doesn't | ||
| 59 | + * indicate the send completion to the application until the stack completes enough sends to put | ||
| 60 | + * back the socket within SO_SNDBUF quota or only one outstanding send condition. | ||
| 61 | + */ | ||
| 62 | + | ||
| 63 | + req.on('socket', () => { | ||
| 64 | + req.socket.on('connect', () => { | ||
| 65 | + // Print tcp send buffer information | ||
| 66 | + console.log(process.report.getReport().libuv.filter((handle) => handle.type === 'tcp')); | ||
| 67 | + | ||
| 68 | + const dataLargerThanTCPSendBuf = Buffer.alloc(1024 * 1024 * 64, 0); | ||
| 69 | + | ||
| 70 | + req.write(dataLargerThanTCPSendBuf); | ||
| 71 | + req.uncork(); | ||
| 72 | + if (process.platform === 'win32') { | ||
| 73 | + assert.ok(req.socket.writableLength === 0); | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + req.write(dataLargerThanTCPSendBuf); | ||
| 77 | + req.uncork(); | ||
| 78 | + if (process.platform === 'win32') { | ||
| 79 | + assert.ok(req.socket.writableLength === 0); | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + req.end(dataLargerThanTCPSendBuf); | ||
| 83 | + assert.ok(req.socket.writableLength > 0); | ||
| 84 | + }); | ||
| 85 | + }); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + function sendSecReq(serverPort, fstReqCliPort) { | ||
| 89 | + // Make the second request, which should be sent on a new socket | ||
| 90 | + // because the first socket is not drained and hence can not be reused | ||
| 91 | + const req = http.request({ | ||
| 92 | + agent, | ||
| 93 | + host: '127.0.0.1', | ||
| 94 | + port: serverPort, | ||
| 95 | + }, (res) => { | ||
| 96 | + res.on('data', noop); | ||
| 97 | + res.on('end', common.mustCall(() => { | ||
| 98 | + setImmediate(sendThrReq, serverPort, req.socket.localPort); | ||
| 99 | + })); | ||
| 100 | + }); | ||
| 101 | + | ||
| 102 | + req.on('socket', common.mustCall((sock) => { | ||
| 103 | + assert.notStrictEqual(sock.localPort, fstReqCliPort); | ||
| 104 | + })); | ||
| 105 | + req.end(); | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + function sendThrReq(serverPort, secReqCliPort) { | ||
| 109 | + // Make the third request, the agent should reuse the second socket we just made | ||
| 110 | + const req = http.request({ | ||
| 111 | + agent, | ||
| 112 | + host: '127.0.0.1', | ||
| 113 | + port: serverPort, | ||
| 114 | + }, noop); | ||
| 115 | + | ||
| 116 | + req.on('socket', common.mustCall((sock) => { | ||
| 117 | + assert.strictEqual(sock.localPort, secReqCliPort); | ||
| 118 | + process.exit(0); | ||
| 119 | + })); | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + function noop() { } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments