Skip to content

[dsbx] feat: per-invocation context for pod runtime env - #30234

Merged
fontanierh merged 1 commit into
mainfrom
fn-invocation-context
Aug 10, 2026
Merged

[dsbx] feat: per-invocation context for pod runtime env#30234
fontanierh merged 1 commit into
mainfrom
fn-invocation-context

Conversation

@fontanierh

Copy link
Copy Markdown
Contributor

Description

First PR of a stack that makes warm function servers serve invocations concurrently (like a web server) instead of one at a time.

Today the warm server injects per-invocation state (user identity, sandbox token) by swapping process.env around each invocation, which forces strict serialization. This PR moves that state into an AsyncLocalStorage invocation context:

  • New pod SDK surface: podEnv(name) reads the active invocation's env; currentUser() and db() now go through it. Outside any context (cold runs, local use) it reads process.env, unchanged behavior.
  • Inside a context, only the context env is consulted. A key absent there stays absent even if process.env has a value, so one invocation's environment can never leak into another.
  • The storage is shared between the runner bundle and the vendored @dust/pod through the Symbol.for registry: they are distinct module graphs (the runner is pre-bundled, the package resolves via NODE_PATH), so a module-level singleton would not be shared.
  • invoke() takes the per-invocation env as an optional argument. The v1 warm server still swaps process.env, so nothing changes until the concurrent server lands on top.

Tests

Unit tests for the context (isolation across concurrent flows, no fallback leakage), context-aware currentUser(), and invoke() with an invocation env. The runner-side test fixture imports the pod package's copy of the context module, so the cross-module-graph Symbol.for contract is exercised for real.

Risk

No behavior change on its own: the cold path reads process.env as before and the v1 server still swaps env. The SDK change ships with the next base image bump.

Deploy Plan

Merges freely; takes effect with the stack's dsbx release + base image bump (last PR of the stack).

@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
playground Ignored Ignored Preview Aug 10, 2026 2:59pm
storybook Ignored Ignored Preview Aug 10, 2026 2:59pm

Request Review

@dust-agent dust-agent Bot 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.

Coding Rules LGTM \o/

@fontanierh
fontanierh force-pushed the fn-invocation-context branch from 63dec61 to c76a5e9 Compare August 8, 2026 14:26
The function runner can now execute an invocation inside an
AsyncLocalStorage context carrying that invocation's environment, and
@dust/pod (currentUser, db, new podEnv) reads the environment through the
context instead of process.env. The storage is shared between the runner
bundle and the vendored package through the Symbol.for registry, since the
two are distinct module graphs.

Inside a context only the context env is consulted, so one invocation's
environment can never leak into another; outside any context (cold runs,
local use) reads fall back to process.env unchanged. invoke() takes the
per-invocation env as an optional argument; the v1 warm server still swaps
process.env, so behavior is unchanged until the concurrent server lands on
top of this.
@fontanierh
fontanierh force-pushed the fn-invocation-context branch from c76a5e9 to 7e98fc6 Compare August 10, 2026 14:45
@fontanierh
fontanierh requested a review from davidebbo August 10, 2026 14:56
@fontanierh
fontanierh merged commit d0c2d9d into main Aug 10, 2026
44 checks passed
@fontanierh
fontanierh deleted the fn-invocation-context branch August 10, 2026 15:04
* each other instead of misreading the store. */
export const INVOCATION_CONTEXT_KEY = "dust.pod.invocation-context.v1";

function contextStorage(): AsyncLocalStorage<InvocationContext> {

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.

Can the two contexts share logic? A number of things look identical.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

not really because they are entirely separate module graphs that cannot share code: The runner is pre-bundled into runner.js and embedded in the dsbx binary and @dust/pod is vendored into the image's global node_modules and resolved by bundles at import time via NODE_PATH

@davidebbo davidebbo 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.

LGTM

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