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