Skip to content

Commit c94efed

Browse files
committed
Merge pull request #1016 from NodeRedis/properly-quit
Calling quit should always close the connection
2 parents 3704ebd + 4848155 commit c94efed

File tree

6 files changed

+114
-5
lines changed

6 files changed

+114
-5
lines changed

README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,16 @@ something like this `Error: Ready check failed: ERR operation not permitted`.
277277
The client exposed the used [stream](https://nodejs.org/api/stream.html) in `client.stream` and if the stream or client had to [buffer](https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback) the command in `client.should_buffer`.
278278
In combination this can be used to implement backpressure by checking the buffer state before sending a command and listening to the stream [drain](https://nodejs.org/api/stream.html#stream_event_drain) event.
279279

280+
## client.quit()
281+
282+
This sends the quit command to the redis server and ends cleanly right after all running commands were properly handled.
283+
If this is called while reconnecting (and therefor no connection to the redis server exists) it is going to end the connection right away instead of
284+
resulting in further reconnections! All offline commands are going to be flushed with an error in that case.
285+
280286
## client.end(flush)
281287

282288
Forcibly close the connection to the Redis server. Note that this does not wait until all replies have been parsed.
283-
If you want to exit cleanly, call `client.quit()` to send the `QUIT` command after you have handled all replies.
289+
If you want to exit cleanly, call `client.quit()` as mentioned above.
284290

285291
You should set flush to true, if you are not absolutely sure you do not care about any other commands.
286292
If you set flush to false all still running commands will silently fail.

changelog.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Features
1010
- Activating monitor mode does now work together with arbitrary commands including pub sub mode
1111
- Pub sub mode is completly rewritten and all known issues fixed
1212
- Added `string_numbers` option to get back strings instead of numbers
13+
- Quit command is from now on always going to end the connection properly
1314

1415
Bugfixes
1516

@@ -21,6 +22,7 @@ Bugfixes
2122
- Fixed pub sub mode crashing if calling unsubscribe / subscribe in various combinations
2223
- Fixed pub sub mode emitting unsubscribe even if no channels were unsubscribed
2324
- Fixed pub sub mode emitting a message without a message published
25+
- Fixed quit command not ending the connection and resulting in further reconnection if called while reconnecting
2426

2527
## v.2.5.3 - 21 Mar, 2016
2628

index.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,7 @@ function handle_offline_command (self, command_obj) {
751751
}
752752
err = new Error(command + " can't be processed. " + msg);
753753
err.command = command;
754+
err.code = 'NR_OFFLINE';
754755
utils.reply_in_order(self, callback, err);
755756
} else {
756757
debug('Queueing ' + command + ' for next server connection.');
@@ -845,8 +846,6 @@ RedisClient.prototype.send_command = function (command, args, callback) {
845846
if (!this.pub_sub_mode) {
846847
this.pub_sub_mode = this.command_queue.length + 1;
847848
}
848-
} else if (command === 'quit') {
849-
this.closing = true;
850849
}
851850
this.command_queue.push(command_obj);
852851

lib/individualCommands.js

+24
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,30 @@ RedisClient.prototype.monitor = RedisClient.prototype.MONITOR = function (callba
6868
});
6969
};
7070

71+
RedisClient.prototype.quit = RedisClient.prototype.QUIT = function (callback) {
72+
var self = this;
73+
var callback_hook = function (err, res) {
74+
// TODO: Improve this by handling everything with coherend error codes and find out if there's anything missing
75+
if (err && (err.code === 'NR_OFFLINE' ||
76+
err.message === 'Redis connection gone from close event.' ||
77+
err.message === 'The command can\'t be processed. The connection has already been closed.'
78+
)) {
79+
// Pretent the quit command worked properly in this case.
80+
// Either the quit landed in the offline queue and was flushed at the reconnect
81+
// or the offline queue is deactivated and the command was rejected right away
82+
// or the stream is not writable
83+
// or while sending the quit, the connection dropped
84+
err = null;
85+
res = 'OK';
86+
}
87+
utils.callback_or_emit(self, callback, err, res);
88+
};
89+
var backpressure_indicator = this.send_command('quit', [], callback_hook);
90+
// Calling quit should always end the connection, no matter if there's a connection or not
91+
this.closing = true;
92+
return backpressure_indicator;
93+
};
94+
7195
// Store info in this.server_info after each call
7296
RedisClient.prototype.info = RedisClient.prototype.INFO = function info (section, callback) {
7397
var self = this;

test/connection.spec.js

+79
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,85 @@ describe('connection tests', function () {
3434
assert.strictEqual(client.stream.listeners('error').length, 1);
3535
});
3636

37+
describe('quit on lost connections', function () {
38+
39+
it('calling quit while the connection is down should not end in reconnecting version a', function (done) {
40+
var called = 0;
41+
client = redis.createClient({
42+
port: 9999,
43+
retry_strategy: function (options) {
44+
var bool = client.quit(function (err, res) {
45+
assert.strictEqual(res, 'OK');
46+
assert.strictEqual(err, null);
47+
assert.strictEqual(called++, -1);
48+
setTimeout(done, 25);
49+
});
50+
assert.strictEqual(bool, false);
51+
assert.strictEqual(called++, 0);
52+
return 5;
53+
}
54+
});
55+
client.set('foo', 'bar', function (err, res) {
56+
assert.strictEqual(err.message, 'Redis connection gone from close event.');
57+
called = -1;
58+
});
59+
});
60+
61+
it('calling quit while the connection is down should not end in reconnecting version b', function (done) {
62+
var called = false;
63+
client = redis.createClient(9999);
64+
client.set('foo', 'bar', function (err, res) {
65+
assert.strictEqual(err.message, 'Redis connection gone from close event.');
66+
called = true;
67+
});
68+
var bool = client.quit(function (err, res) {
69+
assert.strictEqual(res, 'OK');
70+
assert.strictEqual(err, null);
71+
assert(called);
72+
done();
73+
});
74+
assert.strictEqual(bool, false);
75+
});
76+
77+
it('calling quit while the connection is down without offline queue should end the connection right away', function (done) {
78+
var called = false;
79+
client = redis.createClient(9999, {
80+
enable_offline_queue: false
81+
});
82+
client.set('foo', 'bar', function (err, res) {
83+
assert.strictEqual(err.message, 'SET can\'t be processed. The connection is not yet established and the offline queue is deactivated.');
84+
called = true;
85+
});
86+
var bool = client.quit(function (err, res) {
87+
assert.strictEqual(res, 'OK');
88+
assert.strictEqual(err, null);
89+
assert(called);
90+
done();
91+
});
92+
assert.strictEqual(bool, false);
93+
});
94+
95+
it('do not quit before connected or a connection issue is detected', function (done) {
96+
client = redis.createClient();
97+
client.set('foo', 'bar', helper.isString('OK'));
98+
var bool = client.quit(done);
99+
assert.strictEqual(bool, false);
100+
});
101+
102+
it('quit right away if connection drops while quit command is on the fly', function (done) {
103+
client = redis.createClient();
104+
client.once('ready', function () {
105+
client.set('foo', 'bar', helper.isError());
106+
var bool = client.quit(done);
107+
assert.strictEqual(bool, true);
108+
process.nextTick(function () {
109+
client.stream.destroy();
110+
});
111+
});
112+
});
113+
114+
});
115+
37116
helper.allTests(function (parser, ip, args) {
38117

39118
describe('using ' + parser + ' and ' + ip, function () {

test/node_redis.spec.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -235,12 +235,11 @@ describe('The node_redis client', function () {
235235
assert.strictEqual(client.offline_queue.length, 0);
236236
done();
237237
});
238-
}, 100);
238+
}, 50);
239239
});
240240

241241
it('emit an error', function (done) {
242242
if (helper.redisProcess().spawnFailed()) this.skip();
243-
244243
client.quit();
245244
client.on('error', function (err) {
246245
assert.strictEqual(err.message, 'SET can\'t be processed. The connection has already been closed.');

0 commit comments

Comments
 (0)