Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New RESP2 parser #1899

Merged
merged 34 commits into from
Apr 25, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ec1b7a0
parser
leibale Jan 27, 2022
7670d17
Merge branch 'master' of github.com:redis/node-redis into parser
leibale Feb 1, 2022
b0b2464
a new RESP parser :)
leibale Feb 7, 2022
9c7087e
clean code
leibale Feb 7, 2022
35466e7
fix simple string and bulk string cursor
leibale Feb 9, 2022
48fb5ae
performance improvements
leibale Feb 9, 2022
1d09acb
change typescript compiler target
leibale Feb 9, 2022
165bee5
do not use stream.Transform
leibale Feb 9, 2022
ae9fed0
Update decoder.ts
leibale Feb 10, 2022
9953e7e
fix for 1d09acb
leibale Feb 10, 2022
89415cc
improve integer performance
leibale Feb 10, 2022
8097431
revert 1d09acb
leibale Feb 10, 2022
04e9eca
improve RESP2 decoder performance
leibale Feb 10, 2022
f8dacb7
improve performance
leibale Feb 10, 2022
1331698
improve encode performance
leibale Feb 10, 2022
dcdceb1
remove unused import
leibale Feb 10, 2022
6ff6d08
Merge branch 'master' of github.com:redis/node-redis into parser
leibale Feb 14, 2022
53ac472
Merge branch 'master' of github.com:redis/node-redis into parser
leibale Mar 2, 2022
7688d56
Merge branch 'master' of github.com:redis/node-redis into parser
leibale Mar 10, 2022
0cd0a60
Merge branch 'master' of github.com:redis/node-redis into parser
leibale Apr 4, 2022
783f4e7
upgrade benchmark deps
leibale Apr 4, 2022
e141eb0
clean code
leibale Apr 5, 2022
aaaa15c
fix socket error handlers, reset parser on error
leibale Apr 21, 2022
cfbfa2e
fix #2080 - reset pubSubState on socket error
leibale Apr 21, 2022
ab1a50b
Merge branch 'master' of github.com:redis/node-redis into parser
leibale Apr 21, 2022
aea1258
Merge branch 'error-handlers' of github.com:leibale/node-redis into p…
leibale Apr 21, 2022
ce37282
reset decoder on socket error
leibale Apr 21, 2022
8ad0473
fix pubsub
leibale Apr 21, 2022
46c2c5c
Merge branch 'error-handlers' of github.com:leibale/node-redis into p…
leibale Apr 21, 2022
2c23787
fix "RedisSocketInitiator"
leibale Apr 21, 2022
57bd86e
Merge branch 'error-handlers' of github.com:leibale/node-redis into p…
leibale Apr 21, 2022
e307d1f
fix returnStringsAsBuffers
leibale Apr 21, 2022
8ec4855
fix merge
leibale Apr 21, 2022
6b2de37
Merge branch 'master' into parser
leibale Apr 25, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/client/lib/client/RESP2/composers/buffer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { strict as assert } from 'assert';
import BufferComposer from './buffer';

describe('Buffer Composer', () => {
const composer = new BufferComposer();

it('should compose two buffers', () => {
composer.write(Buffer.from([0]));
assert.deepEqual(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Are deepequal comparisons expensive in node? They are in Go.

composer.end(Buffer.from([1])),
Buffer.from([0, 1])
);
});
});
14 changes: 14 additions & 0 deletions packages/client/lib/client/RESP2/composers/buffer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Composer } from './interface';

export default class BufferComposer implements Composer<Buffer> {
#chunks: Array<Buffer> = [];

write(buffer: Buffer): void {
this.#chunks.push(buffer);
}

end(buffer: Buffer): Buffer {
this.write(buffer);
return Buffer.concat(this.#chunks.splice(0));
}
}
5 changes: 5 additions & 0 deletions packages/client/lib/client/RESP2/composers/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface Composer<T> {
write(buffer: Buffer): void;

end(buffer: Buffer): T;
}
14 changes: 14 additions & 0 deletions packages/client/lib/client/RESP2/composers/string.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { strict as assert } from 'assert';
import StringComposer from './string';

describe('String Composer', () => {
const composer = new StringComposer();

it('should compose two strings', () => {
composer.write(Buffer.from([0]));
assert.deepEqual(
composer.end(Buffer.from([1])),
Buffer.from([0, 1]).toString()
);
});
});
18 changes: 18 additions & 0 deletions packages/client/lib/client/RESP2/composers/string.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { StringDecoder } from 'string_decoder';
import { Composer } from './interface';

export default class StringComposer implements Composer<string> {
#decoder = new StringDecoder();

#string = '';

write(buffer: Buffer): void {
this.#string += this.#decoder.write(buffer);
}

end(buffer: Buffer): string {
const string = this.#string + this.#decoder.end(buffer);
this.#string = '';
return string;
}
}
186 changes: 186 additions & 0 deletions packages/client/lib/client/RESP2/decoder.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { strict as assert } from 'assert';
import { SinonSpy, spy } from 'sinon';
import RESP2Decoder from './decoder';
import { ErrorReply } from '../../errors';

interface DecoderAndSpies {
decoder: RESP2Decoder;
returnStringsAsBuffersSpy: SinonSpy;
onReplySpy: SinonSpy;
}

function createDecoderAndSpies(returnStringsAsBuffers: boolean): DecoderAndSpies {
const returnStringsAsBuffersSpy = spy(() => returnStringsAsBuffers),
onReplySpy = spy();

return {
decoder: new RESP2Decoder({
returnStringsAsBuffers: returnStringsAsBuffersSpy,
onReply: onReplySpy
}),
returnStringsAsBuffersSpy,
onReplySpy
};
}

function writeChunks(stream: RESP2Decoder, buffer: Buffer) {
let i = 0;
while (i < buffer.length) {
stream.write(buffer.slice(i, ++i));
}
}

type Replies = Array<Array<unknown>>;

interface TestsOptions {
toWrite: Buffer;
returnStringsAsBuffers: boolean;
replies: Replies;
}

function generateTests({
toWrite,
returnStringsAsBuffers,
replies
}: TestsOptions): void {
Comment on lines +41 to +45
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No commenting


it('single chunk', () => {
const { decoder, returnStringsAsBuffersSpy, onReplySpy } =
createDecoderAndSpies(returnStringsAsBuffers);
decoder.write(toWrite);
assert.equal(returnStringsAsBuffersSpy.callCount, replies.length);
testReplies(onReplySpy, replies);
});

it('multiple chunks', () => {
const { decoder, returnStringsAsBuffersSpy, onReplySpy } =
createDecoderAndSpies(returnStringsAsBuffers);
writeChunks(decoder, toWrite);
assert.equal(returnStringsAsBuffersSpy.callCount, replies.length);
testReplies(onReplySpy, replies);
});
}

function testReplies(spy: SinonSpy, replies: Replies): void {
if (!replies) {
assert.equal(spy.callCount, 0);
return;
}

assert.equal(spy.callCount, replies.length);
for (const [i, reply] of replies.entries()) {
assert.deepEqual(
spy.getCall(i).args,
reply
);
}
}

describe('RESP2Parser', () => {
describe('Simple String', () => {
describe('as strings', () => {
generateTests({
toWrite: Buffer.from('+OK\r\n'),
returnStringsAsBuffers: false,
replies: [['OK']]
});
});

describe('as buffers', () => {
generateTests({
toWrite: Buffer.from('+OK\r\n'),
returnStringsAsBuffers: true,
replies: [[Buffer.from('OK')]]
});
});
});

describe('Error', () => {
generateTests({
toWrite: Buffer.from('-ERR\r\n'),
returnStringsAsBuffers: true,
replies: [[new ErrorReply('ERR')]]
});
});

describe('Integer', () => {
generateTests({
toWrite: Buffer.from(':0\r\n'),
returnStringsAsBuffers: true,
replies: [[0]]
});
});

describe('Bulk String', () => {
describe('null', () => {
generateTests({
toWrite: Buffer.from('$-1\r\n'),
returnStringsAsBuffers: false,
replies: [[null]]
});
});

describe('as strings', () => {
generateTests({
toWrite: Buffer.from('$2\r\naa\r\n'),
returnStringsAsBuffers: false,
replies: [['aa']]
});
});

describe('as buffers', () => {
generateTests({
toWrite: Buffer.from('$2\r\naa\r\n'),
returnStringsAsBuffers: true,
replies: [[Buffer.from('aa')]]
});
});
});

describe('Array', () => {
describe('null', () => {
generateTests({
toWrite: Buffer.from('*-1\r\n'),
returnStringsAsBuffers: false,
replies: [[null]]
});
});

const arrayBuffer = Buffer.from(
'*5\r\n' +
'+OK\r\n' +
'-ERR\r\n' +
':0\r\n' +
'$1\r\na\r\n' +
'*0\r\n'
);

describe('as strings', () => {
generateTests({
toWrite: arrayBuffer,
returnStringsAsBuffers: false,
replies: [[[
'OK',
new ErrorReply('ERR'),
0,
'a',
[]
]]]
});
});

describe('as buffers', () => {
generateTests({
toWrite: arrayBuffer,
returnStringsAsBuffers: true,
replies: [[[
Buffer.from('OK'),
new ErrorReply('ERR'),
0,
Buffer.from('a'),
[]
]]]
});
});
});
});
Loading