Skip to content

Commit adc47d0

Browse files
author
Ruben Bridgewater
committed
Improve the js parser further
Handle big data in constant time again Small performance improvements here and there Add ReplyError subclass
1 parent 4e9ff43 commit adc47d0

File tree

3 files changed

+76
-52
lines changed

3 files changed

+76
-52
lines changed

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
'use strict';
22

33
module.exports = require('./lib/parser');
4+
module.exports.ReplyError = require('./lib/replyError');

lib/javascript.js

Lines changed: 49 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
2-
/*jshint latedef: nofunc */
2+
3+
var ReplyError = require('./replyError');
34

45
/**
56
* Used for lengths only, faster perf on arrays / bulks
@@ -24,7 +25,6 @@ function parseSimpleString(parser) {
2425
}
2526
string += String.fromCharCode(c1);
2627
}
27-
return undefined;
2828
}
2929

3030
/**
@@ -33,12 +33,11 @@ function parseSimpleString(parser) {
3333
* @param parser
3434
* @param start
3535
* @param end
36-
* @param noBuffer
3736
* @returns {*}
3837
*/
39-
function convertBufferRange(parser, start, end, noBuffer) {
38+
function convertBufferRange(parser, start, end) {
4039
// If returnBuffers is active, all return values are returned as buffers besides numbers and errors
41-
if (!noBuffer && parser.optionReturnBuffers === true) {
40+
if (parser.optionReturnBuffers === true) {
4241
return parser.buffer.slice(start, end);
4342
}
4443

@@ -49,25 +48,20 @@ function convertBufferRange(parser, start, end, noBuffer) {
4948
* Parse a '+' redis simple string response but forward the offsets
5049
* onto convertBufferRange to generate a string.
5150
* @param parser
52-
* @param noBuffer
5351
* @returns {*}
5452
*/
55-
function parseSimpleStringViaOffset(parser, noBuffer) {
53+
function parseSimpleStringViaOffset(parser) {
5654
var start = parser.offset;
5755
var offset = parser.offset;
5856
var length = parser.buffer.length;
5957

6058
while (offset < length) {
6159
var c1 = parser.buffer[offset++];
62-
if (c1 === 13) { // \r
63-
var c2 = parser.buffer[offset++];
64-
if (c2 === 10) { // \n
65-
parser.offset = offset;
66-
return convertBufferRange(parser, start, offset - 2, noBuffer);
67-
}
60+
if (c1 === 13) { // \r // Finding a \r is sufficient here, since in a simple string \r is always followed by \n
61+
parser.offset = offset + 1;
62+
return convertBufferRange(parser, start, offset - 1);
6863
}
6964
}
70-
return undefined;
7165
}
7266

7367
/**
@@ -76,14 +70,7 @@ function parseSimpleStringViaOffset(parser, noBuffer) {
7670
* @returns {*}
7771
*/
7872
function parseLength(parser) {
79-
var string;
80-
/* istanbul ignore if */
81-
if (parser.buffer.length > 4096) {
82-
string = parseSimpleStringViaOffset(parser, true);
83-
} else {
84-
string = parseSimpleString(parser);
85-
}
86-
73+
var string = parseSimpleString(parser);
8774
if (string !== undefined) {
8875
var length = +string;
8976
if (length === -1) {
@@ -99,7 +86,7 @@ function parseLength(parser) {
9986
* @returns {*}
10087
*/
10188
function parseInteger(parser) {
102-
var string = parseSimpleStringViaOffset(parser);
89+
var string = parseSimpleString(parser);
10390
if (string !== undefined) {
10491
// If stringNumbers is activated the parser always returns numbers as string
10592
// This is important for big numbers (number > Math.pow(2, 53)) as js numbers
@@ -114,16 +101,18 @@ function parseInteger(parser) {
114101
/**
115102
* Parse a '$' redis bulk string response
116103
* @param parser
117-
* @returns {null}
104+
* @returns {*}
118105
*/
119106
function parseBulkString(parser) {
120107
var length = parseLength(parser);
121-
/* jshint eqnull: true */
122-
if (length == null) {
108+
if (length === null || length === undefined) {
123109
return length;
124110
}
125111
var offsetEnd = parser.offset + length;
126-
if ((offsetEnd + 2) > parser.buffer.length) {
112+
if (offsetEnd + 2 > parser.buffer.length) {
113+
parser.bufferCache.push(parser.buffer);
114+
parser.totalChunkSize = parser.buffer.length;
115+
parser.bigStrSize = length + 2;
127116
return;
128117
}
129118

@@ -139,9 +128,9 @@ function parseBulkString(parser) {
139128
* @returns {Error}
140129
*/
141130
function parseError(parser) {
142-
var string = parseSimpleStringViaOffset(parser);
143-
if (string !== undefined) {
144-
return new Error(string);
131+
var str = parseSimpleString(parser);
132+
if (str !== undefined) {
133+
return new ReplyError(str);
145134
}
146135
}
147136

@@ -162,8 +151,7 @@ function handleError(parser, error) {
162151
*/
163152
function parseArray(parser) {
164153
var length = parseLength(parser);
165-
/* jshint eqnull: true */
166-
if (length == null) { // will break if using ===
154+
if (length === null || length === undefined) {
167155
return length;
168156
}
169157

@@ -189,6 +177,7 @@ function parseArray(parser) {
189177
* @param type
190178
* @returns {*}
191179
*/
180+
192181
function parseType(parser, type) {
193182
switch (type) {
194183
case 36: // $
@@ -202,37 +191,24 @@ function parseType(parser, type) {
202191
case 45: // -
203192
return parseError(parser);
204193
default:
205-
return handleError(parser, new Error('Protocol error, got ' + JSON.stringify(String.fromCharCode(type)) + ' as reply type byte'));
194+
return handleError(parser, new ReplyError('Protocol error, got ' + JSON.stringify(String.fromCharCode(type)) + ' as reply type byte'));
206195
}
207196
}
208197

209-
/**
210-
* Quick buffer appending via buffer copy.
211-
* @param parser
212-
* @param buffer
213-
*/
214-
function appendToBuffer(parser, buffer) {
215-
var oldLength = parser.buffer.length;
216-
var remainingLength = oldLength - parser.offset;
217-
var newLength = remainingLength + buffer.length;
218-
var newBuffer = new Buffer(newLength);
219-
parser.buffer.copy(newBuffer, 0, parser.offset, oldLength);
220-
buffer.copy(newBuffer, remainingLength, 0, buffer.length);
221-
parser.buffer = newBuffer;
222-
parser.offset = 0;
223-
}
224-
225198
/**
226199
* Javascript Redis Parser
227200
* @param options
228201
* @constructor
229202
*/
230203
function JavascriptRedisParser(options) {
231-
this.optionReturnBuffers = !!options.return_buffers;
232-
this.optionStringNumbers = !!options.string_numbers;
204+
this.optionReturnBuffers = !!options.returnBuffers;
205+
this.optionStringNumbers = !!options.stringNumbers;
233206
this.name = 'javascript';
234207
this.offset = 0;
235208
this.buffer = null;
209+
this.bigStrSize = 0;
210+
this.totalChunkSize = 0;
211+
this.bufferCache = [];
236212
}
237213

238214
/**
@@ -243,8 +219,29 @@ JavascriptRedisParser.prototype.execute = function (buffer) {
243219
if (this.buffer === null) {
244220
this.buffer = buffer;
245221
this.offset = 0;
222+
} else if (this.bigStrSize === 0) {
223+
var oldLength = this.buffer.length;
224+
var remainingLength = oldLength - this.offset;
225+
var newLength = remainingLength + buffer.length;
226+
var newBuffer = new Buffer(newLength);
227+
this.buffer.copy(newBuffer, 0, this.offset, oldLength);
228+
buffer.copy(newBuffer, remainingLength, 0, buffer.length);
229+
this.buffer = newBuffer;
230+
this.offset = 0;
231+
} else if (this.totalChunkSize + buffer.length >= this.bigStrSize) {
232+
this.bufferCache.push(buffer);
233+
if (this.offset !== 0) {
234+
this.bufferCache[0] = this.bufferCache[0].slice(this.offset);
235+
}
236+
this.buffer = Buffer.concat(this.bufferCache, this.totalChunkSize + buffer.length - this.offset);
237+
this.bigStrSize = 0;
238+
this.totalChunkSize = 0;
239+
this.bufferCache = [];
240+
this.offset = 0;
246241
} else {
247-
appendToBuffer(this, buffer);
242+
this.bufferCache.push(buffer);
243+
this.totalChunkSize += buffer.length;
244+
return;
248245
}
249246

250247
var length = this.buffer.length;

lib/replyError.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
3+
var util = require('util');
4+
5+
function ReplyError (message) {
6+
var limit = Error.stackTraceLimit;
7+
Error.stackTraceLimit = 2;
8+
Error.captureStackTrace(this, this.constructor);
9+
Error.stackTraceLimit = limit;
10+
Object.defineProperty(this, 'name', {
11+
value: 'ReplyError',
12+
configurable: false,
13+
enumerable: false,
14+
writable: true
15+
});
16+
Object.defineProperty(this, 'message', {
17+
value: message || '',
18+
configurable: false,
19+
enumerable: false,
20+
writable: true
21+
});
22+
}
23+
24+
util.inherits(ReplyError, Error);
25+
26+
module.exports = ReplyError;

0 commit comments

Comments
 (0)