-
-
Notifications
You must be signed in to change notification settings - Fork 292
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce
pex3 cache {dir,info,purge}
. (#2513)
Re-structure the Pex cache to both support versioning as well as adding access tracking for shared (normal) use and for exclusive use when portions of the cache need to be deleted. With this new ground work, add a new `pex3 cache {dir,info,purge}` family of commands for inspecting and safely trimming the Pex cache. Closes #1176 Closes #1655 Closes #2201
- Loading branch information
Showing
49 changed files
with
1,025 additions
and
137 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Copyright 2024 Pex project contributors. | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# Copyright 2024 Pex project contributors. | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
from __future__ import absolute_import, print_function | ||
|
||
import fcntl | ||
import os | ||
from contextlib import contextmanager | ||
|
||
from pex.common import safe_mkdir | ||
from pex.typing import TYPE_CHECKING | ||
from pex.variables import ENV | ||
|
||
if TYPE_CHECKING: | ||
from typing import Iterator, Optional, Tuple | ||
|
||
|
||
# N.B.: The lock file path is last in the lock state tuple to allow for a simple encoding scheme in | ||
# `save_lock_state` that is impervious to a delimiter collision in the lock file path when decoding | ||
# in `_maybe_restore_lock_state` (due to maxsplit). | ||
|
||
_LOCK = None # type: Optional[Tuple[bool, int, str]] | ||
|
||
_PEX_CACHE_ACCESS_LOCK_ENV_VAR = "_PEX_CACHE_ACCESS_LOCK" | ||
|
||
|
||
def save_lock_state(): | ||
# type: () -> None | ||
"""Records any current lock state in a manner that can survive un-importing of this module.""" | ||
|
||
# N.B.: This supports the sole case of a Pex PEX, whose runtime obtains a lock that it must hand | ||
# off to the Pex CLI it spawns. | ||
|
||
global _LOCK | ||
if _LOCK is not None: | ||
exclusive, lock_fd, lock_file = _LOCK | ||
os.environ[_PEX_CACHE_ACCESS_LOCK_ENV_VAR] = "|".join( | ||
(str(int(exclusive)), str(lock_fd), lock_file) | ||
) | ||
|
||
|
||
def _maybe_restore_lock_state(): | ||
# type: () -> None | ||
|
||
saved_lock_state = os.environ.pop(_PEX_CACHE_ACCESS_LOCK_ENV_VAR, None) | ||
if saved_lock_state: | ||
encoded_exclusive, encoded_lock_fd, lock_file = saved_lock_state.split("|", 2) | ||
global _LOCK | ||
_LOCK = bool(int(encoded_exclusive)), int(encoded_lock_fd), lock_file | ||
|
||
|
||
def _lock(exclusive): | ||
# type: (bool) -> str | ||
|
||
lock_fd = None # type: Optional[int] | ||
|
||
global _LOCK | ||
if _LOCK is None: | ||
_maybe_restore_lock_state() | ||
if _LOCK is not None: | ||
existing_exclusive, lock_fd, existing_lock_file = _LOCK | ||
if existing_exclusive == exclusive: | ||
return existing_lock_file | ||
|
||
lock_file = os.path.join(ENV.PEX_ROOT, "access.lck") | ||
|
||
if lock_fd is None: | ||
# N.B.: We don't actually write anything to the lock file but the fcntl file locking | ||
# operations only work on files opened for at least write. | ||
safe_mkdir(os.path.dirname(lock_file)) | ||
lock_fd = os.open(lock_file, os.O_CREAT | os.O_WRONLY) | ||
|
||
# N.B.: Since flock operates on an open file descriptor and these are | ||
# guaranteed to be closed by the operating system when the owning process exits, | ||
# this lock is immune to staleness. | ||
fcntl.flock(lock_fd, fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH) | ||
|
||
_LOCK = exclusive, lock_fd, lock_file | ||
return lock_file | ||
|
||
|
||
def read_write(): | ||
# type: () -> str | ||
"""Obtains the shared Pex cache read-write lock. | ||
This function blocks until it is safe to use the Pex cache. | ||
""" | ||
return _lock(exclusive=False) | ||
|
||
|
||
@contextmanager | ||
def await_delete_lock(): | ||
# type: () -> Iterator[str] | ||
"""Awaits the Pex cache delete lock, yielding the lock file path. | ||
When the context manager exits, the delete lock is held, and it is safe to delete all or | ||
portions of the Pex cache. | ||
""" | ||
lock_file = _lock(exclusive=False) | ||
yield lock_file | ||
_lock(exclusive=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
# Copyright 2024 Pex project contributors. | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
from __future__ import absolute_import | ||
|
||
import os | ||
|
||
from pex.enum import Enum | ||
from pex.typing import TYPE_CHECKING | ||
from pex.variables import ENV, Variables | ||
|
||
if TYPE_CHECKING: | ||
from typing import Iterable, Iterator, Union | ||
|
||
|
||
class CacheDir(Enum["CacheDir.Value"]): | ||
class Value(Enum.Value): | ||
def __init__( | ||
self, | ||
value, # type: str | ||
name, # type: str | ||
version, # type: int | ||
description, # type: str | ||
dependencies=(), # type: Iterable[CacheDir.Value] | ||
): | ||
Enum.Value.__init__(self, value) | ||
self.name = name | ||
self.version = version | ||
self.description = description | ||
self.dependencies = tuple(dependencies) | ||
|
||
@property | ||
def rel_path(self): | ||
# type: () -> str | ||
return os.path.join(self.value, str(self.version)) | ||
|
||
def path( | ||
self, | ||
*subdirs, # type: str | ||
**kwargs # type: Union[str, Variables] | ||
): | ||
# type: (...) -> str | ||
pex_root = kwargs.get("pex_root", ENV) | ||
return os.path.join( | ||
pex_root.PEX_ROOT if isinstance(pex_root, Variables) else pex_root, | ||
self.rel_path, | ||
*subdirs | ||
) | ||
|
||
def iter_transitive_dependents(self): | ||
# type: () -> Iterator[CacheDir.Value] | ||
for cache_dir in CacheDir.values(): | ||
if self in cache_dir.dependencies: | ||
yield cache_dir | ||
for dependent in cache_dir.iter_transitive_dependents(): | ||
yield dependent | ||
|
||
BOOTSTRAP_ZIPS = Value( | ||
"bootstrap_zips", | ||
version=0, | ||
name="Packed Bootstraps", | ||
description="PEX runtime bootstrap code, zipped up for `--layout packed` PEXes.", | ||
) | ||
|
||
BOOTSTRAPS = Value( | ||
"bootstraps", | ||
version=0, | ||
name="Bootstraps", | ||
description="PEX runtime bootstrap code.", | ||
) | ||
|
||
BUILT_WHEELS = Value( | ||
"built_wheels", | ||
version=0, | ||
name="Built Wheels", | ||
description="Wheels built by Pex from resolved sdists when creating PEX files.", | ||
) | ||
|
||
DOCS = Value( | ||
"docs", | ||
version=0, | ||
name="Pex Docs", | ||
description="Artifacts used in serving Pex docs via `pex --docs` and `pex3 docs`.", | ||
) | ||
|
||
DOWNLOADS = Value( | ||
"downloads", | ||
version=0, | ||
name="Lock Artifact Downloads", | ||
description="Distributions downloaded when resolving from a Pex lock file.", | ||
) | ||
|
||
INSTALLED_WHEELS = Value( | ||
"installed_wheels", | ||
version=0, | ||
name="Pre-installed Wheels", | ||
description=( | ||
"Pre-installed wheel chroots used to both build PEXes and serve as runtime `sys.path` " | ||
"entries." | ||
), | ||
) | ||
|
||
INTERPRETERS = Value( | ||
"interpreters", | ||
version=0, | ||
name="Interpreters", | ||
description="Information about interpreters found on the system.", | ||
) | ||
|
||
ISOLATED = Value( | ||
"isolated", | ||
version=0, | ||
name="Isolated Pex Code", | ||
description="The Pex codebase isolated for internal use in subprocesses.", | ||
) | ||
|
||
PACKED_WHEELS = Value( | ||
"packed_wheels", | ||
version=0, | ||
name="Packed Wheels", | ||
description=( | ||
"The same content as {installed_wheels!r}, but zipped up for `--layout packed` " | ||
"PEXes.".format(installed_wheels=INSTALLED_WHEELS.rel_path) | ||
), | ||
) | ||
|
||
PIP = Value( | ||
"pip", | ||
version=0, | ||
name="Pip Versions", | ||
description="Isolated Pip caches and Pip PEXes Pex uses to resolve distributions.", | ||
) | ||
|
||
PLATFORMS = Value( | ||
"platforms", | ||
version=0, | ||
name="Abbreviated Platforms", | ||
description=( | ||
"Information calculated about abbreviated platforms specified via `--platform`." | ||
), | ||
) | ||
|
||
SCIES = Value( | ||
"scies", | ||
version=0, | ||
name="Scie Tools", | ||
description="Tools and caches used when building PEX scies via `--scie {eager,lazy}`.", | ||
) | ||
|
||
TOOLS = Value( | ||
"tools", | ||
version=0, | ||
name="Pex Tools", | ||
description="Caches for the various `PEX_TOOLS=1` / `pex-tools` subcommands.", | ||
) | ||
|
||
USER_CODE = Value( | ||
"user_code", | ||
version=0, | ||
name="User Code", | ||
description=( | ||
"User code added to PEX files using `-D` / `--sources-directory`, `-P` / `--package` " | ||
"and `-M` / `--module`." | ||
), | ||
) | ||
|
||
UNZIPPED_PEXES = Value( | ||
"unzipped_pexes", | ||
version=0, | ||
name="Unzipped PEXes", | ||
description="The unzipped PEX files executed on this machine.", | ||
dependencies=[BOOTSTRAPS, USER_CODE, INSTALLED_WHEELS], | ||
) | ||
|
||
VENVS = Value( | ||
"venvs", | ||
version=0, | ||
name="Virtual Environments", | ||
description="Virtual environments generated at runtime for `--venv` mode PEXes.", | ||
dependencies=[INSTALLED_WHEELS], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Oops, something went wrong.