undici 6.1.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/agent.js CHANGED
@@ -1,13 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  const { InvalidArgumentError } = require('./core/errors')
4
- const { kClients, kRunning, kClose, kDestroy, kDispatch, kInterceptors } = require('./core/symbols')
4
+ const { kClients, kRunning, kClose, kDestroy, kDispatch, kInterceptors, kBusy } = require('./core/symbols')
5
5
  const DispatcherBase = require('./dispatcher-base')
6
6
  const Pool = require('./pool')
7
7
  const Client = require('./client')
8
8
  const util = require('./core/util')
9
9
  const createRedirectInterceptor = require('./interceptor/redirectInterceptor')
10
- const { WeakRef, FinalizationRegistry } = require('./compat/dispatcher-weakref')()
11
10
 
12
11
  const kOnConnect = Symbol('onConnect')
13
12
  const kOnDisconnect = Symbol('onDisconnect')
@@ -15,8 +14,8 @@ const kOnConnectionError = Symbol('onConnectionError')
15
14
  const kMaxRedirections = Symbol('maxRedirections')
16
15
  const kOnDrain = Symbol('onDrain')
17
16
  const kFactory = Symbol('factory')
18
- const kFinalizer = Symbol('finalizer')
19
17
  const kOptions = Symbol('options')
18
+ const kDeleteScheduled = Symbol('deleteScheduled')
20
19
 
21
20
  function defaultFactory (origin, opts) {
22
21
  return opts && opts.connections === 1
@@ -55,12 +54,6 @@ class Agent extends DispatcherBase {
55
54
  this[kMaxRedirections] = maxRedirections
56
55
  this[kFactory] = factory
57
56
  this[kClients] = new Map()
58
- this[kFinalizer] = new FinalizationRegistry(/* istanbul ignore next: gc is undeterministic */ key => {
59
- const ref = this[kClients].get(key)
60
- if (ref !== undefined && ref.deref() === undefined) {
61
- this[kClients].delete(key)
62
- }
63
- })
64
57
 
65
58
  const agent = this
66
59
 
@@ -83,12 +76,8 @@ class Agent extends DispatcherBase {
83
76
 
84
77
  get [kRunning] () {
85
78
  let ret = 0
86
- for (const ref of this[kClients].values()) {
87
- const client = ref.deref()
88
- /* istanbul ignore next: gc is undeterministic */
89
- if (client) {
90
- ret += client[kRunning]
91
- }
79
+ for (const client of this[kClients].values()) {
80
+ ret += client[kRunning]
92
81
  }
93
82
  return ret
94
83
  }
@@ -101,18 +90,38 @@ class Agent extends DispatcherBase {
101
90
  throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.')
102
91
  }
103
92
 
104
- const ref = this[kClients].get(key)
93
+ let dispatcher = this[kClients].get(key)
105
94
 
106
- let dispatcher = ref ? ref.deref() : null
107
95
  if (!dispatcher) {
108
96
  dispatcher = this[kFactory](opts.origin, this[kOptions])
109
- .on('drain', this[kOnDrain])
97
+ .on('drain', (...args) => {
98
+ this[kOnDrain](...args)
99
+
100
+ // We remove the client if it is not busy for 5 minutes
101
+ // to avoid a long list of clients to saturate memory.
102
+ // Ideally, we could use a FinalizationRegistry here, but
103
+ // it is currently very buggy in Node.js.
104
+ // See
105
+ // * https://github.com/nodejs/node/issues/49344
106
+ // * https://github.com/nodejs/node/issues/47748
107
+ // TODO(mcollina): make the timeout configurable or
108
+ // use an event to remove disconnected clients.
109
+ this[kDeleteScheduled] = setTimeout(() => {
110
+ if (dispatcher[kBusy] === 0) {
111
+ this[kClients].destroy().then(() => {})
112
+ this[kClients].delete(key)
113
+ }
114
+ }, 300_000)
115
+ this[kDeleteScheduled].unref()
116
+ })
110
117
  .on('connect', this[kOnConnect])
111
118
  .on('disconnect', this[kOnDisconnect])
112
119
  .on('connectionError', this[kOnConnectionError])
113
120
 
114
- this[kClients].set(key, new WeakRef(dispatcher))
115
- this[kFinalizer].register(dispatcher, key)
121
+ this[kClients].set(key, dispatcher)
122
+ } else if (dispatcher[kDeleteScheduled]) {
123
+ clearTimeout(dispatcher[kDeleteScheduled])
124
+ dispatcher[kDeleteScheduled] = null
116
125
  }
117
126
 
118
127
  return dispatcher.dispatch(opts, handler)
@@ -120,28 +129,20 @@ class Agent extends DispatcherBase {
120
129
 
121
130
  async [kClose] () {
122
131
  const closePromises = []
123
- for (const ref of this[kClients].values()) {
124
- const client = ref.deref()
125
- /* istanbul ignore else: gc is undeterministic */
126
- if (client) {
127
- this[kFinalizer].unregister(client)
128
- closePromises.push(client.close())
129
- }
132
+ for (const client of this[kClients].values()) {
133
+ closePromises.push(client.close())
130
134
  }
135
+ this[kClients].clear()
131
136
 
132
137
  await Promise.all(closePromises)
133
138
  }
134
139
 
135
140
  async [kDestroy] (err) {
136
141
  const destroyPromises = []
137
- for (const ref of this[kClients].values()) {
138
- const client = ref.deref()
139
- /* istanbul ignore else: gc is undeterministic */
140
- if (client) {
141
- this[kFinalizer].unregister(client)
142
- destroyPromises.push(client.destroy(err))
143
- }
142
+ for (const client of this[kClients].values()) {
143
+ destroyPromises.push(client.destroy(err))
144
144
  }
145
+ this[kClients].clear()
145
146
 
146
147
  await Promise.all(destroyPromises)
147
148
  }
@@ -21,16 +21,6 @@ const Dispatcher = require('../dispatcher')
21
21
  const Pluralizer = require('./pluralizer')
22
22
  const PendingInterceptorsFormatter = require('./pending-interceptors-formatter')
23
23
 
24
- class FakeWeakRef {
25
- constructor (value) {
26
- this.value = value
27
- }
28
-
29
- deref () {
30
- return this.value
31
- }
32
- }
33
-
34
24
  class MockAgent extends Dispatcher {
35
25
  constructor (opts) {
36
26
  super(opts)
@@ -103,7 +93,7 @@ class MockAgent extends Dispatcher {
103
93
  }
104
94
 
105
95
  [kMockAgentSet] (origin, dispatcher) {
106
- this[kClients].set(origin, new FakeWeakRef(dispatcher))
96
+ this[kClients].set(origin, dispatcher)
107
97
  }
108
98
 
109
99
  [kFactory] (origin) {
@@ -115,9 +105,9 @@ class MockAgent extends Dispatcher {
115
105
 
116
106
  [kMockAgentGet] (origin) {
117
107
  // First check if we can immediately find it
118
- const ref = this[kClients].get(origin)
119
- if (ref) {
120
- return ref.deref()
108
+ const client = this[kClients].get(origin)
109
+ if (client) {
110
+ return client
121
111
  }
122
112
 
123
113
  // If the origin is not a string create a dummy parent pool and return to user
@@ -128,8 +118,7 @@ class MockAgent extends Dispatcher {
128
118
  }
129
119
 
130
120
  // If we match, create a pool and assign the same dispatches
131
- for (const [keyMatcher, nonExplicitRef] of Array.from(this[kClients])) {
132
- const nonExplicitDispatcher = nonExplicitRef.deref()
121
+ for (const [keyMatcher, nonExplicitDispatcher] of Array.from(this[kClients])) {
133
122
  if (nonExplicitDispatcher && typeof keyMatcher !== 'string' && matchValue(keyMatcher, origin)) {
134
123
  const dispatcher = this[kFactory](origin)
135
124
  this[kMockAgentSet](origin, dispatcher)
@@ -147,7 +136,7 @@ class MockAgent extends Dispatcher {
147
136
  const mockAgentClients = this[kClients]
148
137
 
149
138
  return Array.from(mockAgentClients.entries())
150
- .flatMap(([origin, scope]) => scope.deref()[kDispatches].map(dispatch => ({ ...dispatch, origin })))
139
+ .flatMap(([origin, scope]) => scope[kDispatches].map(dispatch => ({ ...dispatch, origin })))
151
140
  .filter(({ pending }) => pending)
152
141
  }
153
142
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "6.1.0",
3
+ "version": "6.2.0",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {