@npmcli/agent
Advanced tools
+52
-0
@@ -206,2 +206,54 @@ 'use strict' | ||
| } | ||
| // When connect() rejects, agent-base removes only its placeholder socket, so Node never drains this.requests[name] and requests queued past maxSockets hang forever. | ||
| // On a failure we dispatch the next queued request ourselves. | ||
| // See npm/cli#9386 and TooTallNate/proxy-agents#427. | ||
| createSocket (req, options, cb) { | ||
| super.createSocket(req, options, (err, socket) => { | ||
| if (err) { | ||
| this.#drainPendingRequests(req, options) | ||
| } | ||
| cb(err, socket) | ||
| }) | ||
| } | ||
| // Dispatch the next request queued behind maxSockets, reusing the slot the failed connection freed. | ||
| #drainPendingRequests (failedReq, options) { | ||
| const name = this.getName(options) | ||
| const queue = this.requests[name] | ||
| if (!queue || queue.length === 0) { | ||
| return | ||
| } | ||
| // Node's removeSocket() picks a queued request without shifting it off, so drop the failed one to avoid dispatching it twice. | ||
| const failedIndex = queue.indexOf(failedReq) | ||
| if (failedIndex !== -1) { | ||
| queue.splice(failedIndex, 1) | ||
| } | ||
| if (queue.length === 0) { | ||
| delete this.requests[name] | ||
| return | ||
| } | ||
| // Safety belt: only dispatch if a socket slot is genuinely free. | ||
| const socketCount = this.sockets[name] ? this.sockets[name].length : 0 | ||
| if (socketCount >= this.maxSockets || this.totalSocketCount >= this.maxTotalSockets) { | ||
| return | ||
| } | ||
| const nextReq = queue.shift() | ||
| if (queue.length === 0) { | ||
| delete this.requests[name] | ||
| } | ||
| // All queued requests share this origin, so the failed request's options suit the next one. | ||
| // createSocket() recurses here if this connection also fails, draining the whole queue. | ||
| this.createSocket(nextReq, options, (err, socket) => { | ||
| if (err) { | ||
| nextReq.onSocket(null, err) | ||
| } else { | ||
| nextReq.onSocket(socket) | ||
| } | ||
| }) | ||
| } | ||
| } |
+4
-0
@@ -40,2 +40,6 @@ 'use strict' | ||
| // since opts is often passed when initiating requests, it may contain | ||
| // headers, which should not be saved in an agent | ||
| delete normalized.headers | ||
| return normalized | ||
@@ -42,0 +46,0 @@ } |
+9
-7
| { | ||
| "name": "@npmcli/agent", | ||
| "version": "4.0.0", | ||
| "version": "4.0.2", | ||
| "description": "the http/https agent used by the npm cli", | ||
@@ -32,4 +32,6 @@ "main": "lib/index.js", | ||
| "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", | ||
| "version": "4.25.0", | ||
| "publish": "true" | ||
| "version": "4.30.0", | ||
| "publish": "true", | ||
| "updateNpm": false, | ||
| "latestCiVersion": 24 | ||
| }, | ||
@@ -44,7 +46,7 @@ "dependencies": { | ||
| "devDependencies": { | ||
| "@npmcli/eslint-config": "^5.0.0", | ||
| "@npmcli/template-oss": "4.25.0", | ||
| "minipass-fetch": "^4.0.1", | ||
| "@npmcli/eslint-config": "^6.0.0", | ||
| "@npmcli/template-oss": "4.30.0", | ||
| "ip-address": "^10.1.0", | ||
| "minipass-fetch": "^5.0.0", | ||
| "nock": "^14.0.3", | ||
| "socksv5": "^0.0.6", | ||
| "tap": "^16.3.0" | ||
@@ -51,0 +53,0 @@ }, |
19797
11.54%515
10.52%