Skip to content

fix: isolate environment consumers from single-manager failures - #21

Open
StellaHuang95 wants to merge 1 commit into
mainfrom
isolate-environment-consumers
Open

fix: isolate environment consumers from single-manager failures#21
StellaHuang95 wants to merge 1 commit into
mainfrom
isolate-environment-consumers

Conversation

@StellaHuang95

Copy link
Copy Markdown
Owner

Problem

Two user-visible failures where a single environment manager could break unrelated functionality:

  1. Python APIgetEnvironments('all' | 'global') and refreshEnvironments(undefined) used Promise.all, so one manager's rejection discarded every other manager's completed results.
  2. Environment pickerpickEnvironment awaited every manager's getEnvironments('all') sequentially before showing the QuickPick, so opening latency was additive and any manager rejection prevented the picker from opening at all.

Root cause

Both paths fanned out to managers without isolating per-manager failures: Promise.all rejects as soon as any input rejects, and the picker's sequential await loop both serialized discovery and propagated the first rejection before the UI was ever shown.

Fix

  • API: a private, type-safe collectFromManagers() runs managers concurrently with Promise.allSettled, returns the successful managers' results in original manager order, logs each failure with its manager id, and throws a minimal local AggregateEnvironmentError only when every manager fails. An empty manager list resolves with []/void, and single-manager scope paths are unchanged.
  • Picker: the QuickPick opens immediately with Browse/Create (and any recommended item), then loads all managers concurrently after it is shown via a small optional onDidShow controller seam on showQuickPickWithButtons. The seam reuses the existing accept/back/cancel/button/hide/token wiring; controller updates no-op once the picker is accepted, dismissed, or disposed. A manager that rejects is logged and skipped without hiding the others, and sections are built in fixed manager order.

Tests

  • API failure isolation (pythonApi.failureIsolation.unit.test.ts): partial success with manager order, synchronous-throw isolation, total failure throwing AggregateEnvironmentError with all reasons in order, empty-list success, per-manager logging, global scope forwarding, and the refreshEnvironments equivalents.
  • Picker (pickEnvironment.unit.test.ts): opens before slow managers resolve, fixed manager order regardless of completion order, one manager failing does not hide the others (and is logged), every manager failing still leaves a usable Browse/Create picker, a synchronous recommended item, an empty manager list, and late results after close are ignored.
  • Seam (showQuickPickWithButtons.unit.test.ts): existing static callers (accept/hide/back/custom button/token) are unchanged, and the onDidShow controller populates items / toggles busy and no-ops after settle.

@StellaHuang95 StellaHuang95 added the bug Something isn't working label Aug 23, 2026
@StellaHuang95

StellaHuang95 commented Aug 23, 2026

Copy link
Copy Markdown
Owner Author

🔒 Automated review in progress — @StellaHuang95 is auto-reviewing this PR.

Comment thread src/common/pickers/environments.ts Outdated
@StellaHuang95 StellaHuang95 added the review-auto:changes-requested Automated review: posted blocking findings to address. label Aug 23, 2026
@StellaHuang95
StellaHuang95 force-pushed the isolate-environment-consumers branch from c497bf8 to ee1421f Compare August 23, 2026 04:56
Comment thread src/test/common/pickEnvironment.unit.test.ts Outdated
Comment thread src/test/common/pickEnvironment.unit.test.ts
Comment thread src/test/features/pythonApi.failureIsolation.unit.test.ts Outdated
Comment thread src/test/features/pythonApi.failureIsolation.unit.test.ts Outdated
Comment thread src/common/pickers/environments.ts
@StellaHuang95
StellaHuang95 force-pushed the isolate-environment-consumers branch from ee1421f to 17c2e4a Compare August 23, 2026 05:44
Comment thread src/test/common/pickEnvironment.unit.test.ts
Comment thread src/test/common/showQuickPickWithButtons.unit.test.ts
Comment thread src/common/pickers/environments.ts
@StellaHuang95
StellaHuang95 force-pushed the isolate-environment-consumers branch from 17c2e4a to df1778d Compare August 23, 2026 06:21
Comment thread src/test/common/showQuickPickWithButtons.unit.test.ts Outdated
@StellaHuang95
StellaHuang95 force-pushed the isolate-environment-consumers branch from df1778d to 990305f Compare August 23, 2026 07:11
Comment thread src/features/pythonApi.ts Outdated
@StellaHuang95
StellaHuang95 force-pushed the isolate-environment-consumers branch from 990305f to 0685de6 Compare August 23, 2026 07:56
Comment thread src/features/pythonApi.ts Outdated
@StellaHuang95
StellaHuang95 force-pushed the isolate-environment-consumers branch from 0685de6 to 8662b44 Compare August 23, 2026 08:33
Comment thread src/features/pythonApi.ts
@StellaHuang95
StellaHuang95 force-pushed the isolate-environment-consumers branch from 8662b44 to 672b645 Compare August 23, 2026 09:19
Comment thread src/features/pythonApi.ts Outdated
Comment thread src/features/pythonApi.ts
Comment thread src/features/pythonApi.ts
@StellaHuang95
StellaHuang95 force-pushed the isolate-environment-consumers branch from 672b645 to 3c368f7 Compare August 23, 2026 18:15
The Python API's getEnvironments('all'|'global') and refreshEnvironments(undefined)
used Promise.all, so a single manager's rejection hid every other manager's completed
results. The environment picker also awaited every manager sequentially before showing,
so latency was additive and any rejection stopped the picker from opening at all.

- Add a private, type-safe collectFromManagers() helper that runs managers concurrently
  via Promise.allSettled, returns successful results in original manager order, and throws
  AggregateEnvironmentError only when every manager fails (an empty manager list resolves
  with []). Each failure is logged inside its own async boundary as the manager settles, so a
  synchronous throw or a slow/never-settling manager cannot hide the others or defer the
  reporting of a failure. Because getEnvironments and refresh are uncancellable and can mutate
  manager state, each operation is awaited to completion rather than timed out, so a
  slow-but-valid manager is never falsely reported as failed and its results or refresh are
  never dropped or detached. The public API contract documents the all-failed aggregate shape.
- Open the picker immediately with Browse/Create and load each manager behind an async
  boundary after it is shown, through a small optional onDidShow controller seam on
  showQuickPickWithButtons that reuses the existing accept/back/cancel/button wiring. Each
  manager's results publish independently in manager order, so a synchronous throw, a
  rejection, or a permanently pending manager can't block the others; the seam preserves the
  active and selected item by reference across publications, and late updates no-op once the
  picker is closed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@StellaHuang95
StellaHuang95 force-pushed the isolate-environment-consumers branch from 3c368f7 to 423fd28 Compare August 23, 2026 18:19
Comment thread src/features/pythonApi.ts
Comment thread src/features/pythonApi.ts
@StellaHuang95 StellaHuang95 added review-auto:approved Automated review: no blocking findings (approval posted). and removed review-auto:changes-requested Automated review: posted blocking findings to address. labels Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working review-auto:approved Automated review: no blocking findings (approval posted).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant