Skip to content

Cache registered manager version lookups - #2005

Merged
Eduardo Villalpando Mello (edvilme) merged 4 commits into
mainfrom
copilot/implement-version-caching
Nov 11, 2026
Merged

Eduardo Villalpando Mello (edvilme) merged 4 commits into
mainfrom
copilot/implement-version-caching

Conversation

Copilot AI commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Repeated getVersion() calls re-ran underlying tool probes. Registered managers now cache version lookups by the appropriate scope.

  • Environment managers

    • Cache one shared version promise per manager.
  • Package managers

    • Cache version promises by environment.envId.id.
    • Preserve distinct versions across environments.
  • Cache semantics

    • Deduplicate concurrent requests.
    • Cache unavailable (undefined) results.

Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com>
Copilot AI changed the title [WIP] Add version caching for PackageManager and EnvironmentManagers Cache registered manager version lookups Nov 10, 2026
@edvilme Eduardo Villalpando Mello (edvilme) added the bug Issue identified by VS Code Team member as probable bug label Nov 10, 2026
@edvilme
Eduardo Villalpando Mello (edvilme) marked this pull request as ready for review November 10, 2026 00:18
@edvilme

Copy link
Copy Markdown
Contributor

Fixes #2001

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.

🟡 Changes recommended

The version cache omits managerId from its key, allowing environments with identical IDs from different managers to receive the wrong cached version and produce incorrect telemetry.

1 open issue
What changed in this PR

This PR addresses #2004 by caching version lookups in registered manager wrappers, reducing repeated underlying tool probes.

Changes:

  • Cache one version promise per environment manager and one per environment ID for package managers.
  • Reuse pending promises and cache unavailable (undefined) results.
  • Add tests for repeated lookups and distinct package-manager versions across environments.
File Description
src/​test/​managers/​common/​registeredManagers.versionLookup.unit.test.ts Adds version-cache tests for both manager wrappers.
src/​managers/​common/​registeredManagers.ts Adds shared and environment-keyed version-promise caches.

Review effort: Balanced

@rchiodo

Rich Chiodo (rchiodo) commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

🔒 Automated review in progress — Rich Chiodo (@rchiodo) is auto-reviewing this PR.

@rchiodo

Rich Chiodo (rchiodo) commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Reviewed commit: 227d5a947866508c35097554a4a39392192d1c91

Result: ✅ verified

Verification details

Verification: Automated verification is best effort. The PR author remains responsible for testing.

Summary: Offline dependency restoration and TypeScript test compilation succeeded in the container. All 11 targeted repository tests passed, including both tests added by the PR. Additional probes passed for concurrent deduplication, cached undefined results, environment-ID scoping, and independent manager caches. No test failures were observed. **Reviewer dependency follow-up (nonblocking):** Suggest a separate PR adding `@renovatebot/pep440@^4.2.4`, `mocha@^10.8.2`, `sinon@^19.0.2`, `ts-mockito@^2.6.1`, `typescript@^5.1.3` to the official reviewer dependency declarations, using `package.json` as the dependency/version evidence. Do not automatically create that PR. This is reviewer environment maintenance, not a required change to the reviewed PR.

Test runs: 4 passed, 1 not run

  • ⚠️ Not run | Dependency and test-discovery preflight | printf 'AUTOMATION_SANDBOX_PROFILE=%s\n' "$AUTOMATION_SANDBOX_PROFILE"; node --version; npm --version; git status --short; node -e "const fs=require('fs'); const p=require('./package.json'); console.log(JSON.stringify({scripts:p.scripts,devDependencies:p.devDependencies,dependencies:p.dependencies},null,2)); console.log('node_modules present:',fs.existsSync('node_modules')); for(const m of ['typescript','mocha','sinon','ts-mockito','@renovatebot/pep440','source-map-support/register']) { try { console.log(m+': '+require.resolve(m)); } catch(e) { console.log(m+': MISSING'); } }"; cat build/.mocha.unittests.json; find src/test -name 'registeredManagers'
  • ✅ Passed | Registered-manager version lookup and project-scoped cache tests | node ./node_modules/mocha/bin/mocha.js --no-config --ui tdd --require source-map-support/register --require ./out/test/unittests.js --timeout 10000 out/test/managers/common/registeredManagers.versionLookup.unit.test.js out/test/managers/common/projectScopedPackageManagerCache.unit.test.js
  • ✅ Passed | Offline dependency restoration | npm ci --offline --frozen-lockfile
  • ✅ Passed | Compile repository tests | npm run compile-tests
  • ✅ Passed | Focused version-cache semantics probe | node <<'NODE'
    const assert = require('assert').strict;
    require('./out/test/unittests.js');
    const { explain } = require('@renovatebot/pep440');
    const { InternalEnvironmentManager, InternalPackageManager } = require('./out/managers/common/registeredManagers.js');
    function deferred() {
    let resolve;
    const promise = new Promise((r) => { resolve = r; });
    return { promise, resolve };
    }
    (async () => {
    const version = explain('1.2.3');
    const otherVersion = explain('2.0.0');
    assert.ok(version && otherVersion);
    for (const result of [version, undefined]) {
    let calls = 0;
    const pending = deferred();
    const manager = new InternalEnvironmentManager('test:environment', {
    getVersion() { calls++; return pending.promise; },
    });
    const first = manager.getVersion();
    assert.strictEqual(first, pending.promise);
    assert.strictEqual(manager.getVersion(), first);
    assert.strictEqual(calls, 1);
    pending.resolve(result);
    assert.strictEqual(await first, result);
    assert.strictEqual(manager.getVersion(), first);
    assert.strictEqual(await manager.getVersion(), result);
    assert.strictEqual(calls, 1);
    console.log('PASS environment: concurrent deduplication and settled cache, result=' + (result ? result.public : 'undefined'));
    }
    const environment = { envId: { id: 'env', managerId: 'mgr' } };
    const equivalentEnvironment = { envId: { id: 'env', managerId: 'mgr' } };
    const otherEnvironment = { envId: { id: 'other-env', managerId: 'mgr' } };
    for (const results of [[version, otherVersion], [undefined, undefined]]) {
    const pending = [deferred(), deferred()];
    const calls = [];
    const manager = new InternalPackageManager('test:package', {
    getVersion(env) { calls.push(env); return pending[env.envId.id === 'env' ? 0 : 1].promise; },
    });
    const first = manager.getVersion(environment);
    assert.strictEqual(manager.getVersion(environment), first);
    assert.strictEqual(manager.getVersion(equivalentEnvironment), first);
    const second = manager.getVersion(otherEnvironment);
    assert.strictEqual(manager.getVersion(otherEnvironment), second);
    assert.notStrictEqual(first, second);
    assert.deepStrictEqual(calls, [environment, otherEnvironment]);
    pending.forEach((value, i) => value.resolve(results[i]));
    assert.deepStrictEqual(await Promise.all([first, second]), results);
    assert.strictEqual(manager.getVersion(equivalentEnvironment), first);
    assert.strictEqual(manager.getVersion(otherEnvironment), second);
    assert.deepStrictEqual(await Promise.all([manager.getVersion(environment), manager.getVersion(otherEnvironment)]), results);
    assert.strictEqual(calls.length, 2);
    console.log('PASS package: pending/settled deduplication by ID, equivalent objects, distinct environments, results=' + (results[0] ? '1.2.3/2.0.0' : 'undefined/undefined'));
    }
    const noEnvironmentVersion = new InternalEnvironmentManager('test:missing-environment', {});
    const missingEnvironmentPromise = noEnvironmentVersion.getVersion();
    assert.strictEqual(await missingEnvironmentPromise, undefined);
    assert.strictEqual(noEnvironmentVersion.getVersion(), missingEnvironmentPromise);
    const noPackageVersion = new InternalPackageManager('test:missing-package', {});
    const missingPackagePromise = noPackageVersion.getVersion(environment);
    assert.strictEqual(await missingPackagePromise, undefined);
    assert.strictEqual(noPackageVersion.getVersion(equivalentEnvironment), missingPackagePromise);
    assert.notStrictEqual(noPackageVersion.getVersion(otherEnvironment), missingPackagePromise);
    assert.strictEqual(await noPackageVersion.getVersion(otherEnvironment), undefined);
    console.log('PASS both managers: absent getVersion caches undefined at the expected scope');
    for (const Manager of [InternalEnvironmentManager, InternalPackageManager]) {
    let calls = 0;
    const rawManager = { getVersion() { calls++; return Promise.resolve(version); } };
    const first = new Manager('test:first', rawManager);
    const second = new Manager('test:second', rawManager);
    const firstPromise = first.getVersion(environment);
    const secondPromise = second.getVersion(environment);
    assert.notStrictEqual(firstPromise, secondPromise);
    assert.deepStrictEqual(await Promise.all([firstPromise, secondPromise]), [version, version]);
    assert.strictEqual(calls, 2);
    }
    console.log('PASS both managers: wrapper instances have independent version caches');
    console.log('All 6 focused cache scenario groups passed');
    })().catch((error) => { console.error(error); process.exitCode = 1; });
    NODE
⚠️ Dependency and test-discovery preflight diagnostic output
AUTOMATION_SANDBOX_PROFILE=typescript
Node v22.21.1; npm 10.9.4
Git metadata was unavailable in the copied workspace.
node_modules present: false
typescript: MISSING
mocha: MISSING
sinon: MISSING
ts-mockito: MISSING
@renovatebot/pep440: MISSING
source-map-support/register: MISSING
package.json declares the missing direct dependencies. Tests were initially unavailable; subsequent npm ci --offline --frozen-lockfile restored 616 packages successfully, and compilation and targeted tests then passed.
[container exit=0]

@rchiodo Rich Chiodo (rchiodo) 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.

Approved via Review Center.

@rchiodo Rich Chiodo (rchiodo) added the review-auto:approved Automated review: no blocking findings (approval posted). label Nov 10, 2026

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.

Approved via Review Center.

@bschnurr Bill Schnurr (bschnurr) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approved via Review Center.

@edvilme
Eduardo Villalpando Mello (edvilme) merged commit 8e087da into main Nov 11, 2026
18 checks passed
@edvilme
Eduardo Villalpando Mello (edvilme) deleted the copilot/implement-version-caching branch November 11, 2026 19:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Issue identified by VS Code Team member as probable bug review-auto:approved Automated review: no blocking findings (approval posted).

Projects

None yet

6 participants