Skip to content

Commit ea5de2c

Browse files
authored
Fix warm binary request cache hits (#44)
* Fix warm binary request cache hits * Cover cache identity edge cases * Verify global pnpm package ownership * Reject stale unowned pnpm cache targets
1 parent 95f7f1a commit ea5de2c

5 files changed

Lines changed: 293 additions & 9 deletions

File tree

abxpkg/binprovider.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,15 @@ def record_matches(record: object) -> bool:
13261326
list,
13271327
):
13281328
return False
1329+
requested_path = Path(str(bin_name)).expanduser()
1330+
cached_name = typed_record.get("bin_name")
1331+
cached_name_matches = cached_name == str(bin_name) or (
1332+
requested_path.is_absolute()
1333+
and isinstance(cached_name, str)
1334+
and Path(cached_name).name == requested_path.name
1335+
and os.path.abspath(os.path.expanduser(cached_abspath))
1336+
== os.path.abspath(os.path.expanduser(str(requested_path)))
1337+
)
13291338
fingerprint_paths: list[Path] = []
13301339
for raw_fingerprint in raw_fingerprints:
13311340
if not isinstance(raw_fingerprint, dict):
@@ -1337,7 +1346,7 @@ def record_matches(record: object) -> bool:
13371346
fingerprint_paths.append(Path(fingerprint_path))
13381347
return (
13391348
typed_record.get("provider_name") == self.name
1340-
and typed_record.get("bin_name") == str(bin_name)
1349+
and cached_name_matches
13411350
and Path(cached_abspath).expanduser().resolve(strict=False)
13421351
== resolved_abspath
13431352
and typed_record.get("resolved_provider_name") == exec_provider.name

abxpkg/binprovider_pnpm.py

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -830,16 +830,63 @@ def cached_binary_state_mismatch(
830830
bin_name: BinName,
831831
cached_record: Mapping[str, object],
832832
) -> bool:
833-
if self.install_root is None:
834-
return False
835-
modules_dir = self.install_root / "node_modules"
836-
for package in self._package_names_from_install_args(
833+
package_names = self._package_names_from_install_args(
837834
self.get_install_args(bin_name, quiet=True, no_cache=True),
838-
):
839-
if not (modules_dir / package / "package.json").exists():
840-
return True
835+
)
836+
if self.install_root is not None:
837+
modules_dir = self.install_root / "node_modules"
838+
for package in package_names:
839+
if not (modules_dir / package / "package.json").exists():
840+
return True
841+
installed_version = self._installed_package_version(str(bin_name))
842+
else:
843+
raw_abspath = cached_record.get("abspath")
844+
installed_version = (
845+
self._installed_abspath_package_version(
846+
Path(raw_abspath),
847+
package_names=set(package_names),
848+
)
849+
if isinstance(raw_abspath, str)
850+
else None
851+
)
852+
raw_cached_version = cached_record.get("loaded_version")
853+
cached_version = (
854+
SemVer.parse(raw_cached_version)
855+
if isinstance(raw_cached_version, (str, bytes))
856+
else None
857+
)
858+
if installed_version is None:
859+
return True
860+
if cached_version is not None and installed_version != cached_version:
861+
return True
841862
return False
842863

864+
@classmethod
865+
def _installed_abspath_package_version(
866+
cls,
867+
abspath: Path,
868+
*,
869+
package_names: set[str],
870+
) -> SemVer | None:
871+
"""Read the package version behind a global pnpm executable without pnpm."""
872+
package_target = cls.host_projection_target(abspath) or abspath.resolve(
873+
strict=False,
874+
)
875+
for parent in package_target.parents:
876+
package_json = parent / "package.json"
877+
try:
878+
package = json.loads(package_json.read_text())
879+
except (OSError, json.JSONDecodeError):
880+
continue
881+
if (
882+
not isinstance(package, dict)
883+
or package.get("name") not in package_names
884+
):
885+
continue
886+
version = package.get("version")
887+
return SemVer.parse(version) if isinstance(version, str) else None
888+
return None
889+
843890
def _node_modules_dir(self) -> Path | None:
844891
if self.install_root:
845892
return self.install_root / "node_modules"
@@ -1351,6 +1398,22 @@ def default_abspath_handler(
13511398
no_cache=no_cache,
13521399
)
13531400

1401+
def _get_version_at_abspath(
1402+
self,
1403+
bin_name: BinName,
1404+
installed_abspath: HostBinPath,
1405+
*,
1406+
quiet: bool,
1407+
) -> SemVer | None:
1408+
installed_package_version = self._installed_package_version(str(bin_name))
1409+
if installed_package_version is not None:
1410+
return installed_package_version
1411+
return super()._get_version_at_abspath(
1412+
bin_name,
1413+
installed_abspath,
1414+
quiet=quiet,
1415+
)
1416+
13541417
def default_version_handler(
13551418
self,
13561419
bin_name: BinName,

abxpkg/config.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,10 @@ def _cached_records(
569569
*,
570570
require_executable: bool = True,
571571
):
572+
requested_path = os.path.expanduser(os.fspath(binary_name))
573+
requested_abspath = (
574+
os.path.abspath(requested_path) if os.path.isabs(requested_path) else None
575+
)
572576
for provider_name in provider_names:
573577
default_root = os.path.join(lib_dir, provider_name)
574578
provider_roots = dict.fromkeys(
@@ -629,10 +633,22 @@ def _cached_records(
629633
if isinstance(raw_fingerprints, list) and raw_fingerprints
630634
else None
631635
)
636+
record_name = (
637+
record.get("bin_name") if isinstance(record, dict) else None
638+
)
639+
record_name_matches = record_name == binary_name or (
640+
requested_abspath is not None
641+
and isinstance(record_name, str)
642+
and os.path.basename(record_name)
643+
== os.path.basename(requested_path)
644+
and isinstance(record_abspath, str)
645+
and os.path.abspath(os.path.expanduser(record_abspath))
646+
== requested_abspath
647+
)
632648
if (
633649
isinstance(record, dict)
634650
and record.get("provider_name") == provider_name
635-
and record.get("bin_name") == binary_name
651+
and record_name_matches
636652
and isinstance(record_abspath, str)
637653
and os.path.isabs(record_abspath)
638654
and (not require_executable or os.access(record_abspath, os.X_OK))

tests/test_binary_service.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,107 @@ def request_projections(record: dict[str, Any]) -> dict[str, Any]:
449449
)
450450

451451

452+
def test_binary_service_reuses_absolute_path_request_projection(
453+
tmp_path: Path,
454+
) -> None:
455+
from abxpkg.binary_service import BinaryRequestEvent, BinaryService
456+
457+
lib_dir = tmp_path / "lib"
458+
provider = EnvProvider(install_root=lib_dir / "env")
459+
loaded = provider.load("python3")
460+
assert loaded is not None
461+
projected_path = provider.project_binary(loaded, "python3")
462+
assert projected_path is not None
463+
464+
run_id = 0
465+
466+
async def run() -> None:
467+
nonlocal run_id
468+
run_id += 1
469+
bus = abxbus.EventBus(name=f"test_absolute_request_projection_{run_id}")
470+
BinaryService(bus, auto_install=False, lib_dir=lib_dir)
471+
await bus.emit(
472+
BinaryRequestEvent(
473+
name=str(projected_path),
474+
binproviders="env",
475+
),
476+
).now()
477+
await bus.wait_until_idle()
478+
await bus.destroy(clear=False)
479+
480+
asyncio.run(run())
481+
482+
binary_load_calls = 0
483+
484+
def count_binary_loads(frame: Any, event: str, arg: Any) -> None:
485+
del arg
486+
nonlocal binary_load_calls
487+
if event == "call" and frame.f_code is BinaryService._load.__code__:
488+
binary_load_calls += 1
489+
490+
sys.setprofile(count_binary_loads)
491+
threading.setprofile(count_binary_loads)
492+
try:
493+
asyncio.run(run())
494+
finally:
495+
sys.setprofile(None)
496+
threading.setprofile(None)
497+
498+
assert binary_load_calls == 0
499+
500+
501+
def test_binary_service_reuses_normalized_absolute_path_request_projection(
502+
tmp_path: Path,
503+
) -> None:
504+
from abxpkg.binary_service import BinaryRequestEvent, BinaryService
505+
506+
lib_dir = tmp_path / "lib"
507+
provider = EnvProvider(install_root=lib_dir / "env")
508+
loaded = provider.load("python3")
509+
assert loaded is not None
510+
projected_path = provider.project_binary(loaded, "python3")
511+
assert projected_path is not None
512+
requested_path = (
513+
projected_path.parent / ".." / projected_path.parent.name / projected_path.name
514+
)
515+
516+
run_id = 0
517+
518+
async def run() -> None:
519+
nonlocal run_id
520+
run_id += 1
521+
bus = abxbus.EventBus(name=f"test_normalized_request_projection_{run_id}")
522+
BinaryService(bus, auto_install=False, lib_dir=lib_dir)
523+
await bus.emit(
524+
BinaryRequestEvent(
525+
name=str(requested_path),
526+
binproviders="env",
527+
),
528+
).now()
529+
await bus.wait_until_idle()
530+
await bus.destroy(clear=False)
531+
532+
asyncio.run(run())
533+
534+
binary_load_calls = 0
535+
536+
def count_binary_loads(frame: Any, event: str, arg: Any) -> None:
537+
del arg
538+
nonlocal binary_load_calls
539+
if event == "call" and frame.f_code is BinaryService._load.__code__:
540+
binary_load_calls += 1
541+
542+
sys.setprofile(count_binary_loads)
543+
threading.setprofile(count_binary_loads)
544+
try:
545+
asyncio.run(run())
546+
finally:
547+
sys.setprofile(None)
548+
threading.setprofile(None)
549+
550+
assert binary_load_calls == 0
551+
552+
452553
def test_binary_event_env_does_not_prepend_shared_host_projections(
453554
tmp_path: Path,
454555
) -> None:

tests/test_pnpmprovider.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,101 @@ def test_provider_direct_min_version_revalidates_old_install_and_upgrades(
853853
min_release_age=3,
854854
).update("zx", min_version=SemVer("999.0.0"))
855855

856+
def test_literal_version_override_cannot_replace_installed_package_version(
857+
self,
858+
test_machine,
859+
):
860+
test_machine.require_tool("node")
861+
with tempfile.TemporaryDirectory() as tmpdir:
862+
install_root = Path(tmpdir) / "pnpm"
863+
provider = PnpmProvider(
864+
install_root=install_root,
865+
postinstall_scripts=True,
866+
min_release_age=0,
867+
).get_provider_with_overrides(
868+
overrides={
869+
"zx": {
870+
"install_args": ["zx@7.2.3"],
871+
"version": "999.0.0",
872+
},
873+
},
874+
)
875+
876+
installed = provider.install("zx", min_version=SemVer("1.0.0"))
877+
878+
assert installed is not None
879+
package_json = install_root / "node_modules" / "zx" / "package.json"
880+
assert package_json.exists()
881+
import json as _json
882+
883+
observed_version = _json.loads(package_json.read_text())["version"]
884+
assert installed.loaded_version == SemVer("7.2.3")
885+
assert observed_version == "7.2.3"
886+
assert str(installed.loaded_version) != "999.0.0"
887+
888+
def test_global_cache_compares_installed_package_version(self, tmp_path):
889+
global_root = tmp_path / "global"
890+
package_dir = global_root / ".pnpm" / "zx@7.2.3" / "node_modules" / "zx"
891+
executable = package_dir / "build" / "cli.js"
892+
executable.parent.mkdir(parents=True)
893+
executable.write_text("#!/usr/bin/env node\n")
894+
executable.chmod(0o755)
895+
(executable.parent / "package.json").write_text(
896+
'{"name": "not-zx", "version": "999.0.0"}',
897+
)
898+
(package_dir / "package.json").write_text(
899+
'{"name": "zx", "version": "7.2.3"}',
900+
)
901+
launcher = global_root / "bin" / "zx"
902+
launcher.parent.mkdir()
903+
launcher.write_text(
904+
'#!/bin/sh\nexec node "$basedir/../.pnpm/zx@7.2.3/node_modules/zx/build/cli.js" "$@"\n',
905+
)
906+
launcher.chmod(0o755)
907+
provider = PnpmProvider(install_root=None)
908+
909+
assert provider.host_projection_target(launcher) == executable
910+
911+
assert provider.cached_binary_state_mismatch(
912+
"zx",
913+
{"abspath": str(launcher), "loaded_version": "999.0.0"},
914+
)
915+
assert not provider.cached_binary_state_mismatch(
916+
"zx",
917+
{"abspath": str(launcher), "loaded_version": "7.2.3"},
918+
)
919+
920+
def test_global_cache_rejects_executable_without_owned_package(self, tmp_path):
921+
global_root = tmp_path / "global"
922+
executable = (
923+
global_root
924+
/ ".pnpm"
925+
/ "not-zx@7.2.3"
926+
/ "node_modules"
927+
/ "not-zx"
928+
/ "build"
929+
/ "cli.js"
930+
)
931+
executable.parent.mkdir(parents=True)
932+
executable.write_text("#!/usr/bin/env node\n")
933+
executable.chmod(0o755)
934+
(executable.parent.parent / "package.json").write_text(
935+
'{"name": "not-zx", "version": "7.2.3"}',
936+
)
937+
launcher = global_root / "bin" / "zx"
938+
launcher.parent.mkdir()
939+
launcher.write_text(
940+
'#!/bin/sh\nexec node "$basedir/../.pnpm/not-zx@7.2.3/node_modules/not-zx/build/cli.js" "$@"\n',
941+
)
942+
launcher.chmod(0o755)
943+
provider = PnpmProvider(install_root=None)
944+
945+
assert provider.host_projection_target(launcher) == executable
946+
assert provider.cached_binary_state_mismatch(
947+
"zx",
948+
{"abspath": str(launcher), "loaded_version": "7.2.3"},
949+
)
950+
856951
def test_provider_defaults_and_binary_overrides_enforce_min_release_age(
857952
self,
858953
test_machine,

0 commit comments

Comments
 (0)