letify 2.5.3__tar.gz → 2.5.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.5.3 → letify-2.5.4}/PKG-INFO +1 -1
- {letify-2.5.3 → letify-2.5.4}/docs/SPEC.md +1 -0
- {letify-2.5.3 → letify-2.5.4}/letify/__init__.py +1 -1
- {letify-2.5.3 → letify-2.5.4}/letify/protocol/worker.py +44 -1
- {letify-2.5.3 → letify-2.5.4}/letify/runtime/session.py +1 -1
- {letify-2.5.3 → letify-2.5.4}/letify/store/pathdata.py +30 -6
- {letify-2.5.3 → letify-2.5.4}/letify-core/Cargo.lock +3 -3
- {letify-2.5.3 → letify-2.5.4}/letify-core/Cargo.toml +1 -1
- {letify-2.5.3 → letify-2.5.4}/letify-ext/package-lock.json +2 -2
- {letify-2.5.3 → letify-2.5.4}/letify-ext/package.json +1 -1
- {letify-2.5.3 → letify-2.5.4}/pyproject.toml +1 -1
- {letify-2.5.3 → letify-2.5.4}/tests/test_data_streaming.py +43 -0
- {letify-2.5.3 → letify-2.5.4}/.github/workflows/ci.yml +0 -0
- {letify-2.5.3 → letify-2.5.4}/.github/workflows/docs.yml +0 -0
- {letify-2.5.3 → letify-2.5.4}/.github/workflows/publish.yml +0 -0
- {letify-2.5.3 → letify-2.5.4}/.gitignore +0 -0
- {letify-2.5.3 → letify-2.5.4}/.researchtree +0 -0
- {letify-2.5.3 → letify-2.5.4}/CLAUDE.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/LICENSE +0 -0
- {letify-2.5.3 → letify-2.5.4}/PROJECT.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/README.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/COMPONENT.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/INTENT.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/NETWORK.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/guide/01-getting-started.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/guide/02-providers.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/guide/03-execution-modes.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/guide/04-environments-and-data.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/guide/05-concurrency.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/guide/06-cost.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/guide/07-troubleshooting.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/guide/08-releasing.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/guide/README.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/guide/ko/01-/354/213/234/354/236/221/355/225/230/352/270/260.md" +0 -0
- {letify-2.5.3 → letify-2.5.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.5.3 → letify-2.5.4}/docs/guide/ko/03-/354/213/244/355/226/211-/353/260/251/354/213/235.md" +0 -0
- {letify-2.5.3 → letify-2.5.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.5.3 → letify-2.5.4}/docs/guide/ko/05-/353/217/231/354/213/234-/354/213/244/355/226/211.md" +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/guide/ko/06-/353/271/204/354/232/251.md" +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/guide/ko/07-/353/254/270/354/240/234-/355/225/264/352/262/260.md" +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/guide/ko/08-/353/246/264/353/246/254/354/212/244.md" +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/guide/ko/README.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/locales/README_ko.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/docs/mkdocs.yml +0 -0
- {letify-2.5.3 → letify-2.5.4}/examples/README.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/examples/nvfp4-lora/.letify.example +0 -0
- {letify-2.5.3 → letify-2.5.4}/examples/nvfp4-lora/00_smoke.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/examples/nvfp4-lora/01_sweep.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/examples/nvfp4-lora/02_resume.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/examples/nvfp4-lora/03_watch.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/examples/nvfp4-lora/README.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/examples/nvfp4-lora/README_ko.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/examples/nvfp4-lora/common.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/examples/nvfp4-lora/recipe.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/cli.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/config/__init__.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/config/inventory.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/config/login.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/config/schema.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/config/secrets.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/config/writer.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/declare/__init__.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/declare/cache.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/declare/env.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/declare/function.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/declare/instance.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/declare/reduce.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/declare/replicas.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/errors.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/install.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/launcher.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/paths.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/protocol/__init__.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/protocol/codec.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/protocol/driver.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/protocol/handle.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/protocol/wire.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/providers/__init__.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/providers/base.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/providers/colab.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/providers/colab_files.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/providers/colab_keepalive.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/providers/elice.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/providers/kaggle.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/providers/kaggle_adapter.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/providers/local.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/providers/modal.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/providers/modal_adapter.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/providers/naming.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/providers/shell.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/providers/tunnel.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/providers/usage.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/remoting/__init__.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/remoting/capability.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/remoting/device/__init__.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/remoting/device/client.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/remoting/device/cuda.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/remoting/device/executor.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/remoting/device/frames.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/remoting/device/guard.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/remoting/device/tensor.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/remoting/device/trace.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/remoting/device/value.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/remoting/loader.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/remoting/probe.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/render.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/runtime/__init__.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/runtime/bootstrap.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/runtime/broker.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/runtime/channel.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/runtime/lease.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/runtime/pool.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/runtime/telemetry.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/store/__init__.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/store/backends/__init__.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/store/backends/filesystem.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/store/backends/google_auth.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/store/backends/layout.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/store/backends/objects.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/store/cas.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/store/sendorder.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/store/volume.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/stubs.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/tools.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/transport/__init__.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/transport/agent.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/transport/announce.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/transport/link.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/transport/nat.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/transport/pipeline.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/transport/portmap.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/transport/probe.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/transport/quic.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/transport/rendezvous.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/transport/setup.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/transport/sshopts.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify/transport/strategies.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/.gitignore +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/build.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/letify-agent/Cargo.toml +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/letify-agent/src/driver.rs +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/letify-agent/src/main.rs +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/letify-driver/Cargo.toml +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/letify-driver/src/client.rs +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/letify-driver/src/exports.rs +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/letify-driver/src/image.rs +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/letify-driver/src/lib.rs +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/letify-driver/src/status.rs +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/letify-driver/src/table.rs +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/letify-quic/Cargo.toml +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/letify-quic/src/main.rs +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/letify-wire/Cargo.toml +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/letify-wire/src/lib.rs +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/letify-wire/tests/throughput.rs +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-core/rust-toolchain.toml +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-ext/.vscodeignore +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-ext/LICENSE +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-ext/README.md +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-ext/media/icon.svg +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-ext/src/cli.ts +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-ext/src/extension.ts +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-ext/src/history.ts +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-ext/src/model.ts +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-ext/src/poll.ts +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-ext/src/render.ts +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-ext/test/cli.test.ts +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-ext/test/manifest.test.ts +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-ext/test/model.test.ts +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-ext/test/regressions.test.ts +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify-ext/tsconfig.json +0 -0
- {letify-2.5.3 → letify-2.5.4}/letify_providers.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/scripts/sync-main.sh +0 -0
- {letify-2.5.3 → letify-2.5.4}/scripts/version.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/conftest.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/fake_kaggle_adapter.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/fake_modal_adapter.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_cli.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_cli_output.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_colab_keepalive.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_config.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_core.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_data.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_declare.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_device.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_distributed.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_editor_sessions.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_elice.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_frames.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_install.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_kaggle.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_kaggle_cookie.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_kaggle_link.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_link_log.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_notebook.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_parallel_placement.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_paths.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_portmap.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_protocol.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_providers.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_quic.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_remoting.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_render.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_runtime.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_ship.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_store.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_strategies.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_stubs.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_transport.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_tunnel_setup.py +0 -0
- {letify-2.5.3 → letify-2.5.4}/tests/test_version.py +0 -0
|
@@ -1239,6 +1239,7 @@ A `stat` cannot take that snapshot first, because it is on a data loader's hot p
|
|
|
1239
1239
|
#### When a blob does not arrive <!-- id: project-data-streaming-failures -->
|
|
1240
1240
|
|
|
1241
1241
|
- **A blob that never arrives.** A `data_want` unanswered for `data_wait_timeout` seconds on the account, 600 by default, fails that open with `RuntimeFailure` naming the relative path and the digest. The call is not killed, because the body may handle it; the failure is infrastructure, so a retry of the call is allowed.
|
|
1242
|
+
- **A sender that stops.** The background sender raising leaves every blob still queued unsendable. letify says so once, `letify: data sending stopped after <n> of <n> files, <n> not sent: <error>`, and tells the runtime, which wakes every reader waiting on one of those blobs with `RuntimeFailure` naming the file and that reason, and fails a reader that arrives later at once. Waiting it out instead costs `data_wait_timeout` once for every blob still queued, which is how a hundred queued shards become an hour of silence.
|
|
1242
1243
|
- **A client that disconnects mid stream.** The session ends as any lost channel ends it. Every blocked reader is woken with `RuntimeFailure`, the call ends with it, and no partial file is ever placed or read. A `.partial` file is removed when the worker next starts.
|
|
1243
1244
|
- **A call that finishes before the background transfer.** The transfer is cancelled, both the channel sender and any bucket upload not yet started. A blob already committed to the cache stays and counts as held for the next call, and a partial one is removed. The write-back and the `data_evict` request follow the cancellation, in that order.
|
|
1244
1245
|
- **A bucket-backed ephemeral provider.** A pull that fails is retried twice with the same downscoped headers, then a third time with headers the local process derives again, and a pull that still fails fails the open with `RuntimeFailure`. A `data_want` for a digest not yet uploaded moves that object to the front of the upload queue, and the worker pulls it when the local process sends its `data_pull`.
|
|
@@ -364,6 +364,8 @@ def _op_call(request):
|
|
|
364
364
|
import shutil
|
|
365
365
|
_data_unregister(state)
|
|
366
366
|
_data_check_cache(data, state["placed"])
|
|
367
|
+
_DATA_FAILED.pop(data["dir"], None)
|
|
368
|
+
_DATA_FAILED.pop("", None)
|
|
367
369
|
_DATA_POLL_SEEN.pop(data["dir"], None)
|
|
368
370
|
_DATA_POLL_SENT.pop(data["dir"], None)
|
|
369
371
|
shutil.rmtree(data["dir"], ignore_errors=True)
|
|
@@ -1047,6 +1049,9 @@ def _data_register(data):
|
|
|
1047
1049
|
"linked": 0,
|
|
1048
1050
|
"order": {},
|
|
1049
1051
|
"arrived": 0,
|
|
1052
|
+
# Spec "When a blob does not arrive": set when the local sender stops, so a
|
|
1053
|
+
# reader fails at once with its reason rather than waiting out the timeout.
|
|
1054
|
+
"failed": _DATA_FAILED.get(data["dir"]) or _DATA_FAILED.get(""),
|
|
1050
1055
|
"wait": float(data.get("wait") or 600.0),
|
|
1051
1056
|
"observe": bool(data.get("observe", True)),
|
|
1052
1057
|
"prefetch": int(data.get("prefetch") or 64),
|
|
@@ -1238,6 +1243,32 @@ def _data_arrived(blobs, digest):
|
|
|
1238
1243
|
_DATA_READY.notify_all()
|
|
1239
1244
|
|
|
1240
1245
|
|
|
1246
|
+
#: Reasons a sender stopped, by call directory, for a call that registers afterwards.
|
|
1247
|
+
_DATA_FAILED = {}
|
|
1248
|
+
|
|
1249
|
+
|
|
1250
|
+
def _op_data_failed(request):
|
|
1251
|
+
"""The local sender stopped, so the blobs it had left will never arrive.
|
|
1252
|
+
|
|
1253
|
+
Spec "When a blob does not arrive": every reader waiting on one of them is woken with
|
|
1254
|
+
the reason, and a reader that arrives later fails on it at once, because waiting out
|
|
1255
|
+
the per blob timeout costs that timeout once for every blob still queued.
|
|
1256
|
+
"""
|
|
1257
|
+
reason = request.get("reason") or "the local process stopped sending"
|
|
1258
|
+
where = request.get("dir") or ""
|
|
1259
|
+
with _DATA_READY:
|
|
1260
|
+
# Remembered by directory as well as set on the live call, because the sender can
|
|
1261
|
+
# stop before the call request has been read, and the call then registers after
|
|
1262
|
+
# this and would otherwise inherit nothing. Spec "When a blob does not arrive".
|
|
1263
|
+
_DATA_FAILED[where] = reason
|
|
1264
|
+
for state in _DATA_CALLS.values():
|
|
1265
|
+
if where and state["dir"] != where:
|
|
1266
|
+
continue
|
|
1267
|
+
state["failed"] = reason
|
|
1268
|
+
_DATA_READY.notify_all()
|
|
1269
|
+
return {"ok": True, "value": True}
|
|
1270
|
+
|
|
1271
|
+
|
|
1241
1272
|
def _data_want(digests, blocking=False):
|
|
1242
1273
|
"""Ask the local process for these blobs, in this order. Nothing replies to it."""
|
|
1243
1274
|
if not digests:
|
|
@@ -1304,6 +1335,10 @@ def _data_wait(path):
|
|
|
1304
1335
|
entry = state["pending"].get(path)
|
|
1305
1336
|
if entry is None:
|
|
1306
1337
|
return
|
|
1338
|
+
if state["failed"]:
|
|
1339
|
+
raise _DataFailure(
|
|
1340
|
+
"%s was never sent: %s" % (os.path.basename(path), state["failed"])
|
|
1341
|
+
)
|
|
1307
1342
|
digest, size = entry
|
|
1308
1343
|
index = state["order"].get(digest)
|
|
1309
1344
|
if index is not None and index > state["arrived"] + _DATA_MISS_AHEAD:
|
|
@@ -1321,6 +1356,11 @@ def _data_wait(path):
|
|
|
1321
1356
|
try:
|
|
1322
1357
|
with _DATA_READY:
|
|
1323
1358
|
while path in state["pending"] and state["dir"] in _DATA_CALLS:
|
|
1359
|
+
if state["failed"]:
|
|
1360
|
+
raise _DataFailure(
|
|
1361
|
+
"%s was never sent: %s"
|
|
1362
|
+
% (os.path.basename(path), state["failed"])
|
|
1363
|
+
)
|
|
1324
1364
|
left = deadline - time.monotonic()
|
|
1325
1365
|
if left <= 0:
|
|
1326
1366
|
raise _DataFailure(
|
|
@@ -2289,6 +2329,7 @@ _OPS = {
|
|
|
2289
2329
|
"data_put": _op_data_put,
|
|
2290
2330
|
"data_begin": _op_data_begin,
|
|
2291
2331
|
"data_drop": _op_data_drop,
|
|
2332
|
+
"data_failed": _op_data_failed,
|
|
2292
2333
|
"data_put_at": _op_data_put_at,
|
|
2293
2334
|
"data_seal": _op_data_seal,
|
|
2294
2335
|
"data_pull": _op_data_pull,
|
|
@@ -2314,7 +2355,9 @@ _OPS = {
|
|
|
2314
2355
|
}
|
|
2315
2356
|
|
|
2316
2357
|
#: Answered by the frame reader at once, so they work while a call runs.
|
|
2317
|
-
|
|
2358
|
+
# ``data_failed`` is light because it must not queue behind the call it is about: the call
|
|
2359
|
+
# is blocked on a blob the sender will never send, so a queued answer would deadlock.
|
|
2360
|
+
_LIGHT = ("stat", "lease", "reduce_peers", "data_failed")
|
|
2318
2361
|
|
|
2319
2362
|
#: Answered by several replies, the last one marked. These send their own replies.
|
|
2320
2363
|
_STREAMING = ("data_stream",)
|
|
@@ -419,7 +419,7 @@ class Runtime:
|
|
|
419
419
|
needed = [d for d in plan.missing if d in ship_digests and d not in wave]
|
|
420
420
|
if needed:
|
|
421
421
|
wave = needed + wave
|
|
422
|
-
stream = pathdata.Stream(self, blobs, plan)
|
|
422
|
+
stream = pathdata.Stream(self, blobs, plan, collector.call_dir)
|
|
423
423
|
if not plan.missing:
|
|
424
424
|
# Nothing to stream: the runtime holds every file, so the call places them all
|
|
425
425
|
# before the body starts. It needs no manifest, no patch and no observation.
|
|
@@ -23,7 +23,7 @@ from importlib import import_module
|
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
from typing import TYPE_CHECKING, Any
|
|
25
25
|
|
|
26
|
-
from ..errors import ConfigError, RemoteError, RuntimeFailure
|
|
26
|
+
from ..errors import ConfigError, ProtocolError, RemoteError, RuntimeFailure
|
|
27
27
|
|
|
28
28
|
if TYPE_CHECKING:
|
|
29
29
|
from ..providers.base import Provider
|
|
@@ -608,10 +608,12 @@ class Stream:
|
|
|
608
608
|
order after the call starts comes from the runtime rather than from the prediction.
|
|
609
609
|
"""
|
|
610
610
|
|
|
611
|
-
def __init__(self, runtime: Runtime, blobs: str, plan: Plan) -> None:
|
|
611
|
+
def __init__(self, runtime: Runtime, blobs: str, plan: Plan, call_dir: str = "") -> None:
|
|
612
612
|
self.runtime = runtime
|
|
613
613
|
self.blobs = blobs
|
|
614
614
|
self.plan = plan
|
|
615
|
+
#: The call this stream feeds, so a stopped sender fails that call's readers only.
|
|
616
|
+
self.call_dir = call_dir
|
|
615
617
|
#: Digests whose send finished. ``_sent`` holds the ones it began, so a blob the
|
|
616
618
|
#: sender was carrying when the call ended is in one and not the other.
|
|
617
619
|
self.done: set[str] = set()
|
|
@@ -662,13 +664,35 @@ class Stream:
|
|
|
662
664
|
break
|
|
663
665
|
# The pieces still in flight are answered before the stream is released.
|
|
664
666
|
self._flush()
|
|
665
|
-
except Exception:
|
|
666
|
-
#
|
|
667
|
-
#
|
|
668
|
-
|
|
667
|
+
except Exception as exc:
|
|
668
|
+
# Spec "When a blob does not arrive": the sender stopping leaves every blob
|
|
669
|
+
# still queued unsendable, so it is said once and the readers waiting on one
|
|
670
|
+
# are failed with the reason. Waiting it out costs the per blob timeout once
|
|
671
|
+
# for each, which is how 131 queued shards became a silent hour.
|
|
672
|
+
if not self._cancelled.is_set():
|
|
673
|
+
self._stopped(exc)
|
|
669
674
|
finally:
|
|
670
675
|
self.during_seconds = time.monotonic() - started
|
|
671
676
|
|
|
677
|
+
def _stopped(self, exc: BaseException) -> None:
|
|
678
|
+
"""Say the sender stopped, and tell the runtime so its readers stop waiting."""
|
|
679
|
+
from .. import install
|
|
680
|
+
|
|
681
|
+
left = self.dropped
|
|
682
|
+
reason = f"{type(exc).__name__}: {exc}"
|
|
683
|
+
install.log(
|
|
684
|
+
f"data sending stopped after {len(self.done)} of "
|
|
685
|
+
f"{len(self.plan.missing)} files, {len(left)} not sent: {reason}"
|
|
686
|
+
)
|
|
687
|
+
try:
|
|
688
|
+
_worker(
|
|
689
|
+
self.runtime,
|
|
690
|
+
{"op": "data_failed", "dir": self.call_dir, "reason": reason},
|
|
691
|
+
)
|
|
692
|
+
except (RuntimeFailure, RemoteError, ProtocolError):
|
|
693
|
+
# The runtime is gone, which fails every reader on its own.
|
|
694
|
+
pass
|
|
695
|
+
|
|
672
696
|
def _pump(self) -> bool:
|
|
673
697
|
"""Send the next blob of the queue. False once nothing is left to send."""
|
|
674
698
|
self.reorder()
|
|
@@ -138,7 +138,7 @@ dependencies = [
|
|
|
138
138
|
|
|
139
139
|
[[package]]
|
|
140
140
|
name = "letify-agent"
|
|
141
|
-
version = "2.5.
|
|
141
|
+
version = "2.5.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.5.
|
|
149
|
+
version = "2.5.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.5.
|
|
167
|
+
version = "2.5.4"
|
|
168
168
|
|
|
169
169
|
[[package]]
|
|
170
170
|
name = "libc"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "letify-status",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "letify-status",
|
|
9
|
-
"version": "2.5.
|
|
9
|
+
"version": "2.5.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.5.
|
|
5
|
+
"version": "2.5.4",
|
|
6
6
|
"publisher": "darkpyonix",
|
|
7
7
|
"license": "SEE LICENSE IN LICENSE",
|
|
8
8
|
"repository": {
|
|
@@ -1641,3 +1641,46 @@ def test_a_cancelled_send_leaves_no_partial_in_the_blob_cache(
|
|
|
1641
1641
|
root = Path.home() / ".letify" / "runtime" / "data" / "blobs"
|
|
1642
1642
|
leftovers = [p for p in root.rglob("*") if p.is_file() and ".letify-" in p.name]
|
|
1643
1643
|
assert leftovers == [], leftovers
|
|
1644
|
+
|
|
1645
|
+
|
|
1646
|
+
def test_a_sender_that_stops_fails_the_readers_waiting_on_it(
|
|
1647
|
+
let, cpu, project, capsys, monkeypatch
|
|
1648
|
+
) -> None:
|
|
1649
|
+
"""Spec "When a blob does not arrive": waiting it out costs the timeout per blob.
|
|
1650
|
+
|
|
1651
|
+
KICT sent 126.9 GB as 132 shards over the first wave budget. The sender raised on the
|
|
1652
|
+
second one, _run swallowed it, and the body's readers waited in silence: 540 s and
|
|
1653
|
+
counting on one shard, with 131 behind it.
|
|
1654
|
+
"""
|
|
1655
|
+
from letify.store import pathdata
|
|
1656
|
+
|
|
1657
|
+
first = project / "a.bin"
|
|
1658
|
+
second = project / "b.bin"
|
|
1659
|
+
first.write_bytes(b"a" * (1 << 20))
|
|
1660
|
+
second.write_bytes(b"b" * (1 << 20))
|
|
1661
|
+
monkeypatch.setattr(pathdata, "FIRST_WAVE_MIB", 0.0)
|
|
1662
|
+
monkeypatch.setattr(pathdata, "FIRST_WAVE_FILES", 0)
|
|
1663
|
+
|
|
1664
|
+
real_send = pathdata.Stream._send
|
|
1665
|
+
sent: list[str] = []
|
|
1666
|
+
|
|
1667
|
+
def one_then_raise(self, digest, meter):
|
|
1668
|
+
if sent:
|
|
1669
|
+
raise RuntimeFailure("the transfer stream failed")
|
|
1670
|
+
sent.append(digest)
|
|
1671
|
+
return real_send(self, digest, meter)
|
|
1672
|
+
|
|
1673
|
+
monkeypatch.setattr(pathdata.Stream, "_send", one_then_raise)
|
|
1674
|
+
|
|
1675
|
+
@let.function(device=cpu, host=letify.remote)
|
|
1676
|
+
def read_both(one, two) -> int:
|
|
1677
|
+
return len(one.read_bytes()) + len(two.read_bytes())
|
|
1678
|
+
|
|
1679
|
+
started = time.monotonic()
|
|
1680
|
+
with pytest.raises(RuntimeFailure) as failure:
|
|
1681
|
+
read_both(one=first, two=second)
|
|
1682
|
+
elapsed = time.monotonic() - started
|
|
1683
|
+
assert elapsed < 60, f"waited {elapsed:.0f} s, so it waited out a timeout"
|
|
1684
|
+
assert "was never sent" in str(failure.value), failure.value
|
|
1685
|
+
err = capsys.readouterr().err
|
|
1686
|
+
assert "data sending stopped after" in err, err
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.5.3 → letify-2.5.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.5.3 → letify-2.5.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.5.3 → letify-2.5.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.5.3 → letify-2.5.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
|