Skip to content

Add inline script environment persistence (PEP 723 PR 7/16) - #1697

Open
Stella Huang (StellaHuang95) wants to merge 1 commit into
microsoft:mainfrom
StellaHuang95:pep723-pr7-persistence
Open

Add inline script environment persistence (PEP 723 PR 7/16)#1697
Stella Huang (StellaHuang95) wants to merge 1 commit into
microsoft:mainfrom
StellaHuang95:pep723-pr7-persistence

Conversation

@StellaHuang95

Copy link
Copy Markdown
Contributor

Part of #1602 (PEP 723 inline script env support). Design doc: #1601.

This replaces the earlier closed draft #1653 with the finalized implementation rebased on main.

Roadmap context

This is PR 7 of 16 in the PEP 723 inline-script roadmap. It adds durable per-script environment associations to the internal manager.

Phase 2: Manager PR Status
PR 4: InlineScriptEnvManager skeleton merged (#1610)
PR 5a: generic env-creation utilities merged (#1651)
PR 5b: inline-script cache + interpreter utilities merged (#1655)
PR 5c: create() happy path merged (#1656)
PR 6: create() uv-install fallback open (#1696)
PR 7: persistence (get / set + Memento) this PR
PR 8: activation-time discovery follow-up
PR 9: route PEP 723 scripts to the inline manager follow-up

Why this PR

PR 5 can build or reuse an inline-script environment, but the manager does not remember that the resulting environment belongs to a particular script. After an extension-host restart, the in-memory association is gone.

This PR implements the persistence portion of Q4 in the design:

  • maintain an independent environment association for each script;
  • persist script-to-environment executable paths in workspace Memento;
  • lazily and safely rehydrate those associations;
  • re-check current requires-python metadata before returning an environment;
  • report changes so the central environment API can update its last-known state.

What this PR does

Implements per-script set()

  • Accepts one or more local file: URIs and rejects invalid or mixed scopes atomically.
  • Validates that selected environments are owned inline-script cache entries.
  • Persists a normalized script path → environment executable path mapping under a dedicated Memento key.
  • Supports assigning and unassigning individual scripts or batches.
  • Updates in-memory state and emits onDidChangeEnvironment only for effective changes.
  • Leaves the existing create() behavior separate: creation alone does not implicitly establish a persisted association.

Implements per-script get()

  • Reads current PEP 723 metadata before returning an association.
  • Keeps unreadable or temporarily invalid script metadata from destructively clearing state.
  • Returns an in-memory association when valid.
  • Lazily reconstructs persisted environments after restart instead of resolving every script during activation.
  • Re-checks requires-python against the reconstructed Python version before returning it.

Safely rehydrates persisted associations

  • Requires an absolute executable path.
  • Preserves associations while their cache entry is locked or being created.
  • Verifies that the executable exists and is a regular file.
  • Resolves it into a PythonEnvironment and confirms that it belongs to the expected extension-owned cache entry.
  • Removes only definitively stale associations; transient filesystem or resolver failures remain retryable.
  • Emits a change event when a slow rehydration eventually succeeds, including when the public API's initial one-second wait has already elapsed.

Validates warm in-memory associations

  • Periodically revalidates cached associations without performing full resolution on every lookup.
  • Detects executables deleted while VS Code remains open.
  • Detects an environment rebuilt at the same cache path with a different Python version.
  • Preserves busy/locked entries instead of misclassifying them as stale.
  • Coalesces simultaneous validations for the same script.
  • Retains the existing environment object when resolution produces only a new generated ID for the same Python, avoiding false changes and duplicate ID-keyed resources.

Protects persistence and selection from races

  • Serializes Memento read-modify-write operations so concurrent script selections cannot lose one another.
  • Uses per-script association revisions so an older rehydration cannot overwrite a newer selection or unset.
  • Removes stale persisted values conditionally, only if the inspected path is still current.
  • Keeps failed persistence writes from changing in-memory state or emitting success-shaped events.
  • Does not globally serialize unrelated environment operations.

Updates central active-environment tracking

  • Keys inline-script selections by normalized script path rather than containing project, so two scripts in one workspace can retain different environments.
  • Uses per-scope revisions and manager identity checks so slow refreshes cannot overwrite newer selections.
  • Ensures failed selections and failed refreshes do not discard a valid in-flight refresh.
  • Groups same-manager batch unsets and calls the manager once with the complete URI array.
  • Updates central cache entries and events only after the manager operation succeeds.
  • Attributes inline-script change events to the script URI rather than the containing project URI.

Example

Given two scripts in the same workspace:

tools/report.py → Python 3.12 inline environment
tools/import.py → Python 3.13 inline environment

PR 7 stores and retrieves those associations independently. Selecting the environment for import.py does not overwrite the last-known environment for report.py.

After restart:

get(report.py)
→ read persisted executable
→ verify cache ownership and current metadata
→ resolve environment
→ cache and return it

If report.py later changes from requires-python = ">=3.11" to ">=3.13", its persisted Python 3.12 environment is no longer returned as compatible.

Persistence and failure semantics

Condition Behavior
Executable exists and cache ownership is valid Rehydrate and return
Cache entry is locked/in progress Preserve association; retry later
Resolver fails transiently Preserve association; retry later
Executable is definitively missing and unlocked Remove stale association and notify
A newer selection wins during rehydration Discard the stale result
Memento write fails Keep previous in-memory/persisted selection and propagate the error

Tests

Coverage includes:

  • assign, retrieve, unset, and batch persistence;
  • restart-time lazy rehydration and delayed success events;
  • metadata compatibility changes;
  • missing, malformed, unowned, busy, and transient cache states;
  • warm deletion and same-path rebuild detection;
  • concurrent persistence, rehydration, validation, selection, and unset races;
  • failed Memento writes;
  • strict URI-scope validation;
  • independent same-project script selections;
  • stale and failed central refresh ordering;
  • atomic same-manager batch unsets.

npm run compile-tests, npm run lint, the full unit suite, and the focused persistence/central-manager suites are clean.

Performance

  • Rehydration is lazy rather than activation-blocking.
  • Warm associations are cached and validation is throttled.
  • Same-script rehydration and validation work is coalesced.
  • Queues cover only shared persistence and mutation ordering; unrelated script reads and environment-manager operations remain independent.

User impact

No default-path user impact yet. This completes an internal Phase 2 manager capability. Automatic routing and user-facing entry points arrive in later roadmap PRs.

Once routing is wired, script-specific selections will survive extension-host restarts and remain independent even for multiple scripts in the same workspace.

Merge order

The core persistence behavior depends on the merged manager skeleton (#1610). This branch is rebased on current main; PR 8 and PR 9 build on this capability.

@StellaHuang95

Copy link
Copy Markdown
Contributor Author

🔒 Automated review in progress — Stella Huang (@StellaHuang95) is auto-reviewing this PR.

Persist and safely rehydrate per-script environment associations. Harden selection races, cache-lock handling, corrupt-state repair, scope validation, and central batch selection consistency without globally serializing environment operations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1a9f6ba1-9bd3-4664-bc25-a0d34d7a2e91
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature-request Request for new features or functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant