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

Set max session idle time #642

Merged
merged 15 commits into from
Jun 14, 2022
29 changes: 15 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ reference](https://docs.fauna.com/fauna/current/api/fql/).

This Driver supports and is tested on:

- Node.js [*Current*, *Active LTS*, and *Maintenance LTS* releases](https://nodejs.org/en/about/releases/)
- *Current* - v17
- *Active LTS* - currently v16
- *Maintenance LTS* - v12 and v14
- Node.js [_Current_, _Active LTS_, and _Maintenance LTS_ releases](https://nodejs.org/en/about/releases/)
- _Current_ - v17
- _Active LTS_ - currently v16
- _Maintenance LTS_ - v12 and v14
henryfauna marked this conversation as resolved.
Show resolved Hide resolved
- Chrome
- Firefox
- Safari
Expand Down Expand Up @@ -127,13 +127,13 @@ createP.then(function(response) {
`response` is a JSON object containing the FaunaDB response. See the JSDocs for
`faunadb.Client`.

The `metrics` option is used during instantiation to create a client that also
The `metrics` option is used during instantiation to create a client that also
returns usage information about the queries issued to FaunaDB.

```javascript
let client = new faunadb.Client({
let client = new faunadb.Client({
secret: 'YOUR_FAUNADB_SECRET',
metrics: true
metrics: true,
})
```

Expand All @@ -155,7 +155,9 @@ time, and transaction retires consumed by your query:
} // usage data
}
```

Metrics returned in the response will be of `number` data type.

#### Pagination Helpers

This driver contains helpers to provide a simpler API for consuming paged
Expand Down Expand Up @@ -277,17 +279,17 @@ const client = new faunadb.Client({
When running on the Node.js platform, the Fauna client uses [HTTP/2 multiplexing](https://stackoverflow.com/questions/36517829/what-does-multiplexing-mean-in-http-2)
to reuse the same session for many simultaneous requests. After all open requests
have been resolved, the client will keep the session open for a period of time
(500ms by default) to be reused for any new requests.
to be reused for any new requests.
henryfauna marked this conversation as resolved.
Show resolved Hide resolved

The `http2SessionIdleTime` parameter may be used to control how long the HTTP/2
session remains open while the connection is idle. To save on the overhead of
henryfauna marked this conversation as resolved.
Show resolved Hide resolved
closing and re-opening the session, set `http2SessionIdleTime` to a longer time
--- or even `Infinity`, to keep the session alive indefinitely.
closing and re-opening the session, set `http2SessionIdleTime` to a longer time.
The default value is 500ms and the maximum value is 5000ms.
henryfauna marked this conversation as resolved.
Show resolved Hide resolved

While an HTTP/2 session is alive, the client will hold the Node.js event loop
henryfauna marked this conversation as resolved.
Show resolved Hide resolved
open; this prevents the process from terminating. Call `Client#close` to manually
close the session and allow the process to terminate. This is particularly
important if `http2SessionIdleTime` is long or `Infinity`:
important if `http2SessionIdleTime` is long:

```javascript
// sample.js (run it with "node sample.js" command)
Expand All @@ -296,8 +298,8 @@ const { Client, query: Q } = require('faunadb')
async function main() {
const client = new Client({
secret: 'YOUR_FAUNADB_SECRET',
http2SessionIdleTime: Infinity,
// ^^^ Infinity or non-negative integer
http2SessionIdleTime: 1000,
// ^^^ Non-negative integer
henryfauna marked this conversation as resolved.
Show resolved Hide resolved
})
const output = await client.query(Q.Add(1, 1))

Expand All @@ -310,7 +312,6 @@ async function main() {
main().catch(console.error)
```


## Known issues

### Using with Cloudflare Workers
Expand Down
67 changes: 44 additions & 23 deletions src/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,7 @@ var values = require('./values')
* Sets the maximum amount of time (in milliseconds) for query execution on the server
* @param {?number} options.http2SessionIdleTime
* Sets the maximum amount of time (in milliseconds) an HTTP2 session may live
* when there's no activity. Must either be a non-negative integer, or Infinity to allow the
* HTTP2 session to live indefinitely (use `Client#close` to manually terminate the client).
* when there's no activity. Must either be a non-negative integer.
henryfauna marked this conversation as resolved.
Show resolved Hide resolved
* Only applicable for NodeJS environment (when http2 module is used). Default is 500ms;
* can also be configured via the FAUNADB_HTTP2_SESSION_IDLE_TIME environment variable
* which has the highest priority and overrides the option passed into the Client constructor.
Expand All @@ -169,8 +168,6 @@ var values = require('./values')
* Disabled by default. Controls whether or not query metrics are returned.
*/
function Client(options) {
var http2SessionIdleTime = getHttp2SessionIdleTime()

options = util.applyDefaults(options, {
domain: 'db.fauna.com',
scheme: 'https',
Expand All @@ -182,13 +179,13 @@ function Client(options) {
headers: {},
fetch: undefined,
queryTimeout: null,
http2SessionIdleTime: http2SessionIdleTime.value,
http2SessionIdleTime: 500,
checkNewVersion: false,
})

if (http2SessionIdleTime.shouldOverride) {
options.http2SessionIdleTime = http2SessionIdleTime.value
}
options.http2SessionIdleTime = getHttp2SessionIdleTime(
options.http2SessionIdleTime
)
cleve-fauna marked this conversation as resolved.
Show resolved Hide resolved

this._observer = options.observer
this._http = new http.HttpClient(options)
Expand Down Expand Up @@ -299,7 +296,14 @@ Client.prototype.queryWithMetrics = function(expression, options) {
return this._execute('POST', '', query.wrap(expression), null, options, true)
}

Client.prototype._execute = function(method, path, data, query, options, returnMetrics = false) {
Client.prototype._execute = function(
method,
path,
data,
query,
options,
returnMetrics = false
) {
henryfauna marked this conversation as resolved.
Show resolved Hide resolved
query = util.defaults(query, null)

if (
Expand Down Expand Up @@ -356,10 +360,12 @@ Client.prototype._execute = function(method, path, data, query, options, returnM
if (returnMetrics) {
return {
value: responseObject['resource'],
metrics: Object.fromEntries(Array.from(Object.entries(response.headers)).
filter( ([k,v]) => metricsHeaders.includes(k) ).
map(([ k,v ]) => [k, parseInt(v)])
)}
metrics: Object.fromEntries(
Array.from(Object.entries(response.headers))
.filter(([k, v]) => metricsHeaders.includes(k))
.map(([k, v]) => [k, parseInt(v)])
),
}
} else {
return responseObject['resource']
}
Expand All @@ -384,17 +390,32 @@ Client.prototype._handleRequestResult = function(response, result, options) {
errors.FaunaHTTPError.raiseForStatusCode(result)
}

function getHttp2SessionIdleTime() {
var fromEnv = util.getEnvVariable('FAUNADB_HTTP2_SESSION_IDLE_TIME')
var parsed =
// Allow either "Infinity" or parsable integer string.
fromEnv === 'Infinity' ? Infinity : parseInt(fromEnv, 10)
var useEnvVar = !isNaN(parsed)
function getHttp2SessionIdleTime(configuredIdleTime) {
const maxIdleTime = 5000
const defaultIdleTime = 500

return {
shouldOverride: useEnvVar,
value: useEnvVar ? parsed : 500,
}
const envIdleTime = util.getEnvVariable('FAUNADB_HTTP2_SESSION_IDLE_TIME')

const parsedEnv = parseInt(envIdleTime, 10)
const parsedConfig = parseInt(configuredIdleTime, 10)
var value = parsedEnv || parsedConfig || defaultIdleTime
henryfauna marked this conversation as resolved.
Show resolved Hide resolved

const isGreaterThanMax = value > maxIdleTime
const isInfinity =
envIdleTime === 'Infinity' || configuredIdleTime === 'Infinity'

if (isGreaterThanMax)
console.warn(
`The value set for http2SessionIdleTime exceeds the maximum value of ${maxIdleTime} milliseconds.`
)
henryfauna marked this conversation as resolved.
Show resolved Hide resolved
if (isInfinity)
console.warn(
`Infinity is no longer a supported value for http2SessionIdleTime. The maximum value is ${maxIdleTime} milliseconds.`
)

if (isInfinity || isGreaterThanMax) value = maxIdleTime

return value
}

module.exports = Client
60 changes: 55 additions & 5 deletions test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ describe('Client', () => {
})

test('the client does not support a metrics flag', async () => {
expect(() => util.getClient({ metrics: true })).toThrow(new Error('No such option metrics'))
expect(() => util.getClient({ metrics: true })).toThrow(
new Error('No such option metrics')
)
})

test('query does not support a metrics flag', async () => {
Expand All @@ -61,11 +63,9 @@ describe('Client', () => {

test('queryWithMetrics returns the metrics', async () => {
const response = await client.queryWithMetrics(query.Add(1, 1))
expect(Object.keys(response).sort()).
toEqual(['metrics', 'value'])
expect(Object.keys(response).sort()).toEqual(['metrics', 'value'])
})


test('paginates', () => {
return createDocument().then(function(document) {
return client.paginate(document.ref).each(function(page) {
Expand Down Expand Up @@ -176,7 +176,7 @@ describe('Client', () => {

test('Client#close call on Http2Adapter-based Client', async () => {
const client = util.getClient({
http2SessionIdleTime: Infinity,
http2SessionIdleTime: 5000,
})

await client.ping()
Expand Down Expand Up @@ -412,6 +412,56 @@ describe('Client', () => {
requiredKeys.every(key => driverEnvHeader.includes(key))
).toBeDefined()
})

test('http2SessionIdleTime env overrides client config', async () => {
cleve-fauna marked this conversation as resolved.
Show resolved Hide resolved
const prevEnv = process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME
process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME = '999'
const client = util.getClient({
http2SessionIdleTime: 2500,
})
const internalIdleTime = client._http._adapter._http2SessionIdleTime
expect(internalIdleTime).toBe(999)
faunaee marked this conversation as resolved.
Show resolved Hide resolved
process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME = prevEnv
})

test('http2SessionIdleTime respects the max and default', async () => {
var client
var internalIdleTime
const maxIdleTime = 5000
const defaultIdleTime = 500

client = util.getClient({
http2SessionIdleTime: 'Infinity',
})
internalIdleTime = client._http._adapter._http2SessionIdleTime
expect(internalIdleTime).toBe(maxIdleTime)

client = util.getClient({
http2SessionIdleTime: maxIdleTime + 1,
})
internalIdleTime = client._http._adapter._http2SessionIdleTime
expect(internalIdleTime).toBe(maxIdleTime)

client = util.getClient({})
internalIdleTime = client._http._adapter._http2SessionIdleTime
expect(internalIdleTime).toBe(defaultIdleTime)

client = util.getClient({ http2SessionIdleTime: null })
internalIdleTime = client._http._adapter._http2SessionIdleTime
expect(internalIdleTime).toBe(defaultIdleTime)

client = util.getClient({
http2SessionIdleTime: 'Cat',
})
internalIdleTime = client._http._adapter._http2SessionIdleTime
expect(internalIdleTime).toBe(defaultIdleTime)

client = util.getClient({
http2SessionIdleTime: 2500,
})
internalIdleTime = client._http._adapter._http2SessionIdleTime
expect(internalIdleTime).toBe(2500)
})
})

function assertObserverStats(metrics, name) {
Expand Down