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

lib: add nowAbsolute to fast timers #3749

Merged
merged 1 commit into from
Nov 18, 2024
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
13 changes: 13 additions & 0 deletions benchmarks/timers/now-absolute.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { bench, group, run } from 'mitata'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kind of trivial benchmark.

import timers from '../../lib/util/timers.js'

group(() => {
bench('Date.now()', () => {
Date.now()
})
bench('nowAbsolute()', () => {
timers.nowAbsolute()
})
})

await run()
3 changes: 2 additions & 1 deletion 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 { nowAbsolute } = require('../util/timers.js')

/**
* @typedef {import('../../types/cache-interceptor.d.ts').default.CacheKey} CacheKey
Expand Down Expand Up @@ -76,7 +77,7 @@ class MemoryCacheStore {

const topLevelKey = `${key.origin}:${key.path}`

const now = Date.now()
const now = nowAbsolute()
const entry = this.#entries.get(topLevelKey)?.find((entry) => (
entry.deleteAt > now &&
entry.method === key.method &&
Expand Down
5 changes: 3 additions & 2 deletions lib/handler/cache-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {
parseCacheControlHeader,
parseVaryHeader
} = require('../util/cache')
const { nowAbsolute } = require('../util/timers.js')

function noop () {}

Expand Down Expand Up @@ -121,7 +122,7 @@ class CacheHandler extends DecoratorHandler {
return downstreamOnHeaders()
}

const now = Date.now()
const now = nowAbsolute()
const staleAt = determineStaleAt(now, headers, cacheControlDirectives)
if (staleAt) {
const varyDirectives = this.#cacheKey.headers && headers.vary
Expand Down Expand Up @@ -300,7 +301,7 @@ function determineStaleAt (now, headers, cacheControlDirectives) {
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3
const expiresDate = new Date(headers.expire)
if (expiresDate instanceof Date && !isNaN(expiresDate)) {
return now + (Date.now() - expiresDate.getTime())
return now + (nowAbsolute() - expiresDate.getTime())
}
}

Expand Down
5 changes: 3 additions & 2 deletions lib/interceptor/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const CacheHandler = require('../handler/cache-handler')
const MemoryCacheStore = require('../cache/memory-cache-store')
const CacheRevalidationHandler = require('../handler/cache-revalidation-handler')
const { assertCacheStore, assertCacheMethods, makeCacheKey } = require('../util/cache.js')
const { nowAbsolute } = require('../util/timers.js')

const AGE_HEADER = Buffer.from('age')

Expand Down Expand Up @@ -101,7 +102,7 @@ module.exports = (opts = {}) => {
if (typeof handler.onHeaders === 'function') {
// Add the age header
// https://www.rfc-editor.org/rfc/rfc9111.html#name-age
const age = Math.round((Date.now() - cachedAt) / 1000)
const age = Math.round((nowAbsolute() - cachedAt) / 1000)

// TODO (fix): What if rawHeaders already contains age header?
rawHeaders = [...rawHeaders, AGE_HEADER, Buffer.from(`${age}`)]
Expand Down Expand Up @@ -133,7 +134,7 @@ module.exports = (opts = {}) => {
}

// Check if the response is stale
const now = Date.now()
const now = nowAbsolute()
if (now < result.staleAt) {
// Dump request body.
if (util.isStream(opts.body)) {
Expand Down
20 changes: 19 additions & 1 deletion lib/util/timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
*/
let fastNow = 0

/**
* The fastNowAbsolute variable contains the rough result of Date.now()
*
* @type {number}
*/
let fastNowAbsolute = Date.now()

/**
* RESOLUTION_MS represents the target resolution time in milliseconds.
*
Expand Down Expand Up @@ -122,6 +129,8 @@ function onTick () {
*/
fastNow += TICK_MS

fastNowAbsolute = Date.now()

/**
* The `idx` variable is used to iterate over the `fastTimers` array.
* Expired timers are removed by replacing them with the last element in the array.
Expand Down Expand Up @@ -390,6 +399,9 @@ module.exports = {
now () {
return fastNow
},
nowAbsolute () {
return fastNowAbsolute
},
/**
* Trigger the onTick function to process the fastTimers array.
* Exported for testing purposes only.
Expand Down Expand Up @@ -419,5 +431,11 @@ module.exports = {
* Marking as deprecated to discourage any use outside of testing.
* @deprecated
*/
kFastTimer
kFastTimer,
/**
* Exporting for testing purposes only.
* Marking as deprecated to discourage any use outside of testing.
* @deprecated
*/
RESOLUTION_MS
}
31 changes: 16 additions & 15 deletions test/cache-interceptor/cache-stores.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { deepStrictEqual, notEqual, equal } = require('node:assert')
const { Readable } = require('node:stream')
const { once } = require('node:events')
const MemoryCacheStore = require('../../lib/cache/memory-cache-store')
const { nowAbsolute } = require('../../lib/util/timers.js')

cacheStoreTests(MemoryCacheStore)

Expand Down Expand Up @@ -32,9 +33,9 @@ function cacheStoreTests (CacheStore) {
statusCode: 200,
statusMessage: '',
rawHeaders: [Buffer.from('1'), Buffer.from('2'), Buffer.from('3')],
cachedAt: Date.now(),
staleAt: Date.now() + 10000,
deleteAt: Date.now() + 20000
cachedAt: nowAbsolute(),
staleAt: nowAbsolute() + 10000,
deleteAt: nowAbsolute() + 20000
}
const requestBody = ['asd', '123']

Expand Down Expand Up @@ -71,9 +72,9 @@ function cacheStoreTests (CacheStore) {
statusCode: 200,
statusMessage: '',
rawHeaders: [Buffer.from('1'), Buffer.from('2'), Buffer.from('3')],
cachedAt: Date.now(),
staleAt: Date.now() + 10000,
deleteAt: Date.now() + 20000
cachedAt: nowAbsolute(),
staleAt: nowAbsolute() + 10000,
deleteAt: nowAbsolute() + 20000
}
const anotherBody = ['asd2', '1234']

Expand Down Expand Up @@ -108,9 +109,9 @@ function cacheStoreTests (CacheStore) {
statusCode: 200,
statusMessage: '',
rawHeaders: [Buffer.from('1'), Buffer.from('2'), Buffer.from('3')],
cachedAt: Date.now() - 10000,
staleAt: Date.now() - 1,
deleteAt: Date.now() + 20000
cachedAt: nowAbsolute() - 10000,
staleAt: nowAbsolute() - 1,
deleteAt: nowAbsolute() + 20000
}
const requestBody = ['part1', 'part2']

Expand Down Expand Up @@ -140,9 +141,9 @@ function cacheStoreTests (CacheStore) {
const requestValue = {
statusCode: 200,
statusMessage: '',
cachedAt: Date.now() - 20000,
staleAt: Date.now() - 10000,
deleteAt: Date.now() - 5
cachedAt: nowAbsolute() - 20000,
staleAt: nowAbsolute() - 10000,
deleteAt: nowAbsolute() - 5
}
const requestBody = ['part1', 'part2']

Expand Down Expand Up @@ -174,9 +175,9 @@ function cacheStoreTests (CacheStore) {
vary: {
'some-header': 'hello world'
},
cachedAt: Date.now(),
staleAt: Date.now() + 10000,
deleteAt: Date.now() + 20000
cachedAt: nowAbsolute(),
staleAt: nowAbsolute() + 10000,
deleteAt: nowAbsolute() + 20000
}
const requestBody = ['part1', 'part2']

Expand Down
3 changes: 3 additions & 0 deletions test/interceptors/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { createServer } = require('node:http')
const { once } = require('node:events')
const FakeTimers = require('@sinonjs/fake-timers')
const { Client, interceptors, cacheStores } = require('../../index')
const { tick } = require('../../lib/util/timers')

describe('Cache Interceptor', () => {
test('doesn\'t cache request w/ no cache-control header', async () => {
Expand Down Expand Up @@ -160,6 +161,7 @@ describe('Cache Interceptor', () => {
const clock = FakeTimers.install({
shouldClearNativeTimers: true
})
tick(0)

const server = createServer((req, res) => {
res.setHeader('cache-control', 'public, s-maxage=1, stale-while-revalidate=10')
Expand Down Expand Up @@ -205,6 +207,7 @@ describe('Cache Interceptor', () => {
strictEqual(await response.body.text(), 'asd')

clock.tick(1500)
tick(1500)

// Now we send two more requests. Both of these should reach the origin,
// but now with a conditional header asking if the resource has been
Expand Down
12 changes: 12 additions & 0 deletions test/timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,4 +255,16 @@ describe('timers', () => {

await t.completed
})

test('nowAbsolute', (t) => {
t = tspl(t, { plan: 1 })

const actualNow = Date.now()

const lowerBound = actualNow - timers.RESOLUTION_MS
const upperBound = actualNow + timers.RESOLUTION_MS
const fastNowAbsolute = timers.nowAbsolute()

t.equal(fastNowAbsolute >= lowerBound && fastNowAbsolute <= upperBound, true)
})
})
Loading