Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions deps/undici/src/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ typings/
# lock files
package-lock.json
yarn.lock
pnpm-lock.yaml

# IDE files
.idea
Expand Down
24 changes: 22 additions & 2 deletions deps/undici/src/docs/docs/api/CacheStore.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,28 @@ The `MemoryCacheStore` stores the responses in-memory.

**Options**

- `maxSize` - The maximum total size in bytes of all stored responses. Default `Infinity`.
- `maxCount` - The maximum amount of responses to store. Default `Infinity`.
- `maxEntrySize` - The maximum size in bytes that a response's body can be. If a response's body is greater than or equal to this, the response will not be cached.
- `maxEntrySize` - The maximum size in bytes that a response's body can be. If a response's body is greater than or equal to this, the response will not be cached. Default `Infinity`.

### Getters

#### `MemoryCacheStore.size`

Returns the current total size in bytes of all stored responses.

### Methods

#### `MemoryCacheStore.isFull()`

Returns a boolean indicating whether the cache has reached its maximum size or count.

### Events

#### `'maxSizeExceeded'`

Emitted when the cache exceeds its maximum size or count limits. The event payload contains `size`, `maxSize`, `count`, and `maxCount` properties.


### `SqliteCacheStore`

Expand All @@ -26,7 +46,7 @@ The `SqliteCacheStore` is only exposed if the `node:sqlite` api is present.

- `location` - The location of the SQLite database to use. Default `:memory:`.
- `maxCount` - The maximum number of entries to store in the database. Default `Infinity`.
- `maxEntrySize` - The maximum size in bytes that a resposne's body can be. If a response's body is greater than or equal to this, the response will not be cached. Default `Infinity`.
- `maxEntrySize` - The maximum size in bytes that a response's body can be. If a response's body is greater than or equal to this, the response will not be cached. Default `Infinity`.

## Defining a Custom Cache Store

Expand Down
1 change: 1 addition & 0 deletions deps/undici/src/docs/docs/api/Pool.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Extends: [`ClientOptions`](/docs/docs/api/Client.md#parameter-clientoptions)

* **factory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Client(origin, opts)`
* **connections** `number | null` (optional) - Default: `null` - The number of `Client` instances to create. When set to `null`, the `Pool` instance will create an unlimited amount of `Client` instances.
* **clientTtl** `number | null` (optional) - Default: `null` - The amount of time before a `Client` instance is removed from the `Pool` and closed. When set to `null`, `Client` instances will not be removed or closed based on age.

## Instance Properties

Expand Down
1 change: 1 addition & 0 deletions deps/undici/src/docs/docs/api/ProxyAgent.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ For detailed information on the parsing process and potential validation errors,
* **clientFactory** `(origin: URL, opts: Object) => Dispatcher` (optional) - Default: `(origin, opts) => new Pool(origin, opts)`
* **requestTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the request. It extends from [`Client#ConnectOptions`](/docs/docs/api/Client.md#parameter-connectoptions).
* **proxyTls** `BuildOptions` (optional) - Options object passed when creating the underlying socket via the connector builder for the proxy server. It extends from [`Client#ConnectOptions`](/docs/docs/api/Client.md#parameter-connectoptions).
* **proxyTunnel** `boolean` (optional) - By default, ProxyAgent will request that the Proxy facilitate a tunnel between the endpoint and the agent. Setting `proxyTunnel` to false avoids issuing a CONNECT extension, and includes the endpoint domain and path in each request.

Examples:

Expand Down
40 changes: 39 additions & 1 deletion deps/undici/src/lib/cache/memory-cache-store.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'

const { Writable } = require('node:stream')
const { EventEmitter } = require('node:events')
const { assertCacheKey, assertCacheValue } = require('../util/cache.js')

/**
Expand All @@ -12,20 +13,23 @@ const { assertCacheKey, assertCacheValue } = require('../util/cache.js')

/**
* @implements {CacheStore}
* @extends {EventEmitter}
*/
class MemoryCacheStore {
class MemoryCacheStore extends EventEmitter {
#maxCount = Infinity
#maxSize = Infinity
#maxEntrySize = Infinity

#size = 0
#count = 0
#entries = new Map()
#hasEmittedMaxSizeEvent = false

/**
* @param {import('../../types/cache-interceptor.d.ts').default.MemoryCacheStoreOpts | undefined} [opts]
*/
constructor (opts) {
super()
if (opts) {
if (typeof opts !== 'object') {
throw new TypeError('MemoryCacheStore options must be an object')
Expand Down Expand Up @@ -66,6 +70,22 @@ class MemoryCacheStore {
}
}

/**
* Get the current size of the cache in bytes
* @returns {number} The current size of the cache in bytes
*/
get size () {
return this.#size
}

/**
* Check if the cache is full (either max size or max count reached)
* @returns {boolean} True if the cache is full, false otherwise
*/
isFull () {
return this.#size >= this.#maxSize || this.#count >= this.#maxCount
}

/**
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} req
* @returns {import('../../types/cache-interceptor.d.ts').default.GetResult | undefined}
Expand Down Expand Up @@ -144,7 +164,20 @@ class MemoryCacheStore {

store.#size += entry.size

// Check if cache is full and emit event if needed
if (store.#size > store.#maxSize || store.#count > store.#maxCount) {
// Emit maxSizeExceeded event if we haven't already
if (!store.#hasEmittedMaxSizeEvent) {
store.emit('maxSizeExceeded', {
size: store.#size,
maxSize: store.#maxSize,
count: store.#count,
maxCount: store.#maxCount
})
store.#hasEmittedMaxSizeEvent = true
}

// Perform eviction
for (const [key, entries] of store.#entries) {
for (const entry of entries.splice(0, entries.length / 2)) {
store.#size -= entry.size
Expand All @@ -154,6 +187,11 @@ class MemoryCacheStore {
store.#entries.delete(key)
}
}

// Reset the event flag after eviction
if (store.#size < store.#maxSize && store.#count < store.#maxCount) {
store.#hasEmittedMaxSizeEvent = false
}
}

callback(null)
Expand Down
40 changes: 25 additions & 15 deletions deps/undici/src/lib/dispatcher/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,35 @@ class Agent extends DispatcherBase {
}

this[kOnConnect] = (origin, targets) => {
const result = this[kClients].get(origin)
if (result) {
result.count += 1
}
this.emit('connect', origin, [this, ...targets])
}

this[kOnDisconnect] = (origin, targets, err) => {
const result = this[kClients].get(origin)
if (result) {
result.count -= 1
if (result.count <= 0) {
this[kClients].delete(origin)
result.dispatcher.destroy()
}
}
this.emit('disconnect', origin, [this, ...targets], err)
}

this[kOnConnectionError] = (origin, targets, err) => {
// TODO: should this decrement result.count here?
this.emit('connectionError', origin, [this, ...targets], err)
}
}

get [kRunning] () {
let ret = 0
for (const client of this[kClients].values()) {
ret += client[kRunning]
for (const { dispatcher } of this[kClients].values()) {
ret += dispatcher[kRunning]
}
return ret
}
Expand All @@ -73,28 +86,25 @@ class Agent extends DispatcherBase {
throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.')
}

let dispatcher = this[kClients].get(key)

const result = this[kClients].get(key)
let dispatcher = result && result.dispatcher
if (!dispatcher) {
dispatcher = this[kFactory](opts.origin, this[kOptions])
.on('drain', this[kOnDrain])
.on('connect', this[kOnConnect])
.on('disconnect', this[kOnDisconnect])
.on('connectionError', this[kOnConnectionError])

// This introduces a tiny memory leak, as dispatchers are never removed from the map.
// TODO(mcollina): remove te timer when the client/pool do not have any more
// active connections.
this[kClients].set(key, dispatcher)
this[kClients].set(key, { count: 0, dispatcher })
}

return dispatcher.dispatch(opts, handler)
}

async [kClose] () {
const closePromises = []
for (const client of this[kClients].values()) {
closePromises.push(client.close())
for (const { dispatcher } of this[kClients].values()) {
closePromises.push(dispatcher.close())
}
this[kClients].clear()

Expand All @@ -103,8 +113,8 @@ class Agent extends DispatcherBase {

async [kDestroy] (err) {
const destroyPromises = []
for (const client of this[kClients].values()) {
destroyPromises.push(client.destroy(err))
for (const { dispatcher } of this[kClients].values()) {
destroyPromises.push(dispatcher.destroy(err))
}
this[kClients].clear()

Expand All @@ -113,9 +123,9 @@ class Agent extends DispatcherBase {

get stats () {
const allClientStats = {}
for (const client of this[kClients].values()) {
if (client.stats) {
allClientStats[client[kUrl].origin] = client.stats
for (const { dispatcher } of this[kClients].values()) {
if (dispatcher.stats) {
allClientStats[dispatcher[kUrl].origin] = dispatcher.stats
}
}
return allClientStats
Expand Down
20 changes: 17 additions & 3 deletions deps/undici/src/lib/dispatcher/pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ const {
kClients,
kNeedDrain,
kAddClient,
kGetDispatcher
kGetDispatcher,
kRemoveClient
} = require('./pool-base')
const Client = require('./client')
const {
Expand Down Expand Up @@ -35,6 +36,7 @@ class Pool extends PoolBase {
autoSelectFamily,
autoSelectFamilyAttemptTimeout,
allowH2,
clientTtl,
...options
} = {}) {
if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
Expand Down Expand Up @@ -65,12 +67,20 @@ class Pool extends PoolBase {

this[kConnections] = connections || null
this[kUrl] = util.parseOrigin(origin)
this[kOptions] = { ...util.deepClone(options), connect, allowH2 }
this[kOptions] = { ...util.deepClone(options), connect, allowH2, clientTtl }
this[kOptions].interceptors = options.interceptors
? { ...options.interceptors }
: undefined
this[kFactory] = factory

this.on('connect', (origin, targets) => {
if (clientTtl != null && clientTtl > 0) {
for (const target of targets) {
Object.assign(target, { ttl: Date.now() })
}
}
})

this.on('connectionError', (origin, targets, error) => {
// If a connection error occurs, we remove the client from the pool,
// and emit a connectionError event. They will not be re-used.
Expand All @@ -87,8 +97,12 @@ class Pool extends PoolBase {
}

[kGetDispatcher] () {
const clientTtlOption = this[kOptions].clientTtl
for (const client of this[kClients]) {
if (!client[kNeedDrain]) {
// check ttl of client and if it's stale, remove it from the pool
if (clientTtlOption != null && clientTtlOption > 0 && client.ttl && ((Date.now() - client.ttl) > clientTtlOption)) {
this[kRemoveClient](client)
} else if (!client[kNeedDrain]) {
return client
}
}
Expand Down
Loading
Loading