Skip to content

Commit 0ad42ff

Browse files
authored
Drop default 30-second timeout (#2573)
1 parent 6dbf91a commit 0ad42ff

10 files changed

+48
-39
lines changed

docs/basic-config.asciidoc

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ const client = new Client({
1313
cloud: { id: '<cloud-id>' },
1414
auth: { apiKey: 'base64EncodedKey' },
1515
maxRetries: 5,
16-
requestTimeout: 60000,
1716
sniffOnStart: true
1817
})
1918
----
@@ -82,7 +81,7 @@ _Default:_ `3`
8281

8382
|`requestTimeout`
8483
|`number` - Max request timeout in milliseconds for each request. +
85-
_Default:_ `30000`
84+
_Default:_ No value
8685

8786
|`pingTimeout`
8887
|`number` - Max ping request timeout in milliseconds for each request. +

docs/changelog.asciidoc

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212

1313
In 8.0, the top-level `body` parameter that was available on all API functions <<remove-body-key,was deprecated>>. In 9.0 this property is completely removed.
1414

15+
[discrete]
16+
===== Remove the default 30-second timeout on all requests sent to Elasticsearch
17+
18+
Setting HTTP timeouts on Elasticsearch requests goes against Elastic's recommendations. See <<timeout-best-practices>> for more information.
19+
1520
[discrete]
1621
=== 8.17.0
1722

docs/child.asciidoc

+11-12
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
[[child]]
22
=== Creating a child client
33

4-
There are some use cases where you may need multiple instances of the client.
5-
You can easily do that by calling `new Client()` as many times as you need, but
6-
you will lose all the benefits of using one single client, such as the long
7-
living connections and the connection pool handling. To avoid this problem, the
8-
client offers a `child` API, which returns a new client instance that shares the
4+
There are some use cases where you may need multiple instances of the client.
5+
You can easily do that by calling `new Client()` as many times as you need, but
6+
you will lose all the benefits of using one single client, such as the long
7+
living connections and the connection pool handling. To avoid this problem, the
8+
client offers a `child` API, which returns a new client instance that shares the
99
connection pool with the parent client.
1010

11-
NOTE: The event emitter is shared between the parent and the child(ren). If you
12-
extend the parent client, the child client will have the same extensions, while
11+
NOTE: The event emitter is shared between the parent and the child(ren). If you
12+
extend the parent client, the child client will have the same extensions, while
1313
if the child client adds an extension, the parent client will not be extended.
1414

15-
You can pass to the `child` every client option you would pass to a normal
16-
client, but the connection pool specific options (`ssl`, `agent`, `pingTimeout`,
15+
You can pass to the `child` every client option you would pass to a normal
16+
client, but the connection pool specific options (`ssl`, `agent`, `pingTimeout`,
1717
`Connection`, and `resurrectStrategy`).
1818

19-
CAUTION: If you call `close` in any of the parent/child clients, every client
19+
CAUTION: If you call `close` in any of the parent/child clients, every client
2020
will be closed.
2121

2222
[source,js]
@@ -28,9 +28,8 @@ const client = new Client({
2828
})
2929
const child = client.child({
3030
headers: { 'x-foo': 'bar' },
31-
requestTimeout: 1000
3231
})
3332
3433
client.info().then(console.log, console.log)
3534
child.info().then(console.log, console.log)
36-
----
35+
----

docs/connecting.asciidoc

+2-2
Original file line numberDiff line numberDiff line change
@@ -414,8 +414,8 @@ The supported request specific options are:
414414
_Default:_ `null`
415415

416416
|`requestTimeout`
417-
|`number | string` - Max request timeout for the request in milliseconds, it overrides the client default. +
418-
_Default:_ `30000`
417+
|`number | string | null` - Max request timeout for the request in milliseconds. This overrides the client default, which is to not time out at all. See https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-network.html#_http_client_configuration[Elasticsearch best practices for HTML clients] for more info. +
418+
_Default:_ No timeout
419419

420420
|`retryOnTimeout`
421421
|`boolean` - Retry requests that have timed out.

docs/timeout-best-practices.asciidoc

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
[[timeout-best-practices]]
22
=== Timeout best practices
33

4-
This client is configured by default to operate like many HTTP client libraries do, by using a relatively short (30 second) timeout on all requests sent to {es}, raising a `TimeoutError` when that time period has elapsed without receiving a response. However, {es} will always eventually respond to any request, even if it takes several minutes. The {ref}/modules-network.html#_http_client_configuration[official {es} recommendation] is to disable response timeouts entirely by default.
4+
Starting in 9.0.0, this client is configured to not time out any HTTP request by default. {es} will always eventually respond to any request, even if it takes several minutes. Reissuing a request that it has not responded to yet can cause performance side effects. See the {ref}/modules-network.html#_http_client_configuration[official {es} recommendations for HTTP clients] for more information.
55

6-
Since changing this default would be a breaking change, we won't do that until the next major release. In the meantime, here is our recommendation for properly configuring your client:
6+
Prior to 9.0, this client was configured by default to operate like many HTTP client libraries do, by using a relatively short (30 second) timeout on all requests sent to {es}, raising a `TimeoutError` when that time period elapsed without receiving a response.
77

8-
* Ensure keep-alive is enabled; this is the default, so no settings need to be changed, unless you have set `agent` to `false` or provided an alternate `agent` that disables keep-alive
9-
* If using the default `UndiciConnection`, disable request timeouts by setting `timeout` to `0`
10-
* If using the legacy `HttpConnection`, set `timeout` to a very large number (e.g. `86400000`, or one day)
8+
If your circumstances require you to set timeouts on Elasticsearch requests, setting the `requestTimeout` value to a millisecond value will cause this client to operate as it did prior to 9.0.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
},
5858
"devDependencies": {
5959
"@elastic/request-converter": "8.17.0",
60-
"@sinonjs/fake-timers": "github:sinonjs/fake-timers#48f089f",
60+
"@sinonjs/fake-timers": "14.0.0",
6161
"@types/debug": "4.1.12",
6262
"@types/ms": "0.7.34",
6363
"@types/node": "22.10.7",
@@ -89,7 +89,7 @@
8989
"zx": "7.2.3"
9090
},
9191
"dependencies": {
92-
"@elastic/transport": "^8.9.1",
92+
"@elastic/transport": "9.0.0-alpha.1",
9393
"apache-arrow": "^18.0.0",
9494
"tslib": "^2.4.0"
9595
},

src/client.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,15 @@ export interface ClientOptions {
108108
* @defaultValue 3 */
109109
maxRetries?: number
110110
/** @property requestTimeout Max request timeout in milliseconds for each request
111-
* @defaultValue 30000 */
111+
* @defaultValue No timeout
112+
* @remarks Read [the Elasticsearch docs](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-network.html#_http_client_configuration) about HTTP client configuration for details. */
112113
requestTimeout?: number
113114
/** @property pingTimeout Max number of milliseconds a `ClusterConnectionPool` will wait when pinging nodes before marking them dead
114115
* @defaultValue 3000 */
115116
pingTimeout?: number
116117
/** @property sniffInterval Perform a sniff operation every `n` milliseconds
117118
* @remarks Sniffing might not be the best solution for you. Read https://www.elastic.co/blog/elasticsearch-sniffing-best-practices-what-when-why-how to learn more.
118-
* @defaultValue */
119+
* @defaultValue false */
119120
sniffInterval?: number | boolean
120121
/** @property sniffOnStart Perform a sniff once the client is started
121122
* @remarks Sniffing might not be the best solution for you. Read https://www.elastic.co/blog/elasticsearch-sniffing-best-practices-what-when-why-how to learn more.
@@ -244,7 +245,6 @@ export default class Client extends API {
244245
Serializer,
245246
ConnectionPool: (opts.cloud != null) ? CloudConnectionPool : WeightedConnectionPool,
246247
maxRetries: 3,
247-
requestTimeout: 30000,
248248
pingTimeout: 3000,
249249
sniffInterval: false,
250250
sniffOnStart: false,

test/unit/client.test.ts

+18-10
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
* under the License.
1818
*/
1919

20-
import * as http from 'http'
20+
import * as http from 'node:http'
21+
import { URL } from 'node:url'
22+
import { setTimeout } from 'node:timers/promises'
2123
import { test } from 'tap'
22-
import { URL } from 'url'
2324
import FakeTimers from '@sinonjs/fake-timers'
2425
import { buildServer, connection } from '../utils'
2526
import { Client, errors } from '../..'
@@ -451,30 +452,37 @@ test('Ensure new client instance stores requestTimeout for each connection', t =
451452
t.end()
452453
})
453454

454-
test('Ensure new client does not time out at default (30s) when client sets requestTimeout', async t => {
455-
const clock = FakeTimers.install({ toFake: ['setTimeout', 'clearTimeout'] })
455+
test('No request timeout is set by default', t => {
456+
const client = new Client({
457+
node: { url: new URL('http://localhost:9200') },
458+
})
459+
t.equal(client.connectionPool.connections[0].timeout, null)
460+
t.end()
461+
})
462+
463+
test('Ensure new client does not time out if requestTimeout is not set', async t => {
464+
const clock = FakeTimers.install({ toFake: ['setTimeout'] })
456465
t.teardown(() => clock.uninstall())
457466

458467
function handler (_req: http.IncomingMessage, res: http.ServerResponse) {
459-
setTimeout(() => {
460-
t.pass('timeout ended')
468+
setTimeout(1000 * 60 * 60).then(() => {
469+
t.ok('timeout ended')
461470
res.setHeader('content-type', 'application/json')
462471
res.end(JSON.stringify({ success: true }))
463-
}, 31000) // default is 30000
464-
clock.runToLast()
472+
})
473+
clock.tick(1000 * 60 * 60)
465474
}
466475

467476
const [{ port }, server] = await buildServer(handler)
468477

469478
const client = new Client({
470479
node: `http://localhost:${port}`,
471-
requestTimeout: 60000
472480
})
473481

474482
try {
475483
await client.transport.request({ method: 'GET', path: '/' })
476484
} catch (error) {
477-
t.fail('timeout error hit')
485+
t.fail('Error should not be thrown', error)
478486
} finally {
479487
server.stop()
480488
t.end()

test/unit/helpers/bulk.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1611,7 +1611,7 @@ test('errors', t => {
16111611

16121612
test('Flush interval', t => {
16131613
t.test('Slow producer', async t => {
1614-
const clock = FakeTimers.install({ toFake: ['setTimeout', 'clearTimeout'] })
1614+
const clock = FakeTimers.install({ toFake: ['setTimeout', 'clearTimeout'], shouldClearNativeTimers: true })
16151615
t.teardown(() => clock.uninstall())
16161616

16171617
let count = 0
@@ -1663,7 +1663,7 @@ test('Flush interval', t => {
16631663
})
16641664

16651665
t.test('Abort operation', async t => {
1666-
const clock = FakeTimers.install({ toFake: ['setTimeout', 'clearTimeout'] })
1666+
const clock = FakeTimers.install({ toFake: ['setTimeout', 'clearTimeout'], shouldClearNativeTimers: true })
16671667
t.teardown(() => clock.uninstall())
16681668

16691669
let count = 0

test/unit/helpers/msearch.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ test('Multiple searches (concurrency = 1)', t => {
583583

584584
test('Flush interval', t => {
585585
t.plan(2)
586-
const clock = FakeTimers.install({ toFake: ['setTimeout', 'clearTimeout'] })
586+
const clock = FakeTimers.install({ toFake: ['setTimeout', 'clearTimeout'], shouldClearNativeTimers: true })
587587
t.teardown(() => clock.uninstall())
588588

589589
const MockConnection = connection.buildMockConnection({

0 commit comments

Comments
 (0)