Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: memory store #3834

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
123 changes: 24 additions & 99 deletions lib/cache/memory-cache-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,7 @@ class MemoryCacheStore {

const values = this.#getValuesForRequest(key, true)

/**
* @type {(MemoryStoreValue & { index: number }) | undefined}
*/
let value = this.#findValue(key, values)
let valueIndex = value?.index
if (!value) {
// The value doesn't already exist, meaning we haven't cached this
// response before. Let's assign it a value and insert it into our data
Expand All @@ -118,53 +114,12 @@ class MemoryCacheStore {
return undefined
}

this.#entryCount++

value = {
locked: true,
opts
if (this.#entryCount++ > this.#maxEntries) {
this.#prune()
}

// We want to sort our responses in decending order by their deleteAt
// timestamps so that deleting expired responses is faster
if (
values.length === 0 ||
opts.deleteAt < values[values.length - 1].deleteAt
) {
// Our value is either the only response for this path or our deleteAt
// time is sooner than all the other responses
values.push(value)
valueIndex = values.length - 1
} else if (opts.deleteAt >= values[0].deleteAt) {
// Our deleteAt is later than everyone elses
values.unshift(value)
valueIndex = 0
} else {
// We're neither in the front or the end, let's just binary search to
// find our stop we need to be in
let startIndex = 0
let endIndex = values.length
while (true) {
if (startIndex === endIndex) {
values.splice(startIndex, 0, value)
break
}

const middleIndex = Math.floor((startIndex + endIndex) / 2)
const middleValue = values[middleIndex]
if (opts.deleteAt === middleIndex) {
values.splice(middleIndex, 0, value)
valueIndex = middleIndex
break
} else if (opts.deleteAt > middleValue.opts.deleteAt) {
endIndex = middleIndex
continue
} else {
startIndex = middleIndex
continue
}
}
}
value = { locked: true, opts }
values.push(value)
} else {
// Check if there's already another request writing to the value or
// a request reading from it
Expand All @@ -180,7 +135,7 @@ class MemoryCacheStore {
/**
* @type {Buffer[] | null}
*/
let body = key.method !== 'HEAD' ? [] : null
let body = []
const maxEntrySize = this.#maxEntrySize

const writable = new Writable({
Expand All @@ -202,7 +157,6 @@ class MemoryCacheStore {
if (currentSize >= maxEntrySize) {
body = null
this.end()
shiftAtIndex(values, valueIndex)
return callback()
}

Expand Down Expand Up @@ -263,63 +217,34 @@ class MemoryCacheStore {
* to respond with.
* @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} req
* @param {MemoryStoreValue[]} values
* @returns {(MemoryStoreValue & { index: number }) | undefined}
* @returns {(MemoryStoreValue) | undefined}
*/
#findValue (req, values) {
/**
* @type {MemoryStoreValue | undefined}
*/
let value
const now = Date.now()
for (let i = values.length - 1; i >= 0; i--) {
const current = values[i]
const currentCacheValue = current.opts
if (now >= currentCacheValue.deleteAt) {
// We've reached expired values, let's delete them
this.#entryCount -= values.length - i
values.length = i
break
}

let matches = true

if (currentCacheValue.vary) {
if (!req.headers) {
matches = false
break
}
return values.find(({ opts: { deleteAt, vary }, body }) => (
body != null &&
deleteAt > now &&
(!vary || Object.keys(vary).every(key => vary[key] === req.headers?.[key]))
))
}

for (const key in currentCacheValue.vary) {
if (currentCacheValue.vary[key] !== req.headers[key]) {
matches = false
break
#prune () {
const now = Date.now()
for (const [key, cachedPaths] of this.#data) {
for (const [method, prev] of cachedPaths) {
const next = prev.filter(({ opts, body }) => body == null || opts.deleteAt > now)
if (next.length === 0) {
cachedPaths.delete(method)
if (cachedPaths.size === 0) {
this.#data.delete(key)
}
} else if (next.length !== prev.length) {
this.#entryCount -= prev.length - next.length
cachedPaths.set(method, next)
}
}

if (matches) {
value = {
...current,
index: i
}
break
}
}

return value
}
}

/**
* @param {any[]} array Array to modify
* @param {number} idx Index to delete
*/
function shiftAtIndex (array, idx) {
for (let i = idx + 1; idx < array.length; i++) {
array[i - 1] = array[i]
}

array.length--
}

module.exports = MemoryCacheStore
Loading