Skip to content

Commit fc3d722

Browse files
authored
Fb/use redis client reconnect (#53)
* wip 1 * re-add local reconnect strategy * update snapshots for integration tests localhost resolves to ipv6 now * adapt tests to expect registration for reconnecting event * just use ipv4 directly in local case * slightly better way to fix the localhost ambivalence
1 parent 53a6ed9 commit fc3d722

File tree

2 files changed

+42
-35
lines changed

2 files changed

+42
-35
lines changed

src/redisWrapper.js

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ const _localReconnectStrategy = () =>
8080
* @returns {RedisClient|RedisCluster}
8181
* @private
8282
*/
83-
const _createClientBase = () => {
83+
const _createClientBase = (clientName) => {
8484
if (cfEnv.isOnCf) {
8585
try {
8686
// NOTE: settings the user explicitly to empty resolves auth problems, see
@@ -100,29 +100,42 @@ const _createClientBase = () => {
100100
}
101101
return redis.createClient({ url });
102102
} catch (err) {
103-
throw new VError({ name: VERROR_CLUSTER_NAME, cause: err }, "error during create client with redis service");
103+
throw new VError(
104+
{ name: VERROR_CLUSTER_NAME, cause: err, info: { clientName } },
105+
"error during create client with redis service"
106+
);
104107
}
105108
} else {
106109
// NOTE: documentation is buried here https://github.com/redis/node-redis/blob/master/docs/client-configuration.md
107-
// NOTE: we make the host explicit here to avoid ipv4/ipv6 ambivalence problems that got introduced with node v18
108-
return redis.createClient({ socket: { host: "127.0.0.1", reconnectStrategy: _localReconnectStrategy } });
110+
// NOTE: redis behaves a bit odd if you don't make the socket family, aka. ip stack version explicit here. For the
111+
// default family value 0, it will be ipv4 in node v16, ipv6 in node v18 and ipv4+ipv6 in node v20...
112+
return redis.createClient({
113+
url: "redis://localhost:6379",
114+
socket: { family: 4, reconnectStrategy: _localReconnectStrategy },
115+
});
109116
}
110117
};
111118

112-
const _createClientAndConnect = async (errorHandler) => {
119+
const _createClientAndConnect = async (clientName) => {
113120
let client = null;
114121
try {
115-
client = _createClientBase();
122+
client = _createClientBase(clientName);
116123
} catch (err) {
117-
throw new VError({ name: VERROR_CLUSTER_NAME, cause: err }, "error during create client");
124+
throw new VError({ name: VERROR_CLUSTER_NAME, cause: err, info: { clientName } }, "error during create client");
118125
}
119126

120-
client.on("error", errorHandler);
127+
// NOTE: documentation about events is here https://github.com/redis/node-redis?tab=readme-ov-file#events
128+
client.on("error", (err) => {
129+
_logErrorOnEvent(new VError({ name: VERROR_CLUSTER_NAME, cause: err, info: { clientName } }, "caught error event"));
130+
});
131+
client.on("reconnecting", () => {
132+
logger.warning("client '%s' is reconnecting", clientName);
133+
});
121134

122135
try {
123136
await client.connect();
124137
} catch (err) {
125-
throw new VError({ name: VERROR_CLUSTER_NAME, cause: err }, "error during initial connect");
138+
throw new VError({ name: VERROR_CLUSTER_NAME, cause: err, info: { clientName } }, "error during initial connect");
126139
}
127140
return client;
128141
};
@@ -133,17 +146,6 @@ const _closeClientBase = async (client) => {
133146
}
134147
};
135148

136-
const _clientErrorHandlerBase = async (client, err, clientName) => {
137-
_logErrorOnEvent(new VError({ name: VERROR_CLUSTER_NAME, cause: err, info: { clientName } }, "caught error event"));
138-
try {
139-
await _closeClientBase(client);
140-
} catch (closeError) {
141-
_logErrorOnEvent(
142-
new VError({ name: VERROR_CLUSTER_NAME, cause: closeError, info: { clientName } }, "error during client close")
143-
);
144-
}
145-
};
146-
147149
/**
148150
* Lazily create a regular client to be used
149151
* - for getting/setting values
@@ -156,10 +158,7 @@ const _clientErrorHandlerBase = async (client, err, clientName) => {
156158
*/
157159
const getMainClient = async () => {
158160
if (!mainClient) {
159-
mainClient = await _createClientAndConnect(async function (err) {
160-
mainClient = null;
161-
await _clientErrorHandlerBase(this, err, "main");
162-
});
161+
mainClient = await _createClientAndConnect("main");
163162
}
164163
return mainClient;
165164
};
@@ -169,7 +168,10 @@ const getMainClient = async () => {
169168
*
170169
* @private
171170
*/
172-
const closeMainClient = async () => await _closeClientBase(mainClient);
171+
const closeMainClient = async () => {
172+
await _closeClientBase(mainClient);
173+
mainClient = null;
174+
};
173175

174176
/**
175177
* Lazily create a client to be used as a subscriber. Subscriber clients are in a special state and cannot be used for
@@ -182,10 +184,7 @@ const closeMainClient = async () => await _closeClientBase(mainClient);
182184
*/
183185
const getSubscriberClient = async () => {
184186
if (!subscriberClient) {
185-
subscriberClient = await _createClientAndConnect(async function (err) {
186-
subscriberClient = null;
187-
await _clientErrorHandlerBase(this, err, "subscriber");
188-
});
187+
subscriberClient = await _createClientAndConnect("subscriber");
189188
}
190189
return subscriberClient;
191190
};
@@ -195,7 +194,10 @@ const getSubscriberClient = async () => {
195194
*
196195
* @private
197196
*/
198-
const closeSubscriberClient = async () => await _closeClientBase(subscriberClient);
197+
const closeSubscriberClient = async () => {
198+
await _closeClientBase(subscriberClient);
199+
subscriberClient = null;
200+
};
199201

200202
const _clientExec = async (functionName, argsObject) => {
201203
if (!mainClient) {

test/redisWrapper.test.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,10 @@ describe("redis wrapper test", () => {
7373
[
7474
{
7575
"socket": {
76-
"host": "127.0.0.1",
76+
"family": 4,
7777
"reconnectStrategy": [Function],
7878
},
79+
"url": "redis://localhost:6379",
7980
},
8081
]
8182
`);
@@ -105,8 +106,9 @@ describe("redis wrapper test", () => {
105106
expect(redis.createClient).toHaveBeenCalledTimes(1);
106107
expect(client.connect).toHaveBeenCalledTimes(1);
107108
expect(redisWrapper._._getMainClient()).toBe(client);
108-
expect(mockClient.on).toHaveBeenCalledTimes(1);
109+
expect(mockClient.on).toHaveBeenCalledTimes(2);
109110
expect(mockClient.on).toHaveBeenCalledWith("error", expect.any(Function));
111+
expect(mockClient.on).toHaveBeenCalledWith("reconnecting", expect.any(Function));
110112
expect(mockClient.SUBSCRIBE).not.toHaveBeenCalled();
111113
expect(mockClient.PUBLISH).not.toHaveBeenCalled();
112114
expect(loggerSpy.error).not.toHaveBeenCalled();
@@ -116,8 +118,9 @@ describe("redis wrapper test", () => {
116118
const client = await redisWrapper.getSubscriberClient();
117119
expect(redis.createClient).toHaveBeenCalledTimes(1);
118120
expect(redisWrapper._._getSubscriberClient()).toBe(client);
119-
expect(mockClient.on).toHaveBeenCalledTimes(1);
121+
expect(mockClient.on).toHaveBeenCalledTimes(2);
120122
expect(mockClient.on).toHaveBeenCalledWith("error", expect.any(Function));
123+
expect(mockClient.on).toHaveBeenCalledWith("reconnecting", expect.any(Function));
121124
expect(mockClient.SUBSCRIBE).not.toHaveBeenCalled();
122125
expect(mockClient.PUBLISH).not.toHaveBeenCalled();
123126
expect(loggerSpy.error).not.toHaveBeenCalled();
@@ -128,8 +131,9 @@ describe("redis wrapper test", () => {
128131
const client = redisWrapper._._getMainClient();
129132
expect(redis.createClient).toHaveBeenCalledTimes(1);
130133
expect(redis.createClient).toHaveReturnedWith(client);
131-
expect(client.on).toHaveBeenCalledTimes(1);
134+
expect(client.on).toHaveBeenCalledTimes(2);
132135
expect(client.on).toHaveBeenCalledWith("error", expect.any(Function));
136+
expect(client.on).toHaveBeenCalledWith("reconnecting", expect.any(Function));
133137
expect(mockClient.SET).toHaveBeenCalledTimes(1);
134138
expect(mockClient.SET).toHaveBeenCalledWith("key", "value");
135139
expect(result).toBe("SET_return");
@@ -449,8 +453,9 @@ describe("redis wrapper test", () => {
449453
const subscriber = redisWrapper._._getSubscriberClient();
450454
expect(redis.createClient).toHaveBeenCalledTimes(1);
451455
expect(redis.createClient).toHaveReturnedWith(subscriber);
452-
expect(subscriber.on).toHaveBeenCalledTimes(1);
456+
expect(subscriber.on).toHaveBeenCalledTimes(2);
453457
expect(subscriber.on).toHaveBeenCalledWith("error", expect.any(Function));
458+
expect(subscriber.on).toHaveBeenCalledWith("reconnecting", expect.any(Function));
454459
expect(subscriber.SUBSCRIBE).toHaveBeenCalledTimes(1);
455460
expect(subscriber.SUBSCRIBE).toHaveBeenCalledWith(channel, redisWrapper._._subscribedMessageHandler);
456461
expect(mockClient.PUBLISH).not.toHaveBeenCalled();

0 commit comments

Comments
 (0)