modal 1.1.5.dev2__tar.gz → 1.1.5.dev4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/PKG-INFO +1 -1
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/client.pyi +2 -2
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/experimental/flash.py +80 -53
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/experimental/flash.pyi +17 -5
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_version/__init__.py +1 -1
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/LICENSE +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/README.md +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/__init__.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/__main__.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_clustered_functions.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_clustered_functions.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_container_entrypoint.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_functions.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_ipython.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_location.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_object.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_output.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_partial_function.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_pty.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_resolver.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_resources.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_runtime/__init__.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_runtime/asgi.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_runtime/execution_context.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_runtime/telemetry.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_serialization.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_traceback.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_tunnel.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_tunnel.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_type_manager.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/__init__.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/app_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/async_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/blob_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/deprecation.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/docker_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/function_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/git_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/hash_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/http_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/logger.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/mount_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/name_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/package_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/shell_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_utils/time_utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_vendor/__init__.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_vendor/tblib.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/_watcher.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/app.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/app.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/builder/2023.12.txt +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/builder/2024.04.txt +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/builder/2024.10.txt +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/builder/2025.06.txt +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/builder/README.md +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/builder/base-images.json +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/call_graph.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/__init__.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/_download.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/_traceback.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/app.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/cluster.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/config.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/container.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/dict.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/entry_point.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/environment.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/import_refs.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/launch.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/network_file_system.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/profile.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/programs/__init__.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/programs/launch_instance_ssh.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/programs/run_marimo.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/programs/vscode.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/queues.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/run.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/secret.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/token.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/utils.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cli/volume.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/client.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cls.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/cls.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/config.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/container_process.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/container_process.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/dict.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/dict.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/environments.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/environments.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/exception.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/experimental/__init__.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/experimental/ipython.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/file_io.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/file_io.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/file_pattern_matcher.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/functions.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/functions.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/gpu.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/image.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/image.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/io_streams.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/io_streams.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/mount.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/mount.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/network_file_system.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/network_file_system.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/object.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/object.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/output.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/parallel_map.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/parallel_map.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/partial_function.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/partial_function.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/proxy.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/proxy.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/py.typed +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/queue.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/queue.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/retries.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/runner.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/runner.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/running_app.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/sandbox.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/sandbox.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/schedule.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/scheduler_placement.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/secret.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/secret.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/serving.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/serving.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/snapshot.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/snapshot.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/stream_type.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/token_flow.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/token_flow.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/volume.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal/volume.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal.egg-info/requires.txt +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal.egg-info/top_level.txt +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_docs/__init__.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/__init__.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/api.proto +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/api_grpc.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/api_pb2.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/modal_options_grpc.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/options.proto +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/options_grpc.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/options_pb2.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/options_pb2.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/options_pb2_grpc.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/options_pb2_grpc.pyi +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_proto/py.typed +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/modal_version/__main__.py +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/pyproject.toml +0 -0
- {modal-1.1.5.dev2 → modal-1.1.5.dev4}/setup.cfg +0 -0
@@ -33,7 +33,7 @@ class _Client:
|
|
33
33
|
server_url: str,
|
34
34
|
client_type: int,
|
35
35
|
credentials: typing.Optional[tuple[str, str]],
|
36
|
-
version: str = "1.1.5.
|
36
|
+
version: str = "1.1.5.dev4",
|
37
37
|
):
|
38
38
|
"""mdmd:hidden
|
39
39
|
The Modal client object is not intended to be instantiated directly by users.
|
@@ -164,7 +164,7 @@ class Client:
|
|
164
164
|
server_url: str,
|
165
165
|
client_type: int,
|
166
166
|
credentials: typing.Optional[tuple[str, str]],
|
167
|
-
version: str = "1.1.5.
|
167
|
+
version: str = "1.1.5.dev4",
|
168
168
|
):
|
169
169
|
"""mdmd:hidden
|
170
170
|
The Modal client object is not intended to be instantiated directly by users.
|
@@ -21,7 +21,7 @@ from ..client import _Client
|
|
21
21
|
from ..config import logger
|
22
22
|
from ..exception import InvalidError
|
23
23
|
|
24
|
-
|
24
|
+
_MAX_FAILURES = 3
|
25
25
|
|
26
26
|
|
27
27
|
class _FlashManager:
|
@@ -42,7 +42,9 @@ class _FlashManager:
|
|
42
42
|
self.num_failures = 0
|
43
43
|
self.task_id = os.environ["MODAL_TASK_ID"]
|
44
44
|
|
45
|
-
async def
|
45
|
+
async def is_port_connection_healthy(
|
46
|
+
self, process: Optional[subprocess.Popen], timeout: int = 5
|
47
|
+
) -> tuple[bool, Optional[Exception]]:
|
46
48
|
import socket
|
47
49
|
|
48
50
|
start_time = time.monotonic()
|
@@ -50,13 +52,13 @@ class _FlashManager:
|
|
50
52
|
while time.monotonic() - start_time < timeout:
|
51
53
|
try:
|
52
54
|
if process is not None and process.poll() is not None:
|
53
|
-
return Exception(f"Process {process.pid} exited with code {process.returncode}")
|
55
|
+
return False, Exception(f"Process {process.pid} exited with code {process.returncode}")
|
54
56
|
with socket.create_connection(("localhost", self.port), timeout=1):
|
55
|
-
return
|
57
|
+
return True, None
|
56
58
|
except (ConnectionRefusedError, OSError):
|
57
59
|
await asyncio.sleep(0.1)
|
58
60
|
|
59
|
-
return Exception(f"Waited too long for port {self.port} to start accepting connections")
|
61
|
+
return False, Exception(f"Waited too long for port {self.port} to start accepting connections")
|
60
62
|
|
61
63
|
async def _start(self):
|
62
64
|
self.tunnel = await self.tunnel_manager.__aenter__()
|
@@ -74,7 +76,7 @@ class _FlashManager:
|
|
74
76
|
while True:
|
75
77
|
try:
|
76
78
|
# Check if the container should be drained (e.g., too many failures)
|
77
|
-
if self.num_failures >
|
79
|
+
if self.num_failures > _MAX_FAILURES:
|
78
80
|
logger.warning(
|
79
81
|
f"[Modal Flash] Draining task {self.task_id} on {self.tunnel.url} due to too many failures."
|
80
82
|
)
|
@@ -101,35 +103,38 @@ class _FlashManager:
|
|
101
103
|
first_registration = True
|
102
104
|
while True:
|
103
105
|
try:
|
104
|
-
await self.
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
106
|
+
port_check_resp, port_check_error = await self.is_port_connection_healthy(process=self.process)
|
107
|
+
if port_check_resp:
|
108
|
+
resp = await self.client.stub.FlashContainerRegister(
|
109
|
+
api_pb2.FlashContainerRegisterRequest(
|
110
|
+
priority=10,
|
111
|
+
weight=5,
|
112
|
+
host=host,
|
113
|
+
port=port,
|
114
|
+
),
|
115
|
+
timeout=10,
|
116
|
+
)
|
117
|
+
self.num_failures = 0
|
118
|
+
if first_registration:
|
119
|
+
logger.warning(
|
120
|
+
f"[Modal Flash] Listening at {resp.url} over {self.tunnel.url} for task_id {self.task_id}"
|
121
|
+
)
|
122
|
+
first_registration = False
|
123
|
+
else:
|
124
|
+
logger.error(
|
125
|
+
f"[Modal Flash] Deregistering container {self.task_id} on {self.tunnel.url} "
|
126
|
+
f"due to error: {port_check_error}, num_failures: {self.num_failures}"
|
127
|
+
)
|
128
|
+
self.num_failures += 1
|
129
|
+
await retry_transient_errors(
|
130
|
+
self.client.stub.FlashContainerDeregister,
|
131
|
+
api_pb2.FlashContainerDeregisterRequest(),
|
118
132
|
)
|
119
|
-
first_registration = False
|
120
133
|
except asyncio.CancelledError:
|
121
134
|
logger.warning("[Modal Flash] Shutting down...")
|
122
135
|
break
|
123
136
|
except Exception as e:
|
124
137
|
logger.error(f"[Modal Flash] Heartbeat failed: {e}")
|
125
|
-
self.num_failures += 1
|
126
|
-
logger.error(
|
127
|
-
f"[Modal Flash] Deregistering container {self.tunnel.url}, num_failures: {self.num_failures}"
|
128
|
-
)
|
129
|
-
await retry_transient_errors(
|
130
|
-
self.client.stub.FlashContainerDeregister,
|
131
|
-
api_pb2.FlashContainerDeregisterRequest(),
|
132
|
-
)
|
133
138
|
|
134
139
|
try:
|
135
140
|
await asyncio.sleep(1)
|
@@ -167,7 +172,9 @@ FlashManager = synchronize_api(_FlashManager)
|
|
167
172
|
|
168
173
|
@synchronizer.create_blocking
|
169
174
|
async def flash_forward(
|
170
|
-
port: int,
|
175
|
+
port: int,
|
176
|
+
process: Optional[subprocess.Popen] = None,
|
177
|
+
health_check_url: Optional[str] = None,
|
171
178
|
) -> _FlashManager:
|
172
179
|
"""
|
173
180
|
Forward a port to the Modal Flash service, exposing that port as a stable web endpoint.
|
@@ -194,6 +201,7 @@ class _FlashPrometheusAutoscaler:
|
|
194
201
|
target_metric_value: float,
|
195
202
|
min_containers: Optional[int],
|
196
203
|
max_containers: Optional[int],
|
204
|
+
buffer_containers: Optional[int],
|
197
205
|
scale_up_tolerance: float,
|
198
206
|
scale_down_tolerance: float,
|
199
207
|
scale_up_stabilization_window_seconds: int,
|
@@ -221,6 +229,7 @@ class _FlashPrometheusAutoscaler:
|
|
221
229
|
self.target_metric_value = target_metric_value
|
222
230
|
self.min_containers = min_containers
|
223
231
|
self.max_containers = max_containers
|
232
|
+
self.buffer_containers = buffer_containers
|
224
233
|
self.scale_up_tolerance = scale_up_tolerance
|
225
234
|
self.scale_down_tolerance = scale_down_tolerance
|
226
235
|
self.scale_up_stabilization_window_seconds = scale_up_stabilization_window_seconds
|
@@ -286,6 +295,7 @@ class _FlashPrometheusAutoscaler:
|
|
286
295
|
scale_down_stabilization_window_seconds=self.scale_down_stabilization_window_seconds,
|
287
296
|
min_containers=self.min_containers,
|
288
297
|
max_containers=self.max_containers,
|
298
|
+
buffer_containers=self.buffer_containers,
|
289
299
|
)
|
290
300
|
|
291
301
|
logger.warning(
|
@@ -395,6 +405,7 @@ class _FlashPrometheusAutoscaler:
|
|
395
405
|
# Gets metrics from prometheus
|
396
406
|
sum_metric = 0
|
397
407
|
containers_with_metrics = 0
|
408
|
+
buffer_containers = self.buffer_containers or 0
|
398
409
|
container_metrics_list = await asyncio.gather(
|
399
410
|
*[
|
400
411
|
self._get_metrics(f"https://{container.host}:{container.port}/{self.metrics_endpoint}")
|
@@ -411,29 +422,36 @@ class _FlashPrometheusAutoscaler:
|
|
411
422
|
sum_metric += container_metrics[target_metric][0].value
|
412
423
|
containers_with_metrics += 1
|
413
424
|
|
414
|
-
#
|
425
|
+
# n_containers_missing = number of unhealthy containers + number of containers not registered in flash dns
|
415
426
|
n_containers_missing_metric = current_replicas - containers_with_metrics
|
416
|
-
# n_containers_unhealthy
|
427
|
+
# n_containers_unhealthy = number of dns registered containers that are not emitting metrics
|
417
428
|
n_containers_unhealthy = len(containers) - containers_with_metrics
|
418
429
|
|
419
|
-
#
|
420
|
-
|
421
|
-
|
422
|
-
)
|
430
|
+
# number of total containers - buffer containers
|
431
|
+
# This is used in 1) scale ratio denominators 2) provisioning base.
|
432
|
+
# Max is used to handle case when buffer_containers are first initialized.
|
433
|
+
num_provisioned_containers = max(current_replicas - buffer_containers, 1)
|
434
|
+
|
435
|
+
# Scale up assuming that every unhealthy container is at (1 + scale_up_tolerance)x the target metric value.
|
436
|
+
# This way if all containers are unhealthy, we will increase our number of containers.
|
437
|
+
scale_up_target_metric_value = (
|
438
|
+
sum_metric + (1 + self.scale_up_tolerance) * n_containers_unhealthy * target_metric_value
|
439
|
+
) / (num_provisioned_containers)
|
423
440
|
|
424
441
|
# Scale down assuming that every container (including cold starting containers) are at the target metric value.
|
442
|
+
# The denominator is just num_provisioned_containers because we don't want to account for the buffer containers.
|
425
443
|
scale_down_target_metric_value = (sum_metric + n_containers_missing_metric * target_metric_value) / (
|
426
|
-
|
444
|
+
num_provisioned_containers
|
427
445
|
)
|
428
446
|
|
429
447
|
scale_up_ratio = scale_up_target_metric_value / target_metric_value
|
430
448
|
scale_down_ratio = scale_down_target_metric_value / target_metric_value
|
431
449
|
|
432
|
-
desired_replicas =
|
450
|
+
desired_replicas = num_provisioned_containers
|
433
451
|
if scale_up_ratio > 1 + self.scale_up_tolerance:
|
434
|
-
desired_replicas = math.ceil(
|
452
|
+
desired_replicas = math.ceil(desired_replicas * scale_up_ratio)
|
435
453
|
elif scale_down_ratio < 1 - self.scale_down_tolerance:
|
436
|
-
desired_replicas = math.ceil(
|
454
|
+
desired_replicas = math.ceil(desired_replicas * scale_down_ratio)
|
437
455
|
|
438
456
|
logger.warning(
|
439
457
|
f"[Modal Flash] Current replicas: {current_replicas}, "
|
@@ -442,6 +460,7 @@ class _FlashPrometheusAutoscaler:
|
|
442
460
|
f"number of containers with metrics: {containers_with_metrics}, "
|
443
461
|
f"number of containers unhealthy: {n_containers_unhealthy}, "
|
444
462
|
f"number of containers missing metric (includes unhealthy): {n_containers_missing_metric}, "
|
463
|
+
f"number of provisioned containers: {num_provisioned_containers}, "
|
445
464
|
f"scale up ratio: {scale_up_ratio}, "
|
446
465
|
f"scale down ratio: {scale_down_ratio}, "
|
447
466
|
f"desired replicas: {desired_replicas}"
|
@@ -503,6 +522,7 @@ class _FlashPrometheusAutoscaler:
|
|
503
522
|
scale_down_stabilization_window_seconds: int = 60 * 5,
|
504
523
|
min_containers: Optional[int] = None,
|
505
524
|
max_containers: Optional[int] = None,
|
525
|
+
buffer_containers: Optional[int] = None,
|
506
526
|
) -> int:
|
507
527
|
"""
|
508
528
|
Return the target number of containers following (simplified) Kubernetes HPA
|
@@ -553,6 +573,10 @@ class _FlashPrometheusAutoscaler:
|
|
553
573
|
new_replicas = max(min_containers, new_replicas)
|
554
574
|
if max_containers is not None:
|
555
575
|
new_replicas = min(max_containers, new_replicas)
|
576
|
+
|
577
|
+
if buffer_containers is not None:
|
578
|
+
new_replicas += buffer_containers
|
579
|
+
|
556
580
|
return new_replicas
|
557
581
|
|
558
582
|
async def stop(self):
|
@@ -590,6 +614,8 @@ async def flash_prometheus_autoscaler(
|
|
590
614
|
# How often to make autoscaling decisions.
|
591
615
|
# Corresponds to --horizontal-pod-autoscaler-sync-period in Kubernetes.
|
592
616
|
autoscaling_interval_seconds: int = 15,
|
617
|
+
# Whether to include overprovisioned containers in the scale up calculation.
|
618
|
+
buffer_containers: Optional[int] = None,
|
593
619
|
) -> _FlashPrometheusAutoscaler:
|
594
620
|
"""
|
595
621
|
Autoscale a Flash service based on containers' Prometheus metrics.
|
@@ -607,19 +633,20 @@ async def flash_prometheus_autoscaler(
|
|
607
633
|
|
608
634
|
client = await _Client.from_env()
|
609
635
|
autoscaler = _FlashPrometheusAutoscaler(
|
610
|
-
client,
|
611
|
-
app_name,
|
612
|
-
cls_name,
|
613
|
-
metrics_endpoint,
|
614
|
-
target_metric,
|
615
|
-
target_metric_value,
|
616
|
-
min_containers,
|
617
|
-
max_containers,
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
636
|
+
client=client,
|
637
|
+
app_name=app_name,
|
638
|
+
cls_name=cls_name,
|
639
|
+
metrics_endpoint=metrics_endpoint,
|
640
|
+
target_metric=target_metric,
|
641
|
+
target_metric_value=target_metric_value,
|
642
|
+
min_containers=min_containers,
|
643
|
+
max_containers=max_containers,
|
644
|
+
buffer_containers=buffer_containers,
|
645
|
+
scale_up_tolerance=scale_up_tolerance,
|
646
|
+
scale_down_tolerance=scale_down_tolerance,
|
647
|
+
scale_up_stabilization_window_seconds=scale_up_stabilization_window_seconds,
|
648
|
+
scale_down_stabilization_window_seconds=scale_down_stabilization_window_seconds,
|
649
|
+
autoscaling_interval_seconds=autoscaling_interval_seconds,
|
623
650
|
)
|
624
651
|
await autoscaler.start()
|
625
652
|
return autoscaler
|
@@ -15,7 +15,9 @@ class _FlashManager:
|
|
15
15
|
"""Initialize self. See help(type(self)) for accurate signature."""
|
16
16
|
...
|
17
17
|
|
18
|
-
async def
|
18
|
+
async def is_port_connection_healthy(
|
19
|
+
self, process: typing.Optional[subprocess.Popen], timeout: int = 5
|
20
|
+
) -> tuple[bool, typing.Optional[Exception]]: ...
|
19
21
|
async def _start(self): ...
|
20
22
|
async def _drain_container(self):
|
21
23
|
"""Background task that checks if we've encountered too many failures and drains the container if so."""
|
@@ -37,11 +39,15 @@ class FlashManager:
|
|
37
39
|
health_check_url: typing.Optional[str] = None,
|
38
40
|
): ...
|
39
41
|
|
40
|
-
class
|
41
|
-
def __call__(
|
42
|
-
|
42
|
+
class __is_port_connection_healthy_spec(typing_extensions.Protocol[SUPERSELF]):
|
43
|
+
def __call__(
|
44
|
+
self, /, process: typing.Optional[subprocess.Popen], timeout: int = 5
|
45
|
+
) -> tuple[bool, typing.Optional[Exception]]: ...
|
46
|
+
async def aio(
|
47
|
+
self, /, process: typing.Optional[subprocess.Popen], timeout: int = 5
|
48
|
+
) -> tuple[bool, typing.Optional[Exception]]: ...
|
43
49
|
|
44
|
-
|
50
|
+
is_port_connection_healthy: __is_port_connection_healthy_spec[typing_extensions.Self]
|
45
51
|
|
46
52
|
class ___start_spec(typing_extensions.Protocol[SUPERSELF]):
|
47
53
|
def __call__(self, /): ...
|
@@ -120,6 +126,7 @@ class _FlashPrometheusAutoscaler:
|
|
120
126
|
target_metric_value: float,
|
121
127
|
min_containers: typing.Optional[int],
|
122
128
|
max_containers: typing.Optional[int],
|
129
|
+
buffer_containers: typing.Optional[int],
|
123
130
|
scale_up_tolerance: float,
|
124
131
|
scale_down_tolerance: float,
|
125
132
|
scale_up_stabilization_window_seconds: int,
|
@@ -149,6 +156,7 @@ class _FlashPrometheusAutoscaler:
|
|
149
156
|
scale_down_stabilization_window_seconds: int = 300,
|
150
157
|
min_containers: typing.Optional[int] = None,
|
151
158
|
max_containers: typing.Optional[int] = None,
|
159
|
+
buffer_containers: typing.Optional[int] = None,
|
152
160
|
) -> int:
|
153
161
|
"""Return the target number of containers following (simplified) Kubernetes HPA
|
154
162
|
stabilization-window semantics.
|
@@ -181,6 +189,7 @@ class FlashPrometheusAutoscaler:
|
|
181
189
|
target_metric_value: float,
|
182
190
|
min_containers: typing.Optional[int],
|
183
191
|
max_containers: typing.Optional[int],
|
192
|
+
buffer_containers: typing.Optional[int],
|
184
193
|
scale_up_tolerance: float,
|
185
194
|
scale_down_tolerance: float,
|
186
195
|
scale_up_stabilization_window_seconds: int,
|
@@ -247,6 +256,7 @@ class FlashPrometheusAutoscaler:
|
|
247
256
|
scale_down_stabilization_window_seconds: int = 300,
|
248
257
|
min_containers: typing.Optional[int] = None,
|
249
258
|
max_containers: typing.Optional[int] = None,
|
259
|
+
buffer_containers: typing.Optional[int] = None,
|
250
260
|
) -> int:
|
251
261
|
"""Return the target number of containers following (simplified) Kubernetes HPA
|
252
262
|
stabilization-window semantics.
|
@@ -288,6 +298,7 @@ class __flash_prometheus_autoscaler_spec(typing_extensions.Protocol):
|
|
288
298
|
scale_up_stabilization_window_seconds: int = 0,
|
289
299
|
scale_down_stabilization_window_seconds: int = 300,
|
290
300
|
autoscaling_interval_seconds: int = 15,
|
301
|
+
buffer_containers: typing.Optional[int] = None,
|
291
302
|
) -> FlashPrometheusAutoscaler:
|
292
303
|
"""Autoscale a Flash service based on containers' Prometheus metrics.
|
293
304
|
|
@@ -313,6 +324,7 @@ class __flash_prometheus_autoscaler_spec(typing_extensions.Protocol):
|
|
313
324
|
scale_up_stabilization_window_seconds: int = 0,
|
314
325
|
scale_down_stabilization_window_seconds: int = 300,
|
315
326
|
autoscaling_interval_seconds: int = 15,
|
327
|
+
buffer_containers: typing.Optional[int] = None,
|
316
328
|
) -> FlashPrometheusAutoscaler:
|
317
329
|
"""Autoscale a Flash service based on containers' Prometheus metrics.
|
318
330
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|