Skip to content

Commit

Permalink
lib: add nowAbsolute to fast timers (#3749)
Browse files Browse the repository at this point in the history
Signed-off-by: flakey5 <73616808+flakey5@users.noreply.github.com>
  • Loading branch information
flakey5 authored Nov 18, 2024
1 parent 7740c75 commit 24df4a5
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 21 deletions.
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'
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)
})
})

0 comments on commit 24df4a5

Please sign in to comment.