Skip to content

Commit 52fe8e9

Browse files
authored
Merge pull request #2772 from murgatroid99/grpc-js_cardinality_error_hang
grpc-js: Fix client hang when receiving extra messages for a unary response
2 parents 674f4e3 + 7719e37 commit 52fe8e9

File tree

2 files changed

+97
-4
lines changed

2 files changed

+97
-4
lines changed

packages/grpc-js/src/client.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ export class Client {
330330
// eslint-disable-next-line @typescript-eslint/no-explicit-any
331331
onReceiveMessage(message: any) {
332332
if (responseMessage !== null) {
333-
call.cancelWithStatus(Status.INTERNAL, 'Too many responses received');
333+
call.cancelWithStatus(Status.UNIMPLEMENTED, 'Too many responses received');
334334
}
335335
responseMessage = message;
336336
},
@@ -345,7 +345,7 @@ export class Client {
345345
callProperties.callback!(
346346
callErrorFromStatus(
347347
{
348-
code: Status.INTERNAL,
348+
code: Status.UNIMPLEMENTED,
349349
details: 'No message received',
350350
metadata: status.metadata,
351351
},
@@ -463,9 +463,10 @@ export class Client {
463463
// eslint-disable-next-line @typescript-eslint/no-explicit-any
464464
onReceiveMessage(message: any) {
465465
if (responseMessage !== null) {
466-
call.cancelWithStatus(Status.INTERNAL, 'Too many responses received');
466+
call.cancelWithStatus(Status.UNIMPLEMENTED, 'Too many responses received');
467467
}
468468
responseMessage = message;
469+
call.startRead();
469470
},
470471
onReceiveStatus(status: StatusObject) {
471472
if (receivedStatus) {
@@ -478,7 +479,7 @@ export class Client {
478479
callProperties.callback!(
479480
callErrorFromStatus(
480481
{
481-
code: Status.INTERNAL,
482+
code: Status.UNIMPLEMENTED,
482483
details: 'No message received',
483484
metadata: status.metadata,
484485
},

packages/grpc-js/test/test-server-errors.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,98 @@ describe('Server serialization failure handling', () => {
287287
});
288288
});
289289

290+
describe('Cardinality violations', () => {
291+
let client: ServiceClient;
292+
let server: Server;
293+
let responseCount: number = 1;
294+
const testMessage = Buffer.from([]);
295+
before(done => {
296+
const serverServiceDefinition = {
297+
testMethod: {
298+
path: '/TestService/TestMethod/',
299+
requestStream: false,
300+
responseStream: true,
301+
requestSerialize: identity,
302+
requestDeserialize: identity,
303+
responseDeserialize: identity,
304+
responseSerialize: identity
305+
}
306+
};
307+
const clientServiceDefinition = {
308+
testMethod: {
309+
path: '/TestService/TestMethod/',
310+
requestStream: true,
311+
responseStream: false,
312+
requestSerialize: identity,
313+
requestDeserialize: identity,
314+
responseDeserialize: identity,
315+
responseSerialize: identity
316+
}
317+
};
318+
const TestClient = grpc.makeClientConstructor(clientServiceDefinition, 'TestService');
319+
server = new grpc.Server();
320+
server.addService(serverServiceDefinition, {
321+
testMethod(stream: ServerWritableStream<any, any>) {
322+
for (let i = 0; i < responseCount; i++) {
323+
stream.write(testMessage);
324+
}
325+
stream.end();
326+
}
327+
});
328+
server.bindAsync('localhost:0', serverInsecureCreds, (error, port) => {
329+
assert.ifError(error);
330+
client = new TestClient(`localhost:${port}`, clientInsecureCreds);
331+
done();
332+
});
333+
});
334+
beforeEach(() => {
335+
responseCount = 1;
336+
});
337+
after(done => {
338+
client.close();
339+
server.tryShutdown(done);
340+
});
341+
it('Should fail if the client sends too few messages', done => {
342+
const call = client.testMethod((err: ServiceError, data: any) => {
343+
assert(err);
344+
assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED);
345+
done();
346+
});
347+
call.end();
348+
});
349+
it('Should fail if the client sends too many messages', done => {
350+
const call = client.testMethod((err: ServiceError, data: any) => {
351+
assert(err);
352+
assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED);
353+
done();
354+
});
355+
call.write(testMessage);
356+
call.write(testMessage);
357+
call.end();
358+
});
359+
it('Should fail if the server sends too few messages', done => {
360+
responseCount = 0;
361+
const call = client.testMethod((err: ServiceError, data: any) => {
362+
assert(err);
363+
assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED);
364+
done();
365+
});
366+
call.write(testMessage);
367+
call.end();
368+
});
369+
it('Should fail if the server sends too many messages', done => {
370+
responseCount = 2;
371+
const call = client.testMethod((err: ServiceError, data: any) => {
372+
assert(err);
373+
assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED);
374+
done();
375+
});
376+
call.write(testMessage);
377+
call.end();
378+
});
379+
380+
});
381+
290382
describe('Other conditions', () => {
291383
let client: ServiceClient;
292384
let server: Server;

0 commit comments

Comments
 (0)