modal 1.2.3.dev13__tar.gz → 1.2.3.dev15__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.2.3.dev13 → modal-1.2.3.dev15}/PKG-INFO +1 -1
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/grpc_utils.py +95 -20
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/client.pyi +2 -2
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/config.py +12 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/functions.pyi +6 -6
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/api.proto +6 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/api_pb2.py +363 -353
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/api_pb2.pyi +18 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_version/__init__.py +1 -1
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/LICENSE +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/README.md +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/__main__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_billing.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_clustered_functions.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_clustered_functions.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_container_entrypoint.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_functions.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_grpc_client.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_ipython.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_load_context.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_location.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_object.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_output.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_partial_function.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_pty.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_resolver.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_resources.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_runtime/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_runtime/asgi.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_runtime/execution_context.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_runtime/telemetry.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_serialization.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_traceback.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_tunnel.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_tunnel.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_type_manager.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/app_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/async_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/blob_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/deprecation.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/docker_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/function_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/git_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/hash_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/http_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/logger.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/mount_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/name_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/package_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/shell_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_utils/time_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_vendor/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_vendor/tblib.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/_watcher.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/app.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/app.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/billing.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/builder/2023.12.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/builder/2024.04.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/builder/2024.10.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/builder/2025.06.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/builder/README.md +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/builder/base-images.json +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/call_graph.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/_download.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/_traceback.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/app.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/cluster.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/config.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/container.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/dict.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/entry_point.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/environment.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/import_refs.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/launch.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/network_file_system.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/profile.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/programs/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/programs/launch_instance_ssh.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/programs/run_marimo.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/programs/vscode.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/queues.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/run.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/secret.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/token.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cli/volume.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/client.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cls.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/cls.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/container_process.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/container_process.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/dict.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/dict.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/environments.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/environments.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/exception.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/experimental/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/experimental/flash.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/experimental/flash.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/experimental/ipython.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/file_io.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/file_io.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/file_pattern_matcher.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/functions.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/gpu.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/image.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/image.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/io_streams.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/io_streams.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/mount.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/mount.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/network_file_system.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/network_file_system.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/object.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/object.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/output.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/parallel_map.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/parallel_map.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/partial_function.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/partial_function.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/proxy.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/proxy.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/py.typed +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/queue.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/queue.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/retries.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/runner.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/runner.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/running_app.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/sandbox.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/sandbox.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/schedule.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/scheduler_placement.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/secret.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/secret.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/serving.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/serving.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/snapshot.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/snapshot.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/stream_type.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/token_flow.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/token_flow.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/volume.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal/volume.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal.egg-info/requires.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal.egg-info/top_level.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_docs/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/api_grpc.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/py.typed +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/sandbox_router.proto +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/sandbox_router_grpc.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/sandbox_router_pb2.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/sandbox_router_pb2.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/task_command_router.proto +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/modal_version/__main__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/pyproject.toml +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev15}/setup.cfg +0 -0
|
@@ -29,6 +29,7 @@ from modal_proto import api_pb2
|
|
|
29
29
|
from modal_version import __version__
|
|
30
30
|
|
|
31
31
|
from .._traceback import suppress_tb_frames
|
|
32
|
+
from ..config import config
|
|
32
33
|
from .async_utils import retry
|
|
33
34
|
from .logger import logger
|
|
34
35
|
|
|
@@ -72,6 +73,7 @@ RETRYABLE_GRPC_STATUS_CODES = [
|
|
|
72
73
|
Status.INTERNAL,
|
|
73
74
|
Status.UNKNOWN,
|
|
74
75
|
]
|
|
76
|
+
SERVER_RETRY_WARNING_TIME_INTERVAL = 30.0
|
|
75
77
|
|
|
76
78
|
|
|
77
79
|
@dataclass
|
|
@@ -251,6 +253,52 @@ async def retry_transient_errors(
|
|
|
251
253
|
return await _retry_transient_errors(fn, req, retry=Retry(max_retries=max_retries))
|
|
252
254
|
|
|
253
255
|
|
|
256
|
+
def get_server_retry_policy(exc: Exception) -> Optional[api_pb2.RPCRetryPolicy]:
|
|
257
|
+
"""Get server retry policy."""
|
|
258
|
+
if not isinstance(exc, GRPCError) or not exc.details:
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
# Server should not set multiple retry instructions, but if there is more than one, pick the first one
|
|
262
|
+
for entry in exc.details:
|
|
263
|
+
if isinstance(entry, api_pb2.RPCRetryPolicy):
|
|
264
|
+
return entry
|
|
265
|
+
return None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def process_exception_before_retry(
|
|
269
|
+
exc: Exception,
|
|
270
|
+
final_attempt: bool,
|
|
271
|
+
fn_name: str,
|
|
272
|
+
n_retries: int,
|
|
273
|
+
delay: float,
|
|
274
|
+
total_deadline: Optional[float],
|
|
275
|
+
idempotency_key: str,
|
|
276
|
+
):
|
|
277
|
+
"""Process exception before retry, used by `_retry_transient_errors`."""
|
|
278
|
+
with suppress_tb_frames(1):
|
|
279
|
+
if final_attempt:
|
|
280
|
+
logger.debug(
|
|
281
|
+
f"Final attempt failed with {repr(exc)} {n_retries=} {delay=} "
|
|
282
|
+
f"{total_deadline=} for {fn_name} ({idempotency_key[:8]})"
|
|
283
|
+
)
|
|
284
|
+
if isinstance(exc, OSError):
|
|
285
|
+
raise ConnectionError(str(exc))
|
|
286
|
+
elif isinstance(exc, asyncio.TimeoutError):
|
|
287
|
+
raise ConnectionError(str(exc))
|
|
288
|
+
else:
|
|
289
|
+
raise exc
|
|
290
|
+
|
|
291
|
+
if isinstance(exc, AttributeError) and "_write_appdata" not in str(exc):
|
|
292
|
+
# StreamTerminatedError are not properly raised in grpclib<=0.4.7
|
|
293
|
+
# fixed in https://github.com/vmagamedov/grpclib/issues/185
|
|
294
|
+
# TODO: update to newer version (>=0.4.8) once stable
|
|
295
|
+
# Also be sure to remove the AttributeError from the set of exceptions
|
|
296
|
+
# we handle in the retry logic once we drop this check!
|
|
297
|
+
raise exc
|
|
298
|
+
|
|
299
|
+
logger.debug(f"Retryable failure {repr(exc)} {n_retries=} {delay=} for {fn_name} ({idempotency_key[:8]})")
|
|
300
|
+
|
|
301
|
+
|
|
254
302
|
async def _retry_transient_errors(
|
|
255
303
|
fn: typing.Union[
|
|
256
304
|
"modal._grpc_client.UnaryUnaryWrapper[RequestType, ResponseType]",
|
|
@@ -273,12 +321,15 @@ async def _retry_transient_errors(
|
|
|
273
321
|
|
|
274
322
|
delay = retry.base_delay
|
|
275
323
|
n_retries = 0
|
|
324
|
+
n_throttled_retries = 0
|
|
276
325
|
|
|
277
326
|
status_codes = [*RETRYABLE_GRPC_STATUS_CODES, *retry.additional_status_codes]
|
|
278
327
|
|
|
279
328
|
idempotency_key = str(uuid.uuid4())
|
|
280
329
|
|
|
281
330
|
t0 = time.time()
|
|
331
|
+
last_server_retry_warning_time = None
|
|
332
|
+
|
|
282
333
|
if retry.total_timeout is not None:
|
|
283
334
|
total_deadline = t0 + retry.total_timeout
|
|
284
335
|
else:
|
|
@@ -290,14 +341,18 @@ async def _retry_transient_errors(
|
|
|
290
341
|
attempt_metadata = [
|
|
291
342
|
("x-idempotency-key", idempotency_key),
|
|
292
343
|
("x-retry-attempt", str(n_retries)),
|
|
344
|
+
("x-throttle-retry-attempt", str(n_throttled_retries)),
|
|
293
345
|
*metadata,
|
|
294
346
|
]
|
|
295
347
|
if n_retries > 0:
|
|
296
348
|
attempt_metadata.append(("x-retry-delay", str(time.time() - t0)))
|
|
349
|
+
if n_throttled_retries > 0:
|
|
350
|
+
attempt_metadata.append(("x-throttle-retry-delay", str(time.time() - t0)))
|
|
351
|
+
|
|
297
352
|
timeouts = []
|
|
298
353
|
if retry.attempt_timeout is not None:
|
|
299
354
|
timeouts.append(retry.attempt_timeout)
|
|
300
|
-
if
|
|
355
|
+
if total_deadline is not None:
|
|
301
356
|
timeouts.append(max(total_deadline - time.time(), retry.attempt_timeout_floor))
|
|
302
357
|
if timeouts:
|
|
303
358
|
timeout = min(timeouts) # In case the function provided both types of timeouts
|
|
@@ -307,6 +362,42 @@ async def _retry_transient_errors(
|
|
|
307
362
|
with suppress_tb_frames(1):
|
|
308
363
|
return await fn_callable(req, metadata=attempt_metadata, timeout=timeout)
|
|
309
364
|
except (StreamTerminatedError, GRPCError, OSError, asyncio.TimeoutError, AttributeError) as exc:
|
|
365
|
+
# Note that we only catch AttributeError to handle a specific case that works around a bug
|
|
366
|
+
# in grpclib<=0.4.7. See above (search for `write_appdata`).
|
|
367
|
+
|
|
368
|
+
# Server side instruction for retries
|
|
369
|
+
if isinstance(exc, GRPCError) and (server_retry_policy := get_server_retry_policy(exc)):
|
|
370
|
+
server_delay = server_retry_policy.retry_after_secs
|
|
371
|
+
|
|
372
|
+
# When `total_deadline` is not defined and server gives instruction for retries, then use
|
|
373
|
+
# `max_throttle_wait`.
|
|
374
|
+
if total_deadline is None and (max_throttle_wait := config.get("max_throttle_wait")):
|
|
375
|
+
total_deadline = t0 + max_throttle_wait
|
|
376
|
+
|
|
377
|
+
final_attempt = (
|
|
378
|
+
total_deadline is not None
|
|
379
|
+
and time.time() + server_delay + retry.attempt_timeout_floor >= total_deadline
|
|
380
|
+
)
|
|
381
|
+
with suppress_tb_frames(1):
|
|
382
|
+
process_exception_before_retry(
|
|
383
|
+
exc, final_attempt, fn.name, n_retries, server_delay, total_deadline, idempotency_key
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
now = time.time()
|
|
387
|
+
if last_server_retry_warning_time is None or (
|
|
388
|
+
now - last_server_retry_warning_time >= SERVER_RETRY_WARNING_TIME_INTERVAL
|
|
389
|
+
):
|
|
390
|
+
last_server_retry_warning_time = now
|
|
391
|
+
logger.warning(
|
|
392
|
+
f"Warning: Received {exc.status} status: {exc.message}. "
|
|
393
|
+
f"Will retry in {server_delay:0.2f} seconds."
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
n_throttled_retries += 1
|
|
397
|
+
await asyncio.sleep(server_delay)
|
|
398
|
+
continue
|
|
399
|
+
|
|
400
|
+
# Client handles retry
|
|
310
401
|
if isinstance(exc, GRPCError) and exc.status not in status_codes:
|
|
311
402
|
if exc.status == Status.UNAUTHENTICATED:
|
|
312
403
|
raise AuthError(exc.message)
|
|
@@ -320,25 +411,9 @@ async def _retry_transient_errors(
|
|
|
320
411
|
final_attempt = False
|
|
321
412
|
|
|
322
413
|
with suppress_tb_frames(1):
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
f"{total_deadline=} for {fn.name} ({idempotency_key[:8]})"
|
|
327
|
-
)
|
|
328
|
-
if isinstance(exc, OSError):
|
|
329
|
-
raise ConnectionError(str(exc))
|
|
330
|
-
elif isinstance(exc, asyncio.TimeoutError):
|
|
331
|
-
raise ConnectionError(str(exc))
|
|
332
|
-
else:
|
|
333
|
-
raise exc
|
|
334
|
-
|
|
335
|
-
if isinstance(exc, AttributeError) and "_write_appdata" not in str(exc):
|
|
336
|
-
# StreamTerminatedError are not properly raised in grpclib<=0.4.7
|
|
337
|
-
# fixed in https://github.com/vmagamedov/grpclib/issues/185
|
|
338
|
-
# TODO: update to newer version (>=0.4.8) once stable
|
|
339
|
-
raise exc
|
|
340
|
-
|
|
341
|
-
logger.debug(f"Retryable failure {repr(exc)} {n_retries=} {delay=} for {fn.name} ({idempotency_key[:8]})")
|
|
414
|
+
process_exception_before_retry(
|
|
415
|
+
exc, final_attempt, fn.name, n_retries, delay, total_deadline, idempotency_key
|
|
416
|
+
)
|
|
342
417
|
|
|
343
418
|
n_retries += 1
|
|
344
419
|
|
|
@@ -32,7 +32,7 @@ class _Client:
|
|
|
32
32
|
server_url: str,
|
|
33
33
|
client_type: int,
|
|
34
34
|
credentials: typing.Optional[tuple[str, str]],
|
|
35
|
-
version: str = "1.2.3.
|
|
35
|
+
version: str = "1.2.3.dev15",
|
|
36
36
|
):
|
|
37
37
|
"""mdmd:hidden
|
|
38
38
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -163,7 +163,7 @@ class Client:
|
|
|
163
163
|
server_url: str,
|
|
164
164
|
client_type: int,
|
|
165
165
|
credentials: typing.Optional[tuple[str, str]],
|
|
166
|
-
version: str = "1.2.3.
|
|
166
|
+
version: str = "1.2.3.dev15",
|
|
167
167
|
):
|
|
168
168
|
"""mdmd:hidden
|
|
169
169
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -51,6 +51,10 @@ Other possible configuration options are:
|
|
|
51
51
|
Defaults to 10.
|
|
52
52
|
Number of seconds to wait for logs to drain when closing the session,
|
|
53
53
|
before giving up.
|
|
54
|
+
* `max_throttle_wait` (in the .toml file) / `MODAL_MAX_THROTTLE_WAIT` (as an env var).
|
|
55
|
+
Defaults to None (no limit).
|
|
56
|
+
Maximum number of seconds to wait when requests are being throttled (i.e., due
|
|
57
|
+
to rate limiting or other cases that can normally be resolved through backoff).
|
|
54
58
|
* `force_build` (in the .toml file) / `MODAL_FORCE_BUILD` (as an env var).
|
|
55
59
|
Defaults to False.
|
|
56
60
|
When set, ignores the Image cache and builds all Image layers. Note that this
|
|
@@ -217,6 +221,13 @@ def _enforce_suffix_rules(x: str) -> str:
|
|
|
217
221
|
return x
|
|
218
222
|
|
|
219
223
|
|
|
224
|
+
def _int_or_none(x: str) -> Optional[int]:
|
|
225
|
+
if not x:
|
|
226
|
+
return None
|
|
227
|
+
x_int = int(x)
|
|
228
|
+
return x_int if x_int > 0 else None
|
|
229
|
+
|
|
230
|
+
|
|
220
231
|
class _Setting(typing.NamedTuple):
|
|
221
232
|
default: typing.Any = None
|
|
222
233
|
transform: typing.Callable[[str], typing.Any] = lambda x: x # noqa: E731
|
|
@@ -258,6 +269,7 @@ _SETTINGS = {
|
|
|
258
269
|
transform=lambda s: _check_value(["pickle", "cbor"])(s.lower()),
|
|
259
270
|
),
|
|
260
271
|
"dev_suffix": _Setting("", transform=_enforce_suffix_rules),
|
|
272
|
+
"max_throttle_wait": _Setting(None, transform=_int_or_none),
|
|
261
273
|
}
|
|
262
274
|
|
|
263
275
|
|
|
@@ -408,7 +408,7 @@ class Function(
|
|
|
408
408
|
|
|
409
409
|
_call_generator: ___call_generator_spec[typing_extensions.Self]
|
|
410
410
|
|
|
411
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
411
|
+
class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
|
|
412
412
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
413
413
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
414
414
|
...
|
|
@@ -417,7 +417,7 @@ class Function(
|
|
|
417
417
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result."""
|
|
418
418
|
...
|
|
419
419
|
|
|
420
|
-
remote: __remote_spec[modal._functions.
|
|
420
|
+
remote: __remote_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
|
|
421
421
|
|
|
422
422
|
class __remote_gen_spec(typing_extensions.Protocol[SUPERSELF]):
|
|
423
423
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -444,7 +444,7 @@ class Function(
|
|
|
444
444
|
"""
|
|
445
445
|
...
|
|
446
446
|
|
|
447
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
447
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
|
|
448
448
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
449
449
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
450
450
|
|
|
@@ -468,7 +468,7 @@ class Function(
|
|
|
468
468
|
...
|
|
469
469
|
|
|
470
470
|
_experimental_spawn: ___experimental_spawn_spec[
|
|
471
|
-
modal._functions.
|
|
471
|
+
modal._functions.P, modal._functions.ReturnType, typing_extensions.Self
|
|
472
472
|
]
|
|
473
473
|
|
|
474
474
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER, SUPERSELF]):
|
|
@@ -477,7 +477,7 @@ class Function(
|
|
|
477
477
|
|
|
478
478
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P, typing_extensions.Self]
|
|
479
479
|
|
|
480
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
480
|
+
class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER, SUPERSELF]):
|
|
481
481
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
482
482
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
483
483
|
|
|
@@ -498,7 +498,7 @@ class Function(
|
|
|
498
498
|
"""
|
|
499
499
|
...
|
|
500
500
|
|
|
501
|
-
spawn: __spawn_spec[modal._functions.
|
|
501
|
+
spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType, typing_extensions.Self]
|
|
502
502
|
|
|
503
503
|
def get_raw_f(self) -> collections.abc.Callable[..., typing.Any]:
|
|
504
504
|
"""Return the inner Python object wrapped by this Modal Function."""
|
|
@@ -2642,6 +2642,12 @@ message QueuePutRequest {
|
|
|
2642
2642
|
int32 partition_ttl_seconds = 6;
|
|
2643
2643
|
}
|
|
2644
2644
|
|
|
2645
|
+
// Retry repolicy used by GRPCError.details for the server to give instructions
|
|
2646
|
+
// for the client to retry.
|
|
2647
|
+
message RPCRetryPolicy {
|
|
2648
|
+
float retry_after_secs = 1;
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2645
2651
|
// A copy google.rpc.Status for GRPCError.details:
|
|
2646
2652
|
// https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto
|
|
2647
2653
|
// RPCStatus is compatible with google.rpc.Status, so one can encode messages using
|