Skip to content

[SVLS-10070] fix(proxy): remove finished tasks from the Runtime API proxy's JoinSet - #1509

Draft
lym953 wants to merge 1 commit into
yiming.luo/listener-joinset-leakfrom
yiming.luo/proxy-joinset-leak
Draft

lym953 wants to merge 1 commit into
yiming.luo/listener-joinset-leakfrom
yiming.luo/proxy-joinset-leak

Conversation

@lym953

@lym953 lym953 commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator

Problem

The extension can run a proxy in front of the Lambda Runtime API. It starts the proxy when the function uses Lambda Web Adapter or the Datadog wrapper, for example with App and API Protection. For each invocation, the proxy starts two tasks in a Tokio JoinSet: one for the event and one for the response. A JoinSet keeps each finished task until something joins it, and the proxy joins only at shutdown. So the set grows by two tasks per invocation for the life of the execution environment.

This is the same pattern that #1508 fixes in the listener for the tracer's start and end calls. That leak made the extension's memory grow about 14 KB per invocation in #1496. I have not measured the memory growth from the proxy.

This PR

Before each new task, the proxy removes the tasks that have finished, with JoinSet::try_join_next(). This is the same change as #1508. Each module keeps its own copy of the helper, as each already keeps its own shutdown function for its set. The proxy still joins the tasks that are running at shutdown.

This PR is based on #1508. Merge #1508 first.

Testing

Steps

  1. Run cargo test --lib proxy::interceptor.
  2. Make the new helper remove nothing, and run the new test again.

Result

The new test starts 10 tasks that finish at once, calls the helper, and counts the tasks left in the set.

Check Before After
Tasks in the set after the helper runs 10 (test fails) 0 (test passes)

The test covers the helper only. I did not run the proxy on Lambda. The calls to the helper are the two lines added before each spawn.

🤖 Generated with Claude Code

…roxy's JoinSet

The proxy in front of the Lambda Runtime API spawns two tasks per invocation
into a JoinSet that is joined only at shutdown, so the set grows by two finished
tasks per invocation. Remove finished tasks before each spawn, as the lifecycle
listener does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The helper-only test would pass even without production task reaping, so a process-level regression is needed to check bounded memory growth after warmup on Linux and clean shutdown.

1 open issue
What changed in this PR

This PR prevents completed tasks from accumulating in the Lambda Runtime API proxy, applying the lifecycle listener’s cleanup pattern from #1508.

Changes:

  • Drain completed tasks before spawning invocation-event and response tasks.
  • Preserve shutdown handling for tasks still running.
  • Add a unit test verifying that the cleanup helper removes completed tasks.
File Description
bottlecap/​src/​proxy/​interceptor.rs Adds completed-task cleanup at both spawn sites and a helper unit test.

Review effort: Balanced

// Let the spawned tasks finish.
tokio::task::yield_now().await;

reap_finished(&mut join_set);
@datadog-official

datadog-official Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Pipelines

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: d9e3686 | Docs | View more details | Give us feedback!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants