Skip to content

Lambda Response Streaming never completes due to injected SSM refresh setInterval #2981

@eriktim

Description

@eriktim

Environment information

System:
  OS: macOS 15.6.1
  CPU: (10) arm64 Apple M2 Pro
  Memory: 225.67 MB / 16.00 GB
  Shell: /bin/zsh
Binaries:
  Node: 22.13.1 - ~/.nvm/versions/node/v22.13.1/bin/node
  Yarn: 1.22.22 - /opt/homebrew/bin/yarn
  npm: 10.9.2 - ~/.nvm/versions/node/v22.13.1/bin/npm
  pnpm: undefined - undefined
NPM Packages:
  @aws-amplify/auth-construct: 1.8.1
  @aws-amplify/backend: 1.16.1
  @aws-amplify/backend-ai: Not Found
  @aws-amplify/backend-auth: 1.7.1
  @aws-amplify/backend-cli: 1.7.2
  @aws-amplify/backend-data: 1.6.1
  @aws-amplify/backend-deployer: 2.1.1
  @aws-amplify/backend-function: 1.14.1
  @aws-amplify/backend-output-schemas: 1.6.0
  @aws-amplify/backend-output-storage: 1.3.1
  @aws-amplify/backend-secret: 1.4.0
  @aws-amplify/backend-storage: 1.4.1
  @aws-amplify/cli-core: 2.1.1
  @aws-amplify/client-config: 1.7.0
  @aws-amplify/data-construct: 1.16.1
  @aws-amplify/data-schema: 1.21.0
  @aws-amplify/deployed-backend-client: 1.7.0
  @aws-amplify/form-generator: 1.2.1
  @aws-amplify/model-generator: 1.2.0
  @aws-amplify/platform-core: 1.9.0
  @aws-amplify/plugin-types: 1.10.1
  @aws-amplify/sandbox: 2.1.2
  @aws-amplify/schema-generator: 1.4.0
  @aws-cdk/toolkit-lib: 0.3.2
  aws-amplify: 6.14.4
  aws-cdk-lib: 2.189.1
  typescript: 5.8.3
npm warn exec The following package was not found and will be installed: cdk@2.1029.0
No AWS environment variables
No CDK environment variables

Describe the bug

When deploying a Lambda with Amplify Gen 2 that uses awslambda.streamifyResponse for response streaming, the function never terminates. Invocation times out even though responseStream.end() has been called.

Inspection of the transpiled bundle shows Amplify injecting SSM environment variable resolution logic that sets up a recurring setInterval to refresh SSM parameters every 60 seconds. This interval is not unref()’d and pins the Node.js event loop, preventing the Lambda runtime from completing.

Setting context.callbackWaitsForEmptyEventLoop = false allows the function to exit, but that only hides the root issue: it causes Lambda to freeze open handles across warm invocations, creating the risk of timers, sockets, or subscriptions leaking between requests.

Reproduction steps

  1. Create a new Amplify Gen 2 backend.
  2. Create a (simplified) Lambda function handler:
// handler.ts

export const handler = awslambda.streamifyResponse(async (_event, stream, _context) => {
  stream.write("Hello ");
  stream.write("world");
  stream.end();
});
  1. Define the Amplify function with a secret:
// resource.ts

import { defineFunction, secret } from '@aws-amplify/backend';

const streamFunction = defineFunction({
  name: 'StreamFunction',
  runtime: 22,
  entry: './handler.ts',
  timeoutSeconds: 60,
  environment: {
    SOME_SECRET: secret('SOME_SECRET'),
  },
});
  1. Attach a Function URL and enable Invoke mode RESPONSE_STREAM.
// backend.ts

import * as lambda from 'aws-cdk-lib/aws-lambda';

import { streamFunction } from './resource';

const backend = defineBackend({
  ...,
  streamFunction
});

backend.streamFunction.resources.lambda.addFunctionUrl({
      authType: lambda.FunctionUrlAuthType.NONE,
      invokeMode: lambda.InvokeMode.RESPONSE_STREAM,
    });
  1. Deploy.
  2. Invoke the Function URL, e.g. curl https://342dqyq5y5d3yq5gd35df3tg.lambda-url.eu-west-1.on.aws/.
  3. Observe the Lambda runs until timeout and never completes (in CloudWatch Logs), even though the client receives a full response.

Inspecting the built index.mjs shows Amplify injecting:

const SSM_PARAMETER_REFRESH_MS = 1000 * 60;
setInterval(async () => {
  try { await internalAmplifyFunctionResolveSsmParams(); }
  catch (error) { console.debug(error); }
}, SSM_PARAMETER_REFRESH_MS);

This timer keeps the event loop alive indefinitely.

Expected behavior
A streaming Lambda should terminate after stream.end() once response transmission is complete, without requiring callbackWaitsForEmptyEventLoop = false.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions