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
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -281,13 +283,13 @@ have been resolved, the client will keep the session open for a period of time

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
53 changes: 36 additions & 17 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,7 +168,11 @@ var values = require('./values')
* Disabled by default. Controls whether or not query metrics are returned.
*/
function Client(options) {
var http2SessionIdleTime = getHttp2SessionIdleTime()
const http2SessionIdleTime = getHttp2SessionIdleTime(
options ? options.http2SessionIdleTime : undefined
)

if (options) options.http2SessionIdleTime = http2SessionIdleTime

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

if (http2SessionIdleTime.shouldOverride) {
options.http2SessionIdleTime = http2SessionIdleTime.value
}

this._observer = options.observer
this._http = new http.HttpClient(options)
this.stream = stream.StreamAPI(this)
Expand Down Expand Up @@ -384,17 +383,37 @@ 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
const envIdleTime = util.getEnvVariable('FAUNADB_HTTP2_SESSION_IDLE_TIME')

return {
shouldOverride: useEnvVar,
value: useEnvVar ? parsed : 500,
var value = defaultIdleTime
// attemp to set the idle time to the env value and then the configured value
const values = [envIdleTime, configuredIdleTime]
for (let i = 0; i <= values.length; i++) {
henryfauna marked this conversation as resolved.
Show resolved Hide resolved
const rawValue = values[i]
const parsedValue = parseInt(rawValue, 10)
const isNegative = parsedValue < 0
const isInfinity = rawValue === 'Infinity'
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't it be easier to do this check first?

The overall check could be simpler, too:

// Handle Infinity, and NaN (for any other string)
const parsedValue = rawValue === 'Infinity'
  ? Number.MAX_SAFE_INTEGER
  : parseInt(rawValue, 10) || defaultIdleTime

// Handle upper bound
if (parsedValue > maxIdleTime) parsedValue = maxIdleTime

// Handle lower bound
if (parsedValue < 0) parsedValue = defaultIdleTime

return parsedValue

const isGreaterThanMax = parsedValue > maxIdleTime || isInfinity
// if we didn't get infinity or a positive integer move to the next value
if (isNegative) continue
if (!isInfinity && !parsedValue) continue
// if we did get something valid constrain it to the ceiling
value = parsedValue
if (isGreaterThanMax) {
value = maxIdleTime
console.warn(
`The value set for http2SessionIdleTime exceeds the maximum value of
${maxIdleTime} milliseconds. It will be set to ${maxIdleTime} milliseconds instead.`
)
henryfauna marked this conversation as resolved.
Show resolved Hide resolved
}

break
}

return value
}

module.exports = Client
120 changes: 117 additions & 3 deletions test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@ var json = require('../src/_json')
var client

describe('Client', () => {
const env = process.env

beforeAll(() => {
// Hideous way to ensure that the client is initialized.
client = util.client()

return client.query(query.CreateCollection({ name: 'my_collection' }))
})

beforeEach(() => {
process.env = { ...env }
})

afterEach(() => {
process.env = env
util.clearBrowserSimulation()
})

Expand Down Expand Up @@ -65,7 +71,6 @@ describe('Client', () => {
toEqual(['metrics', 'value'])
})


test('paginates', () => {
return createDocument().then(function(document) {
return client.paginate(document.ref).each(function(page) {
Expand Down Expand Up @@ -176,7 +181,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 +417,115 @@ 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
var client
var internalIdleTime
const maxIdleTime = 5000
const defaultIdleTime = 500

process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME = '999'
client = util.getClient({
http2SessionIdleTime: 2500,
})
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 = maxIdleTime + 1
client = util.getClient({
http2SessionIdleTime: 2500,
})
internalIdleTime = client._http._adapter._http2SessionIdleTime
expect(internalIdleTime).toBe(maxIdleTime)

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

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

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

process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME = '-999'
client = util.getClient({
http2SessionIdleTime: 2500,
})
internalIdleTime = client._http._adapter._http2SessionIdleTime
expect(internalIdleTime).toBe(2500)

process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME = '-999'
client = util.getClient({
http2SessionIdleTime: -999,
})
internalIdleTime = client._http._adapter._http2SessionIdleTime
expect(internalIdleTime).toBe(defaultIdleTime)

process.env.FAUNADB_HTTP2_SESSION_IDLE_TIME = '-999'
client = util.getClient({
http2SessionIdleTime: "Infinity",
})
internalIdleTime = client._http._adapter._http2SessionIdleTime
expect(internalIdleTime).toBe(maxIdleTime)
})

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

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)

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

function assertObserverStats(metrics, name) {
Expand Down