Skip to content

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

Open

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.

Activity

  1. pahud commented on Sep 26, 2025

    @pahud

    Hi @eriktim,

    Thank you for this bug report with clear reproduction steps and root cause analysis.

    The problem seems to be in the invoke_ssm_shim.ts file where the setInterval is not unreferenced, causing it to pin the Node.js event loop and prevent Lambda from detecting completion.

    Possible Root Cause:
    The SSM parameter refresh interval keeps the event loop active indefinitely because it's not marked as unreferenced. This prevents streaming Lambda functions from terminating naturally after stream.end().

    Workaround:
    As you mentioned, setting context.callbackWaitsForEmptyEventLoop = false works but isn't ideal as it can cause resource leaks across warm invocations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingp2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions