Skip to content

Commit 56d4a12

Browse files
cosmicBboyclaude
andcommitted
Merge origin/main: adopt #1508's removal of code-bundle memoization
Main removed the alru_cache on build_code_bundle entirely (rebuild after source changes), which solves the stale-bundle problem at the root. Drop this branch's refresh_code_bundle_cache() and skip_cache memo-bypass — they managed a cache that no longer exists. control_plane_available() and Run.first_failure() / ActionDetails.error_message remain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NwQixBcyR5va6BC75jaQx3
2 parents 239f4bd + 1fc0a47 commit 56d4a12

8 files changed

Lines changed: 165 additions & 261 deletions

File tree

examples/integration_tests.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@
3232
# =============================================================================
3333

3434

35-
@pytest.fixture(autouse=True)
36-
def clear_lru_caches():
37-
flyte.refresh_code_bundle_cache()
38-
39-
4035
@pytest.fixture(scope="session")
4136
def flyte_client():
4237
"""

src/flyte/__init__.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -57,24 +57,6 @@ def version() -> str:
5757
return __version__
5858

5959

60-
def refresh_code_bundle_cache() -> None:
61-
"""
62-
Forget every in-process memoized code bundle, so the next `flyte.run` / `flyte.deploy` /
63-
`flyte.serve` (or a fork of a prior run) re-bundles the working tree as it is on disk *now*.
64-
65-
Code bundles are memoized per-process on their build arguments, not on file contents: a
66-
long-lived process that launches a run, edits source files, and launches again would ship
67-
the first launch's bundle — edits and all. Call this after changing files on disk (an agent
68-
task rewriting a workflow it iterates on, a notebook cell editing a module, ...) and before
69-
the next launch.
70-
"""
71-
# Imported lazily: the bundling machinery is not part of the base `import flyte` cost, and
72-
# if it was never imported there are no memoized bundles to forget.
73-
from ._code_bundle import refresh_code_bundle_cache as _refresh
74-
75-
_refresh()
76-
77-
7860
__all__ = [
7961
"AMD_GPU",
8062
"GPU",
@@ -134,7 +116,6 @@ def refresh_code_bundle_cache() -> None:
134116
"logger",
135117
"map",
136118
"new_condition",
137-
"refresh_code_bundle_cache",
138119
"rerun",
139120
"run",
140121
"run_python_script",

src/flyte/_code_bundle/__init__.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,6 @@
7171
for `copy_style="custom"`.
7272
* `flyte._code_bundle.build_pkl_bundle` — cloudpickle the task/app in memory and
7373
upload. Returns a pkl `flyte.models.CodeBundle`.
74-
* `flyte._code_bundle.bundle.refresh_code_bundle_cache` (exported as
75-
`flyte.refresh_code_bundle_cache`) — forget in-process memoized bundles so the
76-
next launch re-bundles files as they are on disk now. For long-lived processes
77-
that edit source between launches.
7874
* `flyte._code_bundle.download_bundle` — the counterpart that runs on the worker:
7975
fetch the tgz/pkl and extract it into the task's working directory.
8076
"""
@@ -86,7 +82,6 @@
8682
build_code_bundle_from_relative_paths,
8783
build_pkl_bundle,
8884
download_bundle,
89-
refresh_code_bundle_cache,
9085
)
9186

9287
__all__ = [
@@ -97,7 +92,6 @@
9792
"build_pkl_bundle",
9893
"default_ignores",
9994
"download_bundle",
100-
"refresh_code_bundle_cache",
10195
]
10296

10397

src/flyte/_code_bundle/bundle.py

Lines changed: 25 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
except ImportError: # pragma: no cover - Windows local dev; task containers are POSIX
2121
fcntl = None # type: ignore[assignment] # ty: ignore[invalid-assignment]
2222

23-
from async_lru import alru_cache
24-
2523
from flyte._logging import log, logger
2624
from flyte._status import status
2725
from flyte._utils import AsyncLRUCache
@@ -184,7 +182,7 @@ async def build_pkl_bundle(
184182
return CodeBundle(pkl=str(dest), computed_version=str_digest)
185183

186184

187-
async def _build_code_bundle(
185+
async def build_code_bundle(
188186
from_dir: Path,
189187
*ignore: Type[Ignore],
190188
extract_dir: str = ".",
@@ -194,7 +192,25 @@ async def _build_code_bundle(
194192
skip_cache: bool = False,
195193
additional_files: tuple[str, ...] = (),
196194
) -> CodeBundle:
197-
"""Unmemoized implementation of `build_code_bundle` — see it for the parameter docs."""
195+
"""
196+
Build the code bundle for the current environment.
197+
198+
Args:
199+
from_dir: The directory of the code to bundle. This is the root directory for the source.
200+
extract_dir: The directory to extract the code bundle to, when in the container. It defaults to the current
201+
working directory.
202+
ignore: The list of ignores to apply. This is a list of Ignore classes.
203+
dryrun: If dryrun is enabled, files will not be uploaded to the control plane.
204+
copy_bundle_to: If set, the bundle will be copied to this path. This is used for testing purposes.
205+
copy_style: What to put into the tarball. (either all, or loaded_modules. if none, skip this function)
206+
skip_cache: If true, skip the persistent SQLite cache lookup and always rebuild/re-upload.
207+
additional_files: Extra absolute paths to bundle in addition to whatever `copy_style`
208+
discovers. Used to implement `Environment.include`. When `copy_style='none'` and
209+
`additional_files` is non-empty, falls back to a relative-paths-only bundle.
210+
211+
Returns:
212+
The code bundle, which contains the path where the code was zipped to.
213+
"""
198214
if copy_style == "none":
199215
if additional_files:
200216
return await build_code_bundle_from_relative_paths(
@@ -203,7 +219,6 @@ async def _build_code_bundle(
203219
extract_dir=extract_dir,
204220
dryrun=dryrun,
205221
copy_bundle_to=copy_bundle_to,
206-
skip_cache=skip_cache,
207222
)
208223
raise ValueError("If copy_style is 'none', just don't make a code bundle")
209224

@@ -274,73 +289,29 @@ async def _build_code_bundle(
274289
return CodeBundle(tgz=remote_path, destination=extract_dir, computed_version=hash_digest, files=files)
275290

276291

277-
_memoized_build_code_bundle = alru_cache(_build_code_bundle)
278-
279-
280-
async def build_code_bundle(
292+
async def build_code_bundle_from_relative_paths(
293+
relative_paths: tuple[str, ...],
281294
from_dir: Path,
282-
*ignore: Type[Ignore],
283295
extract_dir: str = ".",
284296
dryrun: bool = False,
285297
copy_bundle_to: pathlib.Path | None = None,
286-
copy_style: CopyFiles = "loaded_modules",
287298
skip_cache: bool = False,
288-
additional_files: tuple[str, ...] = (),
289299
) -> CodeBundle:
290300
"""
291-
Build the code bundle for the current environment.
292-
293-
Results are memoized in-process on the arguments alone (so one `flyte deploy` bundling many
294-
environments scans and uploads once). If files on disk change while the process lives — e.g.
295-
an agent task that rewrites source and re-launches — call `flyte.refresh_code_bundle_cache`
296-
(or pass `skip_cache=True`) so the next bundle reflects the working tree as it is now.
301+
Build a code bundle from a list of relative paths.
297302
298303
Args:
304+
relative_paths: The list of relative paths to bundle.
299305
from_dir: The directory of the code to bundle. This is the root directory for the source.
300306
extract_dir: The directory to extract the code bundle to, when in the container. It defaults to the current
301307
working directory.
302-
ignore: The list of ignores to apply. This is a list of Ignore classes.
303308
dryrun: If dryrun is enabled, files will not be uploaded to the control plane.
304309
copy_bundle_to: If set, the bundle will be copied to this path. This is used for testing purposes.
305-
copy_style: What to put into the tarball. (either all, or loaded_modules. if none, skip this function)
306-
skip_cache: If true, bypass the in-process memoization and the persistent SQLite cache
307-
lookup — always rescan the working tree and rebuild/re-upload.
308-
additional_files: Extra absolute paths to bundle in addition to whatever `copy_style`
309-
discovers. Used to implement `Environment.include`. When `copy_style='none'` and
310-
`additional_files` is non-empty, falls back to a relative-paths-only bundle.
310+
skip_cache: If true, skip the persistent SQLite cache lookup and always rebuild/re-upload.
311311
312312
Returns:
313313
The code bundle, which contains the path where the code was zipped to.
314314
"""
315-
builder = _build_code_bundle if skip_cache else _memoized_build_code_bundle
316-
return await builder(
317-
from_dir,
318-
*ignore,
319-
extract_dir=extract_dir,
320-
dryrun=dryrun,
321-
copy_bundle_to=copy_bundle_to,
322-
copy_style=copy_style,
323-
skip_cache=skip_cache,
324-
additional_files=additional_files,
325-
)
326-
327-
328-
# Kept for callers that historically reached for `build_code_bundle.cache_clear()`; prefer
329-
# `flyte.refresh_code_bundle_cache`.
330-
build_code_bundle.cache_clear = ( # type: ignore[attr-defined] # ty: ignore[unresolved-attribute]
331-
_memoized_build_code_bundle.cache_clear
332-
)
333-
334-
335-
async def _build_code_bundle_from_relative_paths(
336-
relative_paths: tuple[str, ...],
337-
from_dir: Path,
338-
extract_dir: str = ".",
339-
dryrun: bool = False,
340-
copy_bundle_to: pathlib.Path | None = None,
341-
skip_cache: bool = False,
342-
) -> CodeBundle:
343-
"""Unmemoized implementation of `build_code_bundle_from_relative_paths` — see it for the parameter docs."""
344315
status.step("Bundling code...")
345316
logger.debug("Building code bundle from relative paths.")
346317
from flyte.remote import upload_file
@@ -380,70 +351,6 @@ async def _build_code_bundle_from_relative_paths(
380351
return CodeBundle(tgz=remote_path, destination=extract_dir, computed_version=hash_digest, files=files)
381352

382353

383-
_memoized_build_code_bundle_from_relative_paths = alru_cache(_build_code_bundle_from_relative_paths)
384-
385-
386-
async def build_code_bundle_from_relative_paths(
387-
relative_paths: tuple[str, ...],
388-
from_dir: Path,
389-
extract_dir: str = ".",
390-
dryrun: bool = False,
391-
copy_bundle_to: pathlib.Path | None = None,
392-
skip_cache: bool = False,
393-
) -> CodeBundle:
394-
"""
395-
Build a code bundle from a list of relative paths.
396-
397-
Results are memoized in-process on the arguments alone — see `build_code_bundle` for when
398-
and how to bypass that.
399-
400-
Args:
401-
relative_paths: The list of relative paths to bundle.
402-
from_dir: The directory of the code to bundle. This is the root directory for the source.
403-
extract_dir: The directory to extract the code bundle to, when in the container. It defaults to the current
404-
working directory.
405-
dryrun: If dryrun is enabled, files will not be uploaded to the control plane.
406-
copy_bundle_to: If set, the bundle will be copied to this path. This is used for testing purposes.
407-
skip_cache: If true, bypass the in-process memoization and the persistent SQLite cache
408-
lookup — always rescan the listed files and rebuild/re-upload.
409-
410-
Returns:
411-
The code bundle, which contains the path where the code was zipped to.
412-
"""
413-
builder = _build_code_bundle_from_relative_paths if skip_cache else _memoized_build_code_bundle_from_relative_paths
414-
return await builder(
415-
relative_paths,
416-
from_dir=from_dir,
417-
extract_dir=extract_dir,
418-
dryrun=dryrun,
419-
copy_bundle_to=copy_bundle_to,
420-
skip_cache=skip_cache,
421-
)
422-
423-
424-
# Kept for callers that historically reached for `.cache_clear()`; prefer
425-
# `flyte.refresh_code_bundle_cache`.
426-
build_code_bundle_from_relative_paths.cache_clear = ( # type: ignore[attr-defined] # ty: ignore[unresolved-attribute]
427-
_memoized_build_code_bundle_from_relative_paths.cache_clear
428-
)
429-
430-
431-
def refresh_code_bundle_cache() -> None:
432-
"""
433-
Forget every in-process memoized code bundle, so the next `flyte.run` / `flyte.deploy` /
434-
`flyte.serve` (or a fork of a prior run) re-bundles the working tree as it is on disk *now*.
435-
436-
Code bundles are memoized per-process on their build arguments, not on file contents: a
437-
long-lived process that launches a run, edits source files, and launches again would ship
438-
the first launch's bundle — edits and all. Call this after changing files on disk (an agent
439-
task rewriting a workflow it iterates on, a notebook cell editing a module, ...) and before
440-
the next launch. The persistent content-addressed cache underneath needs no refreshing — a
441-
changed working tree produces a new digest, which simply misses and re-uploads.
442-
"""
443-
_memoized_build_code_bundle.cache_clear()
444-
_memoized_build_code_bundle_from_relative_paths.cache_clear()
445-
446-
447354
@contextlib.asynccontextmanager
448355
async def _bundle_lock(lock_path: pathlib.Path) -> AsyncIterator[bool]:
449356
"""

src/flyte/connectors/_connector.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import asyncio
22
import json
33
import os
4+
import pathlib
5+
import tempfile
46
import typing
57
from abc import ABC, abstractmethod
68
from dataclasses import asdict, dataclass
@@ -248,24 +250,12 @@ async def execute(self, **kwargs) -> Any:
248250
if not storage.is_remote(tctx.raw_data_path.path):
249251
return await TaskTemplate.execute(self, **kwargs)
250252
else:
251-
local_code_bundle = await build_code_bundle(
252-
from_dir=cfg.root_dir,
253-
dryrun=True,
254-
)
255-
if local_code_bundle.tgz is None:
256-
raise RuntimeError("no tgz found in code bundle")
257-
remote_code_path = await storage.put(
258-
local_code_bundle.tgz, prefix + "/code_bundle/" + os.path.basename(local_code_bundle.tgz)
259-
)
253+
code_bundle = await _build_and_upload_code_bundle(cfg.root_dir, prefix)
260254
sc = SerializationContext(
261255
project=tctx.action.project,
262256
domain=tctx.action.domain,
263257
org=tctx.action.org,
264-
code_bundle=CodeBundle(
265-
tgz=remote_code_path,
266-
computed_version=local_code_bundle.computed_version,
267-
destination="/opt/flyte/",
268-
),
258+
code_bundle=code_bundle,
269259
version=tctx.version,
270260
image_cache=await build_images.aio(task.parent_env()) if task.parent_env else None,
271261
root_dir=cfg.root_dir,
@@ -331,6 +321,25 @@ async def execute(self, **kwargs) -> Any:
331321
return tuple(resource.outputs.values())
332322

333323

324+
async def _build_and_upload_code_bundle(from_dir: pathlib.Path, prefix: str) -> CodeBundle:
325+
with tempfile.TemporaryDirectory(prefix="flyte-code-bundle-") as tmp_dir:
326+
local_code_bundle = await build_code_bundle(
327+
from_dir=from_dir,
328+
dryrun=True,
329+
copy_bundle_to=pathlib.Path(tmp_dir),
330+
)
331+
if local_code_bundle.tgz is None:
332+
raise RuntimeError("no tgz found in code bundle")
333+
remote_code_path = await storage.put(
334+
local_code_bundle.tgz, prefix + "/code_bundle/" + os.path.basename(local_code_bundle.tgz)
335+
)
336+
return CodeBundle(
337+
tgz=remote_code_path,
338+
computed_version=local_code_bundle.computed_version,
339+
destination="/opt/flyte/",
340+
)
341+
342+
334343
async def get_resource_proto(resource: Resource) -> connector_pb2.Resource:
335344
if resource.outputs:
336345
interface = NativeInterface.from_types(inputs={}, outputs={k: type(v) for k, v in resource.outputs.items()})

tests/flyte/app/test_app_code_bundling.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ async def test_code_bundle_consistency_with_include_files(temp_app_directory):
5050
from_dir=temp_app_directory,
5151
dryrun=True,
5252
)
53-
build_code_bundle_from_relative_paths.cache_clear()
53+
5454
bundle2 = await build_code_bundle_from_relative_paths(
5555
relative_paths=include_files,
5656
from_dir=temp_app_directory,
@@ -70,7 +70,7 @@ async def test_code_bundle_consistency_with_subdirectory_files(temp_app_director
7070
from_dir=temp_app_directory,
7171
dryrun=True,
7272
)
73-
build_code_bundle_from_relative_paths.cache_clear()
73+
7474
bundle2 = await build_code_bundle_from_relative_paths(
7575
relative_paths=include_files,
7676
from_dir=temp_app_directory,
@@ -89,7 +89,7 @@ async def test_code_bundle_different_files_produce_different_versions(temp_app_d
8989
from_dir=temp_app_directory,
9090
dryrun=True,
9191
)
92-
build_code_bundle_from_relative_paths.cache_clear()
92+
9393
bundle2 = await build_code_bundle_from_relative_paths(
9494
relative_paths=("app.py", "utils.py"),
9595
from_dir=temp_app_directory,
@@ -106,7 +106,7 @@ async def test_code_bundle_file_content_changes_version(temp_app_directory):
106106
from_dir=temp_app_directory,
107107
dryrun=True,
108108
)
109-
build_code_bundle_from_relative_paths.cache_clear()
109+
110110
(temp_app_directory / "app.py").write_text("print('Modified content!')")
111111
bundle2 = await build_code_bundle_from_relative_paths(
112112
relative_paths=("app.py",),

0 commit comments

Comments
 (0)