Skip to content

Commit 4d8537b

Browse files
Set max session idle time (#642)
* set max session idle time * get rid of auto formatter changes * get rid of auto formatter changes take 2 * handle negative values * fix for node 12 syntax compatability * fix * fix comment * remove logging and clarify documentation * add stream test * Beef up test a little. * prep for release * documentation changes * update test * simplify getHttp2IdleTime * fix test flakiness Co-authored-by: Cleve Stuart <[email protected]>
1 parent 56e49bf commit 4d8537b

File tree

6 files changed

+207
-31
lines changed

6 files changed

+207
-31
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 4.6.0
2+
- Enforce a maximum value of 5000 ms for the `http2SessionIdleTime` option [#642](https://github.com/fauna/faunadb-js/pull/642)
3+
- Add checks to `http2SessionIdleTime` so that sane defaults are used in case an invalid value is configured
4+
- Add the missing Native.ROLES type [#638](https://github.com/fauna/faunadb-js/pull/638)
5+
16
## 4.5.4
27
- Disable ability to configure a client and the query method from returning metrics when calling query - fixing bug introduced in 4.5.3 that breaks backward compatibility. Continue supporting queryWithMetrics. [#633](https://github.com/fauna/faunadb-js/pull/633).
38

README.md

+11-9
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ time, and transaction retires consumed by your query:
155155
} // usage data
156156
}
157157
```
158+
158159
Metrics returned in the response will be of `number` data type.
160+
159161
#### Pagination Helpers
160162

161163
This driver contains helpers to provide a simpler API for consuming paged
@@ -280,14 +282,17 @@ have been resolved, the client will keep the session open for a period of time
280282
(500ms by default) to be reused for any new requests.
281283

282284
The `http2SessionIdleTime` parameter may be used to control how long the HTTP/2
283-
session remains open while the connection is idle. To save on the overhead of
284-
closing and re-opening the session, set `http2SessionIdleTime` to a longer time
285-
--- or even `Infinity`, to keep the session alive indefinitely.
285+
session remains open while the query connection is idle. To save on the overhead of
286+
closing and re-opening the session, set `http2SessionIdleTime` to a longer time.
287+
The default value is 500ms and the maximum value is 5000ms.
286288

287-
While an HTTP/2 session is alive, the client will hold the Node.js event loop
289+
Note that `http2SessionIdleTime` has no effect on a stream connection: a stream
290+
is a long-lived connection that is intended to be held open indefinitely.
291+
292+
While an HTTP/2 session is alive, the client holds the Node.js event loop
288293
open; this prevents the process from terminating. Call `Client#close` to manually
289294
close the session and allow the process to terminate. This is particularly
290-
important if `http2SessionIdleTime` is long or `Infinity`:
295+
important if `http2SessionIdleTime` is long:
291296

292297
```javascript
293298
// sample.js (run it with "node sample.js" command)
@@ -296,21 +301,18 @@ const { Client, query: Q } = require('faunadb')
296301
async function main() {
297302
const client = new Client({
298303
secret: 'YOUR_FAUNADB_SECRET',
299-
http2SessionIdleTime: Infinity,
300-
// ^^^ Infinity or non-negative integer
304+
http2SessionIdleTime: 1000, // Must be a non-negative integer
301305
})
302306
const output = await client.query(Q.Add(1, 1))
303307

304308
console.log(output)
305309

306310
client.close()
307-
// ^^^ If it's not called then the process won't terminate
308311
}
309312

310313
main().catch(console.error)
311314
```
312315

313-
314316
## Known issues
315317

316318
### Using with Cloudflare Workers

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "faunadb",
3-
"version": "4.5.4",
3+
"version": "4.6.0",
44
"apiVersion": "4",
55
"description": "FaunaDB Javascript driver for Node.JS and Browsers",
66
"homepage": "https://fauna.com",

src/Client.js

+31-18
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,10 @@ var values = require('./values')
158158
* Sets the maximum amount of time (in milliseconds) for query execution on the server
159159
* @param {?number} options.http2SessionIdleTime
160160
* Sets the maximum amount of time (in milliseconds) an HTTP2 session may live
161-
* when there's no activity. Must either be a non-negative integer, or Infinity to allow the
162-
* HTTP2 session to live indefinitely (use `Client#close` to manually terminate the client).
163-
* Only applicable for NodeJS environment (when http2 module is used). Default is 500ms;
161+
* when there's no activity. Must be a non-negative integer, with a maximum value of 5000.
162+
* If an invalid value is passed a default of 500 ms is applied. If a value
163+
* exceeding 5000 ms is passed (e.g. Infinity) the maximum of 5000 ms is applied.
164+
* Only applicable for NodeJS environment (when http2 module is used).
164165
* can also be configured via the FAUNADB_HTTP2_SESSION_IDLE_TIME environment variable
165166
* which has the highest priority and overrides the option passed into the Client constructor.
166167
* @param {?boolean} options.checkNewVersion
@@ -169,7 +170,11 @@ var values = require('./values')
169170
* Disabled by default. Controls whether or not query metrics are returned.
170171
*/
171172
function Client(options) {
172-
var http2SessionIdleTime = getHttp2SessionIdleTime()
173+
const http2SessionIdleTime = getHttp2SessionIdleTime(
174+
options ? options.http2SessionIdleTime : undefined
175+
)
176+
177+
if (options) options.http2SessionIdleTime = http2SessionIdleTime
173178

174179
options = util.applyDefaults(options, {
175180
domain: 'db.fauna.com',
@@ -182,14 +187,10 @@ function Client(options) {
182187
headers: {},
183188
fetch: undefined,
184189
queryTimeout: null,
185-
http2SessionIdleTime: http2SessionIdleTime.value,
190+
http2SessionIdleTime,
186191
checkNewVersion: false,
187192
})
188193

189-
if (http2SessionIdleTime.shouldOverride) {
190-
options.http2SessionIdleTime = http2SessionIdleTime.value
191-
}
192-
193194
this._observer = options.observer
194195
this._http = new http.HttpClient(options)
195196
this.stream = stream.StreamAPI(this)
@@ -384,17 +385,29 @@ Client.prototype._handleRequestResult = function(response, result, options) {
384385
errors.FaunaHTTPError.raiseForStatusCode(result)
385386
}
386387

387-
function getHttp2SessionIdleTime() {
388-
var fromEnv = util.getEnvVariable('FAUNADB_HTTP2_SESSION_IDLE_TIME')
389-
var parsed =
390-
// Allow either "Infinity" or parsable integer string.
391-
fromEnv === 'Infinity' ? Infinity : parseInt(fromEnv, 10)
392-
var useEnvVar = !isNaN(parsed)
388+
function getHttp2SessionIdleTime(configuredIdleTime) {
389+
const maxIdleTime = 5000
390+
const defaultIdleTime = 500
391+
const envIdleTime = util.getEnvVariable('FAUNADB_HTTP2_SESSION_IDLE_TIME')
393392

394-
return {
395-
shouldOverride: useEnvVar,
396-
value: useEnvVar ? parsed : 500,
393+
var value = defaultIdleTime
394+
// attemp to set the idle time to the env value and then the configured value
395+
const values = [envIdleTime, configuredIdleTime]
396+
for (const rawValue of values) {
397+
const parsedValue = rawValue === 'Infinity'
398+
? Number.MAX_SAFE_INTEGER
399+
: parseInt(rawValue, 10)
400+
const isNegative = parsedValue < 0
401+
const isGreaterThanMax = parsedValue > maxIdleTime
402+
// if we didn't get infinity or a positive integer move to the next value
403+
if (isNegative || !parsedValue) continue
404+
// if we did get something valid constrain it to the ceiling
405+
value = parsedValue
406+
if (isGreaterThanMax) value = maxIdleTime
407+
break
397408
}
409+
410+
return value
398411
}
399412

400413
module.exports = Client

test/client.test.js

+117-3
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@ var json = require('../src/_json')
99
var client
1010

1111
describe('Client', () => {
12+
const env = process.env
13+
1214
beforeAll(() => {
1315
// Hideous way to ensure that the client is initialized.
1416
client = util.client()
15-
1617
return client.query(query.CreateCollection({ name: 'my_collection' }))
1718
})
1819

20+
beforeEach(() => {
21+
process.env = { ...env }
22+
})
23+
1924
afterEach(() => {
25+
process.env = env
2026
util.clearBrowserSimulation()
2127
})
2228

@@ -65,7 +71,6 @@ describe('Client', () => {
6571
toEqual(['metrics', 'value'])
6672
})
6773

68-
6974
test('paginates', () => {
7075
return createDocument().then(function(document) {
7176
return client.paginate(document.ref).each(function(page) {
@@ -176,7 +181,7 @@ describe('Client', () => {
176181

177182
test('Client#close call on Http2Adapter-based Client', async () => {
178183
const client = util.getClient({
179-
http2SessionIdleTime: Infinity,
184+
http2SessionIdleTime: 5000,
180185
})
181186

182187
await client.ping()
@@ -412,6 +417,115 @@ describe('Client', () => {
412417
requiredKeys.every(key => driverEnvHeader.includes(key))
413418
).toBeDefined()
414419
})
420+
421+
test('http2SessionIdleTime env overrides client config', async () => {
422+
var client
423+
var internalIdleTime
424+
const maxIdleTime = 5000
425+
const defaultIdleTime = 500
426+
427+
process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME = '999'
428+
client = util.getClient({
429+
http2SessionIdleTime: 2500,
430+
})
431+
internalIdleTime = client._http._adapter._http2SessionIdleTime
432+
expect(internalIdleTime).toBe(999)
433+
434+
process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME = maxIdleTime + 1
435+
client = util.getClient({
436+
http2SessionIdleTime: 2500,
437+
})
438+
internalIdleTime = client._http._adapter._http2SessionIdleTime
439+
expect(internalIdleTime).toBe(maxIdleTime)
440+
441+
process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME = 'Infinity'
442+
client = util.getClient({
443+
http2SessionIdleTime: 2500,
444+
})
445+
internalIdleTime = client._http._adapter._http2SessionIdleTime
446+
expect(internalIdleTime).toBe(maxIdleTime)
447+
448+
process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME = 'Cat'
449+
client = util.getClient({
450+
http2SessionIdleTime: 2500,
451+
})
452+
internalIdleTime = client._http._adapter._http2SessionIdleTime
453+
expect(internalIdleTime).toBe(2500)
454+
455+
process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME = 'Cat'
456+
client = util.getClient({
457+
http2SessionIdleTime: "Cat",
458+
})
459+
internalIdleTime = client._http._adapter._http2SessionIdleTime
460+
expect(internalIdleTime).toBe(defaultIdleTime)
461+
462+
process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME = '-999'
463+
client = util.getClient({
464+
http2SessionIdleTime: 2500,
465+
})
466+
internalIdleTime = client._http._adapter._http2SessionIdleTime
467+
expect(internalIdleTime).toBe(2500)
468+
469+
process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME = '-999'
470+
client = util.getClient({
471+
http2SessionIdleTime: -999,
472+
})
473+
internalIdleTime = client._http._adapter._http2SessionIdleTime
474+
expect(internalIdleTime).toBe(defaultIdleTime)
475+
476+
process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME = '-999'
477+
client = util.getClient({
478+
http2SessionIdleTime: "Infinity",
479+
})
480+
internalIdleTime = client._http._adapter._http2SessionIdleTime
481+
expect(internalIdleTime).toBe(maxIdleTime)
482+
})
483+
484+
test('http2SessionIdleTime respects the max and default', async () => {
485+
var client
486+
var internalIdleTime
487+
const maxIdleTime = 5000
488+
const defaultIdleTime = 500
489+
process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME = undefined
490+
491+
client = util.getClient({
492+
http2SessionIdleTime: 'Infinity',
493+
})
494+
internalIdleTime = client._http._adapter._http2SessionIdleTime
495+
expect(internalIdleTime).toBe(maxIdleTime)
496+
497+
client = util.getClient({
498+
http2SessionIdleTime: maxIdleTime + 1,
499+
})
500+
internalIdleTime = client._http._adapter._http2SessionIdleTime
501+
expect(internalIdleTime).toBe(maxIdleTime)
502+
503+
client = util.getClient({})
504+
internalIdleTime = client._http._adapter._http2SessionIdleTime
505+
expect(internalIdleTime).toBe(defaultIdleTime)
506+
507+
client = util.getClient({ http2SessionIdleTime: null })
508+
internalIdleTime = client._http._adapter._http2SessionIdleTime
509+
expect(internalIdleTime).toBe(defaultIdleTime)
510+
511+
client = util.getClient({
512+
http2SessionIdleTime: 'Cat',
513+
})
514+
internalIdleTime = client._http._adapter._http2SessionIdleTime
515+
expect(internalIdleTime).toBe(defaultIdleTime)
516+
517+
client = util.getClient({
518+
http2SessionIdleTime: 2500,
519+
})
520+
internalIdleTime = client._http._adapter._http2SessionIdleTime
521+
expect(internalIdleTime).toBe(2500)
522+
523+
client = util.getClient({
524+
http2SessionIdleTime: -2500,
525+
})
526+
internalIdleTime = client._http._adapter._http2SessionIdleTime
527+
expect(internalIdleTime).toBe(defaultIdleTime)
528+
})
415529
})
416530

417531
function assertObserverStats(metrics, name) {

test/stream.test.js

+42
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,48 @@ describe('StreamAPI', () => {
273273
})
274274
.start()
275275
})
276+
277+
test.each([50, 500, 5000])('stays open beyond the value set for http2SessionIdleTime', async (idleTime) => {
278+
const padding = 100
279+
let seen_event = false
280+
const client = util.getClient({
281+
secret: key.secret,
282+
http2SessionIdleTime: idleTime,
283+
})
284+
const oldTimestamp = doc.ts
285+
286+
const getNumberOfSessions = () => Object.keys(client._http._adapter._sessionMap).length
287+
288+
stream = client.stream
289+
.document(doc.ref)
290+
.on('version', (event) => {
291+
seen_event = true
292+
expect(event.action).toEqual("update")
293+
expect(event.document.ts).toBeGreaterThan(oldTimestamp)
294+
})
295+
296+
stream.start()
297+
298+
await util.delay(idleTime + padding)
299+
expect(getNumberOfSessions()).toBe(1)
300+
301+
// this will create a new session as it is not a stream
302+
const { ts: newTimestamp } = await client.query(q.Update(doc.ref, {}))
303+
expect(newTimestamp).toBeGreaterThan(oldTimestamp)
304+
305+
await util.delay(idleTime + padding)
306+
expect(getNumberOfSessions()).toBeGreaterThanOrEqual(1)
307+
expect(seen_event).toBe(true)
308+
309+
seen_event = false
310+
await util.delay(idleTime + padding)
311+
expect(getNumberOfSessions()).toBeGreaterThanOrEqual(1)
312+
expect(seen_event).toBe(false)
313+
314+
stream.close()
315+
await util.delay(idleTime + padding)
316+
expect(getNumberOfSessions()).toBe(0)
317+
}, 30000);
276318
})
277319

278320
describe('document', () => {

0 commit comments

Comments
 (0)