Skip to content

[APPS-2961] Update: run local backend function calls in parallel, with per-call log prefixes - #603

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 1 commit into
masterfrom
tiffany.trinh/apps-2961-parallel-local-execution
Oct 28, 2026
Merged

gh-worker-dd-mergequeue-cf854d[bot] merged 1 commit into
masterfrom
tiffany.trinh/apps-2961-parallel-local-execution

Conversation

@tyffical

@tyffical tyffical commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Architecture

sequenceDiagram
    participant App as App frontend (dev client)
    participant D as Vite dev server
    participant C1 as Call 1 process
    participant C2 as Call 2 process
    participant API as Datadog API
    App->>D: executeAction (call 1)
    App->>D: executeAction (call 2)
    D->>API: One runtime-context lookup, shared by both calls
    API-->>D: Runtime context ($)
    par Calls run in parallel, no local limit
        D->>C1: start
        C1-->>D: Log lines, printed as [fn#35;1] ...
    and
        D->>C2: start
        C2-->>D: Log lines, printed as [fn#35;2] ...
    end
    C1->>D: $.Actions call fails
    D-->>C1: Rejects with the platform's bare string
    C1->>D: result
    D-->>App: 200 with the result
    C2->>D: Uncaught error
    D-->>App: 500 {error: "Execution failed: #lt;message#gt;"}
    Note over App: The dev client throws BackendFunctionError("Execution failed: #lt;message#gt;")
Loading

Changes

10 changes across local-execution-process.ts, dev-server.ts, local-execution-shared.ts, dev-server-transport.ts, the README and tests
What changed File
Runs calls in parallel, with no local limit, and removes the one-at-a-time queue. local-execution-process.ts, local-execution.ts
Prefixes each logged line with [<function>#N]. local-execution-process.ts
Carries thrown strings and failure detail across IPC. local-execution-process.ts, local-execution-child.ts, local-execution-protocol.ts
Concurrent calls share one runtime context query. dev-server.ts
Rejects a failed $.Actions call with the platform's bare string. dev-server.ts
Reports an uncaught failure as Execution failed: <stack or value>. dev-server.ts, local-execution-shared.ts, local-execution-child.ts
Rejects invalid $.Actions calls with deployed-wording strings. local-execution-shared.ts
Throws the response's error, else the status and body. dev-server-transport.ts
Documents parallel calls, the shared query and log prefixes. README.md
Tests each change. local-execution-process.integration.test.ts, dev-server.test.ts, execute-backend-function.test.ts

QA Instructions

Check Result Evidence
Parallel calls Three 1.5 s calls and one failing call finish in about 2 s qa-parallel.mjs
Log prefixes Every line carries its call's [<function>#N] qa-parallel.mjs
One failing call Only it returns 500 Execution failed: Error: qa boom …; the others return 200 qa-parallel.mjs
Overlapping runtime context lookups One Datadog query for all four calls qa-parallel.mjs
Processes Every call's process and the dev server exit qa-parallel.mjs
No connection a connection is required for … run.sh
No arguments or inputs No arguments provided …, or … must have an inputs field that is an object run.sh
Unknown connection rpc error: code = NotFound … run.sh
Real read-only connection Resolves run.sh
Uncaught failure BackendFunctionError: Execution failed: <message or stack> transport-run.sh
yarn typecheck:all  # Expected: exit 0
yarn lint           # Expected: exit 0
yarn cli integrity  # Expected: exit 0, no changes
TMPDIR=/private/tmp yarn test:unit packages/plugins/apps  # Expected: all pass
Local parallel-call harness and raw output
  • Fixture, in an app whose node_modules/vite links to the repo's Vite, with the plugin built by yarn workspace @datadog/vite-plugin build:
// src/parallel.backend.ts
export async function sleepAndLog(ms: number, label: string) {
    console.log(`start ${label}`);
    await new Promise((resolve) => setTimeout(resolve, ms));
    console.error(`end ${label}`);
    return { label, pid: process.pid };
}

export async function failing() {
    console.log('about to fail');
    throw new Error('qa boom');
}
// qa-parallel.mjs
// Usage: node qa-parallel.mjs <worktree> <app-dir>
// Starts a real Vite dev server with the built @datadog/vite-plugin, answers the Datadog runtime
// context query in-process (fake keys, no network), fires 3 sleepAndLog calls and 1 failing call
// at once to /__dd/executeAction, and prints timings, responses, query count and child pids.
import path from 'node:path';
import { pathToFileURL } from 'node:url';

const [worktree, appDir] = process.argv.slice(2);
const realFetch = globalThis.fetch;
let runtimeContextQueries = 0;
globalThis.fetch = async (input, init) => {
    const url = String(input instanceof Request ? input.url : input);
    if (url.endsWith('/api/v2/app-builder/queries/preview-async')) {
        runtimeContextQueries += 1;
        // Datadog's own latency, so calls that arrive together overlap their queries.
        await new Promise((resolve) => setTimeout(resolve, Number(process.env.QA_LATENCY_MS ?? 0)));
        return Response.json({ data: { id: 'qa-receipt' } });
    }
    if (url.includes('/api/v2/app-builder/queries/execution-long-polling/')) {
        const data = { Source: { initiator: { id: 'qa-user', orgId: 'qa-org' }, runAsUser: { id: 'qa-user', orgId: 'qa-org' } } };
        return Response.json({ data: { attributes: { done: true, outputs: { data } } } });
    }
    return realFetch(input, init);
};
process.env.BUILD_PLUGINS_ENV = 'test';
process.env.DD_API_KEY = 'qa-fake-api-key';
process.env.DD_APP_KEY = 'qa-fake-app-key';

const vite = await import(pathToFileURL(path.join(worktree, 'node_modules/vite/dist/node/index.js')).href);
const { datadogVitePlugin } = await import(
    pathToFileURL(path.join(worktree, 'packages/published/vite-plugin/dist/src/index.mjs')).href
);
const server = await vite.createServer({
    configFile: false,
    root: appDir,
    logLevel: 'error',
    server: { port: 0, strictPort: false, hmr: false },
    optimizeDeps: { noDiscovery: true },
    plugins: [datadogVitePlugin({ auth: { site: 'datadoghq.com' }, apps: { enable: true } })],
});
await server.listen();
const baseUrl = `http://localhost:${server.httpServer.address().port}`;
console.log(`QA dev server pid ${process.pid} at ${baseUrl}`);

const proxy = await (await realFetch(`${baseUrl}/src/parallel.backend.ts`)).text();
const names = Object.fromEntries(
    [...proxy.matchAll(/executeBackendFunction\("([^"]+)"/g)].map((m) => [m[1].split('.').pop(), m[1]]),
);
const call = async (fn, args) => {
    const startedAt = Date.now();
    const response = await realFetch(`${baseUrl}/__dd/executeAction`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ functionName: names[fn], args }),
    });
    return { fn, args, status: response.status, body: await response.text(), ms: Date.now() - startedAt };
};

const startedAt = Date.now();
const results = await Promise.all([
    call('sleepAndLog', [1500, 'a']),
    call('sleepAndLog', [1500, 'b']),
    call('failing', []),
    call('sleepAndLog', [1500, 'c']),
]);
const wallMs = Date.now() - startedAt;
for (const result of results) console.log('RESULT', JSON.stringify(result));
console.log(`WALL ${wallMs}ms for 3 x 1500ms calls plus 1 failing call`);
console.log(`RUNTIME_CONTEXT_QUERIES ${runtimeContextQueries}`);
const pids = results.filter((r) => r.status === 200).map((r) => JSON.parse(r.body).result.data.pid);
console.log(`CHILD_PIDS ${pids.join(' ')}`);
await server.close();
# From the repo root, so Volta picks its pinned Node 20.19.4
QA_LATENCY_MS=300 node qa-parallel.mjs "$PWD" ./qa-app > qa-run.log 2>&1
for p in $(grep "^CHILD_PIDS" qa-run.log | cut -d" " -f2-); do ps -p "$p" >/dev/null && echo "pid $p alive" || echo "pid $p gone"; done
QA dev server pid 26855 at http://localhost:5173
[warn|vite|apps] Local execution on Node.js 20.19.4 can't provide `import.meta.resolve` inside the sandbox; production runs Node 24. Upgrade to Node 22.15+ to match it.
[sleepAndLog#1] start c
[sleepAndLog#2] start a
[failing#4] about to fail
[sleepAndLog#3] start b
[sleepAndLog#1] end c
[sleepAndLog#2] end a
[sleepAndLog#3] end b
RESULT {"fn":"sleepAndLog","args":[1500,"a"],"status":200,"body":"{\"success\":true,\"result\":{\"data\":{\"label\":\"a\",\"pid\":26860}}}","ms":2127}
RESULT {"fn":"sleepAndLog","args":[1500,"b"],"status":200,"body":"{\"success\":true,\"result\":{\"data\":{\"label\":\"b\",\"pid\":26861}}}","ms":2172}
RESULT {"fn":"failing","args":[],"status":500,"body":"{\"success\":false,\"error\":\"Execution failed: Error: qa boom\\n    at failing (…/qa-app/src/parallel.backend.ts:10:11)…\"}","ms":649}
RESULT {"fn":"sleepAndLog","args":[1500,"c"],"status":200,"body":"{\"success\":true,\"result\":{\"data\":{\"label\":\"c\",\"pid\":26859}}}","ms":2109}
WALL 2173ms for 3 x 1500ms calls plus 1 failing call
RUNTIME_CONTEXT_QUERIES 1
CHILD_PIDS 26860 26861 26859
pid 26860 gone
pid 26861 gone
pid 26859 gone
pid 26855 gone
Staging fixtures, helper scripts and raw output
  • Fixtures, in an app with the packed plugin:
// src/parity.backend.ts
import { listMonitors } from '@datadog/action-catalog/dd/monitor';
import { list } from '@datadog/action-catalog/github/pulls';

declare const $: any;

function describe(err: unknown) {
    return err instanceof Error ? { type: 'Error', message: err.message } : { type: typeof err, value: err };
}

async function attempt(call: () => Promise<unknown>) {
    try {
        const value = await call();
        return { resolved: Array.isArray(value) ? `array of ${value.length}` : typeof value };
    } catch (err) {
        return describe(err);
    }
}

export async function missingConnection() {
    return attempt(() => $.Actions.github.pulls.list({ inputs: { owner: 'DataDog', repo: 'datadog-agent' } }));
}

export async function missingConnectionTyped() {
    return attempt(() => list({ inputs: { owner: 'DataDog', repo: 'datadog-agent' } } as any));
}

export async function noArguments() {
    return attempt(() => $.Actions.github.pulls.list());
}

export async function missingInputs() {
    return attempt(() => $.Actions.github.pulls.list({ owner: 'DataDog', repo: 'datadog-agent' }));
}

export async function fakeConnection() {
    return attempt(() => listMonitors({ inputs: { limit: 1 }, connectionId: '00000000-0000-0000-0000-000000000000' }));
}

export async function unlistedConnection() {
    return attempt(() => $.Actions.dd.monitor.listMonitors({ inputs: { limit: 1 }, connectionId: 'qa-unlisted-connection' }));
}

export async function realConnection() {
    return attempt(() => listMonitors({ inputs: { limit: 1 }, connectionId: '<read-only-connection-id>' }));
}
// src/parityUncaught.backend.ts
declare const $: any;

export async function uncaughtMissingConnection() {
    return $.Actions.github.pulls.list({ inputs: { owner: 'DataDog', repo: 'datadog-agent' } });
}

export async function uncaughtString() {
    const failure = 'plain string failure';
    throw failure;
}
// src/uncaughtError.backend.ts
export async function uncaughtError() {
    throw new TypeError('plain error x');
}
  • Runner and request logger:
# run.sh
#!/usr/bin/env bash
# Usage (with DD_API_KEY and DD_APP_KEY for a Datadog staging org): run.sh <app-dir> <port> <out-prefix> <fixture>:<fn> ...
# Starts the app's Vite dev server in development mode, POSTs each function to /__dd/executeAction,
# records full response bodies, then stops the server by its own PID.
set -u
APP="$1"; PORT="$2"; OUT="$3"; shift 3
D="$(cd "$(dirname "$0")" && pwd)"
NET_LOG="$OUT.net.log"; VITE_LOG="$OUT.vite.log"; RESP="$OUT.responses.txt"
: > "$NET_LOG"; : > "$RESP"
cd "$APP" || exit 1
if [ -n "$(lsof -ti tcp:$PORT -sTCP:LISTEN)" ]; then echo "port $PORT is busy"; exit 1; fi
env -u DD_OAUTH_ACCESS_TOKEN -u DATADOG_OAUTH_ACCESS_TOKEN DD_SITE=<staging site> QA_NET_LOG="$NET_LOG" \
    NODE_OPTIONS="--import $D/log-requests.mjs" \
    node node_modules/vite/bin/vite.js --mode development --port "$PORT" --strictPort < /dev/null > "$VITE_LOG" 2>&1 &
VPID=$!
echo "vite pid $VPID" >> "$RESP"
for _ in $(seq 1 90); do curl -s -o /dev/null "http://localhost:$PORT/" && break; sleep 1; done
for spec in "$@"; do
  ROUTE=executeAction; TAG=""
  case "$spec" in cloud:*) ROUTE=executeActionViaCloud; TAG="cloud-"; spec="${spec#cloud:}";; esac
  fixture="${spec%%:*}"; fn="${spec#*:}"
  module="$(curl -s "http://localhost:$PORT/src/$fixture.backend.ts")"
  if ! printf %s "$module" | grep -q executeBackendFunction; then
    echo "$fixture: rejected by transform: $(printf %s "$module" | head -c 600)" >> "$RESP"; continue
  fi
  FN="$(printf %s "src/$fixture" | shasum -a 256 | cut -d' ' -f1).$fn"
  echo "=== $ROUTE $fixture.$fn" >> "$RESP"
  curl -s -m 180 -D "$OUT.$TAG$fn.headers" -o "$OUT.$TAG$fn.body" -w 'HTTP %{http_code}\n' -X POST -H 'Content-Type: application/json' \
    -d "{\"functionName\":\"$FN\",\"args\":[]}" "http://localhost:$PORT/__dd/$ROUTE" >> "$RESP"
  cat "$OUT.$TAG$fn.body" >> "$RESP"; echo >> "$RESP"
done
LPID="$(lsof -ti tcp:$PORT -sTCP:LISTEN)"
echo "listener pid $LPID" >> "$RESP"
kill "$VPID" $LPID 2>/dev/null
sleep 3
for p in $VPID $LPID; do if kill -0 "$p" 2>/dev/null; then echo "pid $p still alive" >> "$RESP"; else echo "pid $p stopped" >> "$RESP"; fi; done
// log-requests.mjs, which run.sh preloads to log each Datadog request and long-poll body
// Preloaded with --import: logs method, URL and status of every fetch, plus the request body of
// preview-async POSTs and the response body of every long-poll, to $QA_NET_LOG.
import { appendFileSync } from 'node:fs';

const realFetch = globalThis.fetch;
const log = (line) => appendFileSync(process.env.QA_NET_LOG, line + '\n');
globalThis.fetch = async (input, init) => {
    const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
    const method = init?.method ?? (typeof input === 'object' && 'method' in input ? input.method : 'GET');
    if (url.includes('preview-async') && typeof init?.body === 'string') {
        try {
            const spec = JSON.parse(init.body).data.attributes.query.properties.spec;
            const shown = spec.inputs?.script ? { ...spec, inputs: { ...spec.inputs, script: '<script>' } } : spec;
            log(`REQ ${method} ${url.split('?')[0]} spec=${JSON.stringify(shown)}`);
        } catch {
            log(`REQ ${method} ${url.split('?')[0]} <unparsed body>`);
        }
    }
    const res = await realFetch(input, init);
    if (url.includes('execution-long-polling')) {
        const text = await res.clone().text();
        log(`RES ${res.status} ${url.split('?')[0].replace(/execution-long-polling\/.*/, 'execution-long-polling/<receipt>')} body=${text.slice(0, 400)}`);
    } else {
        log(`RES ${res.status} ${method} ${url.split('?')[0]}`);
    }
    return res;
};
DD_API_KEY=<staging API key> DD_APP_KEY=<staging app key> bash run.sh ./qa-app 5774 out/after parity:missingConnection parity:missingConnectionTyped parity:noArguments parity:missingInputs parity:fakeConnection parity:unlistedConnection parity:realConnection parityUncaught:uncaughtMissingConnection parityUncaught:uncaughtString uncaughtError:uncaughtError
vite pid 28154
=== executeAction parity.missingConnection
HTTP 200
{"success":true,"result":{"data":{"type":"string","value":"a connection is required for com.datadoghq.github.pulls"}}}
=== executeAction parity.missingConnectionTyped
HTTP 200
{"success":true,"result":{"data":{"type":"string","value":"a connection is required for com.datadoghq.github.pulls"}}}
=== executeAction parity.noArguments
HTTP 200
{"success":true,"result":{"data":{"type":"string","value":"No arguments provided to action $.Actions.github.pulls.list"}}}
=== executeAction parity.missingInputs
HTTP 200
{"success":true,"result":{"data":{"type":"string","value":"First argument to action $.Actions.github.pulls.list must have an inputs field that is an object"}}}
=== executeAction parity.fakeConnection
HTTP 200
{"success":true,"result":{"data":{"type":"string","value":"rpc error: code = NotFound desc = connection not found"}}}
=== executeAction parity.unlistedConnection
HTTP 200
{"success":true,"result":{"data":{"type":"string","value":"Action $.Actions.dd.monitor.listMonitors used connection \"qa-unlisted-connection\", which is not in this function's allowed connections: [00000000-0000-0000-0000-000000000000, <read-only-connection-id>]"}}}
=== executeAction parity.realConnection
HTTP 200
{"success":true,"result":{"data":{"resolved":"object"}}}
=== executeAction parityUncaught.uncaughtMissingConnection
HTTP 500
{"success":false,"error":"Execution failed: a connection is required for com.datadoghq.github.pulls"}
=== executeAction parityUncaught.uncaughtString
HTTP 500
{"success":false,"error":"Execution failed: plain string failure"}
=== executeAction uncaughtError.uncaughtError
HTTP 500
{"success":false,"error":"Execution failed: TypeError: plain error x\n    at uncaughtError (/tmp/qa/qa-app/src/uncaughtError.backend.ts?dd-local-exec:3:11)\n    at file:///tmp/qa/qa-app/node_modules/@datadog/vite-plugin/dist/src/local-execution-child.mjs:1:17287\n    at m (file:///tmp/qa/qa-app/node_modules/@datadog/vite-plugin/dist/src/local-execution-child.mjs:1:701)\n    at Vt (file:///tmp/qa/qa-app/node_modules/@datadog/vite-plugin/dist/src/local-execution-child.mjs:1:17280)\n    at processTicksAndRejections (node:internal/process/task_queues:95:5)\n    at file:///tmp/qa/qa-app/node_modules/@datadog/vite-plugin/dist/src/local-execution-child.mjs:1:15433"}
listener pid 28154
pid 28154 stopped
pid 28154 stopped
  • Through executeBackendFunction:
// transport-harness.mjs
// Calls backend functions through the plugin's own packed client runtime, the code app frontends use.
import { createHash } from 'node:crypto';
import { pathToFileURL } from 'node:url';

const [runtimePath, origin, ...specs] = process.argv.slice(2);
const realFetch = globalThis.fetch;
globalThis.fetch = async (url, init) => {
    try {
        return await realFetch(new URL(url, origin), init);
    } catch (error) {
        console.log(JSON.stringify({ fetchFailure: String(error.cause ?? error) }));
        throw error;
    }
};
await import(pathToFileURL(runtimePath).href);
for (const spec of specs) {
    const [fixture, fn] = spec.split(':');
    const name = `${createHash('sha256').update(`src/${fixture}`).digest('hex')}.${fn}`;
    try {
        const data = await globalThis.DD_APPS_RUNTIME.executeBackendFunction(name, []);
        console.log(JSON.stringify({ spec, resolved: data }));
    } catch (error) {
        console.log(JSON.stringify({ spec, name: error.name, message: error.message, statusCode: error.statusCode }));
    }
}
# transport-run.sh
#!/usr/bin/env bash
# Usage (with DD_API_KEY and DD_APP_KEY for a Datadog staging org): transport-run.sh <app-dir> <port> <out-file> <fixture>:<fn> ...
set -u
APP="$1"; PORT="$2"; OUT="$3"; shift 3
D="$(cd "$(dirname "$0")" && pwd)"
cd "$APP" || exit 1
if [ -n "$(lsof -ti tcp:$PORT -sTCP:LISTEN)" ]; then echo "port $PORT is busy"; exit 1; fi
env -u DD_OAUTH_ACCESS_TOKEN -u DATADOG_OAUTH_ACCESS_TOKEN DD_SITE=<staging site> \
    node node_modules/vite/bin/vite.js --mode development --port "$PORT" --strictPort < /dev/null > "$OUT.vite.log" 2>&1 &
VPID=$!
for _ in $(seq 1 90); do curl -s -o /dev/null "http://localhost:$PORT/" && break; sleep 1; done
for spec in "$@"; do curl -s -o /dev/null "http://localhost:$PORT/src/${spec%%:*}.backend.ts"; done
node "$D/transport-harness.mjs" "$APP/node_modules/@datadog/vite-plugin/dist/src/apps-runtime.mjs" "http://localhost:$PORT" "$@" > "$OUT" 2>&1
LPID="$(lsof -ti tcp:$PORT -sTCP:LISTEN)"
kill "$VPID" $LPID 2>/dev/null
sleep 3
for p in $VPID $LPID; do if kill -0 "$p" 2>/dev/null; then echo "pid $p still alive" >> "$OUT"; fi; done
DD_API_KEY=<staging API key> DD_APP_KEY=<staging app key> bash transport-run.sh ./qa-app 5773 out/transport.txt parityUncaught:uncaughtMissingConnection parityUncaught:uncaughtString uncaughtError:uncaughtError parity:realConnection getGreeting:getGreeting
{"spec":"parityUncaught:uncaughtMissingConnection","name":"BackendFunctionError","message":"Execution failed: a connection is required for com.datadoghq.github.pulls","statusCode":500}
{"spec":"parityUncaught:uncaughtString","name":"BackendFunctionError","message":"Execution failed: plain string failure","statusCode":500}
{"spec":"uncaughtError:uncaughtError","name":"BackendFunctionError","message":"Execution failed: TypeError: plain error x\n    at uncaughtError (/tmp/qa/qa-app/src/uncaughtError.backend.ts?dd-local-exec:3:11)\n    at file:///tmp/qa/qa-app/node_modules/@datadog/vite-plugin/dist/src/local-execution-child.mjs:1:17287\n    at m (file:///tmp/qa/qa-app/node_modules/@datadog/vite-plugin/dist/src/local-execution-child.mjs:1:701)\n    at Vt (file:///tmp/qa/qa-app/node_modules/@datadog/vite-plugin/dist/src/local-execution-child.mjs:1:17280)\n    at processTicksAndRejections (node:internal/process/task_queues:95:5)\n    at file:///tmp/qa/qa-app/node_modules/@datadog/vite-plugin/dist/src/local-execution-child.mjs:1:15433","statusCode":500}
{"spec":"parity:realConnection","resolved":{"resolved":"object"}}
{"spec":"getGreeting:getGreeting","resolved":"Hello, undefined! This greeting ran as a backend function."}

Blast Radius

  • Local development only.
  • Error changes reach every local path, dev:verify included.
  • No feature flag. Low risk: a burst of calls starts that many processes.

Out of Scope / Follow-ups

1 item deferred
Item Status Next step
The dev server's own lines about a call (timeouts, exits, $.Actions failures) lack its prefix. Deferred Add it.

Documentation

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Live Debugger Runtime Benchmark

SDK-loaded dormant-probe runtime overhead, measured against an uninstrumented bundle in the same browser session.

Browser Workload Quality Per-call overhead upper
chrome Hot clean <= 2.55 ns
chrome Tiny clean <= 0.02 ns
firefox Hot clean <= 9.77 ns
firefox Tiny clean <= 0.03 ns
safari Hot clean <= 5.44 ns
safari Tiny clean <= 0.39 ns

What do the Tiny and Hot workloads represent?

Browser Debugger SDK: 7.16.2 · built 2026-10-27 · S3 ETag 872de873

Full diagnostics
browser  workload  quality  per-call overhead upper  overhead upper          95% CI        A/A diag        block CI  acf(1)   baseline  instrumented                        samples
-------  --------  -------  -----------------------  --------------  --------------  --------------  --------------  ------  ---------  ------------  -----------------------------
chrome   Hot       clean                 <= 2.55 ns        <= 5.14%   2.52..2.55 ns  -0.02..0.01 ns   2.52..2.55 ns    0.19  52.735 ms     55.428 ms  102 (trim 20%, outliers 4.9%)
chrome   Tiny      clean                 <= 0.02 ns        <= 0.21%  -0.02..0.01 ns  -0.01..0.01 ns  -0.01..0.02 ns   -0.17  56.330 ms     56.387 ms  102 (trim 20%, outliers 7.8%)
firefox  Hot       clean                 <= 9.77 ns       <= 38.80%   9.75..9.77 ns  -0.00..0.01 ns   9.75..9.77 ns   -0.00  39.800 ms     55.240 ms  102 (trim 20%, outliers 6.9%)
firefox  Tiny      clean                 <= 0.03 ns        <= 0.24%   0.02..0.03 ns  -0.01..0.00 ns   0.02..0.03 ns   -0.14  53.750 ms     53.860 ms  102 (trim 20%, outliers 6.9%)
safari   Hot       clean                 <= 5.44 ns       <= 21.74%   5.42..5.44 ns  -0.00..0.01 ns   5.42..5.44 ns    0.07  45.300 ms     55.140 ms  102 (trim 20%, outliers 3.9%)
safari   Tiny      clean                 <= 0.39 ns       <= 10.30%   0.37..0.38 ns  -0.00..0.00 ns   0.37..0.39 ns    0.05  51.420 ms     56.600 ms  102 (trim 20%, outliers 6.9%)

Raw samples are in the live-debugger-runtime-bench-results artifact.

@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-parallel-local-execution branch from 1b04081 to cfac113 Compare October 21, 2026 19:41
@tyffical
tyffical changed the base branch from master to tiffany.trinh/apps-2961-child-process-execution October 21, 2026 19:41
@tyffical tyffical changed the title [APPS-2961] Update: run local backend functions in parallel, each in a fresh sandboxed process [APPS-2961] Update: run local backend function calls in parallel, with per-call log prefixes Oct 21, 2026
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-parallel-local-execution branch from cfac113 to 401268c Compare October 21, 2026 19:51
@tyffical
tyffical requested a balanced review from Copilot October 21, 2026 20:08
@tyffical

Copy link
Copy Markdown
Contributor Author

@codex review

@tyffical
tyffical marked this pull request as ready for review October 21, 2026 20:08
@tyffical
tyffical requested a review from a team as a code owner October 21, 2026 20:08
@tyffical
tyffical requested review from oliverli and removed request for a team October 21, 2026 20:08
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T18:50:01.782740Z cf49bfa Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 401268c033

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/plugins/apps/src/vite/local-execution-process.ts Outdated

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.

🟢 Approval recommended

No blocking issues were identified in concurrency accounting, query sharing, error handling, logging, or regression coverage.

No open issues 🎉

What changed in this PR

Builds on process isolation to run local backend calls concurrently on Vite 6+, while preserving serial execution for the in-process fallback.

Changes:

  • Allows up to 25 concurrent calls, returning HTTP 429 when full.
  • Adds per-call log prefixes and shares in-flight runtime-context queries.
  • Updates regression tests and local-execution documentation.
File Description
packages/​plugins/​apps/​src/​vite/​local-execution.ts Makes the in-process queue helper private.
packages/​plugins/​apps/​src/​vite/​local-execution-process.ts Adds concurrency limits and per-call log prefixes.
packages/​plugins/​apps/​src/​vite/​local-execution-process.test.ts Tests parallelism, slot release, isolation, and logging.
packages/​plugins/​apps/​src/​vite/​dev-server.ts Shares context queries and handles HTTP 429 responses.
packages/​plugins/​apps/​src/​vite/​dev-server.test.ts Tests query sharing, refresh, and concurrency errors.
packages/​plugins/​apps/​README.md Documents concurrent execution and prefixed logs.

Review effort: Balanced

@oliverli oliverli left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved. The change is correct. I added two optional nits.

Comment thread packages/plugins/apps/src/vite/local-execution-process.ts Outdated
Comment thread packages/plugins/apps/README.md Outdated
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-child-process-execution branch from 4c9b050 to aa5b6d7 Compare October 22, 2026 01:06
@tyffical
tyffical requested a review from a team as a code owner October 22, 2026 01:06
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-parallel-local-execution branch from 401268c to 002f14e Compare October 22, 2026 01:07
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-child-process-execution branch from aa5b6d7 to ba6b653 Compare October 22, 2026 02:01
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-parallel-local-execution branch 2 times, most recently from f698ad5 to 7e9b4b4 Compare October 22, 2026 03:37
@tyffical
tyffical requested a balanced review from Copilot October 22, 2026 06:08
@tyffical

Copy link
Copy Markdown
Contributor Author

@codex review

@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-parallel-local-execution branch from 86c8430 to b50260f Compare October 23, 2026 16:18
@tyffical
tyffical removed this pull request from stack #618 October 26, 2026 20:54
@tyffical
tyffical added this pull request to stack #623 October 26, 2026 20:55
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-child-permission-model branch 2 times, most recently from 7dc7d64 to 160bbaa Compare October 27, 2026 00:38
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-parallel-local-execution branch from b50260f to fbd073c Compare October 27, 2026 00:40
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-child-permission-model branch from 160bbaa to c89cc54 Compare October 27, 2026 00:54
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-parallel-local-execution branch from fbd073c to f0977cc Compare October 27, 2026 00:54
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-child-permission-model branch from c89cc54 to 09ba149 Compare October 27, 2026 02:45
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-parallel-local-execution branch from f0977cc to a1c4904 Compare October 27, 2026 02:47
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-child-permission-model branch from 09ba149 to 82157b9 Compare October 27, 2026 20:16
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-parallel-local-execution branch from a1c4904 to 9ac932c Compare October 27, 2026 20:16
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-child-permission-model branch from 82157b9 to 1544220 Compare October 28, 2026 05:39
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-parallel-local-execution branch from 9ac932c to 00ec61c Compare October 28, 2026 05:39
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-child-permission-model branch from 1544220 to bb0797f Compare October 28, 2026 16:12
@tyffical
tyffical force-pushed the tiffany.trinh/apps-2961-parallel-local-execution branch from 00ec61c to 1ed9fb5 Compare October 28, 2026 16:12
…l log prefixes

Each call already gets a fresh process, so calls no longer queue and run
in parallel, with no local limit: production's concurrency limits vary
by org, and dev:verify runs against them. Calls that start while a
runtime context query is in flight share it, so a burst sends Datadog
one query instead of one per call. Each line a call writes is prefixed
with its function name and call number.

A failed $.Actions call now rejects as it does in a deployed app: with a
bare string holding the platform's error detail, the deployed inputs or
no-arguments message, or the local allowlist message, not an Error.

An uncaught failure in the function's code reports as it does in a
deployed app: the dev server's error is "Execution failed: " followed by
the thrown Error's stack or the thrown value, and the dev transport
throws that message instead of the status and raw response body.

Refs: APPS-2961

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot force-pushed the tiffany.trinh/apps-2961-parallel-local-execution branch from 1ed9fb5 to 69f856f Compare October 28, 2026 16:34
@tyffical
tyffical removed this pull request from stack #623 October 28, 2026 16:38
Base automatically changed from tiffany.trinh/apps-2961-child-permission-model to master October 28, 2026 17:00
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot merged commit 2eee9bf into master Oct 28, 2026
9 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot deleted the tiffany.trinh/apps-2961-parallel-local-execution branch October 28, 2026 17:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants