letify 2.6.6__tar.gz → 2.6.7__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.6 → letify-2.6.7}/PKG-INFO +1 -1
- {letify-2.6.6 → letify-2.6.7}/docs/SPEC.md +14 -1
- {letify-2.6.6 → letify-2.6.7}/letify/__init__.py +1 -1
- {letify-2.6.6 → letify-2.6.7}/letify/providers/modal.py +33 -3
- {letify-2.6.6 → letify-2.6.7}/letify-core/Cargo.lock +3 -3
- {letify-2.6.6 → letify-2.6.7}/letify-core/Cargo.toml +1 -1
- {letify-2.6.6 → letify-2.6.7}/letify-ext/package-lock.json +2 -2
- {letify-2.6.6 → letify-2.6.7}/letify-ext/package.json +1 -1
- {letify-2.6.6 → letify-2.6.7}/pyproject.toml +1 -1
- {letify-2.6.6 → letify-2.6.7}/tests/test_providers.py +52 -0
- {letify-2.6.6 → letify-2.6.7}/.github/workflows/ci.yml +0 -0
- {letify-2.6.6 → letify-2.6.7}/.github/workflows/docs.yml +0 -0
- {letify-2.6.6 → letify-2.6.7}/.github/workflows/publish.yml +0 -0
- {letify-2.6.6 → letify-2.6.7}/.gitignore +0 -0
- {letify-2.6.6 → letify-2.6.7}/.researchtree +0 -0
- {letify-2.6.6 → letify-2.6.7}/CLAUDE.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/LICENSE +0 -0
- {letify-2.6.6 → letify-2.6.7}/PROJECT.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/README.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/COMPONENT.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/INTENT.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/NETWORK.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/guide/01-getting-started.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/guide/02-providers.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/guide/03-execution-modes.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/guide/04-environments-and-data.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/guide/05-concurrency.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/guide/06-cost.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/guide/07-troubleshooting.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/guide/08-releasing.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/guide/README.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/guide/ko/01-/354/213/234/354/236/221/355/225/230/352/270/260.md" +0 -0
- {letify-2.6.6 → letify-2.6.7}/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.6 → letify-2.6.7}/docs/guide/ko/03-/354/213/244/355/226/211-/353/260/251/354/213/235.md" +0 -0
- {letify-2.6.6 → letify-2.6.7}/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.6 → letify-2.6.7}/docs/guide/ko/05-/353/217/231/354/213/234-/354/213/244/355/226/211.md" +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/guide/ko/06-/353/271/204/354/232/251.md" +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/guide/ko/07-/353/254/270/354/240/234-/355/225/264/352/262/260.md" +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/guide/ko/08-/353/246/264/353/246/254/354/212/244.md" +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/guide/ko/README.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/locales/README_ko.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/docs/mkdocs.yml +0 -0
- {letify-2.6.6 → letify-2.6.7}/examples/README.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/examples/nvfp4-lora/.letify.example +0 -0
- {letify-2.6.6 → letify-2.6.7}/examples/nvfp4-lora/00_smoke.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/examples/nvfp4-lora/01_sweep.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/examples/nvfp4-lora/02_resume.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/examples/nvfp4-lora/03_watch.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/examples/nvfp4-lora/README.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/examples/nvfp4-lora/README_ko.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/examples/nvfp4-lora/common.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/examples/nvfp4-lora/recipe.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/cli.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/config/__init__.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/config/inventory.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/config/login.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/config/schema.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/config/secrets.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/config/writer.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/declare/__init__.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/declare/cache.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/declare/env.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/declare/function.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/declare/instance.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/declare/reduce.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/declare/replicas.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/errors.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/install.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/launcher.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/paths.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/protocol/__init__.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/protocol/codec.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/protocol/driver.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/protocol/handle.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/protocol/wire.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/protocol/worker.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/providers/__init__.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/providers/base.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/providers/colab.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/providers/colab_files.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/providers/colab_keepalive.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/providers/elice.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/providers/kaggle.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/providers/kaggle_adapter.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/providers/local.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/providers/modal_adapter.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/providers/naming.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/providers/shell.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/providers/tunnel.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/providers/usage.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/remoting/__init__.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/remoting/capability.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/remoting/device/__init__.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/remoting/device/client.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/remoting/device/cuda.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/remoting/device/executor.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/remoting/device/frames.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/remoting/device/guard.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/remoting/device/tensor.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/remoting/device/trace.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/remoting/device/value.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/remoting/loader.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/remoting/probe.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/render.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/runtime/__init__.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/runtime/bootstrap.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/runtime/broker.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/runtime/channel.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/runtime/lease.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/runtime/pool.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/runtime/session.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/runtime/telemetry.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/store/__init__.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/store/backends/__init__.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/store/backends/filesystem.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/store/backends/google_auth.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/store/backends/layout.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/store/backends/objects.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/store/cas.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/store/pathdata.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/store/sendorder.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/store/volume.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/stubs.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/tools.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/transport/__init__.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/transport/agent.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/transport/announce.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/transport/link.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/transport/nat.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/transport/pipeline.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/transport/portmap.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/transport/probe.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/transport/quic.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/transport/rendezvous.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/transport/setup.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/transport/sshopts.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify/transport/strategies.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/.gitignore +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/build.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/letify-agent/Cargo.toml +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/letify-agent/src/driver.rs +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/letify-agent/src/main.rs +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/letify-driver/Cargo.toml +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/letify-driver/src/client.rs +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/letify-driver/src/exports.rs +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/letify-driver/src/image.rs +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/letify-driver/src/lib.rs +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/letify-driver/src/status.rs +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/letify-driver/src/table.rs +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/letify-quic/Cargo.toml +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/letify-quic/src/main.rs +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/letify-wire/Cargo.toml +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/letify-wire/src/lib.rs +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/letify-wire/tests/throughput.rs +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-core/rust-toolchain.toml +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-ext/.vscodeignore +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-ext/LICENSE +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-ext/README.md +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-ext/media/icon.svg +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-ext/src/cli.ts +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-ext/src/extension.ts +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-ext/src/history.ts +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-ext/src/model.ts +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-ext/src/poll.ts +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-ext/src/render.ts +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-ext/test/cli.test.ts +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-ext/test/manifest.test.ts +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-ext/test/model.test.ts +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-ext/test/regressions.test.ts +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify-ext/tsconfig.json +0 -0
- {letify-2.6.6 → letify-2.6.7}/letify_providers.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/scripts/sync-main.sh +0 -0
- {letify-2.6.6 → letify-2.6.7}/scripts/version.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/conftest.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/fake_kaggle_adapter.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/fake_modal_adapter.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_cli.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_cli_output.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_colab_keepalive.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_config.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_core.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_data.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_data_streaming.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_declare.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_device.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_distributed.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_editor_sessions.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_elice.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_frames.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_install.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_kaggle.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_kaggle_cookie.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_kaggle_link.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_link_log.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_notebook.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_parallel_placement.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_paths.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_portmap.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_protocol.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_quic.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_remoting.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_render.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_runtime.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_ship.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_store.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_strategies.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_stubs.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_transport.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_tunnel_setup.py +0 -0
- {letify-2.6.6 → letify-2.6.7}/tests/test_version.py +0 -0
|
@@ -847,7 +847,20 @@ While the channel waits for `HELLO` each read waits at most 30 s, and a lane rea
|
|
|
847
847
|
|
|
848
848
|
Socket buffers are left to the kernel. `SO_SNDBUF` and `SO_RCVBUF` are never set, because setting them turns off the kernel's buffer tuning and the value is capped at `net.core.wmem_max`, 212 KiB by default.
|
|
849
849
|
|
|
850
|
-
|
|
850
|
+
The tunnel's host name is new when the sandbox is, so a resolver that is asked for it too early can
|
|
851
|
+
answer that it does not exist and keep that answer cached. Resolution is therefore retried for
|
|
852
|
+
`DATA_OPEN_WINDOW`, 30 s, every 3 s, before the channel gives up. Measured on a host where
|
|
853
|
+
`github.com` always resolved and a freshly created tunnel name did not, three sessions in a row fell
|
|
854
|
+
back, and 7.6 GB then went over standard input and output, where one piece held the control path long
|
|
855
|
+
enough for the lease renewal to miss its 60 s timeout and the worker pipe closed. Mornings on the
|
|
856
|
+
same host reached 45 to 90 MiB/s on the same path, so the fallback was the whole difference.
|
|
857
|
+
|
|
858
|
+
When the window ends without a connection, or a `tunnel` request fails, or no `HELLO` arrives within
|
|
859
|
+
30 s, the channel prints one line on stderr, `letify: <runtime>: the data channel did not open
|
|
860
|
+
(<reason>, host <name>); frames stay on standard input and output, which the control path shares, so
|
|
861
|
+
a large transfer there can starve the lease`, and keeps using standard input and output. The host name
|
|
862
|
+
is named so a reader can resolve it themselves. The channel tries again on the next call rather than
|
|
863
|
+
staying that way, because the name may simply have been too new. The worker returns to reading standard input when a byte arrives there or its `wait` ends without an authenticated connection, so a request the channel sends over standard input after a failure is answered without waiting for `wait`.
|
|
851
864
|
|
|
852
865
|
A `reexec` request goes over the data connection. The worker replies there and replaces its process, which closes the connection. The channel then sends the worker source over standard input, waits for `HELLO` there, and opens the data channel again.
|
|
853
866
|
|
|
@@ -86,6 +86,13 @@ LISTEN_WAIT = 60
|
|
|
86
86
|
#: Seconds the channel allows for connecting through the tunnel and for the hello after it.
|
|
87
87
|
DATA_CONNECT_TIMEOUT = 30
|
|
88
88
|
|
|
89
|
+
#: Seconds the channel keeps trying to reach a tunnel whose host name does not resolve,
|
|
90
|
+
#: and the pause between attempts. Spec "Modal data channel": the name is new when the
|
|
91
|
+
#: sandbox is, so a resolver asked too early can answer that it does not exist and cache
|
|
92
|
+
#: that answer.
|
|
93
|
+
DATA_OPEN_WINDOW = 30.0
|
|
94
|
+
DATA_OPEN_PAUSE = 3.0
|
|
95
|
+
|
|
89
96
|
#: TCP connections the data channel opens when ``data_streams`` is not set, and the most
|
|
90
97
|
#: it accepts. Spec "Parallel data streams".
|
|
91
98
|
DATA_STREAMS = 4
|
|
@@ -608,6 +615,23 @@ class TlsStream:
|
|
|
608
615
|
self._wire.release()
|
|
609
616
|
|
|
610
617
|
|
|
618
|
+
def _reach(address: tuple[str, int]) -> socket.socket:
|
|
619
|
+
"""Connect to the tunnel, waiting out a host name the resolver does not have yet.
|
|
620
|
+
|
|
621
|
+
Spec "Modal data channel": the name is new when the sandbox is, so a resolver asked
|
|
622
|
+
too early can answer that it does not exist and keep that answer cached. Anything but
|
|
623
|
+
a resolution failure is raised at once, because waiting does not help it.
|
|
624
|
+
"""
|
|
625
|
+
deadline = time.monotonic() + DATA_OPEN_WINDOW
|
|
626
|
+
while True:
|
|
627
|
+
try:
|
|
628
|
+
return socket.create_connection(address, timeout=DATA_CONNECT_TIMEOUT)
|
|
629
|
+
except socket.gaierror:
|
|
630
|
+
if time.monotonic() >= deadline:
|
|
631
|
+
raise
|
|
632
|
+
time.sleep(DATA_OPEN_PAUSE)
|
|
633
|
+
|
|
634
|
+
|
|
611
635
|
class SandboxChannel(FramedChannel):
|
|
612
636
|
"""Carries the worker's frames over a sandbox's pipes, through the adapter.
|
|
613
637
|
|
|
@@ -637,6 +661,9 @@ class SandboxChannel(FramedChannel):
|
|
|
637
661
|
self.sandbox = sandbox
|
|
638
662
|
self.name = name
|
|
639
663
|
self.data_port = data_port
|
|
664
|
+
#: The tunnel host the last open reached for, named in a failure so a reader can
|
|
665
|
+
#: resolve it themselves. Spec "Modal data channel".
|
|
666
|
+
self._tunnel_host: str | None = None
|
|
640
667
|
#: The sandbox's maximum lifetime, so a death at it is named rather than guessed.
|
|
641
668
|
self.lifetime_s = lifetime_s
|
|
642
669
|
self.opened_at = time.monotonic()
|
|
@@ -688,9 +715,11 @@ class SandboxChannel(FramedChannel):
|
|
|
688
715
|
self._connection = self._open_data(self.data_port)
|
|
689
716
|
except (LetifyError, OSError, ValueError, KeyError, TypeError) as exc:
|
|
690
717
|
self._close_socket()
|
|
718
|
+
where = f", host {self._tunnel_host}" if self._tunnel_host else ""
|
|
691
719
|
print(
|
|
692
|
-
f"letify: {self.name}: the data channel did not open ({exc}); "
|
|
693
|
-
"frames stay on standard input and output"
|
|
720
|
+
f"letify: {self.name}: the data channel did not open ({exc}{where}); "
|
|
721
|
+
"frames stay on standard input and output, which the control path "
|
|
722
|
+
"shares, so a large transfer there can starve the lease",
|
|
694
723
|
file=sys.stderr,
|
|
695
724
|
flush=True,
|
|
696
725
|
)
|
|
@@ -707,6 +736,7 @@ class SandboxChannel(FramedChannel):
|
|
|
707
736
|
stdio.request(listen, timeout=DATA_CONNECT_TIMEOUT + LISTEN_WAIT, kill=self._kill)
|
|
708
737
|
tunnel = self.adapter.request("tunnel", sandbox=self.sandbox, port=port)
|
|
709
738
|
host = str(tunnel["host"])
|
|
739
|
+
self._tunnel_host = host
|
|
710
740
|
address = (host, int(tunnel["port"]))
|
|
711
741
|
context = None
|
|
712
742
|
if tunnel.get("tls"):
|
|
@@ -717,7 +747,7 @@ class SandboxChannel(FramedChannel):
|
|
|
717
747
|
def open_lane(index: int) -> None:
|
|
718
748
|
# Spec "Parallel data streams": every lane opens at the same time.
|
|
719
749
|
try:
|
|
720
|
-
raw =
|
|
750
|
+
raw = _reach(address)
|
|
721
751
|
except BaseException as exc:
|
|
722
752
|
errors.append(exc)
|
|
723
753
|
return
|
|
@@ -138,7 +138,7 @@ dependencies = [
|
|
|
138
138
|
|
|
139
139
|
[[package]]
|
|
140
140
|
name = "letify-agent"
|
|
141
|
-
version = "2.6.
|
|
141
|
+
version = "2.6.7"
|
|
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.7"
|
|
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.7"
|
|
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.7",
|
|
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.7",
|
|
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.7",
|
|
6
6
|
"publisher": "darkpyonix",
|
|
7
7
|
"license": "SEE LICENSE IN LICENSE",
|
|
8
8
|
"repository": {
|
|
@@ -2941,3 +2941,55 @@ def test_a_sandbox_that_reached_its_lifetime_says_so(config_file) -> None:
|
|
|
2941
2941
|
assert "3600" in lifetime_detail(age_s=3599.5, lifetime_s=3600)
|
|
2942
2942
|
assert "raise timeout" in lifetime_detail(age_s=3601.0, lifetime_s=3600)
|
|
2943
2943
|
assert lifetime_detail(age_s=120.0, lifetime_s=3600) is None
|
|
2944
|
+
|
|
2945
|
+
|
|
2946
|
+
def test_a_tunnel_name_that_is_not_in_dns_yet_is_waited_for(
|
|
2947
|
+
isolated_home, fake_modal, monkeypatch
|
|
2948
|
+
) -> None:
|
|
2949
|
+
"""Spec "Modal data channel": the host name is new when the sandbox is.
|
|
2950
|
+
|
|
2951
|
+
KICT lost three sessions in a row to a resolver that answered NXDOMAIN for a freshly
|
|
2952
|
+
created tunnel name while github.com resolved every time, and 7.6 GB then went over
|
|
2953
|
+
standard input and output until the lease renewal missed its timeout.
|
|
2954
|
+
"""
|
|
2955
|
+
import socket as socketmod
|
|
2956
|
+
|
|
2957
|
+
from letify.providers import modal as modalmod
|
|
2958
|
+
|
|
2959
|
+
attempts: list[float] = []
|
|
2960
|
+
real = socketmod.create_connection
|
|
2961
|
+
|
|
2962
|
+
def refuse_then_open(address, timeout=None, *args, **kwargs):
|
|
2963
|
+
attempts.append(0.0)
|
|
2964
|
+
if len(attempts) < 3:
|
|
2965
|
+
raise socketmod.gaierror(-3, "Temporary failure in name resolution")
|
|
2966
|
+
return real(address, timeout, *args, **kwargs)
|
|
2967
|
+
|
|
2968
|
+
monkeypatch.setattr(modalmod.socket, "create_connection", refuse_then_open)
|
|
2969
|
+
monkeypatch.setattr(modalmod, "DATA_OPEN_PAUSE", 0.01)
|
|
2970
|
+
provider = provider_of(Modal, "m")
|
|
2971
|
+
runtime = modal_runtime(provider)
|
|
2972
|
+
channel = provider.open_channel(runtime)
|
|
2973
|
+
try:
|
|
2974
|
+
channel.start()
|
|
2975
|
+
assert channel.call(sum, ([1, 2, 3],), {})[0] == 6
|
|
2976
|
+
assert len(attempts) >= 3, f"gave up after {len(attempts)} attempt(s)"
|
|
2977
|
+
finally:
|
|
2978
|
+
channel.close()
|
|
2979
|
+
provider.stop(runtime)
|
|
2980
|
+
|
|
2981
|
+
|
|
2982
|
+
def test_a_data_channel_failure_names_the_host(isolated_home, fake_modal, capfd) -> None:
|
|
2983
|
+
"""Spec "Modal data channel": the host name is named so a reader can resolve it."""
|
|
2984
|
+
fake_modal.fail("tunnel")
|
|
2985
|
+
provider = provider_of(Modal, "m")
|
|
2986
|
+
runtime = modal_runtime(provider)
|
|
2987
|
+
channel = provider.open_channel(runtime)
|
|
2988
|
+
try:
|
|
2989
|
+
channel.start()
|
|
2990
|
+
assert channel.call(sum, ([1, 2, 3],), {})[0] == 6
|
|
2991
|
+
err = capfd.readouterr().err
|
|
2992
|
+
assert "the control path shares" in err, err
|
|
2993
|
+
finally:
|
|
2994
|
+
channel.close()
|
|
2995
|
+
provider.stop(runtime)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.6 → letify-2.6.7}/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.6 → letify-2.6.7}/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.6 → letify-2.6.7}/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.6 → letify-2.6.7}/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
|