modal 1.1.4.dev29__tar.gz → 1.1.4.dev33__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.4.dev29 → modal-1.1.4.dev33}/PKG-INFO +1 -1
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/client.pyi +2 -2
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/experimental/flash.py +81 -8
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/experimental/flash.pyi +51 -6
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/sandbox.py +16 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/sandbox.pyi +1 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_version/__init__.py +1 -1
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/LICENSE +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/README.md +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/__init__.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/__main__.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_clustered_functions.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_clustered_functions.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_container_entrypoint.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_functions.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_ipython.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_location.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_object.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_output.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_partial_function.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_pty.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_resolver.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_resources.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/__init__.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/asgi.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/execution_context.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/telemetry.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_serialization.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_traceback.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_tunnel.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_tunnel.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_type_manager.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/__init__.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/app_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/async_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/blob_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/deprecation.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/docker_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/function_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/git_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/grpc_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/hash_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/http_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/logger.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/mount_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/name_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/package_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/shell_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_utils/time_utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_vendor/__init__.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_vendor/tblib.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/_watcher.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/app.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/app.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/2023.12.txt +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/2024.04.txt +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/2024.10.txt +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/2025.06.txt +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/README.md +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/builder/base-images.json +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/call_graph.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/__init__.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/_download.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/_traceback.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/app.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/cluster.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/config.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/container.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/dict.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/entry_point.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/environment.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/import_refs.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/launch.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/network_file_system.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/profile.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/programs/__init__.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/programs/launch_instance_ssh.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/programs/run_marimo.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/programs/vscode.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/queues.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/run.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/secret.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/token.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/utils.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cli/volume.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/client.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cls.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/cls.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/config.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/container_process.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/container_process.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/dict.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/dict.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/environments.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/environments.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/exception.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/experimental/__init__.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/experimental/ipython.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/file_io.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/file_io.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/file_pattern_matcher.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/functions.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/functions.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/gpu.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/image.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/image.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/io_streams.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/io_streams.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/mount.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/mount.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/network_file_system.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/network_file_system.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/object.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/object.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/output.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/parallel_map.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/parallel_map.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/partial_function.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/partial_function.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/proxy.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/proxy.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/py.typed +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/queue.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/queue.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/retries.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/runner.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/runner.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/running_app.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/schedule.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/scheduler_placement.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/secret.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/secret.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/serving.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/serving.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/snapshot.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/snapshot.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/stream_type.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/token_flow.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/token_flow.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/volume.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal/volume.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal.egg-info/requires.txt +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal.egg-info/top_level.txt +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_docs/__init__.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/__init__.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/api.proto +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/api_grpc.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/api_pb2.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/api_pb2.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/modal_options_grpc.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/options.proto +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/options_grpc.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/options_pb2.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/options_pb2.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/options_pb2_grpc.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/options_pb2_grpc.pyi +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_proto/py.typed +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/modal_version/__main__.py +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/pyproject.toml +0 -0
- {modal-1.1.4.dev29 → modal-1.1.4.dev33}/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.4.
|
36
|
+
version: str = "1.1.4.dev33",
|
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.4.
|
167
|
+
version: str = "1.1.4.dev33",
|
168
168
|
):
|
169
169
|
"""mdmd:hidden
|
170
170
|
The Modal client object is not intended to be instantiated directly by users.
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# Copyright Modal Labs 2025
|
2
2
|
import asyncio
|
3
3
|
import math
|
4
|
+
import os
|
5
|
+
import subprocess
|
4
6
|
import sys
|
5
7
|
import time
|
6
8
|
import traceback
|
@@ -19,28 +21,87 @@ from ..client import _Client
|
|
19
21
|
from ..config import logger
|
20
22
|
from ..exception import InvalidError
|
21
23
|
|
24
|
+
MAX_FAILURES = 3
|
25
|
+
|
22
26
|
|
23
27
|
class _FlashManager:
|
24
|
-
def __init__(
|
28
|
+
def __init__(
|
29
|
+
self,
|
30
|
+
client: _Client,
|
31
|
+
port: int,
|
32
|
+
process: Optional[subprocess.Popen] = None,
|
33
|
+
health_check_url: Optional[str] = None,
|
34
|
+
):
|
25
35
|
self.client = client
|
26
36
|
self.port = port
|
37
|
+
# Health check is not currently being used
|
27
38
|
self.health_check_url = health_check_url
|
39
|
+
self.process = process
|
28
40
|
self.tunnel_manager = _forward_tunnel(port, client=client)
|
29
41
|
self.stopped = False
|
42
|
+
self.num_failures = 0
|
43
|
+
self.task_id = os.environ["MODAL_TASK_ID"]
|
44
|
+
|
45
|
+
async def check_port_connection(self, process: Optional[subprocess.Popen], timeout: int = 10):
|
46
|
+
import socket
|
47
|
+
|
48
|
+
start_time = time.monotonic()
|
49
|
+
|
50
|
+
while time.monotonic() - start_time < timeout:
|
51
|
+
try:
|
52
|
+
if process is not None and process.poll() is not None:
|
53
|
+
return Exception(f"Process {process.pid} exited with code {process.returncode}")
|
54
|
+
with socket.create_connection(("localhost", self.port), timeout=1):
|
55
|
+
return
|
56
|
+
except (ConnectionRefusedError, OSError):
|
57
|
+
await asyncio.sleep(0.1)
|
58
|
+
|
59
|
+
return Exception(f"Waited too long for port {self.port} to start accepting connections")
|
30
60
|
|
31
61
|
async def _start(self):
|
32
62
|
self.tunnel = await self.tunnel_manager.__aenter__()
|
33
|
-
|
34
63
|
parsed_url = urlparse(self.tunnel.url)
|
35
64
|
host = parsed_url.hostname
|
36
65
|
port = parsed_url.port or 443
|
37
66
|
|
38
67
|
self.heartbeat_task = asyncio.create_task(self._run_heartbeat(host, port))
|
68
|
+
self.drain_task = asyncio.create_task(self._drain_container())
|
69
|
+
|
70
|
+
async def _drain_container(self):
|
71
|
+
"""
|
72
|
+
Background task that checks if we've encountered too many failures and drains the container if so.
|
73
|
+
"""
|
74
|
+
while True:
|
75
|
+
try:
|
76
|
+
# Check if the container should be drained (e.g., too many failures)
|
77
|
+
if self.num_failures > MAX_FAILURES:
|
78
|
+
logger.warning(
|
79
|
+
f"[Modal Flash] Draining task {self.task_id} on {self.tunnel.url} due to too many failures."
|
80
|
+
)
|
81
|
+
await self.stop()
|
82
|
+
# handle close upon container exit
|
83
|
+
|
84
|
+
if self.task_id:
|
85
|
+
await self.client.stub.ContainerStop(api_pb2.ContainerStopRequest(task_id=self.task_id))
|
86
|
+
return
|
87
|
+
except asyncio.CancelledError:
|
88
|
+
logger.warning("[Modal Flash] Shutting down...")
|
89
|
+
return
|
90
|
+
except Exception as e:
|
91
|
+
logger.error(f"[Modal Flash] Error draining container: {e}")
|
92
|
+
await asyncio.sleep(1)
|
93
|
+
|
94
|
+
try:
|
95
|
+
await asyncio.sleep(1)
|
96
|
+
except asyncio.CancelledError:
|
97
|
+
logger.warning("[Modal Flash] Shutting down...")
|
98
|
+
return
|
39
99
|
|
40
100
|
async def _run_heartbeat(self, host: str, port: int):
|
41
101
|
first_registration = True
|
42
102
|
while True:
|
43
103
|
try:
|
104
|
+
await self.check_port_connection(process=self.process)
|
44
105
|
resp = await self.client.stub.FlashContainerRegister(
|
45
106
|
api_pb2.FlashContainerRegisterRequest(
|
46
107
|
priority=10,
|
@@ -50,14 +111,25 @@ class _FlashManager:
|
|
50
111
|
),
|
51
112
|
timeout=10,
|
52
113
|
)
|
114
|
+
self.num_failures = 0
|
53
115
|
if first_registration:
|
54
|
-
logger.warning(
|
116
|
+
logger.warning(
|
117
|
+
f"[Modal Flash] Listening at {resp.url} over {self.tunnel.url} for task_id {self.task_id}"
|
118
|
+
)
|
55
119
|
first_registration = False
|
56
120
|
except asyncio.CancelledError:
|
57
121
|
logger.warning("[Modal Flash] Shutting down...")
|
58
122
|
break
|
59
123
|
except Exception as e:
|
60
124
|
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
|
+
)
|
61
133
|
|
62
134
|
try:
|
63
135
|
await asyncio.sleep(1)
|
@@ -94,16 +166,17 @@ FlashManager = synchronize_api(_FlashManager)
|
|
94
166
|
|
95
167
|
|
96
168
|
@synchronizer.create_blocking
|
97
|
-
async def flash_forward(
|
169
|
+
async def flash_forward(
|
170
|
+
port: int, process: Optional[subprocess.Popen] = None, health_check_url: Optional[str] = None
|
171
|
+
) -> _FlashManager:
|
98
172
|
"""
|
99
173
|
Forward a port to the Modal Flash service, exposing that port as a stable web endpoint.
|
100
|
-
|
101
174
|
This is a highly experimental method that can break or be removed at any time without warning.
|
102
175
|
Do not use this method unless explicitly instructed to do so by Modal support.
|
103
176
|
"""
|
104
177
|
client = await _Client.from_env()
|
105
178
|
|
106
|
-
manager = _FlashManager(client, port, health_check_url)
|
179
|
+
manager = _FlashManager(client, port, process=process, health_check_url=health_check_url)
|
107
180
|
await manager._start()
|
108
181
|
return manager
|
109
182
|
|
@@ -127,6 +200,8 @@ class _FlashPrometheusAutoscaler:
|
|
127
200
|
scale_down_stabilization_window_seconds: int,
|
128
201
|
autoscaling_interval_seconds: int,
|
129
202
|
):
|
203
|
+
import aiohttp
|
204
|
+
|
130
205
|
if scale_up_stabilization_window_seconds > self._max_window_seconds:
|
131
206
|
raise InvalidError(
|
132
207
|
f"scale_up_stabilization_window_seconds must be less than or equal to {self._max_window_seconds}"
|
@@ -138,8 +213,6 @@ class _FlashPrometheusAutoscaler:
|
|
138
213
|
if target_metric_value <= 0:
|
139
214
|
raise InvalidError("target_metric_value must be greater than 0")
|
140
215
|
|
141
|
-
import aiohttp
|
142
|
-
|
143
216
|
self.client = client
|
144
217
|
self.app_name = app_name
|
145
218
|
self.cls_name = cls_name
|
@@ -1,14 +1,26 @@
|
|
1
1
|
import modal.client
|
2
2
|
import modal_proto.api_pb2
|
3
|
+
import subprocess
|
3
4
|
import typing
|
4
5
|
import typing_extensions
|
5
6
|
|
6
7
|
class _FlashManager:
|
7
|
-
def __init__(
|
8
|
+
def __init__(
|
9
|
+
self,
|
10
|
+
client: modal.client._Client,
|
11
|
+
port: int,
|
12
|
+
process: typing.Optional[subprocess.Popen] = None,
|
13
|
+
health_check_url: typing.Optional[str] = None,
|
14
|
+
):
|
8
15
|
"""Initialize self. See help(type(self)) for accurate signature."""
|
9
16
|
...
|
10
17
|
|
18
|
+
async def check_port_connection(self, process: typing.Optional[subprocess.Popen], timeout: int = 10): ...
|
11
19
|
async def _start(self): ...
|
20
|
+
async def _drain_container(self):
|
21
|
+
"""Background task that checks if we've encountered too many failures and drains the container if so."""
|
22
|
+
...
|
23
|
+
|
12
24
|
async def _run_heartbeat(self, host: str, port: int): ...
|
13
25
|
def get_container_url(self): ...
|
14
26
|
async def stop(self): ...
|
@@ -17,7 +29,19 @@ class _FlashManager:
|
|
17
29
|
SUPERSELF = typing.TypeVar("SUPERSELF", covariant=True)
|
18
30
|
|
19
31
|
class FlashManager:
|
20
|
-
def __init__(
|
32
|
+
def __init__(
|
33
|
+
self,
|
34
|
+
client: modal.client.Client,
|
35
|
+
port: int,
|
36
|
+
process: typing.Optional[subprocess.Popen] = None,
|
37
|
+
health_check_url: typing.Optional[str] = None,
|
38
|
+
): ...
|
39
|
+
|
40
|
+
class __check_port_connection_spec(typing_extensions.Protocol[SUPERSELF]):
|
41
|
+
def __call__(self, /, process: typing.Optional[subprocess.Popen], timeout: int = 10): ...
|
42
|
+
async def aio(self, /, process: typing.Optional[subprocess.Popen], timeout: int = 10): ...
|
43
|
+
|
44
|
+
check_port_connection: __check_port_connection_spec[typing_extensions.Self]
|
21
45
|
|
22
46
|
class ___start_spec(typing_extensions.Protocol[SUPERSELF]):
|
23
47
|
def __call__(self, /): ...
|
@@ -25,6 +49,17 @@ class FlashManager:
|
|
25
49
|
|
26
50
|
_start: ___start_spec[typing_extensions.Self]
|
27
51
|
|
52
|
+
class ___drain_container_spec(typing_extensions.Protocol[SUPERSELF]):
|
53
|
+
def __call__(self, /):
|
54
|
+
"""Background task that checks if we've encountered too many failures and drains the container if so."""
|
55
|
+
...
|
56
|
+
|
57
|
+
async def aio(self, /):
|
58
|
+
"""Background task that checks if we've encountered too many failures and drains the container if so."""
|
59
|
+
...
|
60
|
+
|
61
|
+
_drain_container: ___drain_container_spec[typing_extensions.Self]
|
62
|
+
|
28
63
|
class ___run_heartbeat_spec(typing_extensions.Protocol[SUPERSELF]):
|
29
64
|
def __call__(self, /, host: str, port: int): ...
|
30
65
|
async def aio(self, /, host: str, port: int): ...
|
@@ -46,17 +81,27 @@ class FlashManager:
|
|
46
81
|
close: __close_spec[typing_extensions.Self]
|
47
82
|
|
48
83
|
class __flash_forward_spec(typing_extensions.Protocol):
|
49
|
-
def __call__(
|
84
|
+
def __call__(
|
85
|
+
self,
|
86
|
+
/,
|
87
|
+
port: int,
|
88
|
+
process: typing.Optional[subprocess.Popen] = None,
|
89
|
+
health_check_url: typing.Optional[str] = None,
|
90
|
+
) -> FlashManager:
|
50
91
|
"""Forward a port to the Modal Flash service, exposing that port as a stable web endpoint.
|
51
|
-
|
52
92
|
This is a highly experimental method that can break or be removed at any time without warning.
|
53
93
|
Do not use this method unless explicitly instructed to do so by Modal support.
|
54
94
|
"""
|
55
95
|
...
|
56
96
|
|
57
|
-
async def aio(
|
97
|
+
async def aio(
|
98
|
+
self,
|
99
|
+
/,
|
100
|
+
port: int,
|
101
|
+
process: typing.Optional[subprocess.Popen] = None,
|
102
|
+
health_check_url: typing.Optional[str] = None,
|
103
|
+
) -> FlashManager:
|
58
104
|
"""Forward a port to the Modal Flash service, exposing that port as a stable web endpoint.
|
59
|
-
|
60
105
|
This is a highly experimental method that can break or be removed at any time without warning.
|
61
106
|
Do not use this method unless explicitly instructed to do so by Modal support.
|
62
107
|
"""
|
@@ -24,6 +24,7 @@ from ._utils.async_utils import TaskContext, synchronize_api
|
|
24
24
|
from ._utils.deprecation import deprecation_warning
|
25
25
|
from ._utils.grpc_utils import retry_transient_errors
|
26
26
|
from ._utils.mount_utils import validate_network_file_systems, validate_volumes
|
27
|
+
from ._utils.name_utils import is_valid_object_name
|
27
28
|
from .client import _Client
|
28
29
|
from .config import config
|
29
30
|
from .container_process import _ContainerProcess
|
@@ -73,6 +74,16 @@ def _validate_exec_args(args: Sequence[str]) -> None:
|
|
73
74
|
)
|
74
75
|
|
75
76
|
|
77
|
+
def _warn_if_invalid_name(name: str) -> None:
|
78
|
+
if not is_valid_object_name(name):
|
79
|
+
deprecation_warning(
|
80
|
+
(2025, 9, 3),
|
81
|
+
f"Sandbox name '{name}' will be considered invalid in a future release."
|
82
|
+
"\n\nNames may contain only alphanumeric characters, dashes, periods, and underscores,"
|
83
|
+
" must be shorter than 64 characters, and cannot conflict with App ID strings.",
|
84
|
+
)
|
85
|
+
|
86
|
+
|
76
87
|
class DefaultSandboxNameOverride(str):
|
77
88
|
"""A singleton class that represents the default sandbox name override.
|
78
89
|
|
@@ -404,6 +415,8 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
404
415
|
from .app import _App
|
405
416
|
|
406
417
|
_validate_exec_args(args)
|
418
|
+
if name is not None:
|
419
|
+
_warn_if_invalid_name(name)
|
407
420
|
|
408
421
|
if block_network and (encrypted_ports or h2_ports or unencrypted_ports):
|
409
422
|
raise InvalidError("Cannot specify open ports when `block_network` is enabled")
|
@@ -787,6 +800,9 @@ class _Sandbox(_Object, type_prefix="sb"):
|
|
787
800
|
):
|
788
801
|
client = client or await _Client.from_env()
|
789
802
|
|
803
|
+
if name is not None and name != _DEFAULT_SANDBOX_NAME_OVERRIDE:
|
804
|
+
_warn_if_invalid_name(name)
|
805
|
+
|
790
806
|
if name is _DEFAULT_SANDBOX_NAME_OVERRIDE:
|
791
807
|
restore_req = api_pb2.SandboxRestoreRequest(
|
792
808
|
snapshot_id=snapshot.object_id,
|
@@ -26,6 +26,7 @@ import typing
|
|
26
26
|
import typing_extensions
|
27
27
|
|
28
28
|
def _validate_exec_args(args: collections.abc.Sequence[str]) -> None: ...
|
29
|
+
def _warn_if_invalid_name(name: str) -> None: ...
|
29
30
|
|
30
31
|
class DefaultSandboxNameOverride(str):
|
31
32
|
"""A singleton class that represents the default sandbox name override.
|
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
|