Skip to content

Commit

Permalink
fix(lambda): fixed heartbeat error (#1351)
Browse files Browse the repository at this point in the history
- The heartbeat error was thrown although the request succeeded.
  • Loading branch information
kirrg001 authored Oct 1, 2024
1 parent cd4bb25 commit 0c001c8
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 14 deletions.
20 changes: 19 additions & 1 deletion packages/serverless/src/backend_connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,15 @@ let heartbeatInterval;
function scheduleLambdaExtensionHeartbeatRequest() {
const executeHeartbeat = () => {
logger.debug('Executing Heartbeat request to Lambda extension.');
const startTime = Date.now();

const req = uninstrumented.http.request(
{
hostname: layerExtensionHostname,
port: layerExtensionPort,
path: '/heartbeat',
method: 'POST',
Connection: 'close',
// This sets a timeout for establishing the socket connection, see setTimeout below for a timeout for an
// idle connection after the socket has been opened.
timeout: layerExtensionTimeout
Expand All @@ -156,9 +158,25 @@ function scheduleLambdaExtensionHeartbeatRequest() {
)
);
}

res.once('data', () => {
// we need to register the handlers to avoid running into a timeout
});

res.once('end', () => {
const endTime = Date.now();
const duration = endTime - startTime;
logger.debug(`Took ${duration}ms to receive response from extension`);
});
}
);

req.once('finish', () => {
const endTime = Date.now();
const duration = endTime - startTime;
logger.debug(`Took ${duration}ms to send data to extension`);
});

function handleHeartbeatError(e) {
// Make sure we do not try to talk to the Lambda extension again.
useLambdaExtension = false;
Expand All @@ -171,7 +189,7 @@ function scheduleLambdaExtensionHeartbeatRequest() {
);
}

req.on('error', e => {
req.once('error', e => {
// req.destroyed indicates that we have run into a timeout and have already handled the timeout error.
if (req.destroyed) {
return;
Expand Down
36 changes: 23 additions & 13 deletions packages/serverless/test/backend_connector_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,23 @@ describe('[UNIT] backend connector', () => {
this.timeout(config.getTestTimeout());

let onStub;
let onceStub;
let destroyStub;
let setTimeoutStub;

beforeEach(() => {
sinon.spy(global, 'setInterval');
sinon.spy(global, 'clearInterval');

onStub = sinon.stub();
onceStub = sinon.stub();
destroyStub = sinon.stub();
setTimeoutStub = sinon.stub();

sinon.stub(uninstrumentedHttp.http, 'request').returns({
on: onStub,
setTimeout: sinon.stub(),
once: onceStub,
setTimeout: setTimeoutStub,
end: sinon.stub(),
removeAllListeners: sinon.stub(),
destroy: destroyStub
Expand Down Expand Up @@ -72,7 +77,7 @@ describe('[UNIT] backend connector', () => {
expect(uninstrumentedHttp.http.request.called).to.be.true;
expect(uninstrumentedHttp.http.request.callCount).to.eql(1);

const onError = onStub.getCalls().find(call => call.firstArg === 'error').callback;
const onError = onceStub.getCalls().find(call => call.firstArg === 'error').callback;
onError();

expect(global.clearInterval.called).to.be.true;
Expand All @@ -86,19 +91,21 @@ describe('[UNIT] backend connector', () => {
expect(uninstrumentedHttp.http.request.called).to.be.true;
expect(uninstrumentedHttp.http.request.callCount).to.eql(1);

return retry(async () => {
const prom = sendBundle();
await delay(200);
expect(setTimeoutStub.called).to.be.true;
expect(destroyStub.called).to.be.false;

const onTimeout = onStub.getCalls().find(call => call.firstArg === 'timeout').callback;
const onEnd = uninstrumentedHttp.http.request
.getCalls()
.find(call => call.firstArg.path === '/bundle').callback;
const firstCallArgs = setTimeoutStub.getCall(0).args;

onTimeout();
expect(global.clearInterval.called).to.be.true;
await delay(200);

setTimeout(onEnd, 200);
// simulate timeout of extension
firstCallArgs[1]();

return retry(async () => {
expect(destroyStub.called).to.be.true;

const prom = sendBundle();
await delay(200);
await prom;

expect(destroyStub.called).to.be.true;
Expand All @@ -117,7 +124,7 @@ describe('[UNIT] backend connector', () => {
const prom = sendBundle();
await delay(200);

const onError = onStub.getCalls().find(call => call.firstArg === 'error').callback;
const onError = onceStub.getCalls().find(call => call.firstArg === 'error').callback;
const onEnd = uninstrumentedHttp.http.request
.getCalls()
.find(call => call.firstArg.path === '/bundle').callback;
Expand Down Expand Up @@ -150,11 +157,14 @@ describe('[UNIT] backend connector', () => {
const prom = sendBundle();
await delay(200);

const onceFinish = onceStub.getCalls().find(call => call.firstArg === 'finish').callback;
const onFinish = onStub.getCalls().find(call => call.firstArg === 'finish').callback;
const onEnd = uninstrumentedHttp.http.request
.getCalls()
.find(call => call.firstArg.path === '/bundle').callback;

onceFinish();

setTimeout(onEnd, 250);
setTimeout(onFinish, 200);

Expand Down

0 comments on commit 0c001c8

Please sign in to comment.