lmcache-cli 0.4.5.dev0__py3-none-any.whl
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.
- lmcache/__init__.py +84 -0
- lmcache/_version.py +24 -0
- lmcache/cli/__init__.py +1 -0
- lmcache/cli/commands/__init__.py +34 -0
- lmcache/cli/commands/base.py +157 -0
- lmcache/cli/commands/bench/__init__.py +557 -0
- lmcache/cli/commands/bench/engine_bench/__init__.py +1 -0
- lmcache/cli/commands/bench/engine_bench/config.py +245 -0
- lmcache/cli/commands/bench/engine_bench/interactive/__init__.py +274 -0
- lmcache/cli/commands/bench/engine_bench/interactive/config.json +10 -0
- lmcache/cli/commands/bench/engine_bench/interactive/schema.py +352 -0
- lmcache/cli/commands/bench/engine_bench/interactive/state.py +327 -0
- lmcache/cli/commands/bench/engine_bench/interactive/terminal.py +291 -0
- lmcache/cli/commands/bench/engine_bench/progress.py +145 -0
- lmcache/cli/commands/bench/engine_bench/request_sender.py +232 -0
- lmcache/cli/commands/bench/engine_bench/stats.py +275 -0
- lmcache/cli/commands/bench/engine_bench/workloads/__init__.py +153 -0
- lmcache/cli/commands/bench/engine_bench/workloads/base.py +122 -0
- lmcache/cli/commands/bench/engine_bench/workloads/long_doc_permutator.py +435 -0
- lmcache/cli/commands/bench/engine_bench/workloads/long_doc_qa.py +281 -0
- lmcache/cli/commands/bench/engine_bench/workloads/multi_round_chat.py +337 -0
- lmcache/cli/commands/bench/engine_bench/workloads/random_prefill.py +178 -0
- lmcache/cli/commands/describe.py +310 -0
- lmcache/cli/commands/kvcache.py +133 -0
- lmcache/cli/commands/mock.py +75 -0
- lmcache/cli/commands/ping.py +113 -0
- lmcache/cli/commands/query/__init__.py +155 -0
- lmcache/cli/commands/query/prompt.py +134 -0
- lmcache/cli/commands/query/request.py +357 -0
- lmcache/cli/commands/server.py +99 -0
- lmcache/cli/commands/tool/__init__.py +63 -0
- lmcache/cli/commands/tool/cache_simulator.py +113 -0
- lmcache/cli/commands/trace/__init__.py +505 -0
- lmcache/cli/commands/trace/dispatch.py +249 -0
- lmcache/cli/commands/trace/driver.py +372 -0
- lmcache/cli/commands/trace/stats.py +289 -0
- lmcache/cli/documents/lmcache.txt +11 -0
- lmcache/cli/main.py +42 -0
- lmcache/cli/metrics/__init__.py +29 -0
- lmcache/cli/metrics/formatter.py +171 -0
- lmcache/cli/metrics/handler.py +94 -0
- lmcache/cli/metrics/metrics.py +161 -0
- lmcache/cli/metrics/section.py +77 -0
- lmcache/connections.py +173 -0
- lmcache/integration/__init__.py +2 -0
- lmcache/integration/base_service_factory.py +165 -0
- lmcache/integration/request_telemetry/__init__.py +1 -0
- lmcache/integration/request_telemetry/base.py +51 -0
- lmcache/integration/request_telemetry/factory.py +113 -0
- lmcache/integration/request_telemetry/fastapi.py +109 -0
- lmcache/integration/request_telemetry/noop.py +35 -0
- lmcache/integration/sglang/__init__.py +2 -0
- lmcache/integration/sglang/sglang_adapter.py +326 -0
- lmcache/integration/sglang/utils.py +39 -0
- lmcache/integration/vllm/__init__.py +1 -0
- lmcache/integration/vllm/lmcache_connector_v1.py +213 -0
- lmcache/integration/vllm/lmcache_connector_v1_085.py +150 -0
- lmcache/integration/vllm/lmcache_mp_connector_0180.py +1072 -0
- lmcache/integration/vllm/tests/test_mm_hash_utils.py +112 -0
- lmcache/integration/vllm/utils.py +433 -0
- lmcache/integration/vllm/vllm_multi_process_adapter.py +1090 -0
- lmcache/integration/vllm/vllm_service_factory.py +339 -0
- lmcache/integration/vllm/vllm_v1_adapter.py +1713 -0
- lmcache/logging.py +107 -0
- lmcache/native_storage_ops.pyi +230 -0
- lmcache/non_cuda_equivalents.py +1424 -0
- lmcache/observability.py +1958 -0
- lmcache/storage_backend/serde/__init__.py +1 -0
- lmcache/storage_backend/serde/cachegen_basics.py +210 -0
- lmcache/storage_backend/serde/cachegen_decoder.py +207 -0
- lmcache/storage_backend/serde/cachegen_encoder.py +394 -0
- lmcache/storage_backend/serde/serde.py +75 -0
- lmcache/tools/__init__.py +1 -0
- lmcache/tools/cache_simulator/README.md +392 -0
- lmcache/tools/cache_simulator/__init__.py +1 -0
- lmcache/tools/cache_simulator/docs/simulate_example.png +0 -0
- lmcache/tools/cache_simulator/docs/sweep_example.png +0 -0
- lmcache/tools/cache_simulator/gen_bench_dataset.py +360 -0
- lmcache/tools/cache_simulator/lru_cache.py +124 -0
- lmcache/tools/cache_simulator/plot_hit_rate.py +231 -0
- lmcache/tools/cache_simulator/simulator.py +795 -0
- lmcache/tools/controller_benchmark/README.md +161 -0
- lmcache/tools/controller_benchmark/__init__.py +1 -0
- lmcache/tools/controller_benchmark/__main__.py +331 -0
- lmcache/tools/controller_benchmark/benchmark.py +660 -0
- lmcache/tools/controller_benchmark/config.py +44 -0
- lmcache/tools/controller_benchmark/constants.py +10 -0
- lmcache/tools/controller_benchmark/handlers/__init__.py +46 -0
- lmcache/tools/controller_benchmark/handlers/admit.py +52 -0
- lmcache/tools/controller_benchmark/handlers/base.py +47 -0
- lmcache/tools/controller_benchmark/handlers/deregister.py +49 -0
- lmcache/tools/controller_benchmark/handlers/evict.py +52 -0
- lmcache/tools/controller_benchmark/handlers/heartbeat.py +56 -0
- lmcache/tools/controller_benchmark/handlers/p2p_lookup.py +47 -0
- lmcache/tools/controller_benchmark/handlers/register.py +56 -0
- lmcache/tools/mp_status_viewer/__init__.py +1 -0
- lmcache/tools/mp_status_viewer/__main__.py +95 -0
- lmcache/usage_context.py +417 -0
- lmcache/utils.py +665 -0
- lmcache/v1/__init__.py +2 -0
- lmcache/v1/api_server/__init__.py +2 -0
- lmcache/v1/api_server/__main__.py +537 -0
- lmcache/v1/basic_check.py +112 -0
- lmcache/v1/cache_controller/__init__.py +9 -0
- lmcache/v1/cache_controller/commands/__init__.py +15 -0
- lmcache/v1/cache_controller/commands/base.py +35 -0
- lmcache/v1/cache_controller/commands/full_sync.py +49 -0
- lmcache/v1/cache_controller/config.py +176 -0
- lmcache/v1/cache_controller/controller_manager.py +535 -0
- lmcache/v1/cache_controller/controllers/__init__.py +11 -0
- lmcache/v1/cache_controller/controllers/full_sync_tracker.py +473 -0
- lmcache/v1/cache_controller/controllers/kv_controller.py +439 -0
- lmcache/v1/cache_controller/controllers/registration_controller.py +282 -0
- lmcache/v1/cache_controller/executor.py +463 -0
- lmcache/v1/cache_controller/frontend/static/css/style.css +201 -0
- lmcache/v1/cache_controller/frontend/static/img/logo.png +0 -0
- lmcache/v1/cache_controller/frontend/static/index.html +234 -0
- lmcache/v1/cache_controller/frontend/static/js/controller_app.js +660 -0
- lmcache/v1/cache_controller/full_sync_sender.py +475 -0
- lmcache/v1/cache_controller/locks.py +149 -0
- lmcache/v1/cache_controller/message.py +828 -0
- lmcache/v1/cache_controller/observability.py +208 -0
- lmcache/v1/cache_controller/utils.py +679 -0
- lmcache/v1/cache_controller/worker.py +665 -0
- lmcache/v1/cache_engine.py +2058 -0
- lmcache/v1/cache_interface.py +19 -0
- lmcache/v1/check/__init__.py +74 -0
- lmcache/v1/check/check_mode_gen.py +86 -0
- lmcache/v1/check/check_mode_test_l2_adapter.py +284 -0
- lmcache/v1/check/check_mode_test_remote.py +155 -0
- lmcache/v1/check/check_mode_test_storage_manager.py +142 -0
- lmcache/v1/check/utils.py +571 -0
- lmcache/v1/compute/__init__.py +2 -0
- lmcache/v1/compute/attention/__init__.py +0 -0
- lmcache/v1/compute/attention/abstract.py +39 -0
- lmcache/v1/compute/attention/flash_attn.py +129 -0
- lmcache/v1/compute/attention/flash_infer_sparse.py +284 -0
- lmcache/v1/compute/attention/metadata.py +85 -0
- lmcache/v1/compute/attention/utils.py +14 -0
- lmcache/v1/compute/blend/__init__.py +7 -0
- lmcache/v1/compute/blend/blender.py +168 -0
- lmcache/v1/compute/blend/metadata.py +34 -0
- lmcache/v1/compute/blend/utils.py +63 -0
- lmcache/v1/compute/models/__init__.py +0 -0
- lmcache/v1/compute/models/base.py +141 -0
- lmcache/v1/compute/models/llama.py +9 -0
- lmcache/v1/compute/models/qwen3.py +24 -0
- lmcache/v1/compute/models/utils.py +68 -0
- lmcache/v1/compute/positional_encoding.py +199 -0
- lmcache/v1/config.py +848 -0
- lmcache/v1/config_base.py +848 -0
- lmcache/v1/distributed/api.py +248 -0
- lmcache/v1/distributed/config.py +321 -0
- lmcache/v1/distributed/error.py +64 -0
- lmcache/v1/distributed/eviction.py +192 -0
- lmcache/v1/distributed/eviction_policy/__init__.py +21 -0
- lmcache/v1/distributed/eviction_policy/factory.py +27 -0
- lmcache/v1/distributed/eviction_policy/lru.py +244 -0
- lmcache/v1/distributed/eviction_policy/noop.py +50 -0
- lmcache/v1/distributed/internal_api.py +170 -0
- lmcache/v1/distributed/l1_manager.py +835 -0
- lmcache/v1/distributed/l2_adapters/__init__.py +67 -0
- lmcache/v1/distributed/l2_adapters/base.py +360 -0
- lmcache/v1/distributed/l2_adapters/config.py +385 -0
- lmcache/v1/distributed/l2_adapters/factory.py +205 -0
- lmcache/v1/distributed/l2_adapters/fs_l2_adapter.py +747 -0
- lmcache/v1/distributed/l2_adapters/fs_native_l2_adapter.py +167 -0
- lmcache/v1/distributed/l2_adapters/mock_l2_adapter.py +516 -0
- lmcache/v1/distributed/l2_adapters/mooncake_store_l2_adapter.py +135 -0
- lmcache/v1/distributed/l2_adapters/native_connector_l2_adapter.py +468 -0
- lmcache/v1/distributed/l2_adapters/native_plugin_l2_adapter.py +199 -0
- lmcache/v1/distributed/l2_adapters/nixl_store_dynamic_l2_adapter.py +831 -0
- lmcache/v1/distributed/l2_adapters/nixl_store_l2_adapter.py +983 -0
- lmcache/v1/distributed/l2_adapters/plugin_l2_adapter.py +210 -0
- lmcache/v1/distributed/l2_adapters/resp_l2_adapter.py +176 -0
- lmcache/v1/distributed/memory_manager.py +179 -0
- lmcache/v1/distributed/storage_controller.py +39 -0
- lmcache/v1/distributed/storage_controllers/__init__.py +43 -0
- lmcache/v1/distributed/storage_controllers/eviction_controller.py +242 -0
- lmcache/v1/distributed/storage_controllers/prefetch_controller.py +830 -0
- lmcache/v1/distributed/storage_controllers/prefetch_policy.py +193 -0
- lmcache/v1/distributed/storage_controllers/store_controller.py +452 -0
- lmcache/v1/distributed/storage_controllers/store_policy.py +213 -0
- lmcache/v1/distributed/storage_manager.py +532 -0
- lmcache/v1/event_manager.py +145 -0
- lmcache/v1/exceptions/__init__.py +16 -0
- lmcache/v1/gpu_connector/__init__.py +126 -0
- lmcache/v1/gpu_connector/gpu_connectors.py +1906 -0
- lmcache/v1/gpu_connector/gpu_ops.py +85 -0
- lmcache/v1/gpu_connector/hpu_connector.py +326 -0
- lmcache/v1/gpu_connector/mock_gpu_connector.py +67 -0
- lmcache/v1/gpu_connector/utils.py +890 -0
- lmcache/v1/gpu_connector/xpu_connectors.py +916 -0
- lmcache/v1/health_monitor/__init__.py +1 -0
- lmcache/v1/health_monitor/base.py +587 -0
- lmcache/v1/health_monitor/checks/__init__.py +1 -0
- lmcache/v1/health_monitor/checks/remote_backend_check.py +304 -0
- lmcache/v1/health_monitor/constants.py +36 -0
- lmcache/v1/internal_api_server/__init__.py +0 -0
- lmcache/v1/internal_api_server/api_registry.py +59 -0
- lmcache/v1/internal_api_server/api_server.py +120 -0
- lmcache/v1/internal_api_server/common/__init__.py +1 -0
- lmcache/v1/internal_api_server/common/env_api.py +22 -0
- lmcache/v1/internal_api_server/common/loglevel_api.py +57 -0
- lmcache/v1/internal_api_server/common/metrics_api.py +29 -0
- lmcache/v1/internal_api_server/common/periodic_thread_api.py +138 -0
- lmcache/v1/internal_api_server/common/run_script_api.py +73 -0
- lmcache/v1/internal_api_server/common/thread_api.py +63 -0
- lmcache/v1/internal_api_server/controller/__init__.py +1 -0
- lmcache/v1/internal_api_server/controller/key_stats_api.py +81 -0
- lmcache/v1/internal_api_server/controller/worker_info_api.py +136 -0
- lmcache/v1/internal_api_server/utils.py +43 -0
- lmcache/v1/internal_api_server/vllm/__init__.py +1 -0
- lmcache/v1/internal_api_server/vllm/backend_api.py +221 -0
- lmcache/v1/internal_api_server/vllm/bypass_api.py +204 -0
- lmcache/v1/internal_api_server/vllm/cache_api.py +895 -0
- lmcache/v1/internal_api_server/vllm/chunk_statistics_api.py +141 -0
- lmcache/v1/internal_api_server/vllm/conf_api.py +147 -0
- lmcache/v1/internal_api_server/vllm/freeze_api.py +172 -0
- lmcache/v1/internal_api_server/vllm/hot_cache_api.py +184 -0
- lmcache/v1/internal_api_server/vllm/inference_api.py +65 -0
- lmcache/v1/internal_api_server/vllm/load_fs_chunks_api.py +320 -0
- lmcache/v1/internal_api_server/vllm/lookup_api.py +145 -0
- lmcache/v1/internal_api_server/vllm/version_api.py +25 -0
- lmcache/v1/kv_layer_groups.py +267 -0
- lmcache/v1/lazy_memory_allocator.py +284 -0
- lmcache/v1/lookup_client/__init__.py +25 -0
- lmcache/v1/lookup_client/abstract_client.py +77 -0
- lmcache/v1/lookup_client/async_lookup_message.py +50 -0
- lmcache/v1/lookup_client/chunk_statistics_lookup_client.py +200 -0
- lmcache/v1/lookup_client/factory.py +251 -0
- lmcache/v1/lookup_client/hit_limit_lookup_client.py +86 -0
- lmcache/v1/lookup_client/lmcache_async_lookup_client.py +407 -0
- lmcache/v1/lookup_client/lmcache_lookup_client.py +285 -0
- lmcache/v1/lookup_client/lmcache_lookup_client_bypass.py +99 -0
- lmcache/v1/lookup_client/mooncake_lookup_client.py +87 -0
- lmcache/v1/lookup_client/record_strategies/__init__.py +77 -0
- lmcache/v1/lookup_client/record_strategies/base.py +327 -0
- lmcache/v1/lookup_client/record_strategies/file_hash.py +130 -0
- lmcache/v1/lookup_client/record_strategies/memory_bloom_filter.py +81 -0
- lmcache/v1/manager.py +539 -0
- lmcache/v1/memory_management.py +2619 -0
- lmcache/v1/metadata.py +114 -0
- lmcache/v1/mp_observability/AGENTS.override.md +21 -0
- lmcache/v1/mp_observability/README.md +204 -0
- lmcache/v1/mp_observability/config.py +340 -0
- lmcache/v1/mp_observability/event.py +100 -0
- lmcache/v1/mp_observability/event_bus.py +313 -0
- lmcache/v1/mp_observability/otel_init.py +145 -0
- lmcache/v1/mp_observability/subscribers/__init__.py +28 -0
- lmcache/v1/mp_observability/subscribers/logging/__init__.py +19 -0
- lmcache/v1/mp_observability/subscribers/logging/l1.py +56 -0
- lmcache/v1/mp_observability/subscribers/logging/l2.py +73 -0
- lmcache/v1/mp_observability/subscribers/logging/lookup_hash.py +209 -0
- lmcache/v1/mp_observability/subscribers/logging/mp_server.py +90 -0
- lmcache/v1/mp_observability/subscribers/logging/sm.py +59 -0
- lmcache/v1/mp_observability/subscribers/metrics/__init__.py +20 -0
- lmcache/v1/mp_observability/subscribers/metrics/l0_lifecycle.py +290 -0
- lmcache/v1/mp_observability/subscribers/metrics/l1.py +55 -0
- lmcache/v1/mp_observability/subscribers/metrics/l1_lifecycle.py +166 -0
- lmcache/v1/mp_observability/subscribers/metrics/l2.py +121 -0
- lmcache/v1/mp_observability/subscribers/metrics/sm.py +69 -0
- lmcache/v1/mp_observability/subscribers/tracing/__init__.py +12 -0
- lmcache/v1/mp_observability/subscribers/tracing/mp_server.py +333 -0
- lmcache/v1/mp_observability/subscribers/tracing/span_registry.py +148 -0
- lmcache/v1/mp_observability/trace/__init__.py +50 -0
- lmcache/v1/mp_observability/trace/codecs.py +255 -0
- lmcache/v1/mp_observability/trace/decorator.py +147 -0
- lmcache/v1/mp_observability/trace/format.py +132 -0
- lmcache/v1/mp_observability/trace/lifecycle.py +83 -0
- lmcache/v1/mp_observability/trace/reader.py +167 -0
- lmcache/v1/mp_observability/trace/recorder.py +300 -0
- lmcache/v1/multiprocess/__init__.py +0 -0
- lmcache/v1/multiprocess/affinity_pool.py +102 -0
- lmcache/v1/multiprocess/blend_server_v2.py +891 -0
- lmcache/v1/multiprocess/config.py +253 -0
- lmcache/v1/multiprocess/custom_types.py +281 -0
- lmcache/v1/multiprocess/futures.py +194 -0
- lmcache/v1/multiprocess/gpu_context.py +511 -0
- lmcache/v1/multiprocess/http_server.py +235 -0
- lmcache/v1/multiprocess/mp_runtime_plugin_launcher.py +130 -0
- lmcache/v1/multiprocess/mq.py +732 -0
- lmcache/v1/multiprocess/protocol.py +86 -0
- lmcache/v1/multiprocess/protocols/README.md +213 -0
- lmcache/v1/multiprocess/protocols/__init__.py +127 -0
- lmcache/v1/multiprocess/protocols/base.py +89 -0
- lmcache/v1/multiprocess/protocols/blend.py +109 -0
- lmcache/v1/multiprocess/protocols/blend_v2.py +57 -0
- lmcache/v1/multiprocess/protocols/controller.py +53 -0
- lmcache/v1/multiprocess/protocols/debug.py +34 -0
- lmcache/v1/multiprocess/protocols/engine.py +146 -0
- lmcache/v1/multiprocess/protocols/observability.py +39 -0
- lmcache/v1/multiprocess/server.py +1134 -0
- lmcache/v1/multiprocess/session.py +190 -0
- lmcache/v1/multiprocess/token_hasher.py +441 -0
- lmcache/v1/offload_server/__init__.py +17 -0
- lmcache/v1/offload_server/abstract_server.py +37 -0
- lmcache/v1/offload_server/message.py +30 -0
- lmcache/v1/offload_server/zmq_server.py +122 -0
- lmcache/v1/periodic_thread.py +579 -0
- lmcache/v1/pin_monitor.py +246 -0
- lmcache/v1/plugin/__init__.py +0 -0
- lmcache/v1/plugin/runtime_plugin_launcher.py +211 -0
- lmcache/v1/protocol.py +317 -0
- lmcache/v1/rpc/__init__.py +17 -0
- lmcache/v1/rpc/transport.py +105 -0
- lmcache/v1/rpc/zmq_transport.py +213 -0
- lmcache/v1/rpc_utils.py +165 -0
- lmcache/v1/server/__init__.py +2 -0
- lmcache/v1/server/__main__.py +170 -0
- lmcache/v1/server/storage_backend/__init__.py +21 -0
- lmcache/v1/server/storage_backend/abstract_backend.py +80 -0
- lmcache/v1/server/storage_backend/local_backend.py +75 -0
- lmcache/v1/server/utils.py +21 -0
- lmcache/v1/standalone/__init__.py +1 -0
- lmcache/v1/standalone/__main__.py +583 -0
- lmcache/v1/standalone/manager.py +80 -0
- lmcache/v1/standalone/standalone_service_factory.py +86 -0
- lmcache/v1/storage_backend/__init__.py +313 -0
- lmcache/v1/storage_backend/abstract_backend.py +445 -0
- lmcache/v1/storage_backend/audit_backend.py +233 -0
- lmcache/v1/storage_backend/batched_message_sender.py +222 -0
- lmcache/v1/storage_backend/cache_policy/__init__.py +45 -0
- lmcache/v1/storage_backend/cache_policy/base_policy.py +87 -0
- lmcache/v1/storage_backend/cache_policy/fifo.py +58 -0
- lmcache/v1/storage_backend/cache_policy/lfu.py +105 -0
- lmcache/v1/storage_backend/cache_policy/lru.py +81 -0
- lmcache/v1/storage_backend/cache_policy/mru.py +61 -0
- lmcache/v1/storage_backend/connector/__init__.py +443 -0
- lmcache/v1/storage_backend/connector/audit_adapter.py +77 -0
- lmcache/v1/storage_backend/connector/audit_connector.py +320 -0
- lmcache/v1/storage_backend/connector/base_connector.py +379 -0
- lmcache/v1/storage_backend/connector/blackhole_adapter.py +21 -0
- lmcache/v1/storage_backend/connector/blackhole_connector.py +37 -0
- lmcache/v1/storage_backend/connector/eic_adapter.py +31 -0
- lmcache/v1/storage_backend/connector/eic_connector.py +757 -0
- lmcache/v1/storage_backend/connector/external_adapter.py +79 -0
- lmcache/v1/storage_backend/connector/fs_adapter.py +51 -0
- lmcache/v1/storage_backend/connector/fs_connector.py +403 -0
- lmcache/v1/storage_backend/connector/infinistore_adapter.py +56 -0
- lmcache/v1/storage_backend/connector/infinistore_connector.py +177 -0
- lmcache/v1/storage_backend/connector/instrumented_connector.py +219 -0
- lmcache/v1/storage_backend/connector/lm_adapter.py +31 -0
- lmcache/v1/storage_backend/connector/lm_connector.py +176 -0
- lmcache/v1/storage_backend/connector/mock_adapter.py +57 -0
- lmcache/v1/storage_backend/connector/mock_connector.py +349 -0
- lmcache/v1/storage_backend/connector/mooncakestore_adapter.py +43 -0
- lmcache/v1/storage_backend/connector/mooncakestore_connector.py +614 -0
- lmcache/v1/storage_backend/connector/redis_adapter.py +181 -0
- lmcache/v1/storage_backend/connector/redis_connector.py +828 -0
- lmcache/v1/storage_backend/connector/s3_adapter.py +59 -0
- lmcache/v1/storage_backend/connector/s3_connector.py +699 -0
- lmcache/v1/storage_backend/connector/sagemaker_hyperpod_adapter.py +233 -0
- lmcache/v1/storage_backend/connector/sagemaker_hyperpod_connector.py +987 -0
- lmcache/v1/storage_backend/connector/valkey_adapter.py +114 -0
- lmcache/v1/storage_backend/connector/valkey_connector.py +627 -0
- lmcache/v1/storage_backend/gds_backend.py +1199 -0
- lmcache/v1/storage_backend/job_executor/__init__.py +0 -0
- lmcache/v1/storage_backend/job_executor/base_executor.py +34 -0
- lmcache/v1/storage_backend/job_executor/pq_executor.py +235 -0
- lmcache/v1/storage_backend/local_cpu_backend.py +810 -0
- lmcache/v1/storage_backend/local_disk_backend.py +656 -0
- lmcache/v1/storage_backend/maru_backend.py +734 -0
- lmcache/v1/storage_backend/naive_serde/__init__.py +50 -0
- lmcache/v1/storage_backend/naive_serde/cachegen_basics.py +133 -0
- lmcache/v1/storage_backend/naive_serde/cachegen_decoder.py +135 -0
- lmcache/v1/storage_backend/naive_serde/cachegen_encoder.py +83 -0
- lmcache/v1/storage_backend/naive_serde/kivi_serde.py +22 -0
- lmcache/v1/storage_backend/naive_serde/naive_serde.py +18 -0
- lmcache/v1/storage_backend/naive_serde/serde.py +37 -0
- lmcache/v1/storage_backend/native_clients/connector_client_base.py +165 -0
- lmcache/v1/storage_backend/native_clients/resp_client.py +35 -0
- lmcache/v1/storage_backend/nixl_storage_backend.py +1400 -0
- lmcache/v1/storage_backend/p2p_backend.py +788 -0
- lmcache/v1/storage_backend/path_sharder.py +117 -0
- lmcache/v1/storage_backend/pd_backend.py +646 -0
- lmcache/v1/storage_backend/plugins/dax_backend.py +1443 -0
- lmcache/v1/storage_backend/plugins/rust_raw_block_backend.py +1361 -0
- lmcache/v1/storage_backend/remote_backend.py +624 -0
- lmcache/v1/storage_backend/resp_client.py +227 -0
- lmcache/v1/storage_backend/storage_backend_listener.py +19 -0
- lmcache/v1/storage_backend/storage_manager.py +1352 -0
- lmcache/v1/system_detection.py +110 -0
- lmcache/v1/token_database.py +551 -0
- lmcache/v1/transfer_channel/__init__.py +83 -0
- lmcache/v1/transfer_channel/abstract.py +285 -0
- lmcache/v1/transfer_channel/mock_memory_channel.py +156 -0
- lmcache/v1/transfer_channel/nixl_channel.py +639 -0
- lmcache/v1/transfer_channel/py_socket_channel.py +260 -0
- lmcache/v1/transfer_channel/transfer_utils.py +63 -0
- lmcache/v1/utils/__init__.py +1 -0
- lmcache/v1/utils/bloom_filter.py +109 -0
- lmcache/v1/utils/cache_utils.py +125 -0
- lmcache_cli-0.4.5.dev0.dist-info/METADATA +185 -0
- lmcache_cli-0.4.5.dev0.dist-info/RECORD +399 -0
- lmcache_cli-0.4.5.dev0.dist-info/WHEEL +5 -0
- lmcache_cli-0.4.5.dev0.dist-info/entry_points.txt +2 -0
- lmcache_cli-0.4.5.dev0.dist-info/licenses/LICENSE +201 -0
- lmcache_cli-0.4.5.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1361 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
# Future
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
# Standard
|
|
7
|
+
from collections import OrderedDict
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any, Callable, List, Optional, Sequence
|
|
11
|
+
import asyncio
|
|
12
|
+
import ctypes
|
|
13
|
+
import json
|
|
14
|
+
import struct
|
|
15
|
+
import threading
|
|
16
|
+
import time
|
|
17
|
+
import zlib
|
|
18
|
+
|
|
19
|
+
# Third Party
|
|
20
|
+
import torch
|
|
21
|
+
|
|
22
|
+
# First Party
|
|
23
|
+
from lmcache.logging import init_logger
|
|
24
|
+
from lmcache.utils import CacheEngineKey, DiskCacheMetadata
|
|
25
|
+
from lmcache.v1.memory_management import MemoryFormat, MemoryObj
|
|
26
|
+
from lmcache.v1.storage_backend.abstract_backend import (
|
|
27
|
+
AllocatorBackendInterface,
|
|
28
|
+
StoragePluginInterface,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
logger = init_logger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_DEFAULT_META_MAGIC = b"LMCIDX01"
|
|
35
|
+
_DEFAULT_META_VERSION = 1
|
|
36
|
+
_META_HEADER_STRUCT = struct.Struct("<8sIQQI")
|
|
37
|
+
TPRankKey = int | str
|
|
38
|
+
PerTPDevicePaths = Mapping[TPRankKey, str]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _round_up(x: int, align: int) -> int:
|
|
42
|
+
"""Round up to nearest multiple of alignment (required for O_DIRECT)."""
|
|
43
|
+
return ((x + align - 1) // align) * align
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _validate_per_tp_device_paths(per_tp_devices: PerTPDevicePaths) -> None:
|
|
47
|
+
"""Validate per-TP device mapping and enforce unique paths."""
|
|
48
|
+
values = list(per_tp_devices.values())
|
|
49
|
+
if len(values) != len(set(values)):
|
|
50
|
+
raise ValueError(
|
|
51
|
+
"Duplicate device path configured in rust_raw_block.per_tp_device_paths"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _get_per_tp_device_path(
|
|
56
|
+
per_tp_devices: PerTPDevicePaths, tp_rank: int
|
|
57
|
+
) -> Optional[str]:
|
|
58
|
+
"""Return the configured device path for a TP rank.
|
|
59
|
+
|
|
60
|
+
Looks up both string and integer forms of ``tp_rank`` so YAML mappings
|
|
61
|
+
with either quoted or unquoted numeric keys are accepted.
|
|
62
|
+
"""
|
|
63
|
+
return per_tp_devices.get(str(tp_rank), per_tp_devices.get(tp_rank))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class _Entry:
|
|
68
|
+
"""In-memory index entry for a stored chunk."""
|
|
69
|
+
|
|
70
|
+
offset: int
|
|
71
|
+
size: int
|
|
72
|
+
meta: DiskCacheMetadata
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class _Inflight:
|
|
77
|
+
offset: int
|
|
78
|
+
meta: DiskCacheMetadata
|
|
79
|
+
canceled: bool = False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class RustRawBlockBackend(StoragePluginInterface):
|
|
83
|
+
"""
|
|
84
|
+
A storage plugin backend that stores KV chunks into a block device (raw)
|
|
85
|
+
using a Rust extension for pread/pwrite.
|
|
86
|
+
|
|
87
|
+
Features:
|
|
88
|
+
- High-throughput I/O via direct block device access
|
|
89
|
+
- O_DIRECT support to bypass page cache (requires aligned buffers)
|
|
90
|
+
- On-device metadata checkpoint for restart recovery
|
|
91
|
+
- Efficient buffer operations via Rust extension
|
|
92
|
+
|
|
93
|
+
- TP > 1 support via per-TP partitions
|
|
94
|
+
|
|
95
|
+
TP > 1 Support:
|
|
96
|
+
----------------
|
|
97
|
+
When using Tensor Parallelism (TP > 1), each TP worker must use a
|
|
98
|
+
separate partition to avoid metadata conflicts and data corruption.
|
|
99
|
+
|
|
100
|
+
Configuration:
|
|
101
|
+
For TP > 1, you must explicitly configure device paths for each TP worker:
|
|
102
|
+
extra_config:
|
|
103
|
+
rust_raw_block.per_tp_device_paths:
|
|
104
|
+
"0": "/dev/nvme0n1p1"
|
|
105
|
+
"1": "/dev/nvme0n1p2"
|
|
106
|
+
"2": "/dev/nvme0n1p3"
|
|
107
|
+
"3": "/dev/nvme0n1p4"
|
|
108
|
+
|
|
109
|
+
Note: Partitions must be pre-created on the device before use.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
config=None,
|
|
115
|
+
metadata=None,
|
|
116
|
+
local_cpu_backend=None,
|
|
117
|
+
loop: Optional[asyncio.AbstractEventLoop] = None,
|
|
118
|
+
dst_device: str = "cpu",
|
|
119
|
+
):
|
|
120
|
+
super().__init__(
|
|
121
|
+
dst_device=dst_device,
|
|
122
|
+
config=config,
|
|
123
|
+
metadata=metadata,
|
|
124
|
+
local_cpu_backend=local_cpu_backend,
|
|
125
|
+
loop=loop,
|
|
126
|
+
)
|
|
127
|
+
if self.loop is None:
|
|
128
|
+
raise ValueError("RustRawBlockBackend requires an asyncio event loop")
|
|
129
|
+
if self.local_cpu_backend is None:
|
|
130
|
+
raise ValueError("RustRawBlockBackend requires local_cpu_backend")
|
|
131
|
+
if self.config is None:
|
|
132
|
+
raise ValueError("RustRawBlockBackend requires config")
|
|
133
|
+
|
|
134
|
+
extra = self.config.extra_config or {}
|
|
135
|
+
|
|
136
|
+
# Support TP > 1 via per-TP device paths.
|
|
137
|
+
# Each TP worker uses its own partition to avoid conflicts.
|
|
138
|
+
self.device_path: str
|
|
139
|
+
if self.metadata is not None and self.metadata.world_size > 1:
|
|
140
|
+
tp_rank = self.metadata.worker_id
|
|
141
|
+
per_tp_devices = extra.get("rust_raw_block.per_tp_device_paths", {})
|
|
142
|
+
if not isinstance(per_tp_devices, Mapping):
|
|
143
|
+
raise ValueError(
|
|
144
|
+
"rust_raw_block.per_tp_device_paths must be a mapping from "
|
|
145
|
+
"TP rank to device path"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if not per_tp_devices:
|
|
149
|
+
raise ValueError(
|
|
150
|
+
"For TP > 1, rust_raw_block.per_tp_device_paths is required. "
|
|
151
|
+
"Each TP worker must have an explicit device path configured."
|
|
152
|
+
)
|
|
153
|
+
_validate_per_tp_device_paths(per_tp_devices)
|
|
154
|
+
|
|
155
|
+
tp_rank_str = str(tp_rank)
|
|
156
|
+
device_path = _get_per_tp_device_path(per_tp_devices, tp_rank)
|
|
157
|
+
if not device_path:
|
|
158
|
+
raise ValueError(
|
|
159
|
+
f"No device path configured for TP rank {tp_rank_str}. "
|
|
160
|
+
f"Available ranks: {list(per_tp_devices.keys())}"
|
|
161
|
+
)
|
|
162
|
+
self.device_path = device_path
|
|
163
|
+
logger.info(
|
|
164
|
+
f"RustRawBlockBackend: TP={self.metadata.world_size} mode, "
|
|
165
|
+
f"using explicit device path for rank {tp_rank}: {self.device_path}"
|
|
166
|
+
)
|
|
167
|
+
else:
|
|
168
|
+
self.device_path = extra.get("rust_raw_block.device_path", "")
|
|
169
|
+
if not self.device_path:
|
|
170
|
+
raise ValueError(
|
|
171
|
+
"extra_config['rust_raw_block.device_path'] is required"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
self.capacity_bytes: int = int(extra.get("rust_raw_block.capacity_bytes", 0))
|
|
175
|
+
self.block_align: int = int(extra.get("rust_raw_block.block_align", 4096))
|
|
176
|
+
self.header_bytes: int = int(extra.get("rust_raw_block.header_bytes", 4096))
|
|
177
|
+
self.use_odirect: bool = bool(extra.get("rust_raw_block.use_odirect", False))
|
|
178
|
+
# Try direct aligned O_DIRECT I/O when allocator buffers permit.
|
|
179
|
+
self.enable_zero_copy: bool = bool(
|
|
180
|
+
extra.get("rust_raw_block.enable_zero_copy", True)
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# On-device metadata region config.
|
|
184
|
+
self.meta_total_bytes: int = int(
|
|
185
|
+
extra.get("rust_raw_block.meta_total_bytes", 128 * 1024 * 1024)
|
|
186
|
+
)
|
|
187
|
+
meta_magic_raw = extra.get("rust_raw_block.meta_magic", "LMCIDX01")
|
|
188
|
+
if isinstance(meta_magic_raw, str):
|
|
189
|
+
self.meta_magic: bytes = meta_magic_raw.encode("ascii")
|
|
190
|
+
elif isinstance(meta_magic_raw, bytes):
|
|
191
|
+
self.meta_magic = meta_magic_raw
|
|
192
|
+
else:
|
|
193
|
+
raise ValueError("rust_raw_block.meta_magic must be str or bytes")
|
|
194
|
+
if len(self.meta_magic) != 8:
|
|
195
|
+
raise ValueError("rust_raw_block.meta_magic must be exactly 8 bytes")
|
|
196
|
+
try:
|
|
197
|
+
self.meta_magic_text: str = self.meta_magic.decode("ascii")
|
|
198
|
+
except UnicodeDecodeError as e:
|
|
199
|
+
raise ValueError("rust_raw_block.meta_magic must be ASCII bytes") from e
|
|
200
|
+
self.meta_version: int = int(
|
|
201
|
+
extra.get("rust_raw_block.meta_version", _DEFAULT_META_VERSION)
|
|
202
|
+
)
|
|
203
|
+
if self.meta_version <= 0:
|
|
204
|
+
raise ValueError("rust_raw_block.meta_version must be > 0")
|
|
205
|
+
self.meta_checkpoint_interval_sec: int = int(
|
|
206
|
+
extra.get("rust_raw_block.meta_checkpoint_interval_sec", 60)
|
|
207
|
+
)
|
|
208
|
+
self.meta_idle_quiet_ms: int = int(
|
|
209
|
+
extra.get("rust_raw_block.meta_idle_quiet_ms", 100)
|
|
210
|
+
)
|
|
211
|
+
self.meta_enable_periodic: bool = bool(
|
|
212
|
+
extra.get("rust_raw_block.meta_enable_periodic", True)
|
|
213
|
+
)
|
|
214
|
+
self.meta_verify_on_load: bool = bool(
|
|
215
|
+
extra.get("rust_raw_block.meta_verify_on_load", True)
|
|
216
|
+
)
|
|
217
|
+
self._meta_copy_count: int = 2
|
|
218
|
+
|
|
219
|
+
get_full_chunk_size_bytes = getattr(
|
|
220
|
+
self.local_cpu_backend, "get_full_chunk_size_bytes", None
|
|
221
|
+
)
|
|
222
|
+
if callable(get_full_chunk_size_bytes):
|
|
223
|
+
full_chunk_bytes = int(get_full_chunk_size_bytes())
|
|
224
|
+
else:
|
|
225
|
+
get_full_chunk_size = getattr(
|
|
226
|
+
self.local_cpu_backend, "get_full_chunk_size", None
|
|
227
|
+
)
|
|
228
|
+
if not callable(get_full_chunk_size):
|
|
229
|
+
raise ValueError(
|
|
230
|
+
"local_cpu_backend must expose get_full_chunk_size_bytes() "
|
|
231
|
+
"or get_full_chunk_size()"
|
|
232
|
+
)
|
|
233
|
+
full_chunk_bytes = int(get_full_chunk_size())
|
|
234
|
+
default_slot_bytes = _round_up(
|
|
235
|
+
self.header_bytes + full_chunk_bytes, self.block_align
|
|
236
|
+
)
|
|
237
|
+
self.slot_bytes: int = int(
|
|
238
|
+
extra.get("rust_raw_block.slot_bytes", default_slot_bytes)
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
if self.slot_bytes < self.header_bytes + 1:
|
|
242
|
+
raise ValueError("rust_raw_block.slot_bytes too small")
|
|
243
|
+
if self.slot_bytes % self.block_align != 0:
|
|
244
|
+
raise ValueError(
|
|
245
|
+
"rust_raw_block.slot_bytes must be multiple of block_align"
|
|
246
|
+
)
|
|
247
|
+
if self.header_bytes % self.block_align != 0:
|
|
248
|
+
raise ValueError(
|
|
249
|
+
"rust_raw_block.header_bytes must be multiple of block_align"
|
|
250
|
+
)
|
|
251
|
+
if self.meta_total_bytes <= 0:
|
|
252
|
+
raise ValueError("rust_raw_block.meta_total_bytes must be > 0")
|
|
253
|
+
if self.meta_total_bytes % self.block_align != 0:
|
|
254
|
+
raise ValueError(
|
|
255
|
+
"rust_raw_block.meta_total_bytes must align to block_align"
|
|
256
|
+
)
|
|
257
|
+
if self.meta_total_bytes <= self.block_align:
|
|
258
|
+
raise ValueError(
|
|
259
|
+
"rust_raw_block.meta_total_bytes must be > block_align "
|
|
260
|
+
"(room for metadata header + payload)"
|
|
261
|
+
)
|
|
262
|
+
self._meta_container_bytes: int = (
|
|
263
|
+
(self.meta_total_bytes // self._meta_copy_count) // self.block_align
|
|
264
|
+
) * self.block_align
|
|
265
|
+
if self._meta_container_bytes <= self.block_align:
|
|
266
|
+
raise ValueError(
|
|
267
|
+
"rust_raw_block.meta_total_bytes must provide room for at least "
|
|
268
|
+
"two metadata copies (header + payload)"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
self._lock = threading.Lock()
|
|
272
|
+
self._index: dict[CacheEngineKey, _Entry] = {}
|
|
273
|
+
self._pinned: set[CacheEngineKey] = set()
|
|
274
|
+
self._inflight: dict[CacheEngineKey, _Inflight] = {}
|
|
275
|
+
self._lru: "OrderedDict[CacheEngineKey, None]" = OrderedDict()
|
|
276
|
+
|
|
277
|
+
self._next_slot: int = 0
|
|
278
|
+
self._free_slots: list[int] = []
|
|
279
|
+
self._max_slots: int = 0
|
|
280
|
+
self._effective_capacity_bytes: int = 0
|
|
281
|
+
self._data_base_offset: int = 0
|
|
282
|
+
|
|
283
|
+
self._dbg_first_n: int = int(extra.get("rust_raw_block.debug_first_n", 4) or 0)
|
|
284
|
+
self._dbg_every_n: int = int(
|
|
285
|
+
extra.get("rust_raw_block.debug_every_n", 256) or 0
|
|
286
|
+
)
|
|
287
|
+
self._dbg_put_batches: int = 0
|
|
288
|
+
self._dbg_put_keys: int = 0
|
|
289
|
+
self._dbg_put_bytes: int = 0
|
|
290
|
+
self._dbg_get_calls: int = 0
|
|
291
|
+
self._dbg_get_bytes: int = 0
|
|
292
|
+
|
|
293
|
+
self._raw = None
|
|
294
|
+
|
|
295
|
+
self._put_lock = threading.Lock()
|
|
296
|
+
self._put_tasks: set[CacheEngineKey] = set()
|
|
297
|
+
|
|
298
|
+
# Metadata checkpoint state.
|
|
299
|
+
self._meta_seq: int = 0
|
|
300
|
+
self._meta_dirty_total: int = 0
|
|
301
|
+
self._meta_persisted: int = 0
|
|
302
|
+
self._inflight_io_count: int = 0
|
|
303
|
+
self._last_io_ts: float = time.monotonic()
|
|
304
|
+
self._meta_stop_evt = threading.Event()
|
|
305
|
+
self._meta_thread: Optional[threading.Thread] = None
|
|
306
|
+
|
|
307
|
+
self._ensure_capacity_and_layout()
|
|
308
|
+
|
|
309
|
+
logger.info(
|
|
310
|
+
"RustRawBlockBackend init: device=%s cap=%s slot=%d align=%d header=%d "
|
|
311
|
+
"meta_total=%d data_base=%d zero_copy=%s",
|
|
312
|
+
self.device_path,
|
|
313
|
+
self.capacity_bytes,
|
|
314
|
+
self.slot_bytes,
|
|
315
|
+
self.block_align,
|
|
316
|
+
self.header_bytes,
|
|
317
|
+
self.meta_total_bytes,
|
|
318
|
+
self._data_base_offset,
|
|
319
|
+
self.enable_zero_copy,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Load latest checkpoint from device (no JSON fallback).
|
|
323
|
+
self._load_checkpoint_from_device()
|
|
324
|
+
|
|
325
|
+
if self.meta_enable_periodic:
|
|
326
|
+
self._meta_thread = threading.Thread(
|
|
327
|
+
target=self._checkpoint_loop,
|
|
328
|
+
name="rust-raw-block-meta-checkpoint",
|
|
329
|
+
daemon=True,
|
|
330
|
+
)
|
|
331
|
+
self._meta_thread.start()
|
|
332
|
+
|
|
333
|
+
def _dbg_should_log(self, n: int) -> bool:
|
|
334
|
+
if not logger.isEnabledFor(10):
|
|
335
|
+
return False
|
|
336
|
+
if self._dbg_first_n and n <= self._dbg_first_n:
|
|
337
|
+
return True
|
|
338
|
+
if self._dbg_every_n and n % self._dbg_every_n == 0:
|
|
339
|
+
return True
|
|
340
|
+
return False
|
|
341
|
+
|
|
342
|
+
def _dbg_key_short(self, key: CacheEngineKey) -> str:
|
|
343
|
+
try:
|
|
344
|
+
return f"chunk_hash={int(key.chunk_hash)}"
|
|
345
|
+
except Exception:
|
|
346
|
+
return "chunk_hash=?"
|
|
347
|
+
|
|
348
|
+
def __str__(self) -> str:
|
|
349
|
+
return "RustRawBlockBackend"
|
|
350
|
+
|
|
351
|
+
def _rawdev(self):
|
|
352
|
+
if self._raw is None:
|
|
353
|
+
try:
|
|
354
|
+
# Third Party
|
|
355
|
+
from lmcache_rust_raw_block_io import RawBlockDevice # type: ignore
|
|
356
|
+
except Exception as e:
|
|
357
|
+
raise RuntimeError(
|
|
358
|
+
"Rust raw-block extension is not installed. "
|
|
359
|
+
"Install / build `rust_raw_block_io` and retry."
|
|
360
|
+
) from e
|
|
361
|
+
self._raw = RawBlockDevice(
|
|
362
|
+
self.device_path,
|
|
363
|
+
writable=True,
|
|
364
|
+
use_odirect=self.use_odirect,
|
|
365
|
+
alignment=self.block_align,
|
|
366
|
+
)
|
|
367
|
+
return self._raw
|
|
368
|
+
|
|
369
|
+
def _build_direct_odirect_view(
|
|
370
|
+
self,
|
|
371
|
+
memory_obj: MemoryObj,
|
|
372
|
+
payload_len: int,
|
|
373
|
+
total_len: int,
|
|
374
|
+
buffer_len: int,
|
|
375
|
+
*,
|
|
376
|
+
zero_tail: bool,
|
|
377
|
+
) -> Optional[memoryview]:
|
|
378
|
+
"""Build direct physical-memory view for O_DIRECT without staging copy."""
|
|
379
|
+
if not self.use_odirect or not self.enable_zero_copy:
|
|
380
|
+
return None
|
|
381
|
+
|
|
382
|
+
ptr_val = getattr(memory_obj, "data_ptr", None)
|
|
383
|
+
if callable(ptr_val):
|
|
384
|
+
try:
|
|
385
|
+
ptr_val = ptr_val()
|
|
386
|
+
except Exception:
|
|
387
|
+
ptr_val = None
|
|
388
|
+
if ptr_val is None:
|
|
389
|
+
return None
|
|
390
|
+
|
|
391
|
+
if buffer_len <= 0:
|
|
392
|
+
return None
|
|
393
|
+
|
|
394
|
+
try:
|
|
395
|
+
ptr = int(ptr_val)
|
|
396
|
+
except Exception:
|
|
397
|
+
return None
|
|
398
|
+
|
|
399
|
+
if ptr <= 0 or ptr % self.block_align != 0:
|
|
400
|
+
return None
|
|
401
|
+
if buffer_len < payload_len:
|
|
402
|
+
return None
|
|
403
|
+
|
|
404
|
+
view_len = min(buffer_len, total_len)
|
|
405
|
+
if view_len < payload_len:
|
|
406
|
+
return None
|
|
407
|
+
|
|
408
|
+
try:
|
|
409
|
+
raw = (ctypes.c_ubyte * view_len).from_address(ptr)
|
|
410
|
+
view = memoryview(raw)
|
|
411
|
+
if zero_tail and total_len > payload_len and view_len >= total_len:
|
|
412
|
+
ctypes.memset(ptr + payload_len, 0, total_len - payload_len)
|
|
413
|
+
return view
|
|
414
|
+
except Exception:
|
|
415
|
+
return None
|
|
416
|
+
|
|
417
|
+
def _ensure_capacity_and_layout(self) -> None:
|
|
418
|
+
if self._effective_capacity_bytes > 0 and self._max_slots > 0:
|
|
419
|
+
return
|
|
420
|
+
|
|
421
|
+
device_size = int(self._rawdev().size_bytes())
|
|
422
|
+
requested = self.capacity_bytes if self.capacity_bytes > 0 else device_size
|
|
423
|
+
self._effective_capacity_bytes = min(requested, device_size)
|
|
424
|
+
self.capacity_bytes = self._effective_capacity_bytes
|
|
425
|
+
|
|
426
|
+
if self.meta_total_bytes >= self._effective_capacity_bytes:
|
|
427
|
+
raise RuntimeError("metadata region exceeds usable device capacity")
|
|
428
|
+
|
|
429
|
+
self._data_base_offset = self.meta_total_bytes
|
|
430
|
+
data_bytes = self._effective_capacity_bytes - self._data_base_offset
|
|
431
|
+
self._max_slots = data_bytes // self.slot_bytes
|
|
432
|
+
if self._max_slots <= 0:
|
|
433
|
+
raise RuntimeError(
|
|
434
|
+
"raw block capacity too small for slot size after metadata"
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
def _slot_to_offset(self, slot: int) -> int:
|
|
438
|
+
return self._data_base_offset + slot * self.slot_bytes
|
|
439
|
+
|
|
440
|
+
def _offset_to_slot(self, offset: int) -> int:
|
|
441
|
+
return (offset - self._data_base_offset) // self.slot_bytes
|
|
442
|
+
|
|
443
|
+
def _allocate_slot(self) -> int:
|
|
444
|
+
self._ensure_capacity_and_layout()
|
|
445
|
+
|
|
446
|
+
if self._free_slots:
|
|
447
|
+
return self._slot_to_offset(self._free_slots.pop())
|
|
448
|
+
|
|
449
|
+
if self._next_slot < self._max_slots:
|
|
450
|
+
slot = self._next_slot
|
|
451
|
+
self._next_slot += 1
|
|
452
|
+
return self._slot_to_offset(slot)
|
|
453
|
+
|
|
454
|
+
raise RuntimeError("No free slots available; eviction required")
|
|
455
|
+
|
|
456
|
+
def _touch(self, key: CacheEngineKey) -> None:
|
|
457
|
+
self._lru.pop(key, None)
|
|
458
|
+
self._lru[key] = None
|
|
459
|
+
|
|
460
|
+
def _append_free_slot_locked(self, slot: int) -> None:
|
|
461
|
+
if slot < 0 or slot >= self._max_slots:
|
|
462
|
+
return
|
|
463
|
+
if slot in self._free_slots:
|
|
464
|
+
return
|
|
465
|
+
self._free_slots.append(slot)
|
|
466
|
+
|
|
467
|
+
def _evict_one(self) -> bool:
|
|
468
|
+
for victim in list(self._lru.keys()):
|
|
469
|
+
if victim in self._pinned or victim in self._inflight:
|
|
470
|
+
continue
|
|
471
|
+
entry = self._index.pop(victim, None)
|
|
472
|
+
if entry is None:
|
|
473
|
+
self._lru.pop(victim, None)
|
|
474
|
+
continue
|
|
475
|
+
self._lru.pop(victim, None)
|
|
476
|
+
self._pinned.discard(victim)
|
|
477
|
+
self._append_free_slot_locked(self._offset_to_slot(int(entry.offset)))
|
|
478
|
+
self._meta_dirty_total += 1
|
|
479
|
+
return True
|
|
480
|
+
return False
|
|
481
|
+
|
|
482
|
+
def contains(self, key: CacheEngineKey, pin: bool = False) -> bool:
|
|
483
|
+
with self._lock:
|
|
484
|
+
ok = key in self._index
|
|
485
|
+
if ok and pin:
|
|
486
|
+
self._pinned.add(key)
|
|
487
|
+
return ok
|
|
488
|
+
|
|
489
|
+
def exists_in_put_tasks(self, key: CacheEngineKey) -> bool:
|
|
490
|
+
with self._put_lock:
|
|
491
|
+
return key in self._put_tasks
|
|
492
|
+
|
|
493
|
+
def pin(self, key: CacheEngineKey) -> bool:
|
|
494
|
+
with self._lock:
|
|
495
|
+
if key in self._index:
|
|
496
|
+
self._pinned.add(key)
|
|
497
|
+
return True
|
|
498
|
+
return False
|
|
499
|
+
|
|
500
|
+
def unpin(self, key: CacheEngineKey) -> bool:
|
|
501
|
+
with self._lock:
|
|
502
|
+
if key in self._pinned:
|
|
503
|
+
self._pinned.remove(key)
|
|
504
|
+
return True
|
|
505
|
+
return key in self._index
|
|
506
|
+
|
|
507
|
+
def remove(self, key: CacheEngineKey, force: bool = True) -> bool: # noqa: ARG002
|
|
508
|
+
with self._lock:
|
|
509
|
+
existed = key in self._index or key in self._inflight
|
|
510
|
+
entry = self._index.pop(key, None)
|
|
511
|
+
inflight = self._inflight.get(key)
|
|
512
|
+
self._pinned.discard(key)
|
|
513
|
+
self._lru.pop(key, None)
|
|
514
|
+
if entry is not None:
|
|
515
|
+
self._append_free_slot_locked(self._offset_to_slot(int(entry.offset)))
|
|
516
|
+
self._meta_dirty_total += 1
|
|
517
|
+
if inflight is not None:
|
|
518
|
+
inflight.canceled = True
|
|
519
|
+
return existed
|
|
520
|
+
|
|
521
|
+
def batched_submit_put_task(
|
|
522
|
+
self,
|
|
523
|
+
keys: Sequence[CacheEngineKey],
|
|
524
|
+
objs: List[MemoryObj],
|
|
525
|
+
transfer_spec: Any = None, # noqa: ARG002
|
|
526
|
+
on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
|
|
527
|
+
):
|
|
528
|
+
if logger.isEnabledFor(10):
|
|
529
|
+
self._dbg_put_batches += 1
|
|
530
|
+
self._dbg_put_keys += int(len(keys))
|
|
531
|
+
try:
|
|
532
|
+
self._dbg_put_bytes += int(sum(len(o.byte_array) for o in objs))
|
|
533
|
+
except Exception:
|
|
534
|
+
pass
|
|
535
|
+
if self._dbg_should_log(self._dbg_put_batches):
|
|
536
|
+
logger.debug(
|
|
537
|
+
"RustRawBlockBackend PUT: keys=%d inflight=%d indexed=%d",
|
|
538
|
+
len(keys),
|
|
539
|
+
len(self._inflight),
|
|
540
|
+
len(self._index),
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
futures = []
|
|
544
|
+
for key, obj in zip(keys, objs, strict=False):
|
|
545
|
+
with self._put_lock:
|
|
546
|
+
if key in self._put_tasks:
|
|
547
|
+
continue
|
|
548
|
+
self._put_tasks.add(key)
|
|
549
|
+
|
|
550
|
+
with self._lock:
|
|
551
|
+
if key in self._index or key in self._inflight:
|
|
552
|
+
with self._put_lock:
|
|
553
|
+
self._put_tasks.discard(key)
|
|
554
|
+
continue
|
|
555
|
+
while True:
|
|
556
|
+
try:
|
|
557
|
+
offset = self._allocate_slot()
|
|
558
|
+
break
|
|
559
|
+
except RuntimeError:
|
|
560
|
+
if not self._evict_one():
|
|
561
|
+
with self._put_lock:
|
|
562
|
+
self._put_tasks.discard(key)
|
|
563
|
+
raise
|
|
564
|
+
|
|
565
|
+
meta = DiskCacheMetadata(
|
|
566
|
+
path=f"{self.device_path}@{offset}",
|
|
567
|
+
size=len(obj.byte_array),
|
|
568
|
+
shape=obj.metadata.shape,
|
|
569
|
+
dtype=obj.metadata.dtype,
|
|
570
|
+
cached_positions=obj.metadata.cached_positions,
|
|
571
|
+
fmt=obj.metadata.fmt,
|
|
572
|
+
pin_count=0,
|
|
573
|
+
)
|
|
574
|
+
self._inflight[key] = _Inflight(offset=offset, meta=meta)
|
|
575
|
+
|
|
576
|
+
header = self._encode_header(key, meta.size)
|
|
577
|
+
obj.ref_count_up()
|
|
578
|
+
assert self.loop is not None
|
|
579
|
+
fut = asyncio.run_coroutine_threadsafe(
|
|
580
|
+
self._submit_write(
|
|
581
|
+
key=key,
|
|
582
|
+
offset=offset,
|
|
583
|
+
header=header,
|
|
584
|
+
memory_obj=obj,
|
|
585
|
+
on_complete_callback=on_complete_callback,
|
|
586
|
+
),
|
|
587
|
+
self.loop,
|
|
588
|
+
)
|
|
589
|
+
futures.append(fut)
|
|
590
|
+
return futures or None
|
|
591
|
+
|
|
592
|
+
def _prepare_write_payload(self, memory_obj: MemoryObj) -> tuple[Any, int, int]:
|
|
593
|
+
"""Prepare payload view and aligned lengths for write path."""
|
|
594
|
+
buf = memory_obj.byte_array
|
|
595
|
+
if hasattr(buf, "cast"):
|
|
596
|
+
buf = buf.cast("B")
|
|
597
|
+
payload_len = len(memory_obj.byte_array)
|
|
598
|
+
total_len = payload_len
|
|
599
|
+
if self.use_odirect:
|
|
600
|
+
total_len = _round_up(payload_len, self.block_align)
|
|
601
|
+
if total_len > (self.slot_bytes - self.header_bytes):
|
|
602
|
+
raise RuntimeError(f"O_DIRECT payload {total_len} > slot capacity")
|
|
603
|
+
direct_view = self._build_direct_odirect_view(
|
|
604
|
+
memory_obj=memory_obj,
|
|
605
|
+
payload_len=payload_len,
|
|
606
|
+
total_len=total_len,
|
|
607
|
+
buffer_len=len(buf),
|
|
608
|
+
zero_tail=True,
|
|
609
|
+
)
|
|
610
|
+
if direct_view is not None:
|
|
611
|
+
buf = direct_view
|
|
612
|
+
return buf, payload_len, total_len
|
|
613
|
+
|
|
614
|
+
async def _submit_write(
|
|
615
|
+
self,
|
|
616
|
+
key: CacheEngineKey,
|
|
617
|
+
offset: int,
|
|
618
|
+
header: bytes,
|
|
619
|
+
memory_obj: MemoryObj,
|
|
620
|
+
on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
|
|
621
|
+
) -> None:
|
|
622
|
+
try:
|
|
623
|
+
buf, payload_len, total_len = self._prepare_write_payload(memory_obj)
|
|
624
|
+
|
|
625
|
+
def _do_write():
|
|
626
|
+
with self._lock:
|
|
627
|
+
self._inflight_io_count += 1
|
|
628
|
+
try:
|
|
629
|
+
raw_dev = self._rawdev()
|
|
630
|
+
hdr_total = (
|
|
631
|
+
_round_up(len(header), self.block_align)
|
|
632
|
+
if self.use_odirect
|
|
633
|
+
else len(header)
|
|
634
|
+
)
|
|
635
|
+
raw_dev.pwrite_from_buffer(offset, header, len(header), hdr_total)
|
|
636
|
+
raw_dev.pwrite_from_buffer(
|
|
637
|
+
offset + self.header_bytes, buf, payload_len, total_len
|
|
638
|
+
)
|
|
639
|
+
except Exception as e:
|
|
640
|
+
logger.error(
|
|
641
|
+
f"Write failed for key {self._dbg_key_short(key)}: {e}"
|
|
642
|
+
)
|
|
643
|
+
raise
|
|
644
|
+
finally:
|
|
645
|
+
with self._lock:
|
|
646
|
+
self._inflight_io_count -= 1
|
|
647
|
+
self._last_io_ts = time.monotonic()
|
|
648
|
+
|
|
649
|
+
write_error: Optional[Exception] = None
|
|
650
|
+
try:
|
|
651
|
+
await asyncio.to_thread(_do_write)
|
|
652
|
+
except Exception as e:
|
|
653
|
+
write_error = e
|
|
654
|
+
|
|
655
|
+
with self._lock:
|
|
656
|
+
inflight = self._inflight.pop(key, None)
|
|
657
|
+
if inflight is not None:
|
|
658
|
+
if inflight.canceled or write_error is not None:
|
|
659
|
+
self._append_free_slot_locked(
|
|
660
|
+
self._offset_to_slot(int(inflight.offset))
|
|
661
|
+
)
|
|
662
|
+
self._meta_dirty_total += 1
|
|
663
|
+
else:
|
|
664
|
+
self._index[key] = _Entry(
|
|
665
|
+
offset=inflight.offset,
|
|
666
|
+
size=inflight.meta.size,
|
|
667
|
+
meta=inflight.meta,
|
|
668
|
+
)
|
|
669
|
+
self._touch(key)
|
|
670
|
+
self._meta_dirty_total += 1
|
|
671
|
+
|
|
672
|
+
if write_error is None:
|
|
673
|
+
if on_complete_callback is not None:
|
|
674
|
+
try:
|
|
675
|
+
on_complete_callback(key)
|
|
676
|
+
except Exception as e:
|
|
677
|
+
logger.warning(
|
|
678
|
+
f"on_complete_callback failed for key {key}: {e}"
|
|
679
|
+
)
|
|
680
|
+
else:
|
|
681
|
+
raise write_error
|
|
682
|
+
finally:
|
|
683
|
+
memory_obj.ref_count_down()
|
|
684
|
+
with self._put_lock:
|
|
685
|
+
self._put_tasks.discard(key)
|
|
686
|
+
|
|
687
|
+
def _encode_header(self, key: CacheEngineKey, payload_len: int) -> bytes:
|
|
688
|
+
magic = b"LMCBLK01"
|
|
689
|
+
chunk_hash = int(key.chunk_hash) & ((1 << 64) - 1)
|
|
690
|
+
hdr = bytearray(self.header_bytes)
|
|
691
|
+
hdr[0:8] = magic
|
|
692
|
+
hdr[8:16] = chunk_hash.to_bytes(8, "little", signed=False)
|
|
693
|
+
hdr[16:24] = int(payload_len).to_bytes(8, "little", signed=False)
|
|
694
|
+
return bytes(hdr)
|
|
695
|
+
|
|
696
|
+
def _decode_slot_header(self, hdr: bytes) -> Optional[tuple[int, int]]:
|
|
697
|
+
if len(hdr) < 24:
|
|
698
|
+
return None
|
|
699
|
+
if hdr[0:8] != b"LMCBLK01":
|
|
700
|
+
return None
|
|
701
|
+
chunk_hash = int.from_bytes(hdr[8:16], "little", signed=False)
|
|
702
|
+
payload_len = int.from_bytes(hdr[16:24], "little", signed=False)
|
|
703
|
+
return chunk_hash, payload_len
|
|
704
|
+
|
|
705
|
+
def _read_slot_header(self, offset: int) -> Optional[tuple[int, int]]:
|
|
706
|
+
raw = self._rawdev()
|
|
707
|
+
buf = bytearray(self.header_bytes)
|
|
708
|
+
try:
|
|
709
|
+
with self._lock:
|
|
710
|
+
self._inflight_io_count += 1
|
|
711
|
+
raw.pread_into(offset, buf, self.header_bytes, self.header_bytes)
|
|
712
|
+
return self._decode_slot_header(buf)
|
|
713
|
+
except Exception:
|
|
714
|
+
return None
|
|
715
|
+
finally:
|
|
716
|
+
with self._lock:
|
|
717
|
+
self._inflight_io_count -= 1
|
|
718
|
+
self._last_io_ts = time.monotonic()
|
|
719
|
+
|
|
720
|
+
def _batched_get_prefix(self, keys: Sequence[CacheEngineKey]) -> list[MemoryObj]:
|
|
721
|
+
if not keys:
|
|
722
|
+
return []
|
|
723
|
+
|
|
724
|
+
items: list[tuple[CacheEngineKey, _Entry]] = []
|
|
725
|
+
with self._lock:
|
|
726
|
+
for key in keys:
|
|
727
|
+
entry = self._index.get(key)
|
|
728
|
+
if entry is None:
|
|
729
|
+
break
|
|
730
|
+
items.append((key, entry))
|
|
731
|
+
if not items:
|
|
732
|
+
return []
|
|
733
|
+
self._inflight_io_count += 1
|
|
734
|
+
|
|
735
|
+
loaded: list[MemoryObj] = []
|
|
736
|
+
touched: list[CacheEngineKey] = []
|
|
737
|
+
try:
|
|
738
|
+
raw_dev = self._rawdev()
|
|
739
|
+
for key, entry in items:
|
|
740
|
+
meta = entry.meta
|
|
741
|
+
assert meta.shape is not None and meta.dtype is not None
|
|
742
|
+
assert self.local_cpu_backend is not None
|
|
743
|
+
memory_obj = self.local_cpu_backend.allocate(
|
|
744
|
+
meta.shape, meta.dtype, meta.fmt
|
|
745
|
+
)
|
|
746
|
+
if memory_obj is None:
|
|
747
|
+
logger.error(
|
|
748
|
+
"Failed to allocate memory for key %s",
|
|
749
|
+
self._dbg_key_short(key),
|
|
750
|
+
)
|
|
751
|
+
break
|
|
752
|
+
|
|
753
|
+
try:
|
|
754
|
+
payload_len = int(meta.size)
|
|
755
|
+
total_len = (
|
|
756
|
+
_round_up(payload_len, self.block_align)
|
|
757
|
+
if self.use_odirect
|
|
758
|
+
else payload_len
|
|
759
|
+
)
|
|
760
|
+
if logger.isEnabledFor(10):
|
|
761
|
+
self._dbg_get_calls += 1
|
|
762
|
+
self._dbg_get_bytes += payload_len
|
|
763
|
+
if self._dbg_should_log(self._dbg_get_calls):
|
|
764
|
+
logger.debug(
|
|
765
|
+
"RustRawBlockBackend GET: %s offset=%d size=%d",
|
|
766
|
+
self._dbg_key_short(key),
|
|
767
|
+
int(entry.offset),
|
|
768
|
+
payload_len,
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
buf = memory_obj.byte_array
|
|
772
|
+
try:
|
|
773
|
+
buf = buf.cast("B")
|
|
774
|
+
except Exception:
|
|
775
|
+
pass
|
|
776
|
+
direct_view = self._build_direct_odirect_view(
|
|
777
|
+
memory_obj=memory_obj,
|
|
778
|
+
payload_len=payload_len,
|
|
779
|
+
total_len=total_len,
|
|
780
|
+
buffer_len=len(buf),
|
|
781
|
+
zero_tail=False,
|
|
782
|
+
)
|
|
783
|
+
if direct_view is not None:
|
|
784
|
+
raw_dev.pread_into(
|
|
785
|
+
entry.offset + self.header_bytes,
|
|
786
|
+
direct_view,
|
|
787
|
+
total_len if len(direct_view) >= total_len else payload_len,
|
|
788
|
+
total_len,
|
|
789
|
+
)
|
|
790
|
+
else:
|
|
791
|
+
raw_dev.pread_into(
|
|
792
|
+
entry.offset + self.header_bytes,
|
|
793
|
+
buf,
|
|
794
|
+
payload_len,
|
|
795
|
+
total_len,
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
memory_obj.metadata.cached_positions = meta.cached_positions
|
|
799
|
+
except Exception as e:
|
|
800
|
+
memory_obj.ref_count_down()
|
|
801
|
+
logger.error(
|
|
802
|
+
"Read failed for key %s: %s", self._dbg_key_short(key), e
|
|
803
|
+
)
|
|
804
|
+
raise
|
|
805
|
+
|
|
806
|
+
loaded.append(memory_obj)
|
|
807
|
+
touched.append(key)
|
|
808
|
+
except Exception:
|
|
809
|
+
for memory_obj in loaded:
|
|
810
|
+
memory_obj.ref_count_down()
|
|
811
|
+
loaded.clear()
|
|
812
|
+
touched.clear()
|
|
813
|
+
raise
|
|
814
|
+
finally:
|
|
815
|
+
with self._lock:
|
|
816
|
+
for key in touched:
|
|
817
|
+
self._touch(key)
|
|
818
|
+
self._inflight_io_count -= 1
|
|
819
|
+
self._last_io_ts = time.monotonic()
|
|
820
|
+
return loaded
|
|
821
|
+
|
|
822
|
+
def get_blocking(self, key: CacheEngineKey) -> Optional[MemoryObj]:
|
|
823
|
+
loaded = self._batched_get_prefix([key])
|
|
824
|
+
return loaded[0] if loaded else None
|
|
825
|
+
|
|
826
|
+
def batched_get_blocking(
|
|
827
|
+
self,
|
|
828
|
+
keys: List[CacheEngineKey],
|
|
829
|
+
) -> List[Optional[MemoryObj]]:
|
|
830
|
+
"""
|
|
831
|
+
Get a batch of cache entries until the first miss.
|
|
832
|
+
|
|
833
|
+
:param List[CacheEngineKey] keys: Ordered keys to retrieve.
|
|
834
|
+
|
|
835
|
+
:return: A list aligned to ``keys`` where the successful prefix contains
|
|
836
|
+
loaded memory objects and the remaining suffix is ``None``.
|
|
837
|
+
|
|
838
|
+
:raises Exception: Propagates raw-device initialization or read failures.
|
|
839
|
+
"""
|
|
840
|
+
if not keys:
|
|
841
|
+
return []
|
|
842
|
+
|
|
843
|
+
loaded = self._batched_get_prefix(keys)
|
|
844
|
+
return [*loaded, *([None] * (len(keys) - len(loaded)))]
|
|
845
|
+
|
|
846
|
+
async def batched_async_contains(
|
|
847
|
+
self,
|
|
848
|
+
lookup_id: str,
|
|
849
|
+
keys: list[CacheEngineKey],
|
|
850
|
+
pin: bool = False,
|
|
851
|
+
) -> int:
|
|
852
|
+
del lookup_id
|
|
853
|
+
hit = 0
|
|
854
|
+
with self._lock:
|
|
855
|
+
for k in keys:
|
|
856
|
+
if k not in self._index:
|
|
857
|
+
break
|
|
858
|
+
if pin:
|
|
859
|
+
self._pinned.add(k)
|
|
860
|
+
hit += 1
|
|
861
|
+
return hit
|
|
862
|
+
|
|
863
|
+
async def batched_get_non_blocking(
|
|
864
|
+
self,
|
|
865
|
+
lookup_id: str,
|
|
866
|
+
keys: list[CacheEngineKey],
|
|
867
|
+
transfer_spec: Any = None,
|
|
868
|
+
) -> list[MemoryObj]:
|
|
869
|
+
"""
|
|
870
|
+
Asynchronously get a batch of cache entries until the first miss.
|
|
871
|
+
|
|
872
|
+
:param str lookup_id: Lookup identifier used by the storage manager.
|
|
873
|
+
:param list[CacheEngineKey] keys: Ordered keys to retrieve.
|
|
874
|
+
:param Any transfer_spec: Unused transfer hint for API compatibility.
|
|
875
|
+
|
|
876
|
+
:return: The successfully loaded prefix of ``keys`` in input order.
|
|
877
|
+
|
|
878
|
+
:raises Exception: Propagates raw-device initialization or read failures.
|
|
879
|
+
"""
|
|
880
|
+
del lookup_id, transfer_spec
|
|
881
|
+
return await asyncio.to_thread(self._batched_get_prefix, keys)
|
|
882
|
+
|
|
883
|
+
def get_allocator_backend(self) -> "AllocatorBackendInterface":
|
|
884
|
+
assert self.local_cpu_backend is not None
|
|
885
|
+
return self.local_cpu_backend
|
|
886
|
+
|
|
887
|
+
def close(self) -> None:
|
|
888
|
+
if logger.isEnabledFor(10):
|
|
889
|
+
logger.debug(
|
|
890
|
+
"RustRawBlockBackend stats: put=%d/%d/%d get=%d/%d",
|
|
891
|
+
self._dbg_put_batches,
|
|
892
|
+
self._dbg_put_keys,
|
|
893
|
+
self._dbg_put_bytes,
|
|
894
|
+
self._dbg_get_calls,
|
|
895
|
+
self._dbg_get_bytes,
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
# Stop checkpoint background thread.
|
|
899
|
+
self._meta_stop_evt.set()
|
|
900
|
+
if self._meta_thread is not None:
|
|
901
|
+
self._meta_thread.join(timeout=5)
|
|
902
|
+
self._meta_thread = None
|
|
903
|
+
|
|
904
|
+
# Wait briefly for inflight put tasks to drain.
|
|
905
|
+
deadline = time.monotonic() + 10.0
|
|
906
|
+
while True:
|
|
907
|
+
with self._put_lock:
|
|
908
|
+
pending = len(self._put_tasks)
|
|
909
|
+
if pending == 0 or time.monotonic() >= deadline:
|
|
910
|
+
break
|
|
911
|
+
time.sleep(0.01)
|
|
912
|
+
|
|
913
|
+
# Force final metadata checkpoint.
|
|
914
|
+
try:
|
|
915
|
+
self._checkpoint_once(force=True)
|
|
916
|
+
except Exception as e:
|
|
917
|
+
logger.warning(f"Failed to write final on-device metadata checkpoint: {e}")
|
|
918
|
+
|
|
919
|
+
if self._raw is not None:
|
|
920
|
+
try:
|
|
921
|
+
self._raw.close()
|
|
922
|
+
except Exception as e:
|
|
923
|
+
logger.warning(f"Failed to close raw block device: {e}")
|
|
924
|
+
finally:
|
|
925
|
+
self._raw = None
|
|
926
|
+
|
|
927
|
+
# ------------------- On-device metadata checkpoint -------------------
|
|
928
|
+
|
|
929
|
+
def _checkpoint_loop(self) -> None:
|
|
930
|
+
interval = max(1, self.meta_checkpoint_interval_sec)
|
|
931
|
+
while not self._meta_stop_evt.wait(interval):
|
|
932
|
+
try:
|
|
933
|
+
self._checkpoint_once(force=False)
|
|
934
|
+
except Exception as e:
|
|
935
|
+
logger.warning(f"Periodic metadata checkpoint failed: {e}")
|
|
936
|
+
|
|
937
|
+
def _meta_payload_capacity(self) -> int:
|
|
938
|
+
return self._meta_container_bytes - self.block_align
|
|
939
|
+
|
|
940
|
+
def _meta_container_offsets(self) -> list[int]:
|
|
941
|
+
return [
|
|
942
|
+
idx * self._meta_container_bytes for idx in range(self._meta_copy_count)
|
|
943
|
+
]
|
|
944
|
+
|
|
945
|
+
def _read_meta_header(self, container_offset: int) -> Optional[dict[str, int]]:
|
|
946
|
+
raw = self._rawdev()
|
|
947
|
+
buf = bytearray(self.block_align)
|
|
948
|
+
try:
|
|
949
|
+
raw.pread_into(container_offset, buf, self.block_align, self.block_align)
|
|
950
|
+
except Exception:
|
|
951
|
+
return None
|
|
952
|
+
|
|
953
|
+
hdr = bytes(buf[: _META_HEADER_STRUCT.size])
|
|
954
|
+
magic, version, seq, payload_len, crc = _META_HEADER_STRUCT.unpack(hdr)
|
|
955
|
+
if magic != self.meta_magic or version != self.meta_version:
|
|
956
|
+
return None
|
|
957
|
+
|
|
958
|
+
payload_cap = self._meta_payload_capacity()
|
|
959
|
+
if payload_len <= 0 or payload_len > payload_cap:
|
|
960
|
+
return None
|
|
961
|
+
|
|
962
|
+
return {
|
|
963
|
+
"seq": int(seq),
|
|
964
|
+
"payload_len": int(payload_len),
|
|
965
|
+
"crc": int(crc),
|
|
966
|
+
"container_offset": int(container_offset),
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
def _load_meta_payload(self, header: dict[str, int]) -> Optional[bytes]:
|
|
970
|
+
raw = self._rawdev()
|
|
971
|
+
payload_len = int(header["payload_len"])
|
|
972
|
+
payload_off = int(header["container_offset"]) + self.block_align
|
|
973
|
+
total_len = _round_up(payload_len, self.block_align)
|
|
974
|
+
buf = bytearray(total_len)
|
|
975
|
+
try:
|
|
976
|
+
raw.pread_into(payload_off, buf, payload_len, total_len)
|
|
977
|
+
except Exception:
|
|
978
|
+
return None
|
|
979
|
+
|
|
980
|
+
payload = bytes(buf[:payload_len])
|
|
981
|
+
crc = zlib.crc32(payload) & 0xFFFFFFFF
|
|
982
|
+
if crc != int(header["crc"]):
|
|
983
|
+
return None
|
|
984
|
+
return payload
|
|
985
|
+
|
|
986
|
+
def _select_latest_checkpoint(
|
|
987
|
+
self,
|
|
988
|
+
) -> tuple[Optional[dict[str, int]], Optional[bytes]]:
|
|
989
|
+
best_header: Optional[dict[str, int]] = None
|
|
990
|
+
best_payload: Optional[bytes] = None
|
|
991
|
+
for offset in self._meta_container_offsets():
|
|
992
|
+
header = self._read_meta_header(offset)
|
|
993
|
+
if header is None:
|
|
994
|
+
continue
|
|
995
|
+
payload = self._load_meta_payload(header)
|
|
996
|
+
if payload is None:
|
|
997
|
+
continue
|
|
998
|
+
if best_header is None or int(header["seq"]) > int(best_header["seq"]):
|
|
999
|
+
best_header = header
|
|
1000
|
+
best_payload = payload
|
|
1001
|
+
return best_header, best_payload
|
|
1002
|
+
|
|
1003
|
+
def _snapshot_state(self) -> tuple[dict[str, Any], int]:
|
|
1004
|
+
with self._lock:
|
|
1005
|
+
dirty_total = self._meta_dirty_total
|
|
1006
|
+
snapshot = {
|
|
1007
|
+
"version": 1,
|
|
1008
|
+
"device_path": self.device_path,
|
|
1009
|
+
"capacity_bytes": self.capacity_bytes,
|
|
1010
|
+
"block_align": self.block_align,
|
|
1011
|
+
"header_bytes": self.header_bytes,
|
|
1012
|
+
"slot_bytes": self.slot_bytes,
|
|
1013
|
+
"meta_total_bytes": self.meta_total_bytes,
|
|
1014
|
+
"meta_magic": self.meta_magic_text,
|
|
1015
|
+
"meta_version": self.meta_version,
|
|
1016
|
+
"data_base_offset": self._data_base_offset,
|
|
1017
|
+
"next_slot": self._next_slot,
|
|
1018
|
+
"free_slots": list(self._free_slots),
|
|
1019
|
+
"lru_keys": [k.to_string() for k in self._lru.keys()],
|
|
1020
|
+
"entries": {
|
|
1021
|
+
k.to_string(): {
|
|
1022
|
+
"offset": e.offset,
|
|
1023
|
+
"size": e.meta.size,
|
|
1024
|
+
"shape": list(e.meta.shape)
|
|
1025
|
+
if e.meta.shape is not None
|
|
1026
|
+
else None,
|
|
1027
|
+
"dtype": k._dtype_str,
|
|
1028
|
+
"fmt": (
|
|
1029
|
+
e.meta.fmt.name
|
|
1030
|
+
if e.meta.fmt is not None and hasattr(e.meta.fmt, "name")
|
|
1031
|
+
else str(e.meta.fmt)
|
|
1032
|
+
if e.meta.fmt is not None
|
|
1033
|
+
else None
|
|
1034
|
+
),
|
|
1035
|
+
"cached_positions": (
|
|
1036
|
+
e.meta.cached_positions.tolist()
|
|
1037
|
+
if e.meta.cached_positions is not None
|
|
1038
|
+
and hasattr(e.meta.cached_positions, "tolist")
|
|
1039
|
+
else None
|
|
1040
|
+
),
|
|
1041
|
+
}
|
|
1042
|
+
for k, e in self._index.items()
|
|
1043
|
+
},
|
|
1044
|
+
}
|
|
1045
|
+
return snapshot, dirty_total
|
|
1046
|
+
|
|
1047
|
+
def _write_checkpoint(self, payload: bytes, dirty_total_snapshot: int) -> bool:
|
|
1048
|
+
payload_cap = self._meta_payload_capacity()
|
|
1049
|
+
if len(payload) > payload_cap:
|
|
1050
|
+
logger.warning(
|
|
1051
|
+
"Metadata payload too large (%d > %d), skipping checkpoint",
|
|
1052
|
+
len(payload),
|
|
1053
|
+
payload_cap,
|
|
1054
|
+
)
|
|
1055
|
+
return False
|
|
1056
|
+
|
|
1057
|
+
next_seq = self._meta_seq + 1
|
|
1058
|
+
target_idx = int((next_seq - 1) % self._meta_copy_count)
|
|
1059
|
+
target = self._meta_container_offsets()[target_idx]
|
|
1060
|
+
|
|
1061
|
+
payload_len = len(payload)
|
|
1062
|
+
payload_total_len = _round_up(payload_len, self.block_align)
|
|
1063
|
+
payload_off = target + self.block_align
|
|
1064
|
+
crc = zlib.crc32(payload) & 0xFFFFFFFF
|
|
1065
|
+
|
|
1066
|
+
header_block = bytearray(self.block_align)
|
|
1067
|
+
header_block[: _META_HEADER_STRUCT.size] = _META_HEADER_STRUCT.pack(
|
|
1068
|
+
self.meta_magic,
|
|
1069
|
+
self.meta_version,
|
|
1070
|
+
int(next_seq),
|
|
1071
|
+
int(payload_len),
|
|
1072
|
+
int(crc),
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
raw = self._rawdev()
|
|
1076
|
+
|
|
1077
|
+
raw.pwrite_from_buffer(payload_off, payload, payload_len, payload_total_len)
|
|
1078
|
+
raw.pwrite_from_buffer(target, header_block, self.block_align, self.block_align)
|
|
1079
|
+
|
|
1080
|
+
with self._lock:
|
|
1081
|
+
self._meta_seq = int(next_seq)
|
|
1082
|
+
self._meta_persisted = max(self._meta_persisted, int(dirty_total_snapshot))
|
|
1083
|
+
|
|
1084
|
+
return True
|
|
1085
|
+
|
|
1086
|
+
def _checkpoint_once(self, force: bool) -> bool:
|
|
1087
|
+
with self._lock:
|
|
1088
|
+
dirty = self._meta_dirty_total > self._meta_persisted
|
|
1089
|
+
idle_ok = self._inflight_io_count == 0 and (
|
|
1090
|
+
time.monotonic() - self._last_io_ts
|
|
1091
|
+
) >= (self.meta_idle_quiet_ms / 1000.0)
|
|
1092
|
+
|
|
1093
|
+
if not dirty:
|
|
1094
|
+
return False
|
|
1095
|
+
if not force and not idle_ok:
|
|
1096
|
+
return False
|
|
1097
|
+
|
|
1098
|
+
snapshot, dirty_total_snapshot = self._snapshot_state()
|
|
1099
|
+
payload = json.dumps(snapshot, separators=(",", ":"), ensure_ascii=True).encode(
|
|
1100
|
+
"utf-8"
|
|
1101
|
+
)
|
|
1102
|
+
|
|
1103
|
+
ok = self._write_checkpoint(payload, dirty_total_snapshot)
|
|
1104
|
+
if ok and logger.isEnabledFor(10):
|
|
1105
|
+
logger.debug(
|
|
1106
|
+
"RustRawBlockBackend: checkpoint saved seq=%d entries=%d",
|
|
1107
|
+
self._meta_seq,
|
|
1108
|
+
len(snapshot.get("entries", {})),
|
|
1109
|
+
)
|
|
1110
|
+
return ok
|
|
1111
|
+
|
|
1112
|
+
def _is_valid_checkpoint_entry(self, offset: int, size: int) -> bool:
|
|
1113
|
+
if offset < self._data_base_offset:
|
|
1114
|
+
return False
|
|
1115
|
+
rel = offset - self._data_base_offset
|
|
1116
|
+
if rel % self.slot_bytes != 0:
|
|
1117
|
+
return False
|
|
1118
|
+
slot = rel // self.slot_bytes
|
|
1119
|
+
if slot < 0 or slot >= self._max_slots:
|
|
1120
|
+
return False
|
|
1121
|
+
return 0 < size <= (self.slot_bytes - self.header_bytes)
|
|
1122
|
+
|
|
1123
|
+
def _apply_loaded_state(self, data: dict[str, Any]) -> bool:
|
|
1124
|
+
if not isinstance(data, dict):
|
|
1125
|
+
return False
|
|
1126
|
+
if int(data.get("version", 0)) != 1:
|
|
1127
|
+
return False
|
|
1128
|
+
if data.get("device_path") and data.get("device_path") != self.device_path:
|
|
1129
|
+
logger.warning("Device metadata device_path mismatch; ignoring metadata")
|
|
1130
|
+
return False
|
|
1131
|
+
if int(data.get("slot_bytes", self.slot_bytes)) != self.slot_bytes:
|
|
1132
|
+
logger.warning("Device metadata slot_bytes mismatch; ignoring metadata")
|
|
1133
|
+
return False
|
|
1134
|
+
if (
|
|
1135
|
+
int(data.get("meta_total_bytes", self.meta_total_bytes))
|
|
1136
|
+
!= self.meta_total_bytes
|
|
1137
|
+
):
|
|
1138
|
+
logger.warning(
|
|
1139
|
+
"Device metadata meta_total_bytes mismatch; ignoring metadata"
|
|
1140
|
+
)
|
|
1141
|
+
return False
|
|
1142
|
+
if str(data.get("meta_magic", self.meta_magic_text)) != self.meta_magic_text:
|
|
1143
|
+
logger.warning("Device metadata meta_magic mismatch; ignoring metadata")
|
|
1144
|
+
return False
|
|
1145
|
+
if int(data.get("meta_version", self.meta_version)) != self.meta_version:
|
|
1146
|
+
logger.warning("Device metadata meta_version mismatch; ignoring metadata")
|
|
1147
|
+
return False
|
|
1148
|
+
|
|
1149
|
+
try:
|
|
1150
|
+
next_slot = int(data.get("next_slot", 0))
|
|
1151
|
+
except Exception:
|
|
1152
|
+
logger.warning("Device metadata next_slot is invalid; ignoring metadata")
|
|
1153
|
+
return False
|
|
1154
|
+
if next_slot < 0 or next_slot > self._max_slots:
|
|
1155
|
+
logger.warning(
|
|
1156
|
+
"Device metadata next_slot out of range (%d); ignoring metadata",
|
|
1157
|
+
next_slot,
|
|
1158
|
+
)
|
|
1159
|
+
return False
|
|
1160
|
+
|
|
1161
|
+
raw_free_slots = data.get("free_slots", [])
|
|
1162
|
+
if not isinstance(raw_free_slots, list):
|
|
1163
|
+
logger.warning("Device metadata free_slots is invalid; ignoring metadata")
|
|
1164
|
+
return False
|
|
1165
|
+
free_slots: list[int] = []
|
|
1166
|
+
seen_slots: set[int] = set()
|
|
1167
|
+
for raw_slot in raw_free_slots:
|
|
1168
|
+
try:
|
|
1169
|
+
slot = int(raw_slot)
|
|
1170
|
+
except Exception:
|
|
1171
|
+
logger.warning(
|
|
1172
|
+
"Device metadata free_slots contains non-integer; ignoring metadata"
|
|
1173
|
+
)
|
|
1174
|
+
return False
|
|
1175
|
+
if slot < 0 or slot >= self._max_slots:
|
|
1176
|
+
logger.warning(
|
|
1177
|
+
"Device metadata free_slots contains out-of-range slot %d; "
|
|
1178
|
+
"ignoring metadata",
|
|
1179
|
+
slot,
|
|
1180
|
+
)
|
|
1181
|
+
return False
|
|
1182
|
+
if slot in seen_slots:
|
|
1183
|
+
continue
|
|
1184
|
+
seen_slots.add(slot)
|
|
1185
|
+
free_slots.append(slot)
|
|
1186
|
+
|
|
1187
|
+
with self._lock:
|
|
1188
|
+
self._next_slot = next_slot
|
|
1189
|
+
self._free_slots = free_slots
|
|
1190
|
+
self._index.clear()
|
|
1191
|
+
self._lru.clear()
|
|
1192
|
+
|
|
1193
|
+
entries = data.get("entries", {})
|
|
1194
|
+
if isinstance(entries, dict):
|
|
1195
|
+
for k_str, entry in entries.items():
|
|
1196
|
+
if not isinstance(entry, dict):
|
|
1197
|
+
logger.warning(
|
|
1198
|
+
"Invalid entry in metadata for key '%s': not a dict.", k_str
|
|
1199
|
+
)
|
|
1200
|
+
continue
|
|
1201
|
+
try:
|
|
1202
|
+
key = CacheEngineKey.from_string(k_str)
|
|
1203
|
+
except Exception as e:
|
|
1204
|
+
logger.warning(
|
|
1205
|
+
"Failed to parse key string '%s' from metadata: %s",
|
|
1206
|
+
k_str,
|
|
1207
|
+
e,
|
|
1208
|
+
)
|
|
1209
|
+
continue
|
|
1210
|
+
|
|
1211
|
+
offset = int(entry.get("offset", 0))
|
|
1212
|
+
size = int(entry.get("size", 0))
|
|
1213
|
+
shape_list = entry.get("shape")
|
|
1214
|
+
fmt_name = entry.get("fmt")
|
|
1215
|
+
cached_positions_list = entry.get("cached_positions")
|
|
1216
|
+
|
|
1217
|
+
if not self._is_valid_checkpoint_entry(offset, size):
|
|
1218
|
+
logger.warning(
|
|
1219
|
+
"Skipping invalid checkpoint entry for key '%s': "
|
|
1220
|
+
"offset=%d size=%d",
|
|
1221
|
+
k_str,
|
|
1222
|
+
offset,
|
|
1223
|
+
size,
|
|
1224
|
+
)
|
|
1225
|
+
continue
|
|
1226
|
+
|
|
1227
|
+
shape = (
|
|
1228
|
+
torch.Size(list(shape_list)) if shape_list is not None else None
|
|
1229
|
+
)
|
|
1230
|
+
fmt = (
|
|
1231
|
+
MemoryFormat[fmt_name]
|
|
1232
|
+
if isinstance(fmt_name, str)
|
|
1233
|
+
and fmt_name in MemoryFormat.__members__
|
|
1234
|
+
else MemoryFormat.UNDEFINED
|
|
1235
|
+
)
|
|
1236
|
+
cached_positions = (
|
|
1237
|
+
torch.tensor(cached_positions_list, dtype=torch.long)
|
|
1238
|
+
if cached_positions_list is not None
|
|
1239
|
+
else None
|
|
1240
|
+
)
|
|
1241
|
+
|
|
1242
|
+
meta = DiskCacheMetadata(
|
|
1243
|
+
path=f"{self.device_path}@{offset}",
|
|
1244
|
+
size=size,
|
|
1245
|
+
shape=shape,
|
|
1246
|
+
dtype=key.dtype,
|
|
1247
|
+
cached_positions=cached_positions,
|
|
1248
|
+
fmt=fmt,
|
|
1249
|
+
pin_count=0,
|
|
1250
|
+
)
|
|
1251
|
+
self._index[key] = _Entry(offset=offset, size=size, meta=meta)
|
|
1252
|
+
|
|
1253
|
+
if self.metadata is not None and self._index:
|
|
1254
|
+
first_loaded_key = next(iter(self._index))
|
|
1255
|
+
expected_worker_id = int(self.metadata.worker_id)
|
|
1256
|
+
loaded_worker_id = int(first_loaded_key.worker_id)
|
|
1257
|
+
if loaded_worker_id != expected_worker_id:
|
|
1258
|
+
logger.warning(
|
|
1259
|
+
"RustRawBlockBackend: loaded metadata may belong to another "
|
|
1260
|
+
"worker (device=%s, current_worker_id=%d, "
|
|
1261
|
+
"first_entry_worker_id=%d, first_entry_key=%s)",
|
|
1262
|
+
self.device_path,
|
|
1263
|
+
expected_worker_id,
|
|
1264
|
+
loaded_worker_id,
|
|
1265
|
+
first_loaded_key.to_string(),
|
|
1266
|
+
)
|
|
1267
|
+
|
|
1268
|
+
# Remove free-slot entries that overlap with loaded index slots.
|
|
1269
|
+
used_slots = {
|
|
1270
|
+
self._offset_to_slot(int(entry.offset))
|
|
1271
|
+
for entry in self._index.values()
|
|
1272
|
+
}
|
|
1273
|
+
self._free_slots = [
|
|
1274
|
+
slot for slot in self._free_slots if slot not in used_slots
|
|
1275
|
+
]
|
|
1276
|
+
|
|
1277
|
+
lru_keys = data.get("lru_keys", [])
|
|
1278
|
+
if isinstance(lru_keys, list) and lru_keys:
|
|
1279
|
+
for k_str in lru_keys:
|
|
1280
|
+
try:
|
|
1281
|
+
key = CacheEngineKey.from_string(k_str)
|
|
1282
|
+
except Exception:
|
|
1283
|
+
continue
|
|
1284
|
+
if key in self._index:
|
|
1285
|
+
self._lru[key] = None
|
|
1286
|
+
else:
|
|
1287
|
+
for key in self._index:
|
|
1288
|
+
self._lru[key] = None
|
|
1289
|
+
|
|
1290
|
+
# Loaded state should start as clean.
|
|
1291
|
+
self._meta_dirty_total = 0
|
|
1292
|
+
self._meta_persisted = 0
|
|
1293
|
+
|
|
1294
|
+
if self.meta_verify_on_load:
|
|
1295
|
+
self._validate_loaded_entries()
|
|
1296
|
+
return True
|
|
1297
|
+
|
|
1298
|
+
def _validate_loaded_entries(self) -> None:
|
|
1299
|
+
to_drop: list[CacheEngineKey] = []
|
|
1300
|
+
with self._lock:
|
|
1301
|
+
entries = list(self._index.items())
|
|
1302
|
+
|
|
1303
|
+
for key, entry in entries:
|
|
1304
|
+
slot_hdr = self._read_slot_header(int(entry.offset))
|
|
1305
|
+
if slot_hdr is None:
|
|
1306
|
+
to_drop.append(key)
|
|
1307
|
+
continue
|
|
1308
|
+
chunk_hash, payload_len = slot_hdr
|
|
1309
|
+
if int(chunk_hash) != (int(key.chunk_hash) & ((1 << 64) - 1)):
|
|
1310
|
+
to_drop.append(key)
|
|
1311
|
+
continue
|
|
1312
|
+
if int(payload_len) != int(entry.size):
|
|
1313
|
+
to_drop.append(key)
|
|
1314
|
+
|
|
1315
|
+
if not to_drop:
|
|
1316
|
+
return
|
|
1317
|
+
|
|
1318
|
+
with self._lock:
|
|
1319
|
+
for key in to_drop:
|
|
1320
|
+
removed_entry: Optional[_Entry] = None
|
|
1321
|
+
if key in self._index:
|
|
1322
|
+
removed_entry = self._index.pop(key)
|
|
1323
|
+
self._lru.pop(key, None)
|
|
1324
|
+
self._pinned.discard(key)
|
|
1325
|
+
if removed_entry is not None:
|
|
1326
|
+
self._append_free_slot_locked(
|
|
1327
|
+
self._offset_to_slot(int(removed_entry.offset))
|
|
1328
|
+
)
|
|
1329
|
+
self._meta_dirty_total += 1
|
|
1330
|
+
|
|
1331
|
+
logger.warning(
|
|
1332
|
+
"RustRawBlockBackend: dropped %d stale metadata entries after slot-header "
|
|
1333
|
+
"validation",
|
|
1334
|
+
len(to_drop),
|
|
1335
|
+
)
|
|
1336
|
+
|
|
1337
|
+
def _load_checkpoint_from_device(self) -> None:
|
|
1338
|
+
header, payload = self._select_latest_checkpoint()
|
|
1339
|
+
if header is None:
|
|
1340
|
+
logger.info(
|
|
1341
|
+
"RustRawBlockBackend: no valid on-device metadata checkpoint found"
|
|
1342
|
+
)
|
|
1343
|
+
return
|
|
1344
|
+
assert payload is not None
|
|
1345
|
+
try:
|
|
1346
|
+
data = json.loads(payload.decode("utf-8"))
|
|
1347
|
+
except Exception:
|
|
1348
|
+
logger.warning("RustRawBlockBackend: failed to decode metadata payload")
|
|
1349
|
+
return
|
|
1350
|
+
applied = self._apply_loaded_state(data)
|
|
1351
|
+
if not applied:
|
|
1352
|
+
logger.warning("RustRawBlockBackend: metadata payload rejected by checks")
|
|
1353
|
+
return
|
|
1354
|
+
self._meta_seq = int(header["seq"])
|
|
1355
|
+
logger.info(
|
|
1356
|
+
"RustRawBlockBackend: loaded on-device metadata checkpoint "
|
|
1357
|
+
"(entries=%d, next_slot=%d, seq=%d)",
|
|
1358
|
+
len(self._index),
|
|
1359
|
+
self._next_slot,
|
|
1360
|
+
self._meta_seq,
|
|
1361
|
+
)
|