Skip to content

Commit 24df4a5

Browse files
authored
lib: add nowAbsolute to fast timers (#3749)
Signed-off-by: flakey5 <[email protected]>
1 parent 7740c75 commit 24df4a5

File tree

8 files changed

+71
-21
lines changed

8 files changed

+71
-21
lines changed

benchmarks/timers/now-absolute.mjs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { bench, group, run } from 'mitata'
2+
import timers from '../../lib/util/timers.js'
3+
4+
group(() => {
5+
bench('Date.now()', () => {
6+
Date.now()
7+
})
8+
bench('nowAbsolute()', () => {
9+
timers.nowAbsolute()
10+
})
11+
})
12+
13+
await run()

lib/cache/memory-cache-store.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22

33
const { Writable } = require('node:stream')
4+
const { nowAbsolute } = require('../util/timers.js')
45

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

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

79-
const now = Date.now()
80+
const now = nowAbsolute()
8081
const entry = this.#entries.get(topLevelKey)?.find((entry) => (
8182
entry.deleteAt > now &&
8283
entry.method === key.method &&

lib/handler/cache-handler.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66
parseCacheControlHeader,
77
parseVaryHeader
88
} = require('../util/cache')
9+
const { nowAbsolute } = require('../util/timers.js')
910

1011
function noop () {}
1112

@@ -121,7 +122,7 @@ class CacheHandler extends DecoratorHandler {
121122
return downstreamOnHeaders()
122123
}
123124

124-
const now = Date.now()
125+
const now = nowAbsolute()
125126
const staleAt = determineStaleAt(now, headers, cacheControlDirectives)
126127
if (staleAt) {
127128
const varyDirectives = this.#cacheKey.headers && headers.vary
@@ -300,7 +301,7 @@ function determineStaleAt (now, headers, cacheControlDirectives) {
300301
// https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3
301302
const expiresDate = new Date(headers.expire)
302303
if (expiresDate instanceof Date && !isNaN(expiresDate)) {
303-
return now + (Date.now() - expiresDate.getTime())
304+
return now + (nowAbsolute() - expiresDate.getTime())
304305
}
305306
}
306307

lib/interceptor/cache.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const CacheHandler = require('../handler/cache-handler')
77
const MemoryCacheStore = require('../cache/memory-cache-store')
88
const CacheRevalidationHandler = require('../handler/cache-revalidation-handler')
99
const { assertCacheStore, assertCacheMethods, makeCacheKey } = require('../util/cache.js')
10+
const { nowAbsolute } = require('../util/timers.js')
1011

1112
const AGE_HEADER = Buffer.from('age')
1213

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

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

135136
// Check if the response is stale
136-
const now = Date.now()
137+
const now = nowAbsolute()
137138
if (now < result.staleAt) {
138139
// Dump request body.
139140
if (util.isStream(opts.body)) {

lib/util/timers.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
*/
2222
let fastNow = 0
2323

24+
/**
25+
* The fastNowAbsolute variable contains the rough result of Date.now()
26+
*
27+
* @type {number}
28+
*/
29+
let fastNowAbsolute = Date.now()
30+
2431
/**
2532
* RESOLUTION_MS represents the target resolution time in milliseconds.
2633
*
@@ -122,6 +129,8 @@ function onTick () {
122129
*/
123130
fastNow += TICK_MS
124131

132+
fastNowAbsolute = Date.now()
133+
125134
/**
126135
* The `idx` variable is used to iterate over the `fastTimers` array.
127136
* Expired timers are removed by replacing them with the last element in the array.
@@ -390,6 +399,9 @@ module.exports = {
390399
now () {
391400
return fastNow
392401
},
402+
nowAbsolute () {
403+
return fastNowAbsolute
404+
},
393405
/**
394406
* Trigger the onTick function to process the fastTimers array.
395407
* Exported for testing purposes only.
@@ -419,5 +431,11 @@ module.exports = {
419431
* Marking as deprecated to discourage any use outside of testing.
420432
* @deprecated
421433
*/
422-
kFastTimer
434+
kFastTimer,
435+
/**
436+
* Exporting for testing purposes only.
437+
* Marking as deprecated to discourage any use outside of testing.
438+
* @deprecated
439+
*/
440+
RESOLUTION_MS
423441
}

test/cache-interceptor/cache-stores.js

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { deepStrictEqual, notEqual, equal } = require('node:assert')
55
const { Readable } = require('node:stream')
66
const { once } = require('node:events')
77
const MemoryCacheStore = require('../../lib/cache/memory-cache-store')
8+
const { nowAbsolute } = require('../../lib/util/timers.js')
89

910
cacheStoreTests(MemoryCacheStore)
1011

@@ -32,9 +33,9 @@ function cacheStoreTests (CacheStore) {
3233
statusCode: 200,
3334
statusMessage: '',
3435
rawHeaders: [Buffer.from('1'), Buffer.from('2'), Buffer.from('3')],
35-
cachedAt: Date.now(),
36-
staleAt: Date.now() + 10000,
37-
deleteAt: Date.now() + 20000
36+
cachedAt: nowAbsolute(),
37+
staleAt: nowAbsolute() + 10000,
38+
deleteAt: nowAbsolute() + 20000
3839
}
3940
const requestBody = ['asd', '123']
4041

@@ -71,9 +72,9 @@ function cacheStoreTests (CacheStore) {
7172
statusCode: 200,
7273
statusMessage: '',
7374
rawHeaders: [Buffer.from('1'), Buffer.from('2'), Buffer.from('3')],
74-
cachedAt: Date.now(),
75-
staleAt: Date.now() + 10000,
76-
deleteAt: Date.now() + 20000
75+
cachedAt: nowAbsolute(),
76+
staleAt: nowAbsolute() + 10000,
77+
deleteAt: nowAbsolute() + 20000
7778
}
7879
const anotherBody = ['asd2', '1234']
7980

@@ -108,9 +109,9 @@ function cacheStoreTests (CacheStore) {
108109
statusCode: 200,
109110
statusMessage: '',
110111
rawHeaders: [Buffer.from('1'), Buffer.from('2'), Buffer.from('3')],
111-
cachedAt: Date.now() - 10000,
112-
staleAt: Date.now() - 1,
113-
deleteAt: Date.now() + 20000
112+
cachedAt: nowAbsolute() - 10000,
113+
staleAt: nowAbsolute() - 1,
114+
deleteAt: nowAbsolute() + 20000
114115
}
115116
const requestBody = ['part1', 'part2']
116117

@@ -140,9 +141,9 @@ function cacheStoreTests (CacheStore) {
140141
const requestValue = {
141142
statusCode: 200,
142143
statusMessage: '',
143-
cachedAt: Date.now() - 20000,
144-
staleAt: Date.now() - 10000,
145-
deleteAt: Date.now() - 5
144+
cachedAt: nowAbsolute() - 20000,
145+
staleAt: nowAbsolute() - 10000,
146+
deleteAt: nowAbsolute() - 5
146147
}
147148
const requestBody = ['part1', 'part2']
148149

@@ -174,9 +175,9 @@ function cacheStoreTests (CacheStore) {
174175
vary: {
175176
'some-header': 'hello world'
176177
},
177-
cachedAt: Date.now(),
178-
staleAt: Date.now() + 10000,
179-
deleteAt: Date.now() + 20000
178+
cachedAt: nowAbsolute(),
179+
staleAt: nowAbsolute() + 10000,
180+
deleteAt: nowAbsolute() + 20000
180181
}
181182
const requestBody = ['part1', 'part2']
182183

test/interceptors/cache.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const { createServer } = require('node:http')
66
const { once } = require('node:events')
77
const FakeTimers = require('@sinonjs/fake-timers')
88
const { Client, interceptors, cacheStores } = require('../../index')
9+
const { tick } = require('../../lib/util/timers')
910

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

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

207209
clock.tick(1500)
210+
tick(1500)
208211

209212
// Now we send two more requests. Both of these should reach the origin,
210213
// but now with a conditional header asking if the resource has been

test/timers.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,4 +255,16 @@ describe('timers', () => {
255255

256256
await t.completed
257257
})
258+
259+
test('nowAbsolute', (t) => {
260+
t = tspl(t, { plan: 1 })
261+
262+
const actualNow = Date.now()
263+
264+
const lowerBound = actualNow - timers.RESOLUTION_MS
265+
const upperBound = actualNow + timers.RESOLUTION_MS
266+
const fastNowAbsolute = timers.nowAbsolute()
267+
268+
t.equal(fastNowAbsolute >= lowerBound && fastNowAbsolute <= upperBound, true)
269+
})
258270
})

0 commit comments

Comments
 (0)