modal 1.2.2.dev8__tar.gz → 1.2.2.dev24__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.2.dev8 → modal-1.2.2.dev24}/PKG-INFO +2 -2
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_clustered_functions.py +1 -3
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_functions.py +33 -49
- modal-1.2.2.dev24/modal/_grpc_client.py +148 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_output.py +3 -4
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_runtime/container_io_manager.py +21 -22
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/async_utils.py +12 -3
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/auth_token_manager.py +1 -4
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/blob_utils.py +3 -4
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/grpc_utils.py +80 -51
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/task_command_router_client.py +3 -4
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/app.py +3 -4
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/config.py +3 -1
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/container.py +1 -2
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/network_file_system.py +1 -4
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/queues.py +1 -2
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/secret.py +1 -2
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/client.py +5 -115
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/client.pyi +2 -91
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cls.py +1 -2
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/config.py +1 -1
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/container_process.py +2 -5
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/dict.py +12 -12
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/environments.py +1 -2
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/experimental/__init__.py +2 -3
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/experimental/flash.py +6 -10
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/file_io.py +13 -27
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/image.py +3 -3
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/io_streams.py +3 -5
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/mount.py +4 -4
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/network_file_system.py +5 -6
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/parallel_map.py +29 -31
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/parallel_map.pyi +3 -9
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/queue.py +17 -18
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/runner.py +8 -8
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/sandbox.py +22 -40
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/sandbox.pyi +0 -1
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/secret.py +4 -5
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/snapshot.py +1 -4
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/token_flow.py +1 -1
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/volume.py +19 -21
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal.egg-info/PKG-INFO +2 -2
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal.egg-info/SOURCES.txt +1 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/api.proto +3 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/api_pb2.py +1028 -1015
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/api_pb2.pyi +29 -3
- modal-1.2.2.dev24/modal_proto/modal_api_grpc.py +194 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_version/__init__.py +1 -1
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/pyproject.toml +1 -1
- modal-1.2.2.dev8/modal_proto/modal_api_grpc.py +0 -194
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/LICENSE +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/README.md +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/__init__.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/__main__.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_billing.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_clustered_functions.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_container_entrypoint.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_ipython.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_location.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_object.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_partial_function.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_pty.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_resolver.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_resources.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_runtime/__init__.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_runtime/asgi.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_runtime/container_io_manager.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_runtime/execution_context.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_runtime/execution_context.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_runtime/gpu_memory_snapshot.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_runtime/telemetry.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_runtime/user_code_imports.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_serialization.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_traceback.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_tunnel.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_tunnel.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_type_manager.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/__init__.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/app_utils.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/bytes_io_segment_payload.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/deprecation.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/docker_utils.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/function_utils.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/git_utils.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/grpc_testing.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/hash_utils.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/http_utils.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/jwt_utils.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/logger.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/mount_utils.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/name_utils.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/package_utils.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/pattern_utils.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/rand_pb_testing.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/shell_utils.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_utils/time_utils.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_vendor/__init__.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_vendor/a2wsgi_wsgi.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_vendor/cloudpickle.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_vendor/tblib.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/_watcher.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/app.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/billing.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/builder/2023.12.312.txt +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/builder/2023.12.txt +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/builder/2024.04.txt +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/builder/2024.10.txt +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/builder/2025.06.txt +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/builder/PREVIEW.txt +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/builder/README.md +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/builder/base-images.json +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/call_graph.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/__init__.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/_download.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/_traceback.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/app.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/cluster.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/dict.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/entry_point.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/environment.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/import_refs.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/launch.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/profile.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/programs/__init__.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/programs/launch_instance_ssh.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/programs/run_jupyter.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/programs/run_marimo.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/programs/vscode.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/run.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/token.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/utils.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cli/volume.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cloud_bucket_mount.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cloud_bucket_mount.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/cls.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/container_process.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/dict.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/environments.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/exception.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/experimental/flash.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/experimental/ipython.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/file_io.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/file_pattern_matcher.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/functions.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/functions.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/gpu.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/image.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/io_streams.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/mount.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/network_file_system.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/object.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/object.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/output.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/partial_function.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/partial_function.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/proxy.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/proxy.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/py.typed +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/queue.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/retries.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/runner.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/running_app.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/schedule.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/scheduler_placement.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/secret.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/serving.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/serving.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/snapshot.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/stream_type.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/token_flow.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal/volume.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal.egg-info/dependency_links.txt +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal.egg-info/entry_points.txt +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal.egg-info/requires.txt +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal.egg-info/top_level.txt +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_docs/__init__.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_docs/gen_cli_docs.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_docs/gen_reference_docs.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_docs/mdmd/__init__.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_docs/mdmd/mdmd.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_docs/mdmd/signatures.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/__init__.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/api_grpc.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/api_pb2_grpc.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/api_pb2_grpc.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/py.typed +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/sandbox_router.proto +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/sandbox_router_grpc.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/sandbox_router_pb2.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/sandbox_router_pb2.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/sandbox_router_pb2_grpc.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/sandbox_router_pb2_grpc.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/task_command_router.proto +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/task_command_router_grpc.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/task_command_router_pb2.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/task_command_router_pb2.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/task_command_router_pb2_grpc.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_proto/task_command_router_pb2_grpc.pyi +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/modal_version/__main__.py +0 -0
- {modal-1.2.2.dev8 → modal-1.2.2.dev24}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modal
|
|
3
|
-
Version: 1.2.2.
|
|
3
|
+
Version: 1.2.2.dev24
|
|
4
4
|
Summary: Python client library for Modal
|
|
5
5
|
Author-email: Modal Labs <support@modal.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -13,7 +13,7 @@ Classifier: Topic :: System :: Distributed Computing
|
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Requires-Python:
|
|
16
|
+
Requires-Python: <3.14,>=3.9
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: aiohttp
|
|
@@ -5,7 +5,6 @@ from dataclasses import dataclass
|
|
|
5
5
|
from typing import Optional
|
|
6
6
|
|
|
7
7
|
from modal._utils.async_utils import synchronize_api
|
|
8
|
-
from modal._utils.grpc_utils import retry_transient_errors
|
|
9
8
|
from modal.client import _Client
|
|
10
9
|
from modal.exception import InvalidError
|
|
11
10
|
from modal_proto import api_pb2
|
|
@@ -61,8 +60,7 @@ async def _initialize_clustered_function(client: _Client, task_id: str, world_si
|
|
|
61
60
|
os.environ["NCCL_NSOCKS_PERTHREAD"] = "1"
|
|
62
61
|
|
|
63
62
|
if world_size > 1:
|
|
64
|
-
resp
|
|
65
|
-
client.stub.TaskClusterHello,
|
|
63
|
+
resp = await client.stub.TaskClusterHello(
|
|
66
64
|
api_pb2.TaskClusterHelloRequest(
|
|
67
65
|
task_id=task_id,
|
|
68
66
|
container_ip=container_ip,
|
|
@@ -53,7 +53,7 @@ from ._utils.function_utils import (
|
|
|
53
53
|
get_function_type,
|
|
54
54
|
is_async,
|
|
55
55
|
)
|
|
56
|
-
from ._utils.grpc_utils import
|
|
56
|
+
from ._utils.grpc_utils import Retry, RetryWarningMessage
|
|
57
57
|
from ._utils.mount_utils import validate_network_file_systems, validate_volumes
|
|
58
58
|
from .call_graph import InputInfo, _reconstruct_call_graph
|
|
59
59
|
from .client import _Client
|
|
@@ -164,21 +164,22 @@ class _Invocation:
|
|
|
164
164
|
|
|
165
165
|
if from_spawn_map:
|
|
166
166
|
request.from_spawn_map = True
|
|
167
|
-
response = await
|
|
168
|
-
client.stub.FunctionMap,
|
|
167
|
+
response = await client.stub.FunctionMap(
|
|
169
168
|
request,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
169
|
+
retry=Retry(
|
|
170
|
+
max_retries=None,
|
|
171
|
+
max_delay=30.0,
|
|
172
|
+
warning_message=RetryWarningMessage(
|
|
173
|
+
message="Warning: `.spawn_map(...)` for function `{self._function_name}` is waiting to create"
|
|
174
|
+
"more function calls. This may be due to hitting rate limits or function backlog limits.",
|
|
175
|
+
warning_interval=10,
|
|
176
|
+
errors_to_warn_for=[Status.RESOURCE_EXHAUSTED],
|
|
177
|
+
),
|
|
178
|
+
additional_status_codes=[Status.RESOURCE_EXHAUSTED],
|
|
177
179
|
),
|
|
178
|
-
additional_status_codes=[Status.RESOURCE_EXHAUSTED],
|
|
179
180
|
)
|
|
180
181
|
else:
|
|
181
|
-
response = await
|
|
182
|
+
response = await client.stub.FunctionMap(request)
|
|
182
183
|
|
|
183
184
|
function_call_id = response.function_call_id
|
|
184
185
|
if response.pipelined_inputs:
|
|
@@ -198,10 +199,7 @@ class _Invocation:
|
|
|
198
199
|
request_put = api_pb2.FunctionPutInputsRequest(
|
|
199
200
|
function_id=function_id, inputs=[item], function_call_id=function_call_id
|
|
200
201
|
)
|
|
201
|
-
inputs_response: api_pb2.FunctionPutInputsResponse = await
|
|
202
|
-
client.stub.FunctionPutInputs,
|
|
203
|
-
request_put,
|
|
204
|
-
)
|
|
202
|
+
inputs_response: api_pb2.FunctionPutInputsResponse = await client.stub.FunctionPutInputs(request_put)
|
|
205
203
|
processed_inputs = inputs_response.inputs
|
|
206
204
|
if not processed_inputs:
|
|
207
205
|
raise Exception("Could not create function call - the input queue seems to be full")
|
|
@@ -243,10 +241,9 @@ class _Invocation:
|
|
|
243
241
|
start_idx=index,
|
|
244
242
|
end_idx=index,
|
|
245
243
|
)
|
|
246
|
-
response: api_pb2.FunctionGetOutputsResponse = await
|
|
247
|
-
self.stub.FunctionGetOutputs,
|
|
244
|
+
response: api_pb2.FunctionGetOutputsResponse = await self.stub.FunctionGetOutputs(
|
|
248
245
|
request,
|
|
249
|
-
attempt_timeout=backend_timeout + ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
246
|
+
retry=Retry(attempt_timeout=backend_timeout + ATTEMPT_TIMEOUT_GRACE_PERIOD),
|
|
250
247
|
)
|
|
251
248
|
|
|
252
249
|
if len(response.outputs) > 0:
|
|
@@ -266,10 +263,7 @@ class _Invocation:
|
|
|
266
263
|
|
|
267
264
|
item = api_pb2.FunctionRetryInputsItem(input_jwt=ctx.input_jwt, input=ctx.item.input)
|
|
268
265
|
request = api_pb2.FunctionRetryInputsRequest(function_call_jwt=ctx.function_call_jwt, inputs=[item])
|
|
269
|
-
await
|
|
270
|
-
self.stub.FunctionRetryInputs,
|
|
271
|
-
request,
|
|
272
|
-
)
|
|
266
|
+
await self.stub.FunctionRetryInputs(request)
|
|
273
267
|
|
|
274
268
|
async def _get_single_output(self, expected_jwt: Optional[str] = None) -> api_pb2.FunctionGetOutputsItem:
|
|
275
269
|
# waits indefinitely for a single result for the function, and clear the outputs buffer after
|
|
@@ -373,10 +367,8 @@ class _Invocation:
|
|
|
373
367
|
start_idx=current_index,
|
|
374
368
|
end_idx=batch_end_index,
|
|
375
369
|
)
|
|
376
|
-
response: api_pb2.FunctionGetOutputsResponse = await
|
|
377
|
-
|
|
378
|
-
request,
|
|
379
|
-
attempt_timeout=ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
370
|
+
response: api_pb2.FunctionGetOutputsResponse = await self.stub.FunctionGetOutputs(
|
|
371
|
+
request, retry=Retry(attempt_timeout=ATTEMPT_TIMEOUT_GRACE_PERIOD)
|
|
380
372
|
)
|
|
381
373
|
|
|
382
374
|
outputs = list(response.outputs)
|
|
@@ -448,7 +440,7 @@ class _InputPlaneInvocation:
|
|
|
448
440
|
)
|
|
449
441
|
|
|
450
442
|
metadata = await client.get_input_plane_metadata(input_plane_region)
|
|
451
|
-
response = await
|
|
443
|
+
response = await stub.AttemptStart(request, metadata=metadata)
|
|
452
444
|
attempt_token = response.attempt_token
|
|
453
445
|
|
|
454
446
|
return _InputPlaneInvocation(
|
|
@@ -468,10 +460,9 @@ class _InputPlaneInvocation:
|
|
|
468
460
|
requested_at=time.time(),
|
|
469
461
|
)
|
|
470
462
|
metadata = await self.client.get_input_plane_metadata(self.input_plane_region)
|
|
471
|
-
await_response: api_pb2.AttemptAwaitResponse = await
|
|
472
|
-
self.stub.AttemptAwait,
|
|
463
|
+
await_response: api_pb2.AttemptAwaitResponse = await self.stub.AttemptAwait(
|
|
473
464
|
await_request,
|
|
474
|
-
attempt_timeout=OUTPUTS_TIMEOUT + ATTEMPT_TIMEOUT_GRACE_PERIOD,
|
|
465
|
+
retry=Retry(attempt_timeout=OUTPUTS_TIMEOUT + ATTEMPT_TIMEOUT_GRACE_PERIOD),
|
|
475
466
|
metadata=metadata,
|
|
476
467
|
)
|
|
477
468
|
|
|
@@ -511,11 +502,7 @@ class _InputPlaneInvocation:
|
|
|
511
502
|
input=self.input_item,
|
|
512
503
|
attempt_token=self.attempt_token,
|
|
513
504
|
)
|
|
514
|
-
retry_response = await
|
|
515
|
-
self.stub.AttemptRetry,
|
|
516
|
-
retry_request,
|
|
517
|
-
metadata=metadata,
|
|
518
|
-
)
|
|
505
|
+
retry_response = await self.stub.AttemptRetry(retry_request, metadata=metadata)
|
|
519
506
|
return retry_response.attempt_token
|
|
520
507
|
|
|
521
508
|
async def run_generator(self):
|
|
@@ -916,7 +903,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
916
903
|
elif webhook_config:
|
|
917
904
|
req.webhook_config.CopyFrom(webhook_config)
|
|
918
905
|
|
|
919
|
-
response = await
|
|
906
|
+
response = await resolver.client.stub.FunctionPrecreate(req)
|
|
920
907
|
self._hydrate(response.function_id, resolver.client, response.handle_metadata)
|
|
921
908
|
|
|
922
909
|
async def _load(self: _Function, resolver: Resolver, existing_object_id: Optional[str]):
|
|
@@ -1125,9 +1112,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1125
1112
|
existing_function_id=existing_object_id or "",
|
|
1126
1113
|
)
|
|
1127
1114
|
try:
|
|
1128
|
-
response: api_pb2.FunctionCreateResponse = await
|
|
1129
|
-
resolver.client.stub.FunctionCreate, request
|
|
1130
|
-
)
|
|
1115
|
+
response: api_pb2.FunctionCreateResponse = await resolver.client.stub.FunctionCreate(request)
|
|
1131
1116
|
except GRPCError as exc:
|
|
1132
1117
|
if exc.status == Status.INVALID_ARGUMENT:
|
|
1133
1118
|
raise InvalidError(exc.message)
|
|
@@ -1264,7 +1249,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1264
1249
|
or "", # TODO: investigate shouldn't environment name always be specified here?
|
|
1265
1250
|
)
|
|
1266
1251
|
|
|
1267
|
-
response = await
|
|
1252
|
+
response = await parent._client.stub.FunctionBindParams(req)
|
|
1268
1253
|
param_bound_func._hydrate(response.bound_function_id, parent._client, response.handle_metadata)
|
|
1269
1254
|
|
|
1270
1255
|
def _deps():
|
|
@@ -1328,7 +1313,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1328
1313
|
scaledown_window=scaledown_window,
|
|
1329
1314
|
)
|
|
1330
1315
|
request = api_pb2.FunctionUpdateSchedulingParamsRequest(function_id=self.object_id, settings=settings)
|
|
1331
|
-
await
|
|
1316
|
+
await self.client.stub.FunctionUpdateSchedulingParams(request)
|
|
1332
1317
|
|
|
1333
1318
|
# One idea would be for FunctionUpdateScheduleParams to return the current (coalesced) settings
|
|
1334
1319
|
# and then we could return them here (would need some ad hoc dataclass, which I don't love)
|
|
@@ -1388,7 +1373,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
|
1388
1373
|
environment_name=_get_environment_name(environment_name, resolver) or "",
|
|
1389
1374
|
)
|
|
1390
1375
|
try:
|
|
1391
|
-
response = await
|
|
1376
|
+
response = await resolver.client.stub.FunctionGet(request)
|
|
1392
1377
|
except NotFoundError as exc:
|
|
1393
1378
|
# refine the error message
|
|
1394
1379
|
env_context = f" (in the '{environment_name}' environment)" if environment_name else ""
|
|
@@ -1888,10 +1873,9 @@ Use the `Function.get_web_url()` method instead.
|
|
|
1888
1873
|
@live_method
|
|
1889
1874
|
async def get_current_stats(self) -> FunctionStats:
|
|
1890
1875
|
"""Return a `FunctionStats` object describing the current function's queue and runner counts."""
|
|
1891
|
-
resp = await
|
|
1892
|
-
self.client.stub.FunctionGetCurrentStats,
|
|
1876
|
+
resp = await self.client.stub.FunctionGetCurrentStats(
|
|
1893
1877
|
api_pb2.FunctionGetCurrentStatsRequest(function_id=self.object_id),
|
|
1894
|
-
total_timeout=10.0,
|
|
1878
|
+
retry=Retry(total_timeout=10.0),
|
|
1895
1879
|
)
|
|
1896
1880
|
return FunctionStats(backlog=resp.backlog, num_total_runners=resp.num_total_tasks)
|
|
1897
1881
|
|
|
@@ -1994,7 +1978,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
1994
1978
|
"""
|
|
1995
1979
|
assert self._client and self._client.stub
|
|
1996
1980
|
request = api_pb2.FunctionGetCallGraphRequest(function_call_id=self.object_id)
|
|
1997
|
-
response = await
|
|
1981
|
+
response = await self._client.stub.FunctionGetCallGraph(request)
|
|
1998
1982
|
return _reconstruct_call_graph(response)
|
|
1999
1983
|
|
|
2000
1984
|
async def cancel(
|
|
@@ -2012,7 +1996,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2012
1996
|
function_call_id=self.object_id, terminate_containers=terminate_containers
|
|
2013
1997
|
)
|
|
2014
1998
|
assert self._client and self._client.stub
|
|
2015
|
-
await
|
|
1999
|
+
await self._client.stub.FunctionCallCancel(request)
|
|
2016
2000
|
|
|
2017
2001
|
@staticmethod
|
|
2018
2002
|
async def from_id(function_call_id: str, client: Optional[_Client] = None) -> "_FunctionCall[Any]":
|
|
@@ -2039,7 +2023,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
|
2039
2023
|
|
|
2040
2024
|
async def _load(self: _FunctionCall, resolver: Resolver, existing_object_id: Optional[str]):
|
|
2041
2025
|
request = api_pb2.FunctionCallFromIdRequest(function_call_id=function_call_id)
|
|
2042
|
-
resp = await
|
|
2026
|
+
resp = await resolver.client.stub.FunctionCallFromId(request)
|
|
2043
2027
|
self._hydrate(function_call_id, resolver.client, resp)
|
|
2044
2028
|
|
|
2045
2029
|
rep = f"FunctionCall.from_id({function_call_id!r})"
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Copyright Modal Labs 2025
|
|
2
|
+
from typing import TYPE_CHECKING, Any, Collection, Generic, Literal, Mapping, Optional, TypeVar, Union
|
|
3
|
+
|
|
4
|
+
import grpclib.client
|
|
5
|
+
from google.protobuf.message import Message
|
|
6
|
+
from grpclib import GRPCError, Status
|
|
7
|
+
|
|
8
|
+
from ._traceback import suppress_tb_frames
|
|
9
|
+
from ._utils.grpc_utils import Retry, _retry_transient_errors
|
|
10
|
+
from .config import config, logger
|
|
11
|
+
from .exception import InvalidError, NotFoundError
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .client import _Client
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_Value = Union[str, bytes]
|
|
18
|
+
_MetadataLike = Union[Mapping[str, _Value], Collection[tuple[str, _Value]]]
|
|
19
|
+
RequestType = TypeVar("RequestType", bound=Message)
|
|
20
|
+
ResponseType = TypeVar("ResponseType", bound=Message)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class grpc_error_converter:
|
|
24
|
+
def __enter__(self):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
def __exit__(self, exc_type, exc, traceback) -> Literal[False]:
|
|
28
|
+
# skip all internal frames from grpclib
|
|
29
|
+
use_full_traceback = config.get("traceback")
|
|
30
|
+
with suppress_tb_frames(1):
|
|
31
|
+
if isinstance(exc, GRPCError):
|
|
32
|
+
if exc.status == Status.NOT_FOUND:
|
|
33
|
+
if use_full_traceback:
|
|
34
|
+
raise NotFoundError(exc.message)
|
|
35
|
+
else:
|
|
36
|
+
raise NotFoundError(exc.message) from None # from None to skip the grpc-internal cause
|
|
37
|
+
|
|
38
|
+
if not use_full_traceback:
|
|
39
|
+
# just include the frame in grpclib that actually raises the GRPCError
|
|
40
|
+
tb = exc.__traceback__
|
|
41
|
+
while tb.tb_next:
|
|
42
|
+
tb = tb.tb_next
|
|
43
|
+
exc.with_traceback(tb)
|
|
44
|
+
raise exc from None # from None to skip the grpc-internal cause
|
|
45
|
+
raise exc
|
|
46
|
+
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class UnaryUnaryWrapper(Generic[RequestType, ResponseType]):
|
|
51
|
+
# Calls a grpclib.UnaryUnaryMethod using a specific Client instance, respecting
|
|
52
|
+
# if that client is closed etc. and possibly introducing Modal-specific retry logic
|
|
53
|
+
wrapped_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType]
|
|
54
|
+
client: "_Client"
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
wrapped_method: grpclib.client.UnaryUnaryMethod[RequestType, ResponseType],
|
|
59
|
+
client: "_Client",
|
|
60
|
+
server_url: str,
|
|
61
|
+
):
|
|
62
|
+
self.wrapped_method = wrapped_method
|
|
63
|
+
self.client = client
|
|
64
|
+
self.server_url = server_url
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def name(self) -> str:
|
|
68
|
+
return self.wrapped_method.name
|
|
69
|
+
|
|
70
|
+
async def __call__(
|
|
71
|
+
self,
|
|
72
|
+
req: RequestType,
|
|
73
|
+
*,
|
|
74
|
+
retry: Optional[Retry] = Retry(),
|
|
75
|
+
timeout: Optional[float] = None,
|
|
76
|
+
metadata: Optional[list[tuple[str, str]]] = None,
|
|
77
|
+
) -> ResponseType:
|
|
78
|
+
with suppress_tb_frames(1):
|
|
79
|
+
if timeout is not None and retry is not None:
|
|
80
|
+
raise InvalidError("Retry must be None when timeout is set")
|
|
81
|
+
|
|
82
|
+
if retry is None:
|
|
83
|
+
return await self.direct(req, timeout=timeout, metadata=metadata)
|
|
84
|
+
|
|
85
|
+
return await _retry_transient_errors(
|
|
86
|
+
self, # type: ignore
|
|
87
|
+
req,
|
|
88
|
+
retry=retry,
|
|
89
|
+
metadata=metadata,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
async def direct(
|
|
93
|
+
self,
|
|
94
|
+
req: RequestType,
|
|
95
|
+
*,
|
|
96
|
+
timeout: Optional[float] = None,
|
|
97
|
+
metadata: Optional[_MetadataLike] = None,
|
|
98
|
+
) -> ResponseType:
|
|
99
|
+
from .client import _Client
|
|
100
|
+
|
|
101
|
+
if self.client._snapshotted:
|
|
102
|
+
logger.debug(f"refreshing client after snapshot for {self.name.rsplit('/', 1)[1]}")
|
|
103
|
+
self.client = await _Client.from_env()
|
|
104
|
+
|
|
105
|
+
# Note: We override the grpclib method's channel (see grpclib's code [1]). I think this is fine
|
|
106
|
+
# since grpclib's code doesn't seem to change very much, but we could also recreate the
|
|
107
|
+
# grpclib stub if we aren't comfortable with this. The downside is then we need to cache
|
|
108
|
+
# the grpclib stub so the rest of our code becomes a bit more complicated.
|
|
109
|
+
#
|
|
110
|
+
# We need to override the channel because after the process is forked or the client is
|
|
111
|
+
# snapshotted, the existing channel may be stale / unusable.
|
|
112
|
+
#
|
|
113
|
+
# [1]: https://github.com/vmagamedov/grpclib/blob/62f968a4c84e3f64e6966097574ff0a59969ea9b/grpclib/client.py#L844
|
|
114
|
+
self.wrapped_method.channel = await self.client._get_channel(self.server_url)
|
|
115
|
+
with suppress_tb_frames(1), grpc_error_converter():
|
|
116
|
+
return await self.client._call_unary(self.wrapped_method, req, timeout=timeout, metadata=metadata)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class UnaryStreamWrapper(Generic[RequestType, ResponseType]):
|
|
120
|
+
wrapped_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType]
|
|
121
|
+
|
|
122
|
+
def __init__(
|
|
123
|
+
self,
|
|
124
|
+
wrapped_method: grpclib.client.UnaryStreamMethod[RequestType, ResponseType],
|
|
125
|
+
client: "_Client",
|
|
126
|
+
server_url: str,
|
|
127
|
+
):
|
|
128
|
+
self.wrapped_method = wrapped_method
|
|
129
|
+
self.client = client
|
|
130
|
+
self.server_url = server_url
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def name(self) -> str:
|
|
134
|
+
return self.wrapped_method.name
|
|
135
|
+
|
|
136
|
+
async def unary_stream(
|
|
137
|
+
self,
|
|
138
|
+
request,
|
|
139
|
+
metadata: Optional[Any] = None,
|
|
140
|
+
):
|
|
141
|
+
from .client import _Client
|
|
142
|
+
|
|
143
|
+
if self.client._snapshotted:
|
|
144
|
+
logger.debug(f"refreshing client after snapshot for {self.name.rsplit('/', 1)[1]}")
|
|
145
|
+
self.client = await _Client.from_env()
|
|
146
|
+
self.wrapped_method.channel = await self.client._get_channel(self.server_url)
|
|
147
|
+
async for response in self.client._call_stream(self.wrapped_method, request, metadata=metadata):
|
|
148
|
+
yield response
|
|
@@ -34,7 +34,7 @@ from rich.text import Text
|
|
|
34
34
|
from modal._utils.time_utils import timestamp_to_localized_str
|
|
35
35
|
from modal_proto import api_pb2
|
|
36
36
|
|
|
37
|
-
from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES,
|
|
37
|
+
from ._utils.grpc_utils import RETRYABLE_GRPC_STATUS_CODES, Retry
|
|
38
38
|
from ._utils.shell_utils import stream_from_stdin, write_to_fd
|
|
39
39
|
from .client import _Client
|
|
40
40
|
from .config import logger
|
|
@@ -489,12 +489,11 @@ async def stream_pty_shell_input(client: _Client, exec_id: str, finish_event: as
|
|
|
489
489
|
"""
|
|
490
490
|
|
|
491
491
|
async def _handle_input(data: bytes, message_index: int):
|
|
492
|
-
await
|
|
493
|
-
client.stub.ContainerExecPutInput,
|
|
492
|
+
await client.stub.ContainerExecPutInput(
|
|
494
493
|
api_pb2.ContainerExecPutInputRequest(
|
|
495
494
|
exec_id=exec_id, input=api_pb2.RuntimeInputMessage(message=data, message_index=message_index)
|
|
496
495
|
),
|
|
497
|
-
total_timeout=10,
|
|
496
|
+
retry=Retry(total_timeout=10),
|
|
498
497
|
)
|
|
499
498
|
|
|
500
499
|
async with stream_from_stdin(_handle_input, use_raw_terminal=True):
|
|
@@ -36,7 +36,7 @@ from modal._traceback import print_exception
|
|
|
36
36
|
from modal._utils.async_utils import TaskContext, aclosing, asyncify, synchronize_api, synchronizer
|
|
37
37
|
from modal._utils.blob_utils import MAX_OBJECT_SIZE_BYTES, blob_download, blob_upload, format_blob_data
|
|
38
38
|
from modal._utils.function_utils import _stream_function_call_data
|
|
39
|
-
from modal._utils.grpc_utils import
|
|
39
|
+
from modal._utils.grpc_utils import Retry
|
|
40
40
|
from modal._utils.package_utils import parse_major_minor_version
|
|
41
41
|
from modal.client import HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT, _Client
|
|
42
42
|
from modal.config import config, logger
|
|
@@ -623,8 +623,8 @@ class _ContainerIOManager:
|
|
|
623
623
|
await self.heartbeat_condition.wait()
|
|
624
624
|
|
|
625
625
|
request = api_pb2.ContainerHeartbeatRequest(canceled_inputs_return_outputs_v2=True)
|
|
626
|
-
response = await
|
|
627
|
-
|
|
626
|
+
response = await self._client.stub.ContainerHeartbeat(
|
|
627
|
+
request, retry=Retry(attempt_timeout=HEARTBEAT_TIMEOUT)
|
|
628
628
|
)
|
|
629
629
|
|
|
630
630
|
if response.HasField("cancel_input_event"):
|
|
@@ -671,10 +671,9 @@ class _ContainerIOManager:
|
|
|
671
671
|
target_concurrency=self._target_concurrency,
|
|
672
672
|
max_concurrency=self._max_concurrency,
|
|
673
673
|
)
|
|
674
|
-
resp = await
|
|
675
|
-
self._client.stub.FunctionGetDynamicConcurrency,
|
|
674
|
+
resp = await self._client.stub.FunctionGetDynamicConcurrency(
|
|
676
675
|
request,
|
|
677
|
-
attempt_timeout=DYNAMIC_CONCURRENCY_TIMEOUT_SECS,
|
|
676
|
+
retry=Retry(attempt_timeout=DYNAMIC_CONCURRENCY_TIMEOUT_SECS),
|
|
678
677
|
)
|
|
679
678
|
if resp.concurrency != self._input_slots.value and not self._stop_concurrency_loop:
|
|
680
679
|
logger.debug(f"Dynamic concurrency set from {self._input_slots.value} to {resp.concurrency}")
|
|
@@ -725,9 +724,9 @@ class _ContainerIOManager:
|
|
|
725
724
|
|
|
726
725
|
if self.input_plane_server_url:
|
|
727
726
|
stub = await self._client.get_stub(self.input_plane_server_url)
|
|
728
|
-
await
|
|
727
|
+
await stub.FunctionCallPutDataOut(req)
|
|
729
728
|
else:
|
|
730
|
-
await
|
|
729
|
+
await self._client.stub.FunctionCallPutDataOut(req)
|
|
731
730
|
|
|
732
731
|
@asynccontextmanager
|
|
733
732
|
async def generator_output_sender(
|
|
@@ -815,9 +814,7 @@ class _ContainerIOManager:
|
|
|
815
814
|
try:
|
|
816
815
|
# If number of active inputs is at max queue size, this will block.
|
|
817
816
|
iteration += 1
|
|
818
|
-
response: api_pb2.FunctionGetInputsResponse = await
|
|
819
|
-
self._client.stub.FunctionGetInputs, request
|
|
820
|
-
)
|
|
817
|
+
response: api_pb2.FunctionGetInputsResponse = await self._client.stub.FunctionGetInputs(request)
|
|
821
818
|
|
|
822
819
|
if response.rate_limit_sleep_duration:
|
|
823
820
|
logger.info(
|
|
@@ -887,11 +884,12 @@ class _ContainerIOManager:
|
|
|
887
884
|
# Limit the batch size to 20 to stay within message size limits and buffer size limits.
|
|
888
885
|
output_batch_size = 20
|
|
889
886
|
for i in range(0, len(outputs), output_batch_size):
|
|
890
|
-
await
|
|
891
|
-
self._client.stub.FunctionPutOutputs,
|
|
887
|
+
await self._client.stub.FunctionPutOutputs(
|
|
892
888
|
api_pb2.FunctionPutOutputsRequest(outputs=outputs[i : i + output_batch_size]),
|
|
893
|
-
|
|
894
|
-
|
|
889
|
+
retry=Retry(
|
|
890
|
+
additional_status_codes=[Status.RESOURCE_EXHAUSTED],
|
|
891
|
+
max_retries=None, # Retry indefinitely, trying every 1s.
|
|
892
|
+
),
|
|
895
893
|
)
|
|
896
894
|
input_ids = [output.input_id for output in outputs]
|
|
897
895
|
self.exit_context(started_at, input_ids)
|
|
@@ -932,7 +930,7 @@ class _ContainerIOManager:
|
|
|
932
930
|
)
|
|
933
931
|
|
|
934
932
|
req = api_pb2.TaskResultRequest(result=result)
|
|
935
|
-
await
|
|
933
|
+
await self._client.stub.TaskResult(req)
|
|
936
934
|
|
|
937
935
|
# Shut down the task gracefully
|
|
938
936
|
raise UserException()
|
|
@@ -1082,13 +1080,14 @@ class _ContainerIOManager:
|
|
|
1082
1080
|
await asyncify(os.sync)()
|
|
1083
1081
|
results = await asyncio.gather(
|
|
1084
1082
|
*[
|
|
1085
|
-
|
|
1086
|
-
self._client.stub.VolumeCommit,
|
|
1083
|
+
self._client.stub.VolumeCommit(
|
|
1087
1084
|
api_pb2.VolumeCommitRequest(volume_id=v_id),
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1085
|
+
retry=Retry(
|
|
1086
|
+
max_retries=9,
|
|
1087
|
+
base_delay=0.25,
|
|
1088
|
+
max_delay=256,
|
|
1089
|
+
delay_factor=2,
|
|
1090
|
+
),
|
|
1092
1091
|
)
|
|
1093
1092
|
for v_id in volume_ids
|
|
1094
1093
|
],
|
|
@@ -51,6 +51,10 @@ def synchronize_api(obj, target_module=None):
|
|
|
51
51
|
return synchronizer.create_blocking(obj, blocking_name, target_module=target_module)
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
# Used for testing to configure the `n_attempts` that `retry` will use.
|
|
55
|
+
RETRY_N_ATTEMPTS_OVERRIDE: Optional[int] = None
|
|
56
|
+
|
|
57
|
+
|
|
54
58
|
def retry(direct_fn=None, *, n_attempts=3, base_delay=0, delay_factor=2, timeout=90):
|
|
55
59
|
"""Decorator that calls an async function multiple times, with a given timeout.
|
|
56
60
|
|
|
@@ -75,8 +79,13 @@ def retry(direct_fn=None, *, n_attempts=3, base_delay=0, delay_factor=2, timeout
|
|
|
75
79
|
def decorator(fn):
|
|
76
80
|
@functools.wraps(fn)
|
|
77
81
|
async def f_wrapped(*args, **kwargs):
|
|
82
|
+
if RETRY_N_ATTEMPTS_OVERRIDE is not None:
|
|
83
|
+
local_n_attempts = RETRY_N_ATTEMPTS_OVERRIDE
|
|
84
|
+
else:
|
|
85
|
+
local_n_attempts = n_attempts
|
|
86
|
+
|
|
78
87
|
delay = base_delay
|
|
79
|
-
for i in range(
|
|
88
|
+
for i in range(local_n_attempts):
|
|
80
89
|
t0 = time.time()
|
|
81
90
|
try:
|
|
82
91
|
return await asyncio.wait_for(fn(*args, **kwargs), timeout=timeout)
|
|
@@ -84,12 +93,12 @@ def retry(direct_fn=None, *, n_attempts=3, base_delay=0, delay_factor=2, timeout
|
|
|
84
93
|
logger.debug(f"Function {fn} was cancelled")
|
|
85
94
|
raise
|
|
86
95
|
except Exception as e:
|
|
87
|
-
if i >=
|
|
96
|
+
if i >= local_n_attempts - 1:
|
|
88
97
|
raise
|
|
89
98
|
logger.debug(
|
|
90
99
|
f"Failed invoking function {fn}: {e}"
|
|
91
100
|
f" (took {time.time() - t0}s, sleeping {delay}s"
|
|
92
|
-
f" and trying {
|
|
101
|
+
f" and trying {local_n_attempts - i - 1} more times)"
|
|
93
102
|
)
|
|
94
103
|
await asyncio.sleep(delay)
|
|
95
104
|
delay *= delay_factor
|
|
@@ -9,7 +9,6 @@ from typing import Any
|
|
|
9
9
|
from modal.exception import ExecutionError
|
|
10
10
|
from modal_proto import api_pb2, modal_api_grpc
|
|
11
11
|
|
|
12
|
-
from .grpc_utils import retry_transient_errors
|
|
13
12
|
from .logger import logger
|
|
14
13
|
|
|
15
14
|
|
|
@@ -66,9 +65,7 @@ class _AuthTokenManager:
|
|
|
66
65
|
# new token. Once we have a new token, the other coroutines will unblock and return from here.
|
|
67
66
|
if self._token and not self._needs_refresh():
|
|
68
67
|
return
|
|
69
|
-
resp: api_pb2.AuthTokenGetResponse = await
|
|
70
|
-
self._stub.AuthTokenGet, api_pb2.AuthTokenGetRequest()
|
|
71
|
-
)
|
|
68
|
+
resp: api_pb2.AuthTokenGetResponse = await self._stub.AuthTokenGet(api_pb2.AuthTokenGetRequest())
|
|
72
69
|
if not resp.token:
|
|
73
70
|
# Not expected
|
|
74
71
|
raise ExecutionError(
|
|
@@ -27,7 +27,6 @@ from modal_proto.modal_api_grpc import ModalClientModal
|
|
|
27
27
|
|
|
28
28
|
from ..exception import ExecutionError
|
|
29
29
|
from .async_utils import TaskContext, retry
|
|
30
|
-
from .grpc_utils import retry_transient_errors
|
|
31
30
|
from .hash_utils import UploadHashes, get_upload_hashes
|
|
32
31
|
from .http_utils import ClientSessionRegistry
|
|
33
32
|
from .logger import logger
|
|
@@ -229,7 +228,7 @@ async def _blob_upload(
|
|
|
229
228
|
content_sha256_base64=upload_hashes.sha256_base64,
|
|
230
229
|
content_length=content_length,
|
|
231
230
|
)
|
|
232
|
-
resp = await
|
|
231
|
+
resp = await stub.BlobCreate(req)
|
|
233
232
|
|
|
234
233
|
if resp.WhichOneof("upload_types_oneof") == "multiparts":
|
|
235
234
|
|
|
@@ -335,7 +334,7 @@ async def blob_download(blob_id: str, stub: ModalClientModal) -> bytes:
|
|
|
335
334
|
logger.debug(f"Downloading large blob {blob_id}")
|
|
336
335
|
t0 = time.time()
|
|
337
336
|
req = api_pb2.BlobGetRequest(blob_id=blob_id)
|
|
338
|
-
resp = await
|
|
337
|
+
resp = await stub.BlobGet(req)
|
|
339
338
|
data = await _download_from_url(resp.download_url)
|
|
340
339
|
size_mib = len(data) / 1024 / 1024
|
|
341
340
|
dur_s = max(time.time() - t0, 0.001) # avoid division by zero
|
|
@@ -348,7 +347,7 @@ async def blob_download(blob_id: str, stub: ModalClientModal) -> bytes:
|
|
|
348
347
|
|
|
349
348
|
async def blob_iter(blob_id: str, stub: ModalClientModal) -> AsyncIterator[bytes]:
|
|
350
349
|
req = api_pb2.BlobGetRequest(blob_id=blob_id)
|
|
351
|
-
resp = await
|
|
350
|
+
resp = await stub.BlobGet(req)
|
|
352
351
|
download_url = resp.download_url
|
|
353
352
|
async with ClientSessionRegistry.get_session().get(download_url) as s3_resp:
|
|
354
353
|
# S3 signal to slow down request rate.
|