letify 2.6.3__tar.gz → 2.6.4__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.
- {letify-2.6.3 → letify-2.6.4}/PKG-INFO +1 -1
- {letify-2.6.3 → letify-2.6.4}/docs/SPEC.md +5 -1
- {letify-2.6.3 → letify-2.6.4}/letify/__init__.py +1 -1
- {letify-2.6.3 → letify-2.6.4}/letify/providers/modal.py +47 -4
- {letify-2.6.3 → letify-2.6.4}/letify-core/Cargo.lock +3 -3
- {letify-2.6.3 → letify-2.6.4}/letify-core/Cargo.toml +1 -1
- {letify-2.6.3 → letify-2.6.4}/letify-ext/package-lock.json +2 -2
- {letify-2.6.3 → letify-2.6.4}/letify-ext/package.json +1 -1
- {letify-2.6.3 → letify-2.6.4}/pyproject.toml +1 -1
- {letify-2.6.3 → letify-2.6.4}/tests/test_providers.py +32 -1
- {letify-2.6.3 → letify-2.6.4}/.github/workflows/ci.yml +0 -0
- {letify-2.6.3 → letify-2.6.4}/.github/workflows/docs.yml +0 -0
- {letify-2.6.3 → letify-2.6.4}/.github/workflows/publish.yml +0 -0
- {letify-2.6.3 → letify-2.6.4}/.gitignore +0 -0
- {letify-2.6.3 → letify-2.6.4}/.researchtree +0 -0
- {letify-2.6.3 → letify-2.6.4}/CLAUDE.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/LICENSE +0 -0
- {letify-2.6.3 → letify-2.6.4}/PROJECT.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/README.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/COMPONENT.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/INTENT.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/NETWORK.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/01-getting-started.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/02-providers.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/03-execution-modes.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/04-environments-and-data.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/05-concurrency.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/06-cost.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/07-troubleshooting.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/08-releasing.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/README.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/ko/01-/354/213/234/354/236/221/355/225/230/352/270/260.md" +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/ko/02-/355/224/204/353/241/234/353/260/224/354/235/264/353/215/224.md" +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/ko/03-/354/213/244/355/226/211-/353/260/251/354/213/235.md" +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/ko/04-/355/231/230/352/262/275/352/263/274-/353/215/260/354/235/264/355/204/260.md" +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/ko/05-/353/217/231/354/213/234-/354/213/244/355/226/211.md" +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/ko/06-/353/271/204/354/232/251.md" +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/ko/07-/353/254/270/354/240/234-/355/225/264/352/262/260.md" +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/ko/08-/353/246/264/353/246/254/354/212/244.md" +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/guide/ko/README.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/locales/README_ko.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/docs/mkdocs.yml +0 -0
- {letify-2.6.3 → letify-2.6.4}/examples/README.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/examples/nvfp4-lora/.letify.example +0 -0
- {letify-2.6.3 → letify-2.6.4}/examples/nvfp4-lora/00_smoke.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/examples/nvfp4-lora/01_sweep.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/examples/nvfp4-lora/02_resume.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/examples/nvfp4-lora/03_watch.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/examples/nvfp4-lora/README.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/examples/nvfp4-lora/README_ko.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/examples/nvfp4-lora/common.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/examples/nvfp4-lora/recipe.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/cli.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/config/__init__.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/config/inventory.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/config/login.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/config/schema.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/config/secrets.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/config/writer.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/declare/__init__.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/declare/cache.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/declare/env.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/declare/function.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/declare/instance.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/declare/reduce.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/declare/replicas.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/errors.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/install.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/launcher.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/paths.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/protocol/__init__.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/protocol/codec.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/protocol/driver.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/protocol/handle.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/protocol/wire.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/protocol/worker.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/providers/__init__.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/providers/base.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/providers/colab.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/providers/colab_files.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/providers/colab_keepalive.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/providers/elice.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/providers/kaggle.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/providers/kaggle_adapter.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/providers/local.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/providers/modal_adapter.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/providers/naming.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/providers/shell.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/providers/tunnel.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/providers/usage.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/remoting/__init__.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/remoting/capability.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/remoting/device/__init__.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/remoting/device/client.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/remoting/device/cuda.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/remoting/device/executor.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/remoting/device/frames.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/remoting/device/guard.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/remoting/device/tensor.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/remoting/device/trace.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/remoting/device/value.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/remoting/loader.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/remoting/probe.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/render.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/runtime/__init__.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/runtime/bootstrap.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/runtime/broker.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/runtime/channel.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/runtime/lease.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/runtime/pool.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/runtime/session.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/runtime/telemetry.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/store/__init__.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/store/backends/__init__.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/store/backends/filesystem.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/store/backends/google_auth.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/store/backends/layout.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/store/backends/objects.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/store/cas.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/store/pathdata.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/store/sendorder.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/store/volume.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/stubs.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/tools.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/transport/__init__.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/transport/agent.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/transport/announce.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/transport/link.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/transport/nat.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/transport/pipeline.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/transport/portmap.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/transport/probe.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/transport/quic.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/transport/rendezvous.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/transport/setup.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/transport/sshopts.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify/transport/strategies.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/.gitignore +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/build.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/letify-agent/Cargo.toml +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/letify-agent/src/driver.rs +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/letify-agent/src/main.rs +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/letify-driver/Cargo.toml +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/letify-driver/src/client.rs +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/letify-driver/src/exports.rs +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/letify-driver/src/image.rs +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/letify-driver/src/lib.rs +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/letify-driver/src/status.rs +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/letify-driver/src/table.rs +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/letify-quic/Cargo.toml +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/letify-quic/src/main.rs +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/letify-wire/Cargo.toml +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/letify-wire/src/lib.rs +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/letify-wire/tests/throughput.rs +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-core/rust-toolchain.toml +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-ext/.vscodeignore +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-ext/LICENSE +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-ext/README.md +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-ext/media/icon.svg +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-ext/src/cli.ts +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-ext/src/extension.ts +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-ext/src/history.ts +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-ext/src/model.ts +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-ext/src/poll.ts +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-ext/src/render.ts +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-ext/test/cli.test.ts +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-ext/test/manifest.test.ts +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-ext/test/model.test.ts +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-ext/test/regressions.test.ts +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify-ext/tsconfig.json +0 -0
- {letify-2.6.3 → letify-2.6.4}/letify_providers.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/scripts/sync-main.sh +0 -0
- {letify-2.6.3 → letify-2.6.4}/scripts/version.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/conftest.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/fake_kaggle_adapter.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/fake_modal_adapter.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_cli.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_cli_output.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_colab_keepalive.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_config.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_core.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_data.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_data_streaming.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_declare.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_device.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_distributed.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_editor_sessions.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_elice.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_frames.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_install.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_kaggle.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_kaggle_cookie.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_kaggle_link.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_link_log.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_notebook.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_parallel_placement.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_paths.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_portmap.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_protocol.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_quic.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_remoting.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_render.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_runtime.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_ship.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_store.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_strategies.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_stubs.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_transport.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_tunnel_setup.py +0 -0
- {letify-2.6.3 → letify-2.6.4}/tests/test_version.py +0 -0
|
@@ -807,7 +807,11 @@ A `read_until` blocks until the sandbox prints a line, and the adapter answers o
|
|
|
807
807
|
- `Modal.stop` calls `abort()` when the adapter is out of step or its lock is still held after 5 s. The call timeout watchdog calls it through `SandboxChannel._kill`, so a call past its `timeout` ends its sandbox too.
|
|
808
808
|
- The adapter's `terminate` for an id it did not create finds the sandbox with `modal.Sandbox.from_id` and terminates it. A sandbox that is already gone is not an error.
|
|
809
809
|
|
|
810
|
-
Modal also bounds a sandbox that nothing terminates. `create` passes `
|
|
810
|
+
Modal also bounds a sandbox that nothing terminates. `create` passes `idle_timeout`, the entry option `idle_timeout`, 600 s by default, after which Modal terminates a sandbox that is idle. That is the bound that stops an abandoned sandbox billing, because an abandoned one is idle by definition once its worker has no one to answer.
|
|
811
|
+
|
|
812
|
+
It passes `timeout`, the entry option `timeout`, as the sandbox's maximum lifetime, and the default is the longest Modal accepts, 86400 s, measured as accepted. It is not a shorter number letify picked, for the reason The declaration gives for `timeout` having no default: a deadline letify invented would end a two hour training run at whatever hour it guessed. The default was 3600 s, exactly one hour, and it ended two Modal sessions of a real training run at one hour each with nothing saying why, which is that sentence happening. An account that wants a hard cap sets `timeout` itself.
|
|
813
|
+
|
|
814
|
+
A sandbox that reaches that lifetime takes its worker with it, so the channel sees a worker that stopped without replying. Where the session's age is within a minute of the lifetime letify asked for, the failure says so instead of listing causes: `the Modal sandbox reached the <seconds> s lifetime letify asked for; raise timeout on the account to run longer`.
|
|
811
815
|
|
|
812
816
|
### Modal data channel <!-- id: modal-data-channel -->
|
|
813
817
|
|
|
@@ -102,6 +102,26 @@ STOP_WAIT = 5.0
|
|
|
102
102
|
#: ``idle_timeout``.
|
|
103
103
|
IDLE_TIMEOUT = 600
|
|
104
104
|
|
|
105
|
+
#: The sandbox's maximum lifetime, the longest Modal accepts, measured as accepted.
|
|
106
|
+
#: Spec "Modal": not a shorter number letify picked, for the reason The declaration gives
|
|
107
|
+
#: for ``timeout`` having no default. The previous 3600 s ended two sessions of a real
|
|
108
|
+
#: training run at one hour each with nothing saying why.
|
|
109
|
+
SANDBOX_TIMEOUT = 86400
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def lifetime_detail(*, age_s: float, lifetime_s: int) -> str | None:
|
|
113
|
+
"""What to say when a sandbox died at the lifetime letify asked for, or None.
|
|
114
|
+
|
|
115
|
+
Spec "Modal": the channel otherwise reports a worker that stopped without replying and
|
|
116
|
+
lists causes, none of which is the deadline letify set itself.
|
|
117
|
+
"""
|
|
118
|
+
if lifetime_s <= 0 or age_s < lifetime_s - 60.0:
|
|
119
|
+
return None
|
|
120
|
+
return (
|
|
121
|
+
f"the Modal sandbox reached the {lifetime_s} s lifetime letify asked for; "
|
|
122
|
+
f"raise timeout on the account to run longer"
|
|
123
|
+
)
|
|
124
|
+
|
|
105
125
|
|
|
106
126
|
class VolumePathMissing(RuntimeFailure):
|
|
107
127
|
"""The adapter answered that a volume path does not exist."""
|
|
@@ -377,6 +397,18 @@ class Modal(Provider):
|
|
|
377
397
|
self._sandboxes: dict[str, str] = {}
|
|
378
398
|
self._adapter: Adapter | None = None
|
|
379
399
|
|
|
400
|
+
@property
|
|
401
|
+
def sandbox_timeout_s(self) -> int:
|
|
402
|
+
"""The sandbox's maximum lifetime, from the account or the longest Modal accepts."""
|
|
403
|
+
value = self.config.option("timeout", SANDBOX_TIMEOUT)
|
|
404
|
+
return max(1, int(value))
|
|
405
|
+
|
|
406
|
+
@property
|
|
407
|
+
def sandbox_idle_timeout_s(self) -> int:
|
|
408
|
+
"""Seconds a sandbox may be idle before Modal terminates it. Spec "Modal"."""
|
|
409
|
+
value = self.config.option("idle_timeout", IDLE_TIMEOUT)
|
|
410
|
+
return max(1, int(value))
|
|
411
|
+
|
|
380
412
|
def adapter(self) -> Adapter:
|
|
381
413
|
"""The adapter acting as this account, started on its first request."""
|
|
382
414
|
if self._adapter is None or self._adapter.closed:
|
|
@@ -440,14 +472,15 @@ class Modal(Provider):
|
|
|
440
472
|
args=["python3", "-u", "-c", BOOTSTRAP],
|
|
441
473
|
packages=list(WORKER_PACKAGES),
|
|
442
474
|
gpu=self.wire_name(runtime.instance) or None,
|
|
443
|
-
timeout=
|
|
444
|
-
idle_timeout=
|
|
475
|
+
timeout=self.sandbox_timeout_s,
|
|
476
|
+
idle_timeout=self.sandbox_idle_timeout_s,
|
|
445
477
|
ports=[] if data_port is None else [data_port],
|
|
446
478
|
)
|
|
447
479
|
sandbox = str(created["sandbox"])
|
|
448
480
|
self._sandboxes[runtime.name] = sandbox
|
|
449
481
|
return SandboxChannel(
|
|
450
|
-
adapter, sandbox, name=runtime.name, data_port=data_port, streams=streams
|
|
482
|
+
adapter, sandbox, name=runtime.name, data_port=data_port, streams=streams,
|
|
483
|
+
lifetime_s=self.sandbox_timeout_s,
|
|
451
484
|
)
|
|
452
485
|
|
|
453
486
|
def stop(self, runtime: Runtime) -> None:
|
|
@@ -598,11 +631,15 @@ class SandboxChannel(FramedChannel):
|
|
|
598
631
|
name: str,
|
|
599
632
|
data_port: int | None = None,
|
|
600
633
|
streams: int = 1,
|
|
634
|
+
lifetime_s: int = 0,
|
|
601
635
|
):
|
|
602
636
|
self.adapter = adapter
|
|
603
637
|
self.sandbox = sandbox
|
|
604
638
|
self.name = name
|
|
605
639
|
self.data_port = data_port
|
|
640
|
+
#: The sandbox's maximum lifetime, so a death at it is named rather than guessed.
|
|
641
|
+
self.lifetime_s = lifetime_s
|
|
642
|
+
self.opened_at = time.monotonic()
|
|
606
643
|
#: TCP connections the data channel carries its frames over.
|
|
607
644
|
self.streams = streams
|
|
608
645
|
self._sockets: list[socket.socket] = []
|
|
@@ -628,7 +665,13 @@ class SandboxChannel(FramedChannel):
|
|
|
628
665
|
self._await_ready()
|
|
629
666
|
|
|
630
667
|
def _raw_text(self) -> str:
|
|
631
|
-
|
|
668
|
+
text = bytes(self._raw[-2000:]).decode("utf-8", "replace")
|
|
669
|
+
# Spec "Modal": a sandbox that reached the lifetime letify asked for takes its
|
|
670
|
+
# worker with it, and the channel otherwise lists causes, none of them this one.
|
|
671
|
+
said = lifetime_detail(
|
|
672
|
+
age_s=time.monotonic() - self.opened_at, lifetime_s=self.lifetime_s
|
|
673
|
+
)
|
|
674
|
+
return f"{said}\n{text}" if said else text
|
|
632
675
|
|
|
633
676
|
def _send_worker(self) -> None:
|
|
634
677
|
# After a reexec the data connection is gone, so the source goes back over stdio.
|
|
@@ -138,7 +138,7 @@ dependencies = [
|
|
|
138
138
|
|
|
139
139
|
[[package]]
|
|
140
140
|
name = "letify-agent"
|
|
141
|
-
version = "2.6.
|
|
141
|
+
version = "2.6.4"
|
|
142
142
|
dependencies = [
|
|
143
143
|
"letify-wire",
|
|
144
144
|
"libloading",
|
|
@@ -146,7 +146,7 @@ dependencies = [
|
|
|
146
146
|
|
|
147
147
|
[[package]]
|
|
148
148
|
name = "letify-driver"
|
|
149
|
-
version = "2.6.
|
|
149
|
+
version = "2.6.4"
|
|
150
150
|
dependencies = [
|
|
151
151
|
"letify-wire",
|
|
152
152
|
]
|
|
@@ -164,7 +164,7 @@ dependencies = [
|
|
|
164
164
|
|
|
165
165
|
[[package]]
|
|
166
166
|
name = "letify-wire"
|
|
167
|
-
version = "2.6.
|
|
167
|
+
version = "2.6.4"
|
|
168
168
|
|
|
169
169
|
[[package]]
|
|
170
170
|
name = "libc"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "letify-status",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "letify-status",
|
|
9
|
-
"version": "2.6.
|
|
9
|
+
"version": "2.6.4",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE",
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@types/node": "^18.19.0",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "letify-status",
|
|
3
3
|
"displayName": "letify Status",
|
|
4
4
|
"description": "letify quota and GPU activity in the status bar, read from the letify CLI.",
|
|
5
|
-
"version": "2.6.
|
|
5
|
+
"version": "2.6.4",
|
|
6
6
|
"publisher": "darkpyonix",
|
|
7
7
|
"license": "SEE LICENSE IN LICENSE",
|
|
8
8
|
"repository": {
|
|
@@ -2218,7 +2218,9 @@ def test_a_modal_sandbox_is_created_with_a_lifetime_and_an_idle_limit(
|
|
|
2218
2218
|
provider.open_channel(runtime)
|
|
2219
2219
|
try:
|
|
2220
2220
|
[created] = fake_modal.requests("create")
|
|
2221
|
-
|
|
2221
|
+
# Spec "Modal": the lifetime is the longest Modal accepts, not an hour letify
|
|
2222
|
+
# picked, because a deadline letify invented ends the user's work at its guess.
|
|
2223
|
+
assert created["timeout"] == 86400
|
|
2222
2224
|
assert created["idle_timeout"] == 600
|
|
2223
2225
|
finally:
|
|
2224
2226
|
provider.stop(runtime)
|
|
@@ -2910,3 +2912,32 @@ def test_colab_punch_rendezvous_completes_during_tailcat_installation(
|
|
|
2910
2912
|
release.set()
|
|
2911
2913
|
installing.join(2)
|
|
2912
2914
|
assert endpoints == [("fake", 22)]
|
|
2915
|
+
|
|
2916
|
+
|
|
2917
|
+
def test_a_modal_sandbox_is_not_given_an_hour_long_deadline(config_file) -> None:
|
|
2918
|
+
"""Spec "Modal": a deadline letify invented would end a two hour run at its guess.
|
|
2919
|
+
|
|
2920
|
+
The default was 3600 s, exactly one hour, and it ended two Modal sessions of a real
|
|
2921
|
+
training run at one hour each with nothing saying why. The declaration's own `timeout`
|
|
2922
|
+
has no default for this reason, and the sandbox's lifetime is the same question.
|
|
2923
|
+
"""
|
|
2924
|
+
from conftest import provider_of
|
|
2925
|
+
|
|
2926
|
+
from letify.providers import Modal
|
|
2927
|
+
|
|
2928
|
+
provider = provider_of(Modal, "lab")
|
|
2929
|
+
assert provider.sandbox_timeout_s == 86400, "letify picked a deadline again"
|
|
2930
|
+
# An idle sandbox is what has to be bounded, and that bound is unchanged.
|
|
2931
|
+
assert provider.sandbox_idle_timeout_s == 600
|
|
2932
|
+
# An account that wants a cap still sets one.
|
|
2933
|
+
assert provider_of(Modal, "lab", timeout=7200).sandbox_timeout_s == 7200
|
|
2934
|
+
|
|
2935
|
+
|
|
2936
|
+
def test_a_sandbox_that_reached_its_lifetime_says_so(config_file) -> None:
|
|
2937
|
+
"""Spec "Modal": the channel otherwise lists causes, none of them this one."""
|
|
2938
|
+
from letify.providers.modal import lifetime_detail
|
|
2939
|
+
|
|
2940
|
+
assert lifetime_detail(age_s=3599.5, lifetime_s=3600) is not None
|
|
2941
|
+
assert "3600" in lifetime_detail(age_s=3599.5, lifetime_s=3600)
|
|
2942
|
+
assert "raise timeout" in lifetime_detail(age_s=3601.0, lifetime_s=3600)
|
|
2943
|
+
assert lifetime_detail(age_s=120.0, lifetime_s=3600) is None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{letify-2.6.3 → letify-2.6.4}/docs/guide/ko/01-/354/213/234/354/236/221/355/225/230/352/270/260.md"
RENAMED
|
File without changes
|
|
File without changes
|
{letify-2.6.3 → letify-2.6.4}/docs/guide/ko/03-/354/213/244/355/226/211-/353/260/251/354/213/235.md"
RENAMED
|
File without changes
|
|
File without changes
|
{letify-2.6.3 → letify-2.6.4}/docs/guide/ko/05-/353/217/231/354/213/234-/354/213/244/355/226/211.md"
RENAMED
|
File without changes
|
|
File without changes
|
{letify-2.6.3 → letify-2.6.4}/docs/guide/ko/07-/353/254/270/354/240/234-/355/225/264/352/262/260.md"
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|