Skip to content

Commit 0986f4d

Browse files
committed
style(apps): tighten comments to their essential why
Trims verbose comments across execution-epoch.ts, local-execution.ts, and their tests down to the load-bearing reasoning — the concurrency/race explanations keep their substance, just without restating mechanics the surrounding code already makes clear.
1 parent a3f9495 commit 0986f4d

2 files changed

Lines changed: 71 additions & 90 deletions

File tree

packages/plugins/apps/src/vite/execution-epoch.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,20 @@
33
// Copyright 2019-Present Datadog, Inc.
44

55
/**
6-
* The generation-counter guard against the "abandoned scope's late cleanup
7-
* corrupts a newer, currently-active scope" race — shared by
8-
* `network-guard.ts`, `env-guard.ts`, and `local-execution.ts`'s own
9-
* execution bookkeeping, each of which independently reimplemented this
10-
* exact pattern before this extraction.
6+
* Generation-counter guard against an abandoned scope's late cleanup
7+
* corrupting a newer, currently-active scope. Shared by `network-guard.ts`,
8+
* `env-guard.ts`, and `local-execution.ts`'s execution bookkeeping — each
9+
* has a shared, process-wide resource (network-guard's monkey-patches,
10+
* env-guard's `process.env` swap, local-execution's action-catalog/
11+
* apps-backend registrations) that an abandoned scope's own cleanup must not
12+
* touch once a newer scope owns it.
1113
*
1214
* A `runScriptLocally` execution isn't cancelled on timeout, only abandoned
13-
* — its `fn` may still be running (or, for a genuine hang, never settle) well
14-
* after a newer execution has started. Any cleanup an abandoned scope's own
15-
* `finally` performs on a shared, process-wide resource (network-guard's
16-
* monkey-patches, env-guard's `process.env` swap) must not run once a newer
17-
* scope has taken over — an equality check against `myGeneration` at the
18-
* moment cleanup actually fires is what makes that check load-bearing rather
19-
* than a race in itself: `activeGeneration` only changes under a later
20-
* `start()`/`forceInvalidate()`, never out from under a still-current scope.
15+
* — its `fn` may still be running, or never settle, well after a newer
16+
* execution has started. Checking `myGeneration` against `activeGeneration`
17+
* at the moment cleanup fires is safe because `activeGeneration` only
18+
* changes under a later `start()`/`forceInvalidate()`, never out from under
19+
* a still-current scope.
2120
*/
2221
export interface EpochScope {
2322
/** True until a newer scope starts, or this one (or every scope) is concluded/invalidated. */

packages/plugins/apps/src/vite/local-execution.ts

Lines changed: 59 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,10 @@ function assertConnectionIdAllowed(
9191
* the wrong `$.Actions`/user identity, with no error at all. See the RFC's
9292
* Decisions and Trade-Offs for the full reasoning.
9393
*
94-
* Implementation: a simple promise-chain mutex. `queueTail` always resolves
95-
* (errors are swallowed via `.catch(() => {})` before being chained) so a
96-
* rejected execution never wedges the queue for whatever runs after it; the
97-
* real rejection is still preserved and returned to that call's own caller.
94+
* `queueTail` is a promise-chain mutex whose chained handlers always resolve
95+
* (rejections are swallowed via `.catch(() => {})`), so a rejected execution
96+
* never wedges the queue — the real rejection still propagates to that
97+
* execution's own caller via `result`.
9898
*/
9999
let queueTail: Promise<unknown> = Promise.resolve();
100100

@@ -192,20 +192,17 @@ function isModuleNotFoundError(error: unknown): boolean {
192192
/**
193193
* Returns a synchronous re-register function bound to the resolved
194194
* `setExecuteActionImplementation` setter, so a later poison/replace doesn't
195-
* need to re-run `loadModule` (an async call) — see `runScriptLocally`'s
196-
* `poisonActionCatalogRegistration` for why that matters: poisoning has to be
195+
* need to re-run `loadModule` (an async call) — poisoning has to be
197196
* synchronous relative to the timeout that triggers it, or a newer
198197
* execution's own registration could land in between and get clobbered.
199-
* Returns `undefined` if action-catalog isn't installed, same as before.
198+
* Returns `undefined` if action-catalog isn't installed.
200199
*
201200
* `isCurrent` is checked inside `register` itself, not just before this
202-
* function's own initial call — `loadModule` above is async and can be slow
203-
* enough for a newer execution to start and validly register its own
204-
* implementation while this call is still in flight. Guarding only the
205-
* initial call would still leave a later call to the returned `register`
206-
* (e.g. `runScriptLocally`'s own poison call) free to clobber that newer
207-
* registration; checking fresh, live epoch state inside `register` itself
208-
* protects every call site the same way, at the moment it actually runs.
201+
* function's own initial call, because `loadModule` above is async: a newer
202+
* execution can start and validly register its own implementation while
203+
* this call is still in flight. Checking only the initial call would leave
204+
* a later call to the returned `register` (e.g. a poison call) free to
205+
* clobber that newer registration.
209206
*/
210207
async function registerActionCatalogIfInstalled(
211208
loadModule: LoadModule,
@@ -398,37 +395,32 @@ async function runScriptLocally(
398395

399396
// A timed-out execution is abandoned, not cancelled — enqueue() lets the
400397
// next queued execution start as soon as this one loses the Promise.race
401-
// below, while this one may still be running in the background. If it
402-
// later calls executeAction, it must not be allowed to silently run under
403-
// whatever execution is now current. There are two distinct call paths,
404-
// each needing its own guard:
398+
// below, while this one may still be running in the background. A late
399+
// executeAction call from it must not run under whatever execution is
400+
// now current, via two distinct paths that each need their own guard:
405401
//
406-
// 1. A raw `$.Actions` call made through a reference the customer's code
402+
// 1. A raw `$.Actions` call through a reference the customer's code
407403
// captured early (e.g. `const { Actions } = $` before any `await`).
408-
// That reference closes over `guardedExecuteAction` specifically, so
409-
// checking `abandoned` inside it protects this path correctly no
410-
// matter what a later execution does.
404+
// That reference closes over `guardedExecuteAction`, so checking
405+
// `abandoned` inside it protects this path regardless of what a
406+
// later execution does.
411407
// 2. An @datadog/action-catalog typed-wrapper call. This does NOT go
412-
// through the closure above — action-catalog stores exactly one
413-
// `executeAction` implementation in shared, module-level state
414-
// (`setExecuteActionImplementation`), and a typed-wrapper call always
415-
// invokes whichever implementation is CURRENTLY registered. Once a
416-
// newer execution registers its own (valid) implementation over this
417-
// one, this execution's `abandoned` flag becomes unreachable — nobody
418-
// calls this closure anymore, so checking it protects nothing. The
419-
// real fix is to proactively replace (poison) the registration itself
420-
// as soon as this execution concludes, via `poisonActionCatalogRegistration`
421-
// below, so a late call finds a rejecting stub already in place
422-
// instead of finding out mid-call that it's stale.
408+
// through that closure — action-catalog stores exactly one
409+
// `executeAction` implementation in shared, module-level state, and a
410+
// typed-wrapper call always invokes whichever implementation is
411+
// CURRENTLY registered. Once a newer execution registers its own
412+
// implementation over this one, this execution's `abandoned` flag
413+
// becomes unreachable — nobody calls this closure anymore. The fix is
414+
// to proactively replace (poison) the registration itself as soon as
415+
// this execution concludes, via `poisonActionCatalogRegistration`
416+
// below, so a late call finds a rejecting stub already in place.
423417
//
424418
// Poisoning is only safe if nothing newer has started yet — a newer
425-
// execution may already be running (having registered its own valid
426-
// implementation) by the time this one's `fn()` finally settles or its
427-
// timeout fires, and clobbering ITS registration would be the same bug
428-
// in a new shape. `scope` gates that everywhere (see `concludeExecution`
429-
// below) — including the `finally` block, since settling late for an
430-
// abandoned execution is exactly the case this guards against, not just
431-
// the timeout path.
419+
// execution may already be running by the time this one's `fn()`
420+
// finally settles or its timeout fires, and clobbering its registration
421+
// would be the same bug in a new shape. `scope` gates every poison call
422+
// against that, including in the `finally` block below, where settling
423+
// late for an abandoned execution is the case that guard exists for.
432424
const scope = executionEpoch.start();
433425
let abandoned = false;
434426
let reRegisterActionCatalog: ((nextExecuteAction: ExecuteAction) => void) | undefined;
@@ -446,10 +438,9 @@ async function runScriptLocally(
446438
return executeAction(fqn, inputs, connectionId);
447439
};
448440

449-
// An external package's setter throwing on re-registration must not be
450-
// allowed to propagate — that would otherwise mask a successful result
451-
// via `finally`, or abort the `setTimeout` callback below before it can
452-
// reject.
441+
// An external package's setter throwing on re-registration must not
442+
// propagate, or it would mask a successful result via `finally`, or
443+
// abort the `setTimeout` callback below before it can reject.
453444
const poisonActionCatalogRegistration = () => {
454445
try {
455446
reRegisterActionCatalog?.(() =>
@@ -475,21 +466,17 @@ async function runScriptLocally(
475466
}
476467
};
477468

478-
// Shared by the finally block below and the timeout handler: only safe
479-
// to poison if nothing newer has started yet. For the timeout handler
480-
// this is (currently) always true, since enqueue's mutex can't start a
481-
// newer execution before this same synchronous callback runs — but the
482-
// finally block runs whenever this execution's own `fn()` settles,
483-
// which for an abandoned execution can be well after a newer one has
484-
// already started and registered, so the check is load-bearing there.
485-
// Kept in one place so both call sites stay consistent.
469+
// Shared by the finally block below and the timeout handler, so both stay
470+
// consistent: only safe to poison if nothing newer has started yet. The
471+
// finally block is where this matters — it runs whenever this
472+
// execution's own `fn()` settles, which for an abandoned execution can be
473+
// well after a newer one has already started and registered.
486474
//
487-
// Poisons BEFORE concluding, not after: `register()`'s own re-registration
488-
// guard (see `registerActionCatalogIfInstalled`) checks `scope.isCurrent()`
489-
// too, and the poison calls below route back through that same `register`
490-
// closure — concluding first would make the scope look already-inactive
491-
// to that inner check, silently skipping the poison it was meant to
492-
// perform.
475+
// Poisons BEFORE concluding: `register()`'s own re-registration guard
476+
// also checks `scope.isCurrent()`, and the poison calls below route
477+
// through that same `register` closure — concluding first would make
478+
// the scope look already-inactive to that inner check and silently skip
479+
// the poison.
493480
const concludeExecution = () => {
494481
if (scope.isCurrent()) {
495482
abandoned = true;
@@ -509,11 +496,9 @@ async function runScriptLocally(
509496
(globalThis as Record<string, unknown>).$ = $;
510497
// Publish each handle the moment its own promise resolves, not via a
511498
// destructuring assignment after Promise.all: if one registration
512-
// call rejects (or just outlasts the timeout) while the other has
513-
// already invoked its shared setter, a post-Promise.all assignment
514-
// would never run at all, losing the completed registration's
515-
// poison handle and leaving a timed-out execution's live setter
516-
// reachable by a newer execution.
499+
// rejects or outlasts the timeout while the other has already fired
500+
// its shared setter, a post-Promise.all assignment would never run,
501+
// losing the completed registration's poison handle.
517502
await Promise.all([
518503
registerActionCatalogIfInstalled(
519504
loadModule,
@@ -530,11 +515,10 @@ async function runScriptLocally(
530515
),
531516
]);
532517
if (abandoned) {
533-
// This execution was already abandoned while the registration
534-
// calls above were still in flight — registering is a side
535-
// effect of the calls themselves, not something we can undo, so
536-
// immediately poison what they just registered rather than
537-
// leaving it live for a newer execution to inherit.
518+
// Abandoned while the registration calls above were still in
519+
// flight — registering is a side effect of those calls, not
520+
// something we can undo, so poison what they just registered
521+
// instead of leaving it live for a newer execution to inherit.
538522
poisonActionCatalogRegistration();
539523
poisonBackendRuntimeRegistration();
540524
}
@@ -551,22 +535,20 @@ async function runScriptLocally(
551535
}
552536
if (abandoned) {
553537
// Already known-abandoned before the customer function was
554-
// even reached (the registration Promise.all above was slow
555-
// enough to outlast the timeout, or this loadModule call
556-
// was) — no point invoking it now; concludeExecution()
557-
// below is a no-op re-poison at this point, which is fine.
538+
// even reached (the registration Promise.all above, or this
539+
// loadModule call, outlasted the timeout) — no point
540+
// invoking it now.
558541
throw new Error(
559542
`Execution of "${func.name}" was abandoned after timing out before it could start.`,
560543
);
561544
}
562545
const result = await fn(...args);
563546
return { data: assertJsonSerializable(result, func) };
564547
} finally {
565-
// Covers the loadModule/export-validation steps above too, not
566-
// just the fn() call — if either of those throws (including the
567-
// native-module load failure case below), this execution has
568-
// still concluded and must poison the registrations the same
569-
// way a successful or thrown fn() call would.
548+
// Also covers the loadModule/export-validation steps above, not
549+
// just the fn() call — however this execution ends, it has
550+
// concluded and must poison the registrations the same way a
551+
// successful or thrown fn() call would.
570552
concludeExecution();
571553
}
572554
};

0 commit comments

Comments
 (0)