Skip to content

Commit 21138af

Browse files
authored
fix(cluster): handle connection errors by reconnection (#762)
Previously, exceptions caused by DNS resolving will not trigger any reconnections and fail silently. That makes errors much harder to be handled. This fix catches these exceptions and triggers a reconnection to keep the same behaviors as v3. Closes #753
1 parent 77231b5 commit 21138af

File tree

4 files changed

+95
-7
lines changed

4 files changed

+95
-7
lines changed

lib/cluster/ClusterOptions.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export interface IClusterOptions {
1616
*
1717
* @default (times) => Math.min(100 + times * 2, 2000)
1818
*/
19-
clusterRetryStrategy?: (times: number) => number | null
19+
clusterRetryStrategy?: (times: number, reason?: Error) => number | null
2020

2121
/**
2222
* See Redis class.

lib/cluster/index.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,11 @@ class Cluster extends EventEmitter {
190190
}
191191
}.bind(this))
192192
this.subscriber.start()
193-
}).catch(reject)
193+
}).catch((err) => {
194+
this.setStatus('close')
195+
this.handleCloseEvent(err)
196+
reject(err)
197+
})
194198
})
195199
}
196200

@@ -200,10 +204,13 @@ class Cluster extends EventEmitter {
200204
* @private
201205
* @memberof Cluster
202206
*/
203-
private handleCloseEvent(): void {
207+
private handleCloseEvent(reason?: Error): void {
208+
if (reason) {
209+
debug('closed because %s', reason)
210+
}
204211
let retryDelay
205212
if (!this.manuallyClosing && typeof this.options.clusterRetryStrategy === 'function') {
206-
retryDelay = this.options.clusterRetryStrategy.call(this, ++this.retryAttempts)
213+
retryDelay = this.options.clusterRetryStrategy.call(this, ++this.retryAttempts, reason)
207214
}
208215
if (typeof retryDelay === 'number') {
209216
this.setStatus('reconnecting')

test/functional/cluster/connect.js

+16-3
Original file line numberDiff line numberDiff line change
@@ -355,11 +355,24 @@ describe('cluster:connect', function () {
355355
});
356356

357357
it('throws when startupNodes is empty', (done) => {
358-
const cluster = new Redis.Cluster(null, {lazyConnect: true})
358+
const message = '`startupNodes` should contain at least one node.'
359+
let pending = 2
360+
const cluster = new Redis.Cluster(null, {
361+
lazyConnect: true,
362+
clusterRetryStrategy(_, reason) {
363+
expect(reason.message).to.eql(message)
364+
if (!--pending) {
365+
done()
366+
}
367+
return false
368+
}
369+
})
359370
cluster.connect().catch(err => {
360-
expect(err.message).to.eql('`startupNodes` should contain at least one node.')
371+
expect(err.message).to.eql(message)
361372
cluster.disconnect()
362-
done()
373+
if (!--pending) {
374+
done()
375+
}
363376
})
364377
})
365378
});

test/functional/cluster/dnsLookup.js

+68
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,72 @@ describe('cluster:dnsLookup', () => {
5555
done()
5656
})
5757
})
58+
59+
it('reconnects when dns lookup fails', (done) => {
60+
const slotTable = [
61+
[0, 1000, ['127.0.0.1', 30001]],
62+
[1001, 16383, ['127.0.0.1', 30002]]
63+
]
64+
new MockServer(30001, (argv, c) => {
65+
}, slotTable)
66+
new MockServer(30002, (argv, c) => {
67+
}, slotTable)
68+
69+
let retried = false
70+
const cluster = new Redis.Cluster([
71+
{ host: 'localhost', port: '30001' }
72+
], {
73+
dnsLookup (_, callback) {
74+
if (retried) {
75+
callback(null, '127.0.0.1')
76+
} else {
77+
callback(new Error('Random Exception'))
78+
}
79+
},
80+
clusterRetryStrategy: function (_, reason) {
81+
expect(reason.message).to.eql('Random Exception')
82+
expect(retried).to.eql(false)
83+
retried = true
84+
return 0;
85+
}
86+
})
87+
cluster.on('ready', () => {
88+
cluster.disconnect();
89+
done();
90+
})
91+
})
92+
93+
it('reconnects when dns lookup thrown an error', (done) => {
94+
const slotTable = [
95+
[0, 1000, ['127.0.0.1', 30001]],
96+
[1001, 16383, ['127.0.0.1', 30002]]
97+
]
98+
new MockServer(30001, (argv, c) => {
99+
}, slotTable)
100+
new MockServer(30002, (argv, c) => {
101+
}, slotTable)
102+
103+
let retried = false
104+
const cluster = new Redis.Cluster([
105+
{ host: 'localhost', port: '30001' }
106+
], {
107+
dnsLookup (_, callback) {
108+
if (retried) {
109+
callback(null, '127.0.0.1')
110+
} else {
111+
throw new Error('Random Exception')
112+
}
113+
},
114+
clusterRetryStrategy: function (_, reason) {
115+
expect(reason.message).to.eql('Random Exception')
116+
expect(retried).to.eql(false)
117+
retried = true
118+
return 0;
119+
}
120+
})
121+
cluster.on('ready', () => {
122+
cluster.disconnect();
123+
done();
124+
})
125+
})
58126
})

0 commit comments

Comments
 (0)