Skip to content

Commit d454e40

Browse files
author
Ruben Bridgewater
committed
Fix an issue with .multi after a reconnect on node 0.10
Add .path to .createClient options object for unix sockets
1 parent c3502c7 commit d454e40

File tree

6 files changed

+113
-87
lines changed

6 files changed

+113
-87
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ redis - a node.js redis client
33

44
[![Build Status](https://travis-ci.org/NodeRedis/node_redis.png)](https://travis-ci.org/NodeRedis/node_redis)
55
[![Coverage Status](https://coveralls.io/repos/NodeRedis/node_redis/badge.svg?branch=)](https://coveralls.io/r/NodeRedis/node_redis?branch=)
6-
[![Windows Tests](https://ci.appveyor.com/api/projects/status/koc3xraik0xq3b56/branch/master?svg=true)](https://ci.appveyor.com/project/BridgeAR/node-redis/branch/master)
6+
[![Windows Tests](https://ci.appveyor.com/api/projects/status/koc3xraik0xq3b56/branch/master?svg=true&label=Windows%20Tests)](https://ci.appveyor.com/project/BridgeAR/node-redis/branch/master)
77
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/NodeRedis/node_redis?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
88

99
This is a complete and feature rich Redis client for node.js. It supports all Redis commands and focuses on performance.
@@ -179,6 +179,7 @@ port and host are probably fine and you don't need to supply any arguments. `cre
179179
#### `options` is an object with the following possible properties:
180180
* `host`: *127.0.0.1*; The host to connect to
181181
* `port`: *6370*; The port to connect to
182+
* `path`: *null*; The unix socket string to connect to
182183
* `parser`: *hiredis*; Which Redis protocol reply parser to use. If `hiredis` is not installed it will fallback to `javascript`.
183184
* `return_buffers`: *false*; If set to `true`, then all replies will be sent to callbacks as Buffers instead of Strings.
184185
* `detect_buffers`: *false*; If set to `true`, then replies will be sent to callbacks as Buffers

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Features
1414
- Removed the high water mark and low water mark. Such a mechanism should be implemented by a user instead
1515
- The `drain` event is from now on only emitted if the stream really had to buffer
1616
- Reduced the default connect_timeout to be one hour instead of 24h ([@BridgeAR](https://github.com/BridgeAR))
17+
- Added .path to redis.createClient(options); ([@BridgeAR](https://github.com/BridgeAR))
1718

1819
Bugfixes
1920

index.js

Lines changed: 56 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ var debug = function(msg) {
2121
};
2222

2323
function noop () {}
24+
function clone (obj) { return JSON.parse(JSON.stringify(obj || {})); }
2425

2526
exports.debug_mode = /\bredis\b/i.test(process.env.NODE_DEBUG);
2627

@@ -34,31 +35,22 @@ try {
3435

3536
parsers.push(require('./lib/parsers/javascript'));
3637

37-
function RedisClient(stream, options) {
38+
function RedisClient(options) {
3839
// Copy the options so they are not mutated
39-
options = JSON.parse(JSON.stringify(options || {}));
40+
options = clone(options);
41+
events.EventEmitter.call(this);
4042
var self = this;
41-
42-
this.pipeline = 0;
43-
var cork;
44-
if (!stream.cork) {
45-
cork = function (len) {
46-
self.pipeline = len;
47-
self.pipeline_queue = new Queue(len);
48-
};
49-
this.uncork = noop;
43+
var cnx_options = {};
44+
if (options.path) {
45+
cnx_options.path = options.path;
46+
this.address = options.path;
5047
} else {
51-
cork = function (len) {
52-
self.pipeline = len;
53-
self.pipeline_queue = new Queue(len);
54-
self.stream.cork();
55-
};
48+
cnx_options.port = options.port || default_port;
49+
cnx_options.host = options.host || default_host;
50+
cnx_options.family = options.family === 'IPv6' ? 6 : 4;
51+
this.address = cnx_options.host + ':' + cnx_options.port;
5652
}
57-
this.once('ready', function () {
58-
self.cork = cork;
59-
});
60-
61-
this.stream = stream;
53+
this.connection_option = cnx_options;
6254
this.connection_id = ++connection_id;
6355
this.connected = false;
6456
this.ready = false;
@@ -69,10 +61,8 @@ function RedisClient(stream, options) {
6961
if (options.socket_keepalive === undefined) {
7062
options.socket_keepalive = true;
7163
}
72-
if (options.rename_commands) {
73-
for (var command in options.rename_commands) { // jshint ignore: line
74-
options.rename_commands[command.toLowerCase()] = options.rename_commands[command];
75-
}
64+
for (var command in options.rename_commands) { // jshint ignore: line
65+
options.rename_commands[command.toLowerCase()] = options.rename_commands[command];
7666
}
7767
options.return_buffers = !!options.return_buffers;
7868
options.detect_buffers = !!options.detect_buffers;
@@ -98,14 +88,15 @@ function RedisClient(stream, options) {
9888
this.parser_module = null;
9989
this.selected_db = null; // Save the selected db here, used when reconnecting
10090
this.old_state = null;
91+
this.pipeline = 0;
10192
this.options = options;
10293

103-
this.install_stream_listeners();
104-
events.EventEmitter.call(this);
94+
self.stream = net.createConnection(cnx_options);
95+
self.install_stream_listeners();
10596
}
10697
util.inherits(RedisClient, events.EventEmitter);
10798

108-
RedisClient.prototype.install_stream_listeners = function() {
99+
RedisClient.prototype.install_stream_listeners = function () {
109100
var self = this;
110101

111102
if (this.options.connect_timeout) {
@@ -144,9 +135,7 @@ RedisClient.prototype.install_stream_listeners = function() {
144135
};
145136

146137
RedisClient.prototype.cork = noop;
147-
RedisClient.prototype.uncork = function () {
148-
this.stream.uncork();
149-
};
138+
RedisClient.prototype.uncork = noop;
150139

151140
RedisClient.prototype.initialize_retry_vars = function () {
152141
this.retry_timer = null;
@@ -332,6 +321,24 @@ RedisClient.prototype.on_ready = function () {
332321
this.old_state = null;
333322
}
334323

324+
var cork;
325+
if (!this.stream.cork) {
326+
cork = function (len) {
327+
self.pipeline = len;
328+
self.pipeline_queue = new Queue(len);
329+
};
330+
} else {
331+
cork = function (len) {
332+
self.pipeline = len;
333+
self.pipeline_queue = new Queue(len);
334+
self.stream.cork();
335+
};
336+
this.uncork = function () {
337+
self.stream.uncork();
338+
};
339+
}
340+
this.cork = cork;
341+
335342
// magically restore any modal commands from a previous connection
336343
if (this.selected_db !== null) {
337344
// this trick works if and only if the following send_command
@@ -472,7 +479,7 @@ var retry_connection = function (self) {
472479
self.attempts += 1;
473480
self.retry_delay = Math.round(self.retry_delay * self.retry_backoff);
474481

475-
self.stream = net.createConnection(self.connectionOption);
482+
self.stream = net.createConnection(self.connection_option);
476483
self.install_stream_listeners();
477484

478485
self.retry_timer = null;
@@ -488,6 +495,9 @@ RedisClient.prototype.connection_gone = function (why) {
488495
debug('Redis connection is gone from ' + why + ' event.');
489496
this.connected = false;
490497
this.ready = false;
498+
// Deactivate cork to work with the offline queue
499+
this.cork = noop;
500+
this.pipeline = 0;
491501

492502
if (this.old_state === null) {
493503
var state = {
@@ -1219,56 +1229,30 @@ Multi.prototype.exec = Multi.prototype.EXEC = Multi.prototype.exec_batch = funct
12191229
return this._client.should_buffer;
12201230
};
12211231

1222-
var createClient_unix = function (path, options){
1223-
var cnxOptions = {
1224-
path: path
1225-
};
1226-
var net_client = net.createConnection(cnxOptions);
1227-
var redis_client = new RedisClient(net_client, options);
1228-
1229-
redis_client.connectionOption = cnxOptions;
1230-
redis_client.address = path;
1231-
1232-
return redis_client;
1233-
};
1234-
1235-
var createClient_tcp = function (port_arg, host_arg, options) {
1236-
var cnxOptions = {
1237-
port : port_arg || default_port,
1238-
host : host_arg || default_host,
1239-
family : options.family === 'IPv6' ? 6 : 4
1240-
};
1241-
var net_client = net.createConnection(cnxOptions);
1242-
var redis_client = new RedisClient(net_client, options);
1243-
1244-
redis_client.connectionOption = cnxOptions;
1245-
redis_client.address = cnxOptions.host + ':' + cnxOptions.port;
1246-
1247-
return redis_client;
1248-
};
1249-
12501232
var createClient = function (port_arg, host_arg, options) {
12511233
if (typeof port_arg === 'object' || port_arg === undefined) {
12521234
options = port_arg || options || {};
1253-
return createClient_tcp(+options.port, options.host, options);
1254-
}
1255-
if (typeof port_arg === 'number' || typeof port_arg === 'string' && /^\d+$/.test(port_arg)) {
1256-
return createClient_tcp(port_arg, host_arg, options || {});
1257-
}
1258-
if (typeof port_arg === 'string') {
1259-
options = host_arg || options || {};
1260-
1235+
} else if (typeof port_arg === 'number' || typeof port_arg === 'string' && /^\d+$/.test(port_arg)) {
1236+
options = clone(options);
1237+
options.host = host_arg;
1238+
options.port = port_arg;
1239+
} else if (typeof port_arg === 'string') {
1240+
options = clone(host_arg || options);
12611241
var parsed = URL.parse(port_arg, true, true);
12621242
if (parsed.hostname) {
12631243
if (parsed.auth) {
12641244
options.auth_pass = parsed.auth.split(':')[1];
12651245
}
1266-
return createClient_tcp(parsed.port, parsed.hostname, options);
1246+
options.host = parsed.hostname;
1247+
options.port = parsed.port;
1248+
} else {
1249+
options.path = port_arg;
12671250
}
1268-
1269-
return createClient_unix(port_arg, options);
12701251
}
1271-
throw new Error('Unknown type of connection in createClient()');
1252+
if (!options) {
1253+
throw new Error('Unknown type of connection in createClient()');
1254+
}
1255+
return new RedisClient(options);
12721256
};
12731257

12741258
exports.createClient = createClient;

test/commands/multi.spec.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,31 @@ describe("The 'multi' method", function () {
6565
multi1.get('m1');
6666
multi1.exec(done);
6767
});
68+
69+
it("executes a pipelined multi properly after a reconnect in combination with the offline queue", function (done) {
70+
client.once('ready', function () {
71+
client.stream.destroy();
72+
var called = false;
73+
var multi1 = client.multi();
74+
multi1.set("m1", "123");
75+
multi1.get('m1');
76+
multi1.exec(function (err, res) {
77+
assert(!err);
78+
called = true;
79+
});
80+
client.once('ready', function () {
81+
var multi1 = client.multi();
82+
multi1.set("m2", "456");
83+
multi1.get('m2');
84+
multi1.exec(function (err, res) {
85+
assert(called);
86+
assert(!err);
87+
assert.strictEqual(res, '456');
88+
done();
89+
});
90+
});
91+
});
92+
});
6893
});
6994

7095
describe("when connection is broken", function () {

test/connection.spec.js

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ var assert = require("assert");
44
var config = require("./lib/config");
55
var helper = require('./helper');
66
var redis = config.redis;
7-
var net = require('net');
87

98
describe("connection tests", function () {
109
helper.allTests(function(parser, ip, args) {
@@ -68,7 +67,7 @@ describe("connection tests", function () {
6867
client.on('error', function(err) {
6968
if (/Redis connection in broken state: connection timeout.*?exceeded./.test(err.message)) {
7069
setTimeout(function () {
71-
assert(time === connect_timeout);
70+
assert.strictEqual(time, connect_timeout);
7271
done();
7372
}, 500);
7473
}
@@ -133,7 +132,9 @@ describe("connection tests", function () {
133132
host: '192.168.74.167',
134133
connect_timeout: connect_timeout
135134
});
136-
assert(client.stream._events.timeout);
135+
process.nextTick(function() {
136+
assert(client.stream._events.timeout);
137+
});
137138
assert.strictEqual(client.address, '192.168.74.167:6379');
138139
var time = Date.now();
139140

@@ -153,18 +154,22 @@ describe("connection tests", function () {
153154
parser: parser,
154155
host: '192.168.74.167'
155156
});
156-
assert(client.stream._events.timeout === undefined);
157+
process.nextTick(function() {
158+
assert.strictEqual(client.stream._events.timeout, undefined);
159+
});
157160
});
158161

159162
it("clears the socket timeout after a connection has been established", function (done) {
160163
client = redis.createClient({
161164
parser: parser,
162165
connect_timeout: 1000
163166
});
164-
assert.strictEqual(client.stream._idleTimeout, 1000);
167+
process.nextTick(function() {
168+
assert.strictEqual(client.stream._idleTimeout, 1000);
169+
});
165170
client.on('connect', function () {
166171
assert.strictEqual(client.stream._idleTimeout, -1);
167-
assert(client.stream._events.timeout === undefined);
172+
assert.strictEqual(client.stream._events.timeout, undefined);
168173
done();
169174
});
170175
});
@@ -182,6 +187,22 @@ describe("connection tests", function () {
182187
});
183188
});
184189

190+
it("connect with path provided in the options object", function (done) {
191+
client = redis.createClient({
192+
path: '/tmp/redis.sock',
193+
parser: parser,
194+
connect_timeout: 1000
195+
});
196+
197+
var end = helper.callFuncAfter(done, 2);
198+
199+
client.once('ready', function() {
200+
end();
201+
});
202+
203+
client.set('foo', 'bar', end);
204+
});
205+
185206
it("connects correctly with args", function (done) {
186207
client = redis.createClient.apply(redis.createClient, args);
187208
client.on("error", done);
@@ -247,13 +268,7 @@ describe("connection tests", function () {
247268

248269
it("works with missing options object for new redis instances", function () {
249270
// This is needed for libraries that have their own createClient function like fakeredis
250-
var cnxOptions = {
251-
port : 6379,
252-
host : '127.0.0.1',
253-
family : ip === 'IPv6' ? 6 : 4
254-
};
255-
var net_client = net.createConnection(cnxOptions);
256-
client = new redis.RedisClient(net_client);
271+
client = new redis.RedisClient({ on: function () {}});
257272
});
258273

259274
it("throws on strange connection info", function () {

test/helper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ module.exports = {
161161
},
162162
killConnection: function (client) {
163163
// Change the connection option to a non existing one and destroy the stream
164-
client.connectionOption = {
164+
client.connection_option = {
165165
port: 65535,
166166
host: '127.0.0.1',
167167
family: 4

0 commit comments

Comments
 (0)