Skip to content

Commit ae91a25

Browse files
authored
fix: assume blocking unless HEAD (nodejs#3771)
* disable failing test * fix: assume blocking unless HEAD This is a safer default. * fixup * fixup: benchmark
1 parent 3596e82 commit ae91a25

14 files changed

+37
-20
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,8 @@ Refs: https://tools.ietf.org/html/rfc7231#section-5.1.1
402402
### Pipelining
403403

404404
Undici will only use pipelining if configured with a `pipelining` factor
405-
greater than `1`.
405+
greater than `1`. Also it is important to pass `blocking: false` to the
406+
request options to properly pipeline requests.
406407

407408
Undici always assumes that connections are persistent and will immediately
408409
pipeline requests, without checking whether the connection is persistent.

benchmarks/benchmark.js

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ const superagentAgent = new http.Agent({
8989
const undiciOptions = {
9090
path: '/',
9191
method: 'GET',
92+
blocking: false,
93+
reset: false,
9294
headersTimeout,
9395
bodyTimeout
9496
}

docs/docs/api/Dispatcher.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
197197
* **headers** `UndiciHeaders | string[]` (optional) - Default: `null`.
198198
* **query** `Record<string, any> | null` (optional) - Default: `null` - Query string params to be embedded in the request URL. Note that both keys and values of query are encoded using `encodeURIComponent`. If for some reason you need to send them unencoded, embed query params into path directly instead.
199199
* **idempotent** `boolean` (optional) - Default: `true` if `method` is `'HEAD'` or `'GET'` - Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceding requests in the pipeline has completed.
200-
* **blocking** `boolean` (optional) - Default: `false` - Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received.
200+
* **blocking** `boolean` (optional) - Default: `method !== 'HEAD'` - Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received.
201201
* **upgrade** `string | null` (optional) - Default: `null` - Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`.
202202
* **bodyTimeout** `number | null` (optional) - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 300 seconds.
203203
* **headersTimeout** `number | null` (optional) - The amount of time, in milliseconds, the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 300 seconds.
@@ -1021,7 +1021,7 @@ The `dns` interceptor enables you to cache DNS lookups for a given duration, per
10211021
- It can be either `'4` or `6`.
10221022
- It will only take effect if `dualStack` is `false`.
10231023
- `lookup: (hostname: string, options: LookupOptions, callback: (err: NodeJS.ErrnoException | null, addresses: DNSInterceptorRecord[]) => void) => void` - Custom lookup function. Default: `dns.lookup`.
1024-
- For more info see [dns.lookup](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback).
1024+
- For more info see [dns.lookup](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback).
10251025
- `pick: (origin: URL, records: DNSInterceptorRecords, affinity: 4 | 6) => DNSInterceptorRecord` - Custom pick function. Default: `RoundRobin`.
10261026
- The function should return a single record from the records array.
10271027
- By default a simplified version of Round Robin is used.

lib/core/request.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ class Request {
143143
? method === 'HEAD' || method === 'GET'
144144
: idempotent
145145

146-
this.blocking = blocking == null ? false : blocking
146+
this.blocking = blocking ?? this.method !== 'HEAD'
147147

148148
this.reset = reset == null ? null : reset
149149

test/client-connect.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ test('connect aborted after connect', async (t) => {
3333
client.connect({
3434
path: '/',
3535
signal,
36-
opaque: 'asd'
36+
opaque: 'asd',
37+
blocking: false
3738
}, (err, { opaque }) => {
3839
t.strictEqual(opaque, 'asd')
3940
t.ok(err instanceof errors.RequestAbortedError)

test/client-idempotent-body.js

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ test('idempotent retry', async (t) => {
3333
path: '/',
3434
method: 'PUT',
3535
idempotent: true,
36+
blocking: false,
3637
body
3738
}, () => {
3839
throw _err

test/client-pipelining.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ test('20 times GET with pipelining 10', async (t) => {
6161
})
6262

6363
function makeRequestAndExpectUrl (client, i, t, cb) {
64-
return client.request({ path: '/' + i, method: 'GET' }, (err, { statusCode, headers, body }) => {
64+
return client.request({ path: '/' + i, method: 'GET', blocking: false }, (err, { statusCode, headers, body }) => {
6565
cb()
6666
t.ifError(err)
6767
t.strictEqual(statusCode, 200)
@@ -587,7 +587,8 @@ test('pipelining empty pipeline before reset', async (t) => {
587587

588588
client.request({
589589
path: '/',
590-
method: 'GET'
590+
method: 'GET',
591+
blocking: false
591592
}, (err, data) => {
592593
t.ifError(err)
593594
data.body

test/client-upgrade.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,8 @@ test('upgrade wait for empty pipeline', async (t) => {
256256

257257
client.request({
258258
path: '/',
259-
method: 'GET'
259+
method: 'GET',
260+
blocking: false
260261
}, (err) => {
261262
t.ifError(err)
262263
})

test/client.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -1442,7 +1442,8 @@ test('increase pipelining', async (t) => {
14421442

14431443
client.request({
14441444
path: '/',
1445-
method: 'GET'
1445+
method: 'GET',
1446+
blocking: false
14461447
}, () => {
14471448
if (!client.destroyed) {
14481449
t.fail()
@@ -1451,7 +1452,8 @@ test('increase pipelining', async (t) => {
14511452

14521453
client.request({
14531454
path: '/',
1454-
method: 'GET'
1455+
method: 'GET',
1456+
blocking: false
14551457
}, () => {
14561458
if (!client.destroyed) {
14571459
t.fail()

test/node-test/client-abort.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ test('abort pipelined', async (t) => {
126126
let counter = 0
127127
client.dispatch({
128128
method: 'GET',
129-
path: '/'
129+
path: '/',
130+
blocking: false
130131
}, {
131132
onConnect (abort) {
132133
// This request will be retried
@@ -151,7 +152,8 @@ test('abort pipelined', async (t) => {
151152

152153
client.dispatch({
153154
method: 'GET',
154-
path: '/'
155+
path: '/',
156+
blocking: false
155157
}, {
156158
onConnect (abort) {
157159
abort()

test/node-test/client-connect.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ test('connect wait for empty pipeline', async (t) => {
142142

143143
client.request({
144144
path: '/',
145-
method: 'GET'
145+
method: 'GET',
146+
blocking: false
146147
}, (err) => {
147148
p.ifError(err)
148149
})

test/pipeline-pipelining.js

+10-5
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,17 @@ test('pipeline pipelining', async (t) => {
2626
t.equal(client[kRunning], 0)
2727
client.pipeline({
2828
method: 'GET',
29-
path: '/'
29+
path: '/',
30+
blocking: false
3031
}, ({ body }) => body).end().resume()
3132
t.equal(client[kBusy], true)
3233
t.deepStrictEqual(client[kRunning], 0)
3334
t.deepStrictEqual(client[kPending], 1)
3435

3536
client.pipeline({
3637
method: 'GET',
37-
path: '/'
38+
path: '/',
39+
blocking: false
3840
}, ({ body }) => body).end().resume()
3941
t.equal(client[kBusy], true)
4042
t.deepStrictEqual(client[kRunning], 0)
@@ -74,7 +76,8 @@ test('pipeline pipelining retry', async (t) => {
7476
client[kConnect](() => {
7577
client.pipeline({
7678
method: 'GET',
77-
path: '/'
79+
path: '/',
80+
blocking: false
7881
}, ({ body }) => body).end().resume()
7982
.on('error', (err) => {
8083
t.ok(err)
@@ -85,15 +88,17 @@ test('pipeline pipelining retry', async (t) => {
8588

8689
client.pipeline({
8790
method: 'GET',
88-
path: '/'
91+
path: '/',
92+
blocking: false
8993
}, ({ body }) => body).end().resume()
9094
t.equal(client[kBusy], true)
9195
t.deepStrictEqual(client[kRunning], 0)
9296
t.deepStrictEqual(client[kPending], 2)
9397

9498
client.pipeline({
9599
method: 'GET',
96-
path: '/'
100+
path: '/',
101+
blocking: false
97102
}, ({ body }) => body).end().resume()
98103
t.equal(client[kBusy], true)
99104
t.deepStrictEqual(client[kRunning], 0)

test/promises.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ test('20 times GET with pipelining 10, async await support', async (t) => {
245245

246246
async function makeRequestAndExpectUrl (client, i, t) {
247247
try {
248-
const { statusCode, body } = await client.request({ path: '/' + i, method: 'GET' })
248+
const { statusCode, body } = await client.request({ path: '/' + i, method: 'GET', blocking: false })
249249
t.strictEqual(statusCode, 200)
250250
const bufs = []
251251
body.on('data', (buf) => {

types/dispatcher.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ declare namespace Dispatcher {
108108
query?: Record<string, any>;
109109
/** Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceding requests in the pipeline have completed. Default: `true` if `method` is `HEAD` or `GET`. */
110110
idempotent?: boolean;
111-
/** Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received. */
111+
/** Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received. Defaults to `method !== 'HEAD'`. */
112112
blocking?: boolean;
113113
/** Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`. Default: `method === 'CONNECT' || null`. */
114114
upgrade?: boolean | string | null;

0 commit comments

Comments
 (0)