modAL 1.2.3.dev13__tar.gz → 1.2.3.dev14__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.dev14}/PKG-INFO +1 -1
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/grpc_utils.py +90 -20
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/client.pyi +2 -2
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/config.py +8 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal.egg-info/PKG-INFO +1 -1
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/api.proto +6 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/api_pb2.py +363 -353
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/api_pb2.pyi +18 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_version/__init__.py +1 -1
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/LICENSE +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/README.md +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/__main__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_billing.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_clustered_functions.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_clustered_functions.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_container_entrypoint.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_functions.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_grpc_client.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_ipython.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_load_context.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_location.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_object.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_output.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_partial_function.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_pty.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_resolver.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_resources.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/asgi.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/container_io_manager.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/execution_context.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/telemetry.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_serialization.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_traceback.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_tunnel.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_tunnel.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_type_manager.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/app_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/async_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/auth_token_manager.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/blob_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/deprecation.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/docker_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/function_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/git_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/hash_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/http_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/logger.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/mount_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/name_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/package_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/shell_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/task_command_router_client.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_utils/time_utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_vendor/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_vendor/tblib.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/_watcher.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/app.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/app.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/billing.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/2023.12.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/2024.04.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/2024.10.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/2025.06.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/README.md +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/builder/base-images.json +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/call_graph.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/_download.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/_traceback.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/app.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/cluster.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/config.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/container.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/dict.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/entry_point.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/environment.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/import_refs.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/launch.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/network_file_system.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/profile.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/programs/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/programs/launch_instance_ssh.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/programs/run_marimo.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/programs/vscode.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/queues.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/run.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/secret.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/token.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/utils.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cli/volume.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/client.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cls.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/cls.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/container_process.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/container_process.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/dict.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/dict.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/environments.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/environments.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/exception.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/experimental/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/experimental/flash.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/experimental/flash.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/experimental/ipython.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/file_io.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/file_io.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/file_pattern_matcher.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/functions.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/functions.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/gpu.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/image.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/image.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/io_streams.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/io_streams.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/mount.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/mount.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/network_file_system.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/network_file_system.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/object.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/object.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/output.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/parallel_map.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/parallel_map.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/partial_function.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/partial_function.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/proxy.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/proxy.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/py.typed +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/queue.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/queue.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/retries.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/runner.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/runner.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/running_app.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/sandbox.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/sandbox.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/schedule.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/scheduler_placement.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/secret.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/secret.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/serving.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/serving.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/snapshot.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/snapshot.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/stream_type.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/token_flow.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/token_flow.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/volume.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal/volume.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal.egg-info/SOURCES.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal.egg-info/requires.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal.egg-info/top_level.txt +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_docs/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/__init__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/api_grpc.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/modal_api_grpc.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/py.typed +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/sandbox_router.proto +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/sandbox_router_grpc.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/sandbox_router_pb2.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/sandbox_router_pb2.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/task_command_router.proto +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/modal_version/__main__.py +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/pyproject.toml +0 -0
- {modal-1.2.3.dev13 → modal-1.2.3.dev14}/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,50 @@ 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
|
+
raise exc
|
|
296
|
+
|
|
297
|
+
logger.debug(f"Retryable failure {repr(exc)} {n_retries=} {delay=} for {fn_name} ({idempotency_key[:8]})")
|
|
298
|
+
|
|
299
|
+
|
|
254
300
|
async def _retry_transient_errors(
|
|
255
301
|
fn: typing.Union[
|
|
256
302
|
"modal._grpc_client.UnaryUnaryWrapper[RequestType, ResponseType]",
|
|
@@ -273,12 +319,15 @@ async def _retry_transient_errors(
|
|
|
273
319
|
|
|
274
320
|
delay = retry.base_delay
|
|
275
321
|
n_retries = 0
|
|
322
|
+
n_throttled_retries = 0
|
|
276
323
|
|
|
277
324
|
status_codes = [*RETRYABLE_GRPC_STATUS_CODES, *retry.additional_status_codes]
|
|
278
325
|
|
|
279
326
|
idempotency_key = str(uuid.uuid4())
|
|
280
327
|
|
|
281
328
|
t0 = time.time()
|
|
329
|
+
last_server_retry_warning_time = None
|
|
330
|
+
|
|
282
331
|
if retry.total_timeout is not None:
|
|
283
332
|
total_deadline = t0 + retry.total_timeout
|
|
284
333
|
else:
|
|
@@ -290,14 +339,18 @@ async def _retry_transient_errors(
|
|
|
290
339
|
attempt_metadata = [
|
|
291
340
|
("x-idempotency-key", idempotency_key),
|
|
292
341
|
("x-retry-attempt", str(n_retries)),
|
|
342
|
+
("x-throttle-retry-attempt", str(n_throttled_retries)),
|
|
293
343
|
*metadata,
|
|
294
344
|
]
|
|
295
345
|
if n_retries > 0:
|
|
296
346
|
attempt_metadata.append(("x-retry-delay", str(time.time() - t0)))
|
|
347
|
+
if n_throttled_retries > 0:
|
|
348
|
+
attempt_metadata.append(("x-throttle-retry-delay", str(time.time() - t0)))
|
|
349
|
+
|
|
297
350
|
timeouts = []
|
|
298
351
|
if retry.attempt_timeout is not None:
|
|
299
352
|
timeouts.append(retry.attempt_timeout)
|
|
300
|
-
if
|
|
353
|
+
if total_deadline is not None:
|
|
301
354
|
timeouts.append(max(total_deadline - time.time(), retry.attempt_timeout_floor))
|
|
302
355
|
if timeouts:
|
|
303
356
|
timeout = min(timeouts) # In case the function provided both types of timeouts
|
|
@@ -307,6 +360,39 @@ async def _retry_transient_errors(
|
|
|
307
360
|
with suppress_tb_frames(1):
|
|
308
361
|
return await fn_callable(req, metadata=attempt_metadata, timeout=timeout)
|
|
309
362
|
except (StreamTerminatedError, GRPCError, OSError, asyncio.TimeoutError, AttributeError) as exc:
|
|
363
|
+
# Server side instruction for retries
|
|
364
|
+
if isinstance(exc, GRPCError) and (server_retry_policy := get_server_retry_policy(exc)):
|
|
365
|
+
server_delay = server_retry_policy.retry_after_secs
|
|
366
|
+
|
|
367
|
+
# When `total_deadline` is not defined and server gives instruction for retries, then use
|
|
368
|
+
# `max_throttle_wait`.
|
|
369
|
+
if total_deadline is None and (max_throttle_wait := config.get("max_throttle_wait")):
|
|
370
|
+
total_deadline = t0 + max_throttle_wait
|
|
371
|
+
|
|
372
|
+
final_attempt = (
|
|
373
|
+
total_deadline is not None
|
|
374
|
+
and time.time() + server_delay + retry.attempt_timeout_floor >= total_deadline
|
|
375
|
+
)
|
|
376
|
+
with suppress_tb_frames(1):
|
|
377
|
+
process_exception_before_retry(
|
|
378
|
+
exc, final_attempt, fn.name, n_retries, server_delay, total_deadline, idempotency_key
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
now = time.time()
|
|
382
|
+
if last_server_retry_warning_time is None or (
|
|
383
|
+
now - last_server_retry_warning_time >= SERVER_RETRY_WARNING_TIME_INTERVAL
|
|
384
|
+
):
|
|
385
|
+
last_server_retry_warning_time = now
|
|
386
|
+
logger.warning(
|
|
387
|
+
f"Warning: Received failure {exc.status}: {exc.message}. "
|
|
388
|
+
f"Will retry in {server_delay:0.2f} seconds."
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
n_throttled_retries += 1
|
|
392
|
+
await asyncio.sleep(server_delay)
|
|
393
|
+
continue
|
|
394
|
+
|
|
395
|
+
# Client handles retry
|
|
310
396
|
if isinstance(exc, GRPCError) and exc.status not in status_codes:
|
|
311
397
|
if exc.status == Status.UNAUTHENTICATED:
|
|
312
398
|
raise AuthError(exc.message)
|
|
@@ -320,25 +406,9 @@ async def _retry_transient_errors(
|
|
|
320
406
|
final_attempt = False
|
|
321
407
|
|
|
322
408
|
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]})")
|
|
409
|
+
process_exception_before_retry(
|
|
410
|
+
exc, final_attempt, fn.name, n_retries, delay, total_deadline, idempotency_key
|
|
411
|
+
)
|
|
342
412
|
|
|
343
413
|
n_retries += 1
|
|
344
414
|
|
|
@@ -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.dev14",
|
|
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.dev14",
|
|
167
167
|
):
|
|
168
168
|
"""mdmd:hidden
|
|
169
169
|
The Modal client object is not intended to be instantiated directly by users.
|
|
@@ -217,6 +217,13 @@ def _enforce_suffix_rules(x: str) -> str:
|
|
|
217
217
|
return x
|
|
218
218
|
|
|
219
219
|
|
|
220
|
+
def _int_or_none(x: str) -> Optional[int]:
|
|
221
|
+
if not x:
|
|
222
|
+
return None
|
|
223
|
+
x_int = int(x)
|
|
224
|
+
return x_int if x_int > 0 else None
|
|
225
|
+
|
|
226
|
+
|
|
220
227
|
class _Setting(typing.NamedTuple):
|
|
221
228
|
default: typing.Any = None
|
|
222
229
|
transform: typing.Callable[[str], typing.Any] = lambda x: x # noqa: E731
|
|
@@ -258,6 +265,7 @@ _SETTINGS = {
|
|
|
258
265
|
transform=lambda s: _check_value(["pickle", "cbor"])(s.lower()),
|
|
259
266
|
),
|
|
260
267
|
"dev_suffix": _Setting("", transform=_enforce_suffix_rules),
|
|
268
|
+
"max_throttle_wait": _Setting(None, transform=_int_or_none),
|
|
261
269
|
}
|
|
262
270
|
|
|
263
271
|
|
|
@@ -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
|