Skip to content

Commit 72e2673

Browse files
committed
Merge remote-tracking branch 'upstream/v5' into cmd-parser-with-commands
2 parents 93f897d + 85d5bf4 commit 72e2673

File tree

8 files changed

+136
-7
lines changed

8 files changed

+136
-7
lines changed

docs/client-configuration.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
| socket.keepAlive | `true` | Toggle [`keep-alive`](https://nodejs.org/api/net.html#socketsetkeepaliveenable-initialdelay) functionality |
1414
| socket.keepAliveInitialDelay | `5000` | If set to a positive number, it sets the initial delay before the first keepalive probe is sent on an idle socket |
1515
| socket.tls | | See explanation and examples [below](#TLS) |
16-
| socket.reconnectStrategy | `retries => Math.min(retries * 50, 500)` | A function containing the [Reconnect Strategy](#reconnect-strategy) logic |
16+
| socket.reconnectStrategy | Exponential backoff with a maximum of 2000 ms; plus 0-200 ms random jitter. | A function containing the [Reconnect Strategy](#reconnect-strategy) logic |
1717
| username | | ACL username ([see ACL guide](https://redis.io/topics/acl)) |
1818
| password | | ACL password or the old "--requirepass" password |
1919
| name | | Client name ([see `CLIENT SETNAME`](https://redis.io/commands/client-setname)) |
@@ -35,12 +35,19 @@ When the socket closes unexpectedly (without calling `.quit()`/`.disconnect()`),
3535
2. `number` -> wait for `X` milliseconds before reconnecting.
3636
3. `(retries: number, cause: Error) => false | number | Error` -> `number` is the same as configuring a `number` directly, `Error` is the same as `false`, but with a custom error.
3737

38-
By default the strategy is `Math.min(retries * 50, 500)`, but it can be overwritten like so:
38+
By default the strategy uses exponential backoff, but it can be overwritten like so:
3939

4040
```javascript
4141
createClient({
4242
socket: {
43-
reconnectStrategy: retries => Math.min(retries * 50, 1000)
43+
reconnectStrategy: retries => {
44+
// Generate a random jitter between 0 – 200 ms:
45+
const jitter = Math.floor(Math.random() * 200);
46+
// Delay is an exponential back off, (times^2) * 50 ms, with a maximum value of 2000 ms:
47+
const delay = Math.min(Math.pow(2, retries) * 50, 2000);
48+
49+
return delay + jitter;
50+
}
4451
}
4552
});
4653
```

packages/client/lib/client/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,19 @@ export default class RedisClient<
951951
} while (cursor !== '0');
952952
}
953953

954+
async* hScanValuesIterator(
955+
this: RedisClientType<M, F, S, RESP, TYPE_MAPPING>,
956+
key: RedisArgument,
957+
options?: ScanCommonOptions & ScanIteratorOptions
958+
) {
959+
let cursor = options?.cursor ?? '0';
960+
do {
961+
const reply = await this.hScanNoValues(key, cursor, options);
962+
cursor = reply.cursor;
963+
yield reply.fields;
964+
} while (cursor !== '0');
965+
}
966+
954967
async* sScanIterator(
955968
this: RedisClientType<M, F, S, RESP, TYPE_MAPPING>,
956969
key: RedisArgument,

packages/client/lib/client/socket.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,12 @@ export default class RedisSocket extends EventEmitter {
9797
return retryIn;
9898
} catch (err) {
9999
this.emit('error', err);
100-
return Math.min(retries * 50, 500);
100+
return this.defaultReconnectStrategy(retries);
101101
}
102102
};
103103
}
104104

105-
return retries => Math.min(retries * 50, 500);
105+
return this.defaultReconnectStrategy;
106106
}
107107

108108
#createSocketFactory(options?: RedisSocketOptions) {
@@ -333,4 +333,13 @@ export default class RedisSocket extends EventEmitter {
333333
this.#isSocketUnrefed = true;
334334
this.#socket?.unref();
335335
}
336+
337+
defaultReconnectStrategy(retries: number) {
338+
// Generate a random jitter between 0 – 200 ms:
339+
const jitter = Math.floor(Math.random() * 200);
340+
// Delay is an exponential back off, (times^2) * 50 ms, with a maximum value of 2000 ms:
341+
const delay = Math.min(Math.pow(2, retries) * 50, 2000);
342+
343+
return delay + jitter;
344+
}
336345
}

packages/client/lib/commands/CLIENT_KILL.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ describe('CLIENT KILL', () => {
6666
);
6767
});
6868

69+
it('MAXAGE', () => {
70+
assert.deepEqual(
71+
CLIENT_KILL.transformArguments({
72+
filter: CLIENT_KILL_FILTERS.MAXAGE,
73+
maxAge: 10
74+
}),
75+
['CLIENT', 'KILL', 'MAXAGE', '10']
76+
);
77+
});
78+
6979
describe('SKIP_ME', () => {
7080
it('undefined', () => {
7181
assert.deepEqual(

packages/client/lib/commands/CLIENT_KILL.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ export const CLIENT_KILL_FILTERS = {
77
ID: 'ID',
88
TYPE: 'TYPE',
99
USER: 'USER',
10-
SKIP_ME: 'SKIPME'
10+
SKIP_ME: 'SKIPME',
11+
MAXAGE: 'MAXAGE'
1112
} as const;
1213

1314
type CLIENT_KILL_FILTERS = typeof CLIENT_KILL_FILTERS;
@@ -40,7 +41,11 @@ export type ClientKillSkipMe = CLIENT_KILL_FILTERS['SKIP_ME'] | (ClientKillFilte
4041
skipMe: boolean;
4142
});
4243

43-
export type ClientKillFilter = ClientKillAddress | ClientKillLocalAddress | ClientKillId | ClientKillType | ClientKillUser | ClientKillSkipMe;
44+
export interface ClientKillMaxAge extends ClientKillFilterCommon<CLIENT_KILL_FILTERS['MANAGE']> {
45+
maxAge: number;
46+
}
47+
48+
export type ClientKillFilter = ClientKillAddress | ClientKillLocalAddress | ClientKillId | ClientKillType | ClientKillUser | ClientKillSkipMe | ClientKillMaxAge;
4449

4550
export default {
4651
FIRST_KEY_INDEX: undefined,
@@ -97,5 +102,9 @@ function pushFilter(parser: CommandParser, filter: ClientKillFilter): void {
97102
case CLIENT_KILL_FILTERS.SKIP_ME:
98103
parser.push(filter.skipMe ? 'yes' : 'no');
99104
break;
105+
106+
case CLIENT_KILL_FILTERS.MAXAGE:
107+
args.push(filter.maxAge.toString());
108+
break;
100109
}
101110
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { strict as assert } from 'node:assert';
2+
import testUtils, { GLOBAL } from '../test-utils';
3+
import HSCAN_NOVALUES from './HSCAN_NOVALUES';
4+
5+
describe('HSCAN_NOVALUES', () => {
6+
describe('transformArguments', () => {
7+
it('cusror only', () => {
8+
assert.deepEqual(
9+
HSCAN_NOVALUES.transformArguments('key', '0'),
10+
['HSCAN', 'key', '0', 'NOVALUES']
11+
);
12+
});
13+
14+
it('with MATCH', () => {
15+
assert.deepEqual(
16+
HSCAN_NOVALUES.transformArguments('key', '0', {
17+
MATCH: 'pattern'
18+
}),
19+
['HSCAN', 'key', '0', 'MATCH', 'pattern', 'NOVALUES']
20+
);
21+
});
22+
23+
it('with COUNT', () => {
24+
assert.deepEqual(
25+
HSCAN_NOVALUES.transformArguments('key', '0', {
26+
COUNT: 1
27+
}),
28+
['HSCAN', 'key', '0', 'COUNT', '1', 'NOVALUES']
29+
);
30+
});
31+
32+
it('with MATCH & COUNT', () => {
33+
assert.deepEqual(
34+
HSCAN_NOVALUES.transformArguments('key', '0', {
35+
MATCH: 'pattern',
36+
COUNT: 1
37+
}),
38+
['HSCAN', 'key', '0', 'MATCH', 'pattern', 'COUNT', '1', 'NOVALUES']
39+
);
40+
});
41+
});
42+
43+
testUtils.testWithClient('client.hScanNoValues', async client => {
44+
const [, reply] = await Promise.all([
45+
client.hSet('key', 'field', 'value'),
46+
client.hScanNoValues('key', '0')
47+
]);
48+
49+
assert.deepEqual(reply, {
50+
cursor: '0',
51+
fields: [
52+
'field',
53+
]
54+
});
55+
}, GLOBAL.SERVERS.OPEN);
56+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { RedisArgument, BlobStringReply, Command } from '../RESP/types';
2+
import { ScanCommonOptions, pushScanArguments } from './SCAN';
3+
4+
export default {
5+
FIRST_KEY_INDEX: 1,
6+
IS_READ_ONLY: true,
7+
transformArguments(
8+
key: RedisArgument,
9+
cursor: RedisArgument,
10+
options?: ScanCommonOptions
11+
) {
12+
const args = pushScanArguments(['HSCAN', key], cursor, options);
13+
args.push('NOVALUES');
14+
return args;
15+
},
16+
transformReply([cursor, fields]: [BlobStringReply, Array<BlobStringReply>]) {
17+
return {
18+
cursor,
19+
fields
20+
};
21+
}
22+
} as const satisfies Command;

packages/client/lib/commands/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ import HRANDFIELD_COUNT_WITHVALUES from './HRANDFIELD_COUNT_WITHVALUES';
144144
import HRANDFIELD_COUNT from './HRANDFIELD_COUNT';
145145
import HRANDFIELD from './HRANDFIELD';
146146
import HSCAN from './HSCAN';
147+
import HSCAN_NOVALUES from './HSCAN_NOVALUES';
147148
import HSET from './HSET';
148149
import HSETNX from './HSETNX';
149150
import HSTRLEN from './HSTRLEN';
@@ -623,6 +624,8 @@ export default {
623624
hRandField: HRANDFIELD,
624625
HSCAN,
625626
hScan: HSCAN,
627+
HSCAN_NOVALUES,
628+
hScanNoValues: HSCAN_NOVALUES,
626629
HSET,
627630
hSet: HSET,
628631
HSETNX,

0 commit comments

Comments
 (0)