@@ -26,6 +26,8 @@ export const DEFAULT_REQUEST_TIMEOUT = 0;
26
26
export class NodeHttpHandler implements HttpHandler < NodeHttpHandlerOptions > {
27
27
private config ?: ResolvedNodeHttpHandlerConfig ;
28
28
private configProvider : Promise < ResolvedNodeHttpHandlerConfig > ;
29
+ private socketWarningTimestamp = 0 ;
30
+ private socketCheckTimeoutId = ( null as unknown ) as NodeJS . Timeout ;
29
31
30
32
// Node http handler is hard-coded to http/1.1: https://github.com/nodejs/node/blob/ff5664b83b89c55e4ab5d5f60068fb457f1f5872/lib/_http_server.js#L286
31
33
public readonly metadata = { handlerProtocol : "http/1.1" } ;
@@ -45,6 +47,53 @@ export class NodeHttpHandler implements HttpHandler<NodeHttpHandlerOptions> {
45
47
return new NodeHttpHandler ( instanceOrOptions as NodeHttpHandlerOptions ) ;
46
48
}
47
49
50
+ /**
51
+ * @internal
52
+ *
53
+ * @param agent - http(s) agent in use by the NodeHttpHandler instance.
54
+ * @returns timestamp of last emitted warning.
55
+ */
56
+ public static checkSocketUsage ( agent : hAgent | hsAgent , socketWarningTimestamp : number ) : number {
57
+ // note, maxSockets is per origin.
58
+ const { sockets, requests, maxSockets } = agent ;
59
+
60
+ if ( typeof maxSockets !== "number" || maxSockets === Infinity ) {
61
+ return socketWarningTimestamp ;
62
+ }
63
+
64
+ const interval = 15_000 ;
65
+ if ( Date . now ( ) - interval < socketWarningTimestamp ) {
66
+ return socketWarningTimestamp ;
67
+ }
68
+
69
+ let socketsInUse = 0 ;
70
+ let requestsEnqueued = 0 ;
71
+
72
+ if ( sockets ) {
73
+ for ( const key in sockets ) {
74
+ socketsInUse = Math . max ( socketsInUse , sockets [ key ] ?. length ?? 0 ) ;
75
+ }
76
+ }
77
+
78
+ if ( requests ) {
79
+ for ( const key in requests ) {
80
+ requestsEnqueued = Math . max ( requestsEnqueued , requests [ key ] ?. length ?? 0 ) ;
81
+ }
82
+ }
83
+
84
+ // This threshold is somewhat arbitrary.
85
+ // A few enqueued requests is not worth warning about.
86
+ if ( socketsInUse >= maxSockets && requestsEnqueued >= 2 * maxSockets ) {
87
+ console . warn (
88
+ "@smithy/node-http-handler:WARN" ,
89
+ `socket usage at capacity=${ socketsInUse } and ${ requestsEnqueued } additional requests are enqueued.` ,
90
+ "See https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/node-configuring-maxsockets.html"
91
+ ) ;
92
+ return Date . now ( ) ;
93
+ }
94
+ return socketWarningTimestamp ;
95
+ }
96
+
48
97
constructor ( options ?: NodeHttpHandlerOptions | Provider < NodeHttpHandlerOptions | void > ) {
49
98
this . configProvider = new Promise ( ( resolve , reject ) => {
50
99
if ( typeof options === "function" ) {
@@ -81,10 +130,13 @@ export class NodeHttpHandler implements HttpHandler<NodeHttpHandlerOptions> {
81
130
if ( ! this . config ) {
82
131
this . config = await this . configProvider ;
83
132
}
133
+
84
134
return new Promise ( ( _resolve , _reject ) => {
85
135
let writeRequestBodyPromise : Promise < void > | undefined = undefined ;
86
136
const resolve = async ( arg : { response : HttpResponse } ) => {
87
137
await writeRequestBodyPromise ;
138
+ // if requests are still resolving, cancel the socket usage check.
139
+ clearTimeout ( this . socketCheckTimeoutId ) ;
88
140
_resolve ( arg ) ;
89
141
} ;
90
142
const reject = async ( arg : unknown ) => {
@@ -106,6 +158,14 @@ export class NodeHttpHandler implements HttpHandler<NodeHttpHandlerOptions> {
106
158
107
159
// determine which http(s) client to use
108
160
const isSSL = request . protocol === "https:" ;
161
+ const agent = isSSL ? this . config . httpsAgent : this . config . httpAgent ;
162
+
163
+ // If the request is taking a long time, check socket usage and potentially warn.
164
+ // This warning will be cancelled if the request resolves.
165
+ this . socketCheckTimeoutId = setTimeout ( ( ) => {
166
+ this . socketWarningTimestamp = NodeHttpHandler . checkSocketUsage ( agent , this . socketWarningTimestamp ) ;
167
+ } , ( this . config . requestTimeout ?? 2000 ) + ( this . config . connectionTimeout ?? 1000 ) ) ;
168
+
109
169
const queryString = buildQueryString ( request . query || { } ) ;
110
170
let auth = undefined ;
111
171
if ( request . username != null || request . password != null ) {
@@ -126,7 +186,7 @@ export class NodeHttpHandler implements HttpHandler<NodeHttpHandlerOptions> {
126
186
method : request . method ,
127
187
path,
128
188
port : request . port ,
129
- agent : isSSL ? this . config . httpsAgent : this . config . httpAgent ,
189
+ agent,
130
190
auth,
131
191
} ;
132
192
0 commit comments