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,1443 @@
|
|
|
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 concurrent.futures import Future, ThreadPoolExecutor
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any, Callable, List, Optional, Sequence, cast
|
|
11
|
+
import asyncio
|
|
12
|
+
import ctypes
|
|
13
|
+
import mmap
|
|
14
|
+
import os
|
|
15
|
+
import threading
|
|
16
|
+
|
|
17
|
+
# Third Party
|
|
18
|
+
import torch
|
|
19
|
+
|
|
20
|
+
# First Party
|
|
21
|
+
from lmcache.logging import init_logger
|
|
22
|
+
from lmcache.utils import CacheEngineKey, DiskCacheMetadata
|
|
23
|
+
from lmcache.v1.config import LMCacheEngineConfig
|
|
24
|
+
from lmcache.v1.memory_management import MemoryFormat, MemoryObj
|
|
25
|
+
from lmcache.v1.metadata import LMCacheMetadata
|
|
26
|
+
from lmcache.v1.storage_backend.abstract_backend import StoragePluginInterface
|
|
27
|
+
from lmcache.v1.storage_backend.local_cpu_backend import LocalCPUBackend
|
|
28
|
+
|
|
29
|
+
if torch.cuda.is_available():
|
|
30
|
+
# First Party
|
|
31
|
+
import lmcache.c_ops as lmc_ops
|
|
32
|
+
else:
|
|
33
|
+
# First Party
|
|
34
|
+
import lmcache.non_cuda_equivalents as lmc_ops
|
|
35
|
+
|
|
36
|
+
logger = init_logger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _to_bool(value: Any) -> bool:
|
|
40
|
+
if isinstance(value, bool):
|
|
41
|
+
return value
|
|
42
|
+
if value is None:
|
|
43
|
+
return False
|
|
44
|
+
return str(value).strip().lower() in {"true", "1", "yes", "on"}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class _Entry:
|
|
49
|
+
"""In-memory index entry for a stored chunk."""
|
|
50
|
+
|
|
51
|
+
offset: int
|
|
52
|
+
meta: DiskCacheMetadata
|
|
53
|
+
slot_id: int
|
|
54
|
+
generation: int
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class _Inflight:
|
|
59
|
+
"""In-progress put operation tracking."""
|
|
60
|
+
|
|
61
|
+
offset: int
|
|
62
|
+
meta: DiskCacheMetadata
|
|
63
|
+
slot_id: int
|
|
64
|
+
generation: int
|
|
65
|
+
canceled: bool = False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class _SlotState:
|
|
70
|
+
"""Slot state for a stored DAX chunk."""
|
|
71
|
+
|
|
72
|
+
generation: int
|
|
73
|
+
committed: bool = False
|
|
74
|
+
borrow_count: int = 0
|
|
75
|
+
pending_free: bool = False
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class _RestoreItem:
|
|
80
|
+
"""Reserved DAX read metadata for one item in a batched restore."""
|
|
81
|
+
|
|
82
|
+
result_index: int
|
|
83
|
+
key: CacheEngineKey
|
|
84
|
+
offset: int
|
|
85
|
+
size: int
|
|
86
|
+
shape: torch.Size
|
|
87
|
+
dtype: torch.dtype
|
|
88
|
+
fmt: MemoryFormat
|
|
89
|
+
cached_positions: Optional[torch.Tensor]
|
|
90
|
+
slot_id: int
|
|
91
|
+
generation: int
|
|
92
|
+
memory_obj: Optional[MemoryObj] = None
|
|
93
|
+
slab_offset: int = 0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class _RestoreSpan:
|
|
98
|
+
"""One contiguous source span copied from DAX into the staging slab."""
|
|
99
|
+
|
|
100
|
+
src_offset: int
|
|
101
|
+
slab_offset: int
|
|
102
|
+
size: int
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass
|
|
106
|
+
class _RestoreRegion:
|
|
107
|
+
"""One restore region executed by a persistent worker."""
|
|
108
|
+
|
|
109
|
+
region_index: int
|
|
110
|
+
slab_offset: int
|
|
111
|
+
total_bytes: int
|
|
112
|
+
items: list[_RestoreItem]
|
|
113
|
+
spans: list[_RestoreSpan]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass
|
|
117
|
+
class _RestoreWave:
|
|
118
|
+
"""One wave of region work against the fixed-size retrieve slab."""
|
|
119
|
+
|
|
120
|
+
regions: list[_RestoreRegion]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class DaxBackend(StoragePluginInterface):
|
|
124
|
+
"""Storage plugin backend for /dev/dax mmap-backed KV cache."""
|
|
125
|
+
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
config: Optional[LMCacheEngineConfig] = None,
|
|
129
|
+
metadata: Optional[LMCacheMetadata] = None,
|
|
130
|
+
local_cpu_backend: Optional[LocalCPUBackend] = None,
|
|
131
|
+
loop: Optional[asyncio.AbstractEventLoop] = None,
|
|
132
|
+
dst_device: str = "cpu",
|
|
133
|
+
) -> None:
|
|
134
|
+
"""Initialize a DAX-backed storage backend."""
|
|
135
|
+
super().__init__(
|
|
136
|
+
dst_device=dst_device,
|
|
137
|
+
config=config,
|
|
138
|
+
metadata=metadata,
|
|
139
|
+
local_cpu_backend=local_cpu_backend,
|
|
140
|
+
loop=loop,
|
|
141
|
+
)
|
|
142
|
+
if self.config is None:
|
|
143
|
+
raise ValueError("DaxBackend requires config")
|
|
144
|
+
if self.metadata is None:
|
|
145
|
+
raise ValueError("DaxBackend requires metadata")
|
|
146
|
+
|
|
147
|
+
if self.metadata.world_size != 1:
|
|
148
|
+
raise ValueError(
|
|
149
|
+
"DaxBackend currently only supports TP=1 "
|
|
150
|
+
f"(world_size={self.metadata.world_size})"
|
|
151
|
+
)
|
|
152
|
+
if self.metadata.get_num_groups() != 1:
|
|
153
|
+
raise ValueError(
|
|
154
|
+
"DaxBackend currently supports only single-group KV layout"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
extra = self.config.extra_config or {}
|
|
158
|
+
self.device_path = str(extra.get("dax.device_path", "")).strip()
|
|
159
|
+
if not self.device_path:
|
|
160
|
+
raise ValueError("extra_config['dax.device_path'] is required")
|
|
161
|
+
|
|
162
|
+
self.async_put = _to_bool(extra.get("dax.async_put", False))
|
|
163
|
+
if self.async_put and self.loop is None:
|
|
164
|
+
raise ValueError("DaxBackend async_put=true requires an asyncio event loop")
|
|
165
|
+
|
|
166
|
+
self.max_dax_size = float(extra.get("dax.max_dax_size", 0))
|
|
167
|
+
if self.max_dax_size <= 0:
|
|
168
|
+
raise ValueError("extra_config['dax.max_dax_size'] must be > 0")
|
|
169
|
+
|
|
170
|
+
if self.local_cpu_backend is None:
|
|
171
|
+
raise ValueError("DaxBackend requires local_cpu_backend")
|
|
172
|
+
|
|
173
|
+
# Total size in bytes of the mapped DAX arena.
|
|
174
|
+
self._arena_bytes = int(self.max_dax_size * 1024**3)
|
|
175
|
+
if self._arena_bytes <= 0:
|
|
176
|
+
raise ValueError("dax.max_dax_size results in zero-sized arena")
|
|
177
|
+
|
|
178
|
+
self._fd: Optional[int] = None
|
|
179
|
+
self._mmap_obj: Optional[mmap.mmap] = None
|
|
180
|
+
self._base_ptr: int = 0
|
|
181
|
+
# Python memoryview exposing the mapped arena for byte-level access.
|
|
182
|
+
self._arena_view: Optional[memoryview] = None
|
|
183
|
+
self._restore_executor: Optional[ThreadPoolExecutor] = None
|
|
184
|
+
self._restore_dispatch_executor: Optional[ThreadPoolExecutor] = None
|
|
185
|
+
self._retrieve_staging_slab_ptr: int = 0
|
|
186
|
+
self._retrieve_staging_slab_bytes: int = 0
|
|
187
|
+
self._restore_region_bytes: int = 0
|
|
188
|
+
self._restore_workers: int = 0
|
|
189
|
+
self._restore_max_regions: int = 0
|
|
190
|
+
self._open_arena()
|
|
191
|
+
try:
|
|
192
|
+
assert self.local_cpu_backend is not None
|
|
193
|
+
full_chunk_size = int(self.local_cpu_backend.get_full_chunk_size_bytes())
|
|
194
|
+
self.slot_bytes = max(1, int(full_chunk_size))
|
|
195
|
+
self._max_slots = self._arena_bytes // self.slot_bytes
|
|
196
|
+
if self._max_slots <= 0:
|
|
197
|
+
raise RuntimeError(
|
|
198
|
+
"dax.max_dax_size is too small for the configured chunk size"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
default_restore_workers = min(8, max(1, os.cpu_count() or 1))
|
|
202
|
+
self._restore_workers = self._get_positive_int_extra(
|
|
203
|
+
extra,
|
|
204
|
+
"dax.restore_workers",
|
|
205
|
+
default_restore_workers,
|
|
206
|
+
)
|
|
207
|
+
self._restore_max_regions = self._get_positive_int_extra(
|
|
208
|
+
extra,
|
|
209
|
+
"dax.restore_max_regions",
|
|
210
|
+
self._restore_workers,
|
|
211
|
+
)
|
|
212
|
+
default_staging_slab_bytes = max(
|
|
213
|
+
256 * 1024 * 1024,
|
|
214
|
+
self._restore_max_regions * self.slot_bytes,
|
|
215
|
+
)
|
|
216
|
+
self._retrieve_staging_slab_bytes = self._get_positive_int_extra(
|
|
217
|
+
extra,
|
|
218
|
+
"dax.retrieve_staging_slab_bytes",
|
|
219
|
+
default_staging_slab_bytes,
|
|
220
|
+
)
|
|
221
|
+
min_required_slab = self._restore_max_regions * self.slot_bytes
|
|
222
|
+
if self._retrieve_staging_slab_bytes < min_required_slab:
|
|
223
|
+
raise ValueError(
|
|
224
|
+
"extra_config['dax.retrieve_staging_slab_bytes'] must be at "
|
|
225
|
+
f"least {min_required_slab} bytes"
|
|
226
|
+
)
|
|
227
|
+
self._restore_region_bytes = (
|
|
228
|
+
self._retrieve_staging_slab_bytes // self._restore_max_regions
|
|
229
|
+
)
|
|
230
|
+
if self._restore_region_bytes < self.slot_bytes:
|
|
231
|
+
raise ValueError(
|
|
232
|
+
"dax.retrieve_staging_slab_bytes does not leave enough space "
|
|
233
|
+
"per restore region for one full chunk"
|
|
234
|
+
)
|
|
235
|
+
self._retrieve_staging_slab_ptr = int(
|
|
236
|
+
lmc_ops.alloc_pinned_ptr(self._retrieve_staging_slab_bytes, 0)
|
|
237
|
+
)
|
|
238
|
+
self._restore_executor = ThreadPoolExecutor(
|
|
239
|
+
max_workers=self._restore_workers,
|
|
240
|
+
thread_name_prefix="dax-restore",
|
|
241
|
+
)
|
|
242
|
+
self._restore_dispatch_executor = ThreadPoolExecutor(
|
|
243
|
+
max_workers=1,
|
|
244
|
+
thread_name_prefix="dax-restore-dispatch",
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
self._state_lock = threading.RLock()
|
|
248
|
+
self._state_condition = threading.Condition(self._state_lock)
|
|
249
|
+
|
|
250
|
+
self._index: dict[CacheEngineKey, _Entry] = {}
|
|
251
|
+
self._pin_counts: dict[CacheEngineKey, int] = {}
|
|
252
|
+
self._inflight: dict[CacheEngineKey, _Inflight] = {}
|
|
253
|
+
self._lru: "OrderedDict[CacheEngineKey, None]" = OrderedDict()
|
|
254
|
+
self._slot_states: dict[int, _SlotState] = {}
|
|
255
|
+
|
|
256
|
+
self._next_slot = 0
|
|
257
|
+
self._free_slots: set[int] = set()
|
|
258
|
+
self._active_ops = 0
|
|
259
|
+
self._active_puts = 0
|
|
260
|
+
self._closing = False
|
|
261
|
+
self._closed = False
|
|
262
|
+
|
|
263
|
+
logger.info(
|
|
264
|
+
"DaxBackend init: device=%s dax_size=%d slot=%d max_slots=%d "
|
|
265
|
+
"restore_workers=%d restore_regions=%d restore_slab=%d",
|
|
266
|
+
self.device_path,
|
|
267
|
+
self._arena_bytes,
|
|
268
|
+
self.slot_bytes,
|
|
269
|
+
self._max_slots,
|
|
270
|
+
self._restore_workers,
|
|
271
|
+
self._restore_max_regions,
|
|
272
|
+
self._retrieve_staging_slab_bytes,
|
|
273
|
+
)
|
|
274
|
+
except Exception:
|
|
275
|
+
fd, mmap_obj, arena_view = self._fd, self._mmap_obj, self._arena_view
|
|
276
|
+
self._fd = None
|
|
277
|
+
self._mmap_obj = None
|
|
278
|
+
self._base_ptr = 0
|
|
279
|
+
self._arena_view = None
|
|
280
|
+
self._release_restore_resources()
|
|
281
|
+
self._release_arena_resources(fd, mmap_obj, arena_view)
|
|
282
|
+
raise
|
|
283
|
+
|
|
284
|
+
def __str__(self) -> str:
|
|
285
|
+
return "DaxBackend"
|
|
286
|
+
|
|
287
|
+
def contains(self, key: CacheEngineKey, pin: bool = False) -> bool:
|
|
288
|
+
"""Check whether ``key`` exists in the backend.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
key: The cache key to look up.
|
|
292
|
+
pin: If ``True`` and the key is present, atomically
|
|
293
|
+
increment its pin count.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
``True`` if the key is present, ``False`` otherwise.
|
|
297
|
+
"""
|
|
298
|
+
with self._state_lock:
|
|
299
|
+
ok = key in self._index
|
|
300
|
+
if ok and pin:
|
|
301
|
+
self._pin_counts[key] = self._pin_counts.get(key, 0) + 1
|
|
302
|
+
return ok
|
|
303
|
+
|
|
304
|
+
def exists_in_put_tasks(self, key: CacheEngineKey) -> bool:
|
|
305
|
+
"""Check whether ``key`` is tracked as an in-flight put task.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
key: The cache key to check.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
``True`` if ``key`` is in the in-flight put task set.
|
|
312
|
+
"""
|
|
313
|
+
with self._state_lock:
|
|
314
|
+
return key in self._inflight
|
|
315
|
+
|
|
316
|
+
def pin(self, key: CacheEngineKey) -> bool:
|
|
317
|
+
"""Increment the pin count for ``key`` if it exists.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
key: The cache key to pin.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
``True`` if the key was found and pinned, ``False`` otherwise.
|
|
324
|
+
"""
|
|
325
|
+
with self._state_lock:
|
|
326
|
+
if key in self._index:
|
|
327
|
+
self._pin_counts[key] = self._pin_counts.get(key, 0) + 1
|
|
328
|
+
return True
|
|
329
|
+
return False
|
|
330
|
+
|
|
331
|
+
def unpin(self, key: CacheEngineKey) -> bool:
|
|
332
|
+
"""Decrement the pin count for ``key``.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
key: The cache key to unpin.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
``True`` if ``key`` is present in the backend after the
|
|
339
|
+
operation, ``False`` otherwise.
|
|
340
|
+
"""
|
|
341
|
+
with self._state_lock:
|
|
342
|
+
count = self._pin_counts.get(key, 0)
|
|
343
|
+
if count > 0:
|
|
344
|
+
if count == 1:
|
|
345
|
+
del self._pin_counts[key]
|
|
346
|
+
else:
|
|
347
|
+
self._pin_counts[key] = count - 1
|
|
348
|
+
return True
|
|
349
|
+
return key in self._index
|
|
350
|
+
|
|
351
|
+
def remove(self, key: CacheEngineKey, force: bool = True) -> bool:
|
|
352
|
+
"""Remove ``key`` from the backend if present.
|
|
353
|
+
|
|
354
|
+
If the key is in-flight, it is marked canceled. If it is
|
|
355
|
+
committed, its slot is scheduled for reclamation according to
|
|
356
|
+
the current slot state.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
key: The cache key to remove.
|
|
360
|
+
force: Unused; accepted for interface compatibility.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
``True`` if the key was present (committed or in-flight).
|
|
364
|
+
"""
|
|
365
|
+
del force
|
|
366
|
+
with self._state_lock:
|
|
367
|
+
existed = key in self._index or key in self._inflight
|
|
368
|
+
entry = self._index.pop(key, None)
|
|
369
|
+
inflight = self._inflight.get(key)
|
|
370
|
+
self._pin_counts.pop(key, None)
|
|
371
|
+
self._lru.pop(key, None)
|
|
372
|
+
if entry is not None:
|
|
373
|
+
self._schedule_slot_reclaim_locked(entry.slot_id, entry.generation)
|
|
374
|
+
if inflight is not None:
|
|
375
|
+
inflight.canceled = True
|
|
376
|
+
return existed
|
|
377
|
+
|
|
378
|
+
def batched_submit_put_task(
|
|
379
|
+
self,
|
|
380
|
+
keys: Sequence[CacheEngineKey],
|
|
381
|
+
objs: List[MemoryObj],
|
|
382
|
+
transfer_spec: Any = None,
|
|
383
|
+
on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
|
|
384
|
+
) -> Optional[List[Future]]:
|
|
385
|
+
"""Store a batch of memory objects in the DAX arena."""
|
|
386
|
+
del transfer_spec
|
|
387
|
+
if len(keys) != len(objs):
|
|
388
|
+
raise ValueError(
|
|
389
|
+
"keys and objs must have the same length, "
|
|
390
|
+
f"got {len(keys)} and {len(objs)}"
|
|
391
|
+
)
|
|
392
|
+
futures: List[Future] = []
|
|
393
|
+
|
|
394
|
+
for key, obj in zip(keys, objs, strict=True):
|
|
395
|
+
should_finish_put = False
|
|
396
|
+
try:
|
|
397
|
+
# Multi-tensor objects are not yet supported.
|
|
398
|
+
num_shapes = len(obj.get_shapes())
|
|
399
|
+
if num_shapes > 1:
|
|
400
|
+
logger.error(
|
|
401
|
+
"DaxBackend does not support multi-tensor allocations: "
|
|
402
|
+
"key=%s has %d tensors. "
|
|
403
|
+
"Use single-tensor format or extend metadata.",
|
|
404
|
+
key,
|
|
405
|
+
num_shapes,
|
|
406
|
+
)
|
|
407
|
+
continue
|
|
408
|
+
size = int(obj.get_size())
|
|
409
|
+
obj_metadata = obj.metadata
|
|
410
|
+
shape = obj_metadata.shape
|
|
411
|
+
dtype = obj_metadata.dtype
|
|
412
|
+
cached_positions = obj_metadata.cached_positions
|
|
413
|
+
fmt = obj_metadata.fmt
|
|
414
|
+
|
|
415
|
+
with self._state_lock:
|
|
416
|
+
if self._closing:
|
|
417
|
+
raise RuntimeError("DaxBackend is closing")
|
|
418
|
+
if key in self._index or key in self._inflight:
|
|
419
|
+
continue
|
|
420
|
+
|
|
421
|
+
if size > self.slot_bytes:
|
|
422
|
+
raise ValueError(
|
|
423
|
+
f"DaxBackend: object size {size} for key {key} "
|
|
424
|
+
f"exceeds slot size {self.slot_bytes}"
|
|
425
|
+
)
|
|
426
|
+
while True:
|
|
427
|
+
try:
|
|
428
|
+
slot_id = self._allocate_slot_locked()
|
|
429
|
+
break
|
|
430
|
+
except RuntimeError:
|
|
431
|
+
if not self._evict_one_locked():
|
|
432
|
+
raise
|
|
433
|
+
offset = slot_id * self.slot_bytes
|
|
434
|
+
generation = self._reserve_slot_state_locked(slot_id)
|
|
435
|
+
|
|
436
|
+
meta = DiskCacheMetadata(
|
|
437
|
+
path=f"{self.device_path}@{offset}",
|
|
438
|
+
size=size,
|
|
439
|
+
shape=shape,
|
|
440
|
+
dtype=dtype,
|
|
441
|
+
cached_positions=cached_positions,
|
|
442
|
+
fmt=fmt,
|
|
443
|
+
pin_count=0,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
self._inflight[key] = _Inflight(
|
|
447
|
+
offset=offset,
|
|
448
|
+
meta=meta,
|
|
449
|
+
slot_id=slot_id,
|
|
450
|
+
generation=generation,
|
|
451
|
+
canceled=False,
|
|
452
|
+
)
|
|
453
|
+
self._active_puts += 1
|
|
454
|
+
should_finish_put = True
|
|
455
|
+
|
|
456
|
+
if self.async_put and self.loop is not None and self.loop.is_running():
|
|
457
|
+
obj.ref_count_up()
|
|
458
|
+
try:
|
|
459
|
+
fut = asyncio.run_coroutine_threadsafe(
|
|
460
|
+
self._submit_write(
|
|
461
|
+
key=key,
|
|
462
|
+
offset=offset,
|
|
463
|
+
size=size,
|
|
464
|
+
memory_obj=obj,
|
|
465
|
+
on_complete_callback=on_complete_callback,
|
|
466
|
+
),
|
|
467
|
+
self.loop,
|
|
468
|
+
)
|
|
469
|
+
except Exception:
|
|
470
|
+
with self._state_lock:
|
|
471
|
+
self._finalize_inflight_locked(key, write_failed=True)
|
|
472
|
+
obj.ref_count_down()
|
|
473
|
+
raise
|
|
474
|
+
futures.append(fut)
|
|
475
|
+
should_finish_put = False
|
|
476
|
+
continue
|
|
477
|
+
|
|
478
|
+
try:
|
|
479
|
+
self._do_write(offset, obj, size)
|
|
480
|
+
except Exception as e:
|
|
481
|
+
with self._state_lock:
|
|
482
|
+
self._finalize_inflight_locked(key, write_failed=True)
|
|
483
|
+
raise RuntimeError(
|
|
484
|
+
f"DaxBackend write failed for key {key}: {e}"
|
|
485
|
+
) from e
|
|
486
|
+
|
|
487
|
+
with self._state_lock:
|
|
488
|
+
should_invoke_callback = self._finalize_inflight_locked(
|
|
489
|
+
key,
|
|
490
|
+
write_failed=False,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
if should_invoke_callback:
|
|
494
|
+
self._invoke_on_complete_callback(key, on_complete_callback)
|
|
495
|
+
finally:
|
|
496
|
+
if should_finish_put:
|
|
497
|
+
with self._state_lock:
|
|
498
|
+
if self._active_puts > 0:
|
|
499
|
+
self._active_puts -= 1
|
|
500
|
+
else:
|
|
501
|
+
logger.warning(
|
|
502
|
+
"DaxBackend active put count underflow for key %s", key
|
|
503
|
+
)
|
|
504
|
+
self._state_condition.notify_all()
|
|
505
|
+
|
|
506
|
+
return futures or None
|
|
507
|
+
|
|
508
|
+
def get_blocking(self, key: CacheEngineKey) -> Optional[MemoryObj]:
|
|
509
|
+
"""Return the memory object for a key, or ``None`` if unavailable."""
|
|
510
|
+
with self._state_lock:
|
|
511
|
+
if self._closing:
|
|
512
|
+
return None
|
|
513
|
+
entry = self._index.get(key)
|
|
514
|
+
if entry is None:
|
|
515
|
+
return None
|
|
516
|
+
meta = entry.meta
|
|
517
|
+
shape = meta.shape
|
|
518
|
+
dtype = meta.dtype
|
|
519
|
+
fmt = meta.fmt
|
|
520
|
+
if shape is None or dtype is None or fmt is None:
|
|
521
|
+
return None
|
|
522
|
+
state = self._slot_states.get(entry.slot_id)
|
|
523
|
+
if (
|
|
524
|
+
state is None
|
|
525
|
+
or state.generation != entry.generation
|
|
526
|
+
or not state.committed
|
|
527
|
+
):
|
|
528
|
+
return None
|
|
529
|
+
state.borrow_count += 1
|
|
530
|
+
self._active_ops += 1
|
|
531
|
+
offset, size = entry.offset, int(meta.size)
|
|
532
|
+
cached_positions = meta.cached_positions
|
|
533
|
+
slot_id, generation = entry.slot_id, entry.generation
|
|
534
|
+
|
|
535
|
+
assert self.local_cpu_backend is not None
|
|
536
|
+
memory_obj: Optional[MemoryObj] = None
|
|
537
|
+
read_ok = False
|
|
538
|
+
try:
|
|
539
|
+
memory_obj = self.local_cpu_backend.allocate(shape, dtype, fmt)
|
|
540
|
+
if memory_obj is None:
|
|
541
|
+
return None
|
|
542
|
+
self._do_read(offset, memory_obj, size)
|
|
543
|
+
memory_obj.metadata.cached_positions = cached_positions
|
|
544
|
+
read_ok = True
|
|
545
|
+
return memory_obj
|
|
546
|
+
except Exception:
|
|
547
|
+
if memory_obj is not None:
|
|
548
|
+
memory_obj.ref_count_down()
|
|
549
|
+
raise
|
|
550
|
+
finally:
|
|
551
|
+
with self._state_lock:
|
|
552
|
+
if self._active_ops > 0:
|
|
553
|
+
self._active_ops -= 1
|
|
554
|
+
state = self._slot_states.get(slot_id)
|
|
555
|
+
if state is not None and state.generation == generation:
|
|
556
|
+
if state.borrow_count > 0:
|
|
557
|
+
state.borrow_count -= 1
|
|
558
|
+
if read_ok:
|
|
559
|
+
current = self._index.get(key)
|
|
560
|
+
if (
|
|
561
|
+
current is not None
|
|
562
|
+
and current.slot_id == slot_id
|
|
563
|
+
and current.generation == generation
|
|
564
|
+
):
|
|
565
|
+
self._touch_locked(key)
|
|
566
|
+
if state.pending_free and state.borrow_count == 0:
|
|
567
|
+
state.pending_free = False
|
|
568
|
+
self._free_slot_locked(slot_id)
|
|
569
|
+
self._state_condition.notify_all()
|
|
570
|
+
|
|
571
|
+
async def batched_async_contains(
|
|
572
|
+
self,
|
|
573
|
+
lookup_id: str,
|
|
574
|
+
keys: list[CacheEngineKey],
|
|
575
|
+
pin: bool = False,
|
|
576
|
+
) -> int:
|
|
577
|
+
"""Return the number of consecutive keys present in the index.
|
|
578
|
+
|
|
579
|
+
Iterates ``keys`` in order and stops at the first miss.
|
|
580
|
+
|
|
581
|
+
Args:
|
|
582
|
+
lookup_id: Caller-supplied identifier (not used by this backend).
|
|
583
|
+
keys: Ordered list of cache keys to check.
|
|
584
|
+
pin: If ``True``, pin each found key.
|
|
585
|
+
|
|
586
|
+
Returns:
|
|
587
|
+
The count of consecutive hits from the start of ``keys``.
|
|
588
|
+
"""
|
|
589
|
+
del lookup_id
|
|
590
|
+
hit = 0
|
|
591
|
+
with self._state_lock:
|
|
592
|
+
for key in keys:
|
|
593
|
+
if key not in self._index:
|
|
594
|
+
break
|
|
595
|
+
if pin:
|
|
596
|
+
self._pin_counts[key] = self._pin_counts.get(key, 0) + 1
|
|
597
|
+
hit += 1
|
|
598
|
+
return hit
|
|
599
|
+
|
|
600
|
+
async def batched_get_non_blocking(
|
|
601
|
+
self,
|
|
602
|
+
lookup_id: str,
|
|
603
|
+
keys: list[CacheEngineKey],
|
|
604
|
+
transfer_spec: Any = None,
|
|
605
|
+
) -> list[MemoryObj]:
|
|
606
|
+
"""Retrieve memory objects for consecutive keys asynchronously.
|
|
607
|
+
|
|
608
|
+
Schedules one batched restore job on the persistent dispatch
|
|
609
|
+
executor and returns only the consecutive hit prefix. Stops at the
|
|
610
|
+
first key that is not found or is no longer readable.
|
|
611
|
+
|
|
612
|
+
Args:
|
|
613
|
+
lookup_id: Caller-supplied identifier (not used by this backend).
|
|
614
|
+
keys: Ordered list of cache keys to retrieve.
|
|
615
|
+
transfer_spec: Transfer hint (not used by this backend).
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
A list of ``MemoryObj`` instances for the consecutive hits.
|
|
619
|
+
"""
|
|
620
|
+
del lookup_id, transfer_spec
|
|
621
|
+
if not keys:
|
|
622
|
+
return []
|
|
623
|
+
|
|
624
|
+
dispatch_executor = self._restore_dispatch_executor
|
|
625
|
+
if dispatch_executor is None:
|
|
626
|
+
raise RuntimeError("DaxBackend restore dispatch executor is not available")
|
|
627
|
+
|
|
628
|
+
loop = asyncio.get_running_loop()
|
|
629
|
+
result = await loop.run_in_executor(
|
|
630
|
+
dispatch_executor,
|
|
631
|
+
self._restore_batch,
|
|
632
|
+
list(keys),
|
|
633
|
+
True,
|
|
634
|
+
)
|
|
635
|
+
return cast(list[MemoryObj], result)
|
|
636
|
+
|
|
637
|
+
def batched_get_blocking(
|
|
638
|
+
self,
|
|
639
|
+
keys: List[CacheEngineKey],
|
|
640
|
+
) -> List[Optional[MemoryObj]]:
|
|
641
|
+
"""Restore a batch of DAX-backed cache entries synchronously.
|
|
642
|
+
|
|
643
|
+
The returned list preserves the input order. Entries that are missing
|
|
644
|
+
or no longer readable remain ``None`` so callers keep positional
|
|
645
|
+
alignment with ``keys``.
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
keys: Ordered cache keys to restore from the DAX arena.
|
|
649
|
+
|
|
650
|
+
Returns:
|
|
651
|
+
A list aligned with ``keys`` containing restored ``MemoryObj``
|
|
652
|
+
instances or ``None`` for entries that could not be read.
|
|
653
|
+
"""
|
|
654
|
+
if not keys:
|
|
655
|
+
return []
|
|
656
|
+
|
|
657
|
+
dispatch_executor = self._restore_dispatch_executor
|
|
658
|
+
if dispatch_executor is None:
|
|
659
|
+
raise RuntimeError("DaxBackend restore dispatch executor is not available")
|
|
660
|
+
|
|
661
|
+
batch_keys = list(keys)
|
|
662
|
+
if threading.current_thread().name.startswith("dax-restore-dispatch"):
|
|
663
|
+
return cast(
|
|
664
|
+
List[Optional[MemoryObj]],
|
|
665
|
+
self._restore_batch(batch_keys, False),
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
future = dispatch_executor.submit(
|
|
669
|
+
self._restore_batch,
|
|
670
|
+
batch_keys,
|
|
671
|
+
False,
|
|
672
|
+
)
|
|
673
|
+
return cast(List[Optional[MemoryObj]], future.result())
|
|
674
|
+
|
|
675
|
+
def batched_contains(
|
|
676
|
+
self,
|
|
677
|
+
keys: List[CacheEngineKey],
|
|
678
|
+
pin: bool = False,
|
|
679
|
+
) -> int:
|
|
680
|
+
"""Return the number of consecutive keys present in the index.
|
|
681
|
+
|
|
682
|
+
Synchronous variant of :meth:`batched_async_contains`.
|
|
683
|
+
|
|
684
|
+
Args:
|
|
685
|
+
keys: Ordered list of cache keys to check.
|
|
686
|
+
pin: If ``True``, pin each found key.
|
|
687
|
+
|
|
688
|
+
Returns:
|
|
689
|
+
The count of consecutive hits from the start of ``keys``.
|
|
690
|
+
"""
|
|
691
|
+
hit = 0
|
|
692
|
+
with self._state_lock:
|
|
693
|
+
for key in keys:
|
|
694
|
+
if key not in self._index:
|
|
695
|
+
break
|
|
696
|
+
if pin:
|
|
697
|
+
self._pin_counts[key] = self._pin_counts.get(key, 0) + 1
|
|
698
|
+
hit += 1
|
|
699
|
+
return hit
|
|
700
|
+
|
|
701
|
+
def batched_remove(
|
|
702
|
+
self,
|
|
703
|
+
keys: list[CacheEngineKey],
|
|
704
|
+
force: bool = True,
|
|
705
|
+
) -> int:
|
|
706
|
+
"""Remove multiple keys from the backend.
|
|
707
|
+
|
|
708
|
+
Args:
|
|
709
|
+
keys: The cache keys to remove.
|
|
710
|
+
force: Passed through to :meth:`remove`.
|
|
711
|
+
|
|
712
|
+
Returns:
|
|
713
|
+
The number of keys that were actually present and removed.
|
|
714
|
+
"""
|
|
715
|
+
removed = 0
|
|
716
|
+
for key in keys:
|
|
717
|
+
removed += int(self.remove(key, force=force))
|
|
718
|
+
return removed
|
|
719
|
+
|
|
720
|
+
def get_allocator_backend(self) -> LocalCPUBackend:
|
|
721
|
+
"""Return the CPU allocator backend used for read buffers.
|
|
722
|
+
|
|
723
|
+
Raises:
|
|
724
|
+
RuntimeError: If no ``local_cpu_backend`` is available.
|
|
725
|
+
"""
|
|
726
|
+
if self.local_cpu_backend is None:
|
|
727
|
+
raise RuntimeError("DaxBackend has no allocator backend available")
|
|
728
|
+
return self.local_cpu_backend
|
|
729
|
+
|
|
730
|
+
def close(self) -> None:
|
|
731
|
+
"""Quiesce outstanding operations and release the mapped DAX arena."""
|
|
732
|
+
restore_executor = None
|
|
733
|
+
restore_dispatch_executor = None
|
|
734
|
+
staging_slab_ptr = 0
|
|
735
|
+
with self._state_lock:
|
|
736
|
+
if self._closed:
|
|
737
|
+
return
|
|
738
|
+
self._closing = True
|
|
739
|
+
while self._active_puts > 0 or self._active_ops > 0:
|
|
740
|
+
if not self._state_condition.wait(timeout=30.0):
|
|
741
|
+
logger.warning(
|
|
742
|
+
"DaxBackend close: still waiting for %d puts, %d ops",
|
|
743
|
+
self._active_puts,
|
|
744
|
+
self._active_ops,
|
|
745
|
+
)
|
|
746
|
+
if self._closed:
|
|
747
|
+
return
|
|
748
|
+
self._closed = True
|
|
749
|
+
restore_executor = self._restore_executor
|
|
750
|
+
restore_dispatch_executor = self._restore_dispatch_executor
|
|
751
|
+
staging_slab_ptr = self._retrieve_staging_slab_ptr
|
|
752
|
+
self._restore_executor = None
|
|
753
|
+
self._restore_dispatch_executor = None
|
|
754
|
+
self._retrieve_staging_slab_ptr = 0
|
|
755
|
+
self._retrieve_staging_slab_bytes = 0
|
|
756
|
+
self._restore_region_bytes = 0
|
|
757
|
+
self._index.clear()
|
|
758
|
+
self._inflight.clear()
|
|
759
|
+
self._lru.clear()
|
|
760
|
+
self._pin_counts.clear()
|
|
761
|
+
self._slot_states.clear()
|
|
762
|
+
self._free_slots.clear()
|
|
763
|
+
fd = self._fd
|
|
764
|
+
mmap_obj = self._mmap_obj
|
|
765
|
+
arena_view = self._arena_view
|
|
766
|
+
self._fd = None
|
|
767
|
+
self._mmap_obj = None
|
|
768
|
+
self._base_ptr = 0
|
|
769
|
+
self._arena_view = None
|
|
770
|
+
|
|
771
|
+
if restore_dispatch_executor is not None:
|
|
772
|
+
restore_dispatch_executor.shutdown(wait=True)
|
|
773
|
+
if restore_executor is not None:
|
|
774
|
+
restore_executor.shutdown(wait=True)
|
|
775
|
+
self._release_restore_resources(
|
|
776
|
+
restore_slab_ptr=staging_slab_ptr,
|
|
777
|
+
)
|
|
778
|
+
self._release_arena_resources(fd, mmap_obj, arena_view)
|
|
779
|
+
|
|
780
|
+
# ------------------------------------------------------------------
|
|
781
|
+
# Private / helper methods
|
|
782
|
+
# ------------------------------------------------------------------
|
|
783
|
+
|
|
784
|
+
@staticmethod
|
|
785
|
+
def _get_positive_int_extra(
|
|
786
|
+
extra_config: dict[str, Any],
|
|
787
|
+
key: str,
|
|
788
|
+
default: int,
|
|
789
|
+
) -> int:
|
|
790
|
+
value = extra_config.get(key, default)
|
|
791
|
+
try:
|
|
792
|
+
parsed = int(value)
|
|
793
|
+
except (TypeError, ValueError) as e:
|
|
794
|
+
raise ValueError(f"extra_config['{key}'] must be a positive integer") from e
|
|
795
|
+
if parsed <= 0:
|
|
796
|
+
raise ValueError(f"extra_config['{key}'] must be a positive integer")
|
|
797
|
+
return parsed
|
|
798
|
+
|
|
799
|
+
def _release_restore_resources(
|
|
800
|
+
self,
|
|
801
|
+
restore_slab_ptr: Optional[int] = None,
|
|
802
|
+
) -> None:
|
|
803
|
+
"""Shut down restore workers and free the pinned retrieve slab.
|
|
804
|
+
|
|
805
|
+
Args:
|
|
806
|
+
restore_slab_ptr: Optional explicit slab pointer to free. When not
|
|
807
|
+
provided, the backend releases its current staging slab and
|
|
808
|
+
clears the associated bookkeeping fields.
|
|
809
|
+
"""
|
|
810
|
+
dispatch_executor = self._restore_dispatch_executor
|
|
811
|
+
if dispatch_executor is not None:
|
|
812
|
+
dispatch_executor.shutdown(wait=True)
|
|
813
|
+
self._restore_dispatch_executor = None
|
|
814
|
+
|
|
815
|
+
restore_executor = self._restore_executor
|
|
816
|
+
if restore_executor is not None:
|
|
817
|
+
restore_executor.shutdown(wait=True)
|
|
818
|
+
self._restore_executor = None
|
|
819
|
+
|
|
820
|
+
ptr = (
|
|
821
|
+
self._retrieve_staging_slab_ptr
|
|
822
|
+
if restore_slab_ptr is None
|
|
823
|
+
else restore_slab_ptr
|
|
824
|
+
)
|
|
825
|
+
if ptr:
|
|
826
|
+
try:
|
|
827
|
+
lmc_ops.free_pinned_ptr(ptr)
|
|
828
|
+
except Exception as e:
|
|
829
|
+
logger.warning("Failed to free DAX retrieve slab: %s", e)
|
|
830
|
+
|
|
831
|
+
if restore_slab_ptr is None:
|
|
832
|
+
self._retrieve_staging_slab_ptr = 0
|
|
833
|
+
self._retrieve_staging_slab_bytes = 0
|
|
834
|
+
self._restore_region_bytes = 0
|
|
835
|
+
|
|
836
|
+
@staticmethod
|
|
837
|
+
def _release_arena_resources(
|
|
838
|
+
fd: Optional[int],
|
|
839
|
+
mmap_obj: Optional[mmap.mmap],
|
|
840
|
+
arena_view: Optional[memoryview],
|
|
841
|
+
) -> None:
|
|
842
|
+
"""Release the mapped DAX arena resources in close order.
|
|
843
|
+
|
|
844
|
+
Args:
|
|
845
|
+
fd: File descriptor for the DAX device.
|
|
846
|
+
mmap_obj: Mmap object backing the arena mapping.
|
|
847
|
+
arena_view: Memoryview exported from the mmap.
|
|
848
|
+
"""
|
|
849
|
+
if arena_view is not None:
|
|
850
|
+
try:
|
|
851
|
+
arena_view.release()
|
|
852
|
+
except Exception as e:
|
|
853
|
+
logger.warning("Failed to release DAX memoryview: %s", e)
|
|
854
|
+
|
|
855
|
+
if mmap_obj is not None:
|
|
856
|
+
try:
|
|
857
|
+
mmap_obj.close()
|
|
858
|
+
except Exception as e:
|
|
859
|
+
logger.warning("Failed to close DAX mmap: %s", e)
|
|
860
|
+
|
|
861
|
+
if fd is not None:
|
|
862
|
+
try:
|
|
863
|
+
os.close(fd)
|
|
864
|
+
except Exception as e:
|
|
865
|
+
logger.warning("Failed to close DAX fd: %s", e)
|
|
866
|
+
|
|
867
|
+
def _open_arena(self) -> None:
|
|
868
|
+
fd: Optional[int] = None
|
|
869
|
+
mmap_obj: Optional[mmap.mmap] = None
|
|
870
|
+
arena_view: Optional[memoryview] = None
|
|
871
|
+
try:
|
|
872
|
+
fd = os.open(self.device_path, os.O_RDWR)
|
|
873
|
+
except OSError as e:
|
|
874
|
+
raise RuntimeError(
|
|
875
|
+
f"Failed to open dax device {self.device_path}: {e}"
|
|
876
|
+
) from e
|
|
877
|
+
try:
|
|
878
|
+
try:
|
|
879
|
+
capacity_bytes = os.fstat(fd).st_size
|
|
880
|
+
if capacity_bytes > 0 and self._arena_bytes > capacity_bytes:
|
|
881
|
+
raise RuntimeError(
|
|
882
|
+
f"dax.max_dax_size ({self._arena_bytes} bytes) exceeds "
|
|
883
|
+
f"device capacity ({capacity_bytes} bytes)"
|
|
884
|
+
)
|
|
885
|
+
except OSError:
|
|
886
|
+
# Some dax devices may not report size via fstat.
|
|
887
|
+
logger.warning(
|
|
888
|
+
"Could not determine DAX device capacity via fstat; "
|
|
889
|
+
"skipping dax.max_dax_size validation"
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
mmap_obj = mmap.mmap(
|
|
893
|
+
fd,
|
|
894
|
+
self._arena_bytes,
|
|
895
|
+
flags=mmap.MAP_SHARED,
|
|
896
|
+
prot=mmap.PROT_READ | mmap.PROT_WRITE,
|
|
897
|
+
)
|
|
898
|
+
base_ptr = ctypes.addressof(ctypes.c_char.from_buffer(mmap_obj))
|
|
899
|
+
arena_view = memoryview(mmap_obj)
|
|
900
|
+
self._fd = fd
|
|
901
|
+
self._mmap_obj = mmap_obj
|
|
902
|
+
self._base_ptr = base_ptr
|
|
903
|
+
self._arena_view = arena_view
|
|
904
|
+
except Exception as e:
|
|
905
|
+
DaxBackend._release_arena_resources(fd, mmap_obj, arena_view)
|
|
906
|
+
if isinstance(e, RuntimeError):
|
|
907
|
+
raise
|
|
908
|
+
raise RuntimeError(
|
|
909
|
+
f"Failed to mmap dax arena ({self._arena_bytes} bytes) from "
|
|
910
|
+
f"{self.device_path}: {e}"
|
|
911
|
+
) from e
|
|
912
|
+
|
|
913
|
+
def _reserve_slot_state_locked(self, slot_id: int) -> int:
|
|
914
|
+
existing = self._slot_states.get(slot_id)
|
|
915
|
+
new_gen = (existing.generation if existing is not None else 0) + 1
|
|
916
|
+
self._slot_states[slot_id] = _SlotState(generation=new_gen)
|
|
917
|
+
return new_gen
|
|
918
|
+
|
|
919
|
+
def _mark_slot_committed_locked(self, slot_id: int, generation: int) -> None:
|
|
920
|
+
state = self._slot_states.get(slot_id)
|
|
921
|
+
if state is None or state.generation != generation:
|
|
922
|
+
return
|
|
923
|
+
state.committed = True
|
|
924
|
+
state.pending_free = False
|
|
925
|
+
|
|
926
|
+
def _schedule_slot_reclaim_locked(self, slot_id: int, generation: int) -> None:
|
|
927
|
+
"""Mark a slot uncommitted and free it immediately or defer if borrowed."""
|
|
928
|
+
state = self._slot_states.get(slot_id)
|
|
929
|
+
if state is None or state.generation != generation:
|
|
930
|
+
return
|
|
931
|
+
state.committed = False
|
|
932
|
+
if state.borrow_count == 0:
|
|
933
|
+
state.pending_free = False
|
|
934
|
+
self._free_slot_locked(slot_id)
|
|
935
|
+
else:
|
|
936
|
+
state.pending_free = True
|
|
937
|
+
|
|
938
|
+
def _finalize_inflight_locked(
|
|
939
|
+
self,
|
|
940
|
+
key: CacheEngineKey,
|
|
941
|
+
write_failed: bool,
|
|
942
|
+
) -> bool:
|
|
943
|
+
"""Resolve an in-flight put: commit on success, reclaim on failure."""
|
|
944
|
+
inflight = self._inflight.pop(key, None)
|
|
945
|
+
if inflight is None:
|
|
946
|
+
return False
|
|
947
|
+
if inflight.canceled or write_failed:
|
|
948
|
+
self._schedule_slot_reclaim_locked(inflight.slot_id, inflight.generation)
|
|
949
|
+
return False
|
|
950
|
+
self._mark_slot_committed_locked(inflight.slot_id, inflight.generation)
|
|
951
|
+
self._index[key] = _Entry(
|
|
952
|
+
offset=inflight.offset,
|
|
953
|
+
meta=inflight.meta,
|
|
954
|
+
slot_id=inflight.slot_id,
|
|
955
|
+
generation=inflight.generation,
|
|
956
|
+
)
|
|
957
|
+
self._touch_locked(key)
|
|
958
|
+
return True
|
|
959
|
+
|
|
960
|
+
def _invoke_on_complete_callback(
|
|
961
|
+
self,
|
|
962
|
+
key: CacheEngineKey,
|
|
963
|
+
on_complete_callback: Optional[Callable[[CacheEngineKey], None]],
|
|
964
|
+
) -> None:
|
|
965
|
+
if on_complete_callback is None:
|
|
966
|
+
return
|
|
967
|
+
try:
|
|
968
|
+
on_complete_callback(key)
|
|
969
|
+
except Exception as e:
|
|
970
|
+
logger.warning("on_complete_callback failed for key %s: %s", key, e)
|
|
971
|
+
|
|
972
|
+
def _allocate_slot_locked(self) -> int:
|
|
973
|
+
if self._free_slots:
|
|
974
|
+
return self._free_slots.pop()
|
|
975
|
+
if self._next_slot < self._max_slots:
|
|
976
|
+
slot = self._next_slot
|
|
977
|
+
self._next_slot += 1
|
|
978
|
+
return slot
|
|
979
|
+
raise RuntimeError("No free slots available; eviction required")
|
|
980
|
+
|
|
981
|
+
def _free_slot_locked(self, slot_id: int) -> None:
|
|
982
|
+
if slot_id < 0:
|
|
983
|
+
return
|
|
984
|
+
self._free_slots.add(slot_id)
|
|
985
|
+
|
|
986
|
+
def _touch_locked(self, key: CacheEngineKey) -> None:
|
|
987
|
+
self._lru.pop(key, None)
|
|
988
|
+
self._lru[key] = None
|
|
989
|
+
|
|
990
|
+
def _evict_one_locked(self) -> bool:
|
|
991
|
+
for victim in list(self._lru.keys()):
|
|
992
|
+
if self._pin_counts.get(victim, 0) > 0 or victim in self._inflight:
|
|
993
|
+
continue
|
|
994
|
+
entry = self._index.get(victim)
|
|
995
|
+
if entry is None:
|
|
996
|
+
continue
|
|
997
|
+
state = self._slot_states.get(entry.slot_id)
|
|
998
|
+
if (
|
|
999
|
+
state is None
|
|
1000
|
+
or state.generation != entry.generation
|
|
1001
|
+
or state.borrow_count > 0
|
|
1002
|
+
):
|
|
1003
|
+
continue
|
|
1004
|
+
self._index.pop(victim, None)
|
|
1005
|
+
self._lru.pop(victim, None)
|
|
1006
|
+
self._pin_counts.pop(victim, None)
|
|
1007
|
+
self._schedule_slot_reclaim_locked(entry.slot_id, entry.generation)
|
|
1008
|
+
return True
|
|
1009
|
+
return False
|
|
1010
|
+
|
|
1011
|
+
def _reserve_restore_items(
|
|
1012
|
+
self,
|
|
1013
|
+
keys: Sequence[CacheEngineKey],
|
|
1014
|
+
*,
|
|
1015
|
+
prefix_only: bool,
|
|
1016
|
+
) -> tuple[list[_RestoreItem], list[Optional[MemoryObj]]]:
|
|
1017
|
+
"""Reserve readable entries and build the aligned result list.
|
|
1018
|
+
|
|
1019
|
+
Args:
|
|
1020
|
+
keys: Ordered keys requested by the caller.
|
|
1021
|
+
prefix_only: When ``True``, stop at the first miss or unreadable
|
|
1022
|
+
entry so async-prefetch keeps prefix-hit semantics.
|
|
1023
|
+
|
|
1024
|
+
Returns:
|
|
1025
|
+
A tuple containing the reserved restore items and a result list
|
|
1026
|
+
aligned with ``keys`` that is prefilled with ``None`` placeholders.
|
|
1027
|
+
"""
|
|
1028
|
+
results: list[Optional[MemoryObj]] = [None] * len(keys)
|
|
1029
|
+
reserved: list[_RestoreItem] = []
|
|
1030
|
+
|
|
1031
|
+
with self._state_lock:
|
|
1032
|
+
if self._closing:
|
|
1033
|
+
return reserved, results
|
|
1034
|
+
|
|
1035
|
+
for result_index, key in enumerate(keys):
|
|
1036
|
+
entry = self._index.get(key)
|
|
1037
|
+
if entry is None:
|
|
1038
|
+
if prefix_only:
|
|
1039
|
+
break
|
|
1040
|
+
continue
|
|
1041
|
+
|
|
1042
|
+
meta = entry.meta
|
|
1043
|
+
shape = meta.shape
|
|
1044
|
+
dtype = meta.dtype
|
|
1045
|
+
fmt = meta.fmt
|
|
1046
|
+
if shape is None or dtype is None or fmt is None:
|
|
1047
|
+
if prefix_only:
|
|
1048
|
+
break
|
|
1049
|
+
continue
|
|
1050
|
+
|
|
1051
|
+
state = self._slot_states.get(entry.slot_id)
|
|
1052
|
+
if (
|
|
1053
|
+
state is None
|
|
1054
|
+
or state.generation != entry.generation
|
|
1055
|
+
or not state.committed
|
|
1056
|
+
):
|
|
1057
|
+
if prefix_only:
|
|
1058
|
+
break
|
|
1059
|
+
continue
|
|
1060
|
+
|
|
1061
|
+
state.borrow_count += 1
|
|
1062
|
+
reserved.append(
|
|
1063
|
+
_RestoreItem(
|
|
1064
|
+
result_index=result_index,
|
|
1065
|
+
key=key,
|
|
1066
|
+
offset=entry.offset,
|
|
1067
|
+
size=int(meta.size),
|
|
1068
|
+
shape=shape,
|
|
1069
|
+
dtype=dtype,
|
|
1070
|
+
fmt=fmt,
|
|
1071
|
+
cached_positions=meta.cached_positions,
|
|
1072
|
+
slot_id=entry.slot_id,
|
|
1073
|
+
generation=entry.generation,
|
|
1074
|
+
)
|
|
1075
|
+
)
|
|
1076
|
+
|
|
1077
|
+
if reserved:
|
|
1078
|
+
self._active_ops += 1
|
|
1079
|
+
|
|
1080
|
+
return reserved, results
|
|
1081
|
+
|
|
1082
|
+
def _allocate_restore_outputs(self, reserved: Sequence[_RestoreItem]) -> None:
|
|
1083
|
+
"""Allocate CPU restore buffers for the reserved DAX items.
|
|
1084
|
+
|
|
1085
|
+
Args:
|
|
1086
|
+
reserved: Restore items whose output ``MemoryObj`` fields will be
|
|
1087
|
+
populated in-place.
|
|
1088
|
+
|
|
1089
|
+
Raises:
|
|
1090
|
+
RuntimeError: If the local CPU allocator cannot provide enough
|
|
1091
|
+
output buffers for the batch.
|
|
1092
|
+
"""
|
|
1093
|
+
assert self.local_cpu_backend is not None
|
|
1094
|
+
|
|
1095
|
+
grouped_items: OrderedDict[
|
|
1096
|
+
tuple[tuple[int, ...], torch.dtype, MemoryFormat], list[_RestoreItem]
|
|
1097
|
+
] = OrderedDict()
|
|
1098
|
+
for item in reserved:
|
|
1099
|
+
grouped_items.setdefault(
|
|
1100
|
+
(tuple(item.shape), item.dtype, item.fmt),
|
|
1101
|
+
[],
|
|
1102
|
+
).append(item)
|
|
1103
|
+
|
|
1104
|
+
for group_items in grouped_items.values():
|
|
1105
|
+
first = group_items[0]
|
|
1106
|
+
outputs: Optional[list[MemoryObj]] = None
|
|
1107
|
+
if len(group_items) > 1:
|
|
1108
|
+
outputs = self.local_cpu_backend.batched_allocate(
|
|
1109
|
+
first.shape,
|
|
1110
|
+
first.dtype,
|
|
1111
|
+
len(group_items),
|
|
1112
|
+
first.fmt,
|
|
1113
|
+
)
|
|
1114
|
+
|
|
1115
|
+
if outputs is None:
|
|
1116
|
+
outputs = []
|
|
1117
|
+
for _ in group_items:
|
|
1118
|
+
memory_obj = self.local_cpu_backend.allocate(
|
|
1119
|
+
first.shape,
|
|
1120
|
+
first.dtype,
|
|
1121
|
+
first.fmt,
|
|
1122
|
+
)
|
|
1123
|
+
if memory_obj is None:
|
|
1124
|
+
for allocated in outputs:
|
|
1125
|
+
allocated.ref_count_down()
|
|
1126
|
+
raise RuntimeError(
|
|
1127
|
+
"DaxBackend batched restore allocation failed"
|
|
1128
|
+
)
|
|
1129
|
+
outputs.append(memory_obj)
|
|
1130
|
+
|
|
1131
|
+
for item, memory_obj in zip(group_items, outputs, strict=True):
|
|
1132
|
+
item.memory_obj = memory_obj
|
|
1133
|
+
|
|
1134
|
+
def _build_restore_waves(
|
|
1135
|
+
self,
|
|
1136
|
+
reserved: Sequence[_RestoreItem],
|
|
1137
|
+
) -> list[_RestoreWave]:
|
|
1138
|
+
"""Plan slab-backed restore work as waves of parallel regions.
|
|
1139
|
+
|
|
1140
|
+
Args:
|
|
1141
|
+
reserved: Restore items that already have output buffers assigned.
|
|
1142
|
+
|
|
1143
|
+
Returns:
|
|
1144
|
+
A list of restore waves, where each wave contains region copies that
|
|
1145
|
+
can run in parallel without overlapping slab space.
|
|
1146
|
+
"""
|
|
1147
|
+
if not reserved:
|
|
1148
|
+
return []
|
|
1149
|
+
|
|
1150
|
+
sorted_items = sorted(reserved, key=lambda item: item.offset)
|
|
1151
|
+
waves: list[_RestoreWave] = []
|
|
1152
|
+
next_item_idx = 0
|
|
1153
|
+
|
|
1154
|
+
while next_item_idx < len(sorted_items):
|
|
1155
|
+
regions: list[_RestoreRegion] = []
|
|
1156
|
+
for region_index in range(self._restore_max_regions):
|
|
1157
|
+
if next_item_idx >= len(sorted_items):
|
|
1158
|
+
break
|
|
1159
|
+
|
|
1160
|
+
region_items: list[_RestoreItem] = []
|
|
1161
|
+
region_spans: list[_RestoreSpan] = []
|
|
1162
|
+
used_bytes = 0
|
|
1163
|
+
|
|
1164
|
+
while next_item_idx < len(sorted_items):
|
|
1165
|
+
item = sorted_items[next_item_idx]
|
|
1166
|
+
if item.size > self._restore_region_bytes:
|
|
1167
|
+
raise RuntimeError(
|
|
1168
|
+
f"DaxBackend restore item size {item.size} exceeds "
|
|
1169
|
+
"region capacity "
|
|
1170
|
+
f"{self._restore_region_bytes}"
|
|
1171
|
+
)
|
|
1172
|
+
if (
|
|
1173
|
+
used_bytes > 0
|
|
1174
|
+
and used_bytes + item.size > self._restore_region_bytes
|
|
1175
|
+
):
|
|
1176
|
+
break
|
|
1177
|
+
|
|
1178
|
+
item.slab_offset = used_bytes
|
|
1179
|
+
region_items.append(item)
|
|
1180
|
+
if (
|
|
1181
|
+
region_spans
|
|
1182
|
+
and region_spans[-1].src_offset + region_spans[-1].size
|
|
1183
|
+
== item.offset
|
|
1184
|
+
and region_spans[-1].slab_offset + region_spans[-1].size
|
|
1185
|
+
== item.slab_offset
|
|
1186
|
+
):
|
|
1187
|
+
region_spans[-1].size += item.size
|
|
1188
|
+
else:
|
|
1189
|
+
region_spans.append(
|
|
1190
|
+
_RestoreSpan(
|
|
1191
|
+
src_offset=item.offset,
|
|
1192
|
+
slab_offset=item.slab_offset,
|
|
1193
|
+
size=item.size,
|
|
1194
|
+
)
|
|
1195
|
+
)
|
|
1196
|
+
|
|
1197
|
+
used_bytes += item.size
|
|
1198
|
+
next_item_idx += 1
|
|
1199
|
+
|
|
1200
|
+
regions.append(
|
|
1201
|
+
_RestoreRegion(
|
|
1202
|
+
region_index=region_index,
|
|
1203
|
+
slab_offset=region_index * self._restore_region_bytes,
|
|
1204
|
+
total_bytes=used_bytes,
|
|
1205
|
+
items=region_items,
|
|
1206
|
+
spans=region_spans,
|
|
1207
|
+
)
|
|
1208
|
+
)
|
|
1209
|
+
|
|
1210
|
+
waves.append(_RestoreWave(regions=regions))
|
|
1211
|
+
|
|
1212
|
+
return waves
|
|
1213
|
+
|
|
1214
|
+
def _batched_memcpy(
|
|
1215
|
+
self,
|
|
1216
|
+
src_ptrs: Sequence[int],
|
|
1217
|
+
dst_ptrs: Sequence[int],
|
|
1218
|
+
sizes: Sequence[int],
|
|
1219
|
+
) -> None:
|
|
1220
|
+
"""Copy a batch of byte ranges, preferring the native helper.
|
|
1221
|
+
|
|
1222
|
+
Args:
|
|
1223
|
+
src_ptrs: Source addresses for each copy.
|
|
1224
|
+
dst_ptrs: Destination addresses for each copy.
|
|
1225
|
+
sizes: Byte counts for each copy.
|
|
1226
|
+
"""
|
|
1227
|
+
if not src_ptrs:
|
|
1228
|
+
return
|
|
1229
|
+
if hasattr(lmc_ops, "batched_memcpy"):
|
|
1230
|
+
lmc_ops.batched_memcpy(list(src_ptrs), list(dst_ptrs), list(sizes))
|
|
1231
|
+
return
|
|
1232
|
+
|
|
1233
|
+
for src_ptr, dst_ptr, size in zip(src_ptrs, dst_ptrs, sizes, strict=True):
|
|
1234
|
+
ctypes.memmove(
|
|
1235
|
+
ctypes.c_void_p(dst_ptr),
|
|
1236
|
+
ctypes.c_void_p(src_ptr),
|
|
1237
|
+
size,
|
|
1238
|
+
)
|
|
1239
|
+
|
|
1240
|
+
def _restore_region(self, region: _RestoreRegion) -> None:
|
|
1241
|
+
"""Restore one region from DAX into the assigned output buffers.
|
|
1242
|
+
|
|
1243
|
+
Args:
|
|
1244
|
+
region: Copy plan describing the DAX spans to stage and the output
|
|
1245
|
+
buffers to populate from the shared slab.
|
|
1246
|
+
|
|
1247
|
+
Raises:
|
|
1248
|
+
RuntimeError: If the shared retrieve slab is unavailable.
|
|
1249
|
+
"""
|
|
1250
|
+
if region.total_bytes <= 0 or not region.items:
|
|
1251
|
+
return
|
|
1252
|
+
if self._retrieve_staging_slab_ptr == 0:
|
|
1253
|
+
raise RuntimeError("DaxBackend retrieve slab is not allocated")
|
|
1254
|
+
|
|
1255
|
+
slab_base_ptr = self._retrieve_staging_slab_ptr + region.slab_offset
|
|
1256
|
+
dax_src_ptrs = [self._base_ptr + span.src_offset for span in region.spans]
|
|
1257
|
+
slab_dst_ptrs = [slab_base_ptr + span.slab_offset for span in region.spans]
|
|
1258
|
+
dax_copy_sizes = [span.size for span in region.spans]
|
|
1259
|
+
self._batched_memcpy(dax_src_ptrs, slab_dst_ptrs, dax_copy_sizes)
|
|
1260
|
+
|
|
1261
|
+
slab_src_ptrs = [slab_base_ptr + item.slab_offset for item in region.items]
|
|
1262
|
+
dst_ptrs = [cast(MemoryObj, item.memory_obj).data_ptr for item in region.items]
|
|
1263
|
+
out_sizes = [item.size for item in region.items]
|
|
1264
|
+
self._batched_memcpy(slab_src_ptrs, dst_ptrs, out_sizes)
|
|
1265
|
+
|
|
1266
|
+
def _run_restore_waves(self, waves: Sequence[_RestoreWave]) -> None:
|
|
1267
|
+
"""Execute restore waves and wait for all region copies to finish.
|
|
1268
|
+
|
|
1269
|
+
Args:
|
|
1270
|
+
waves: Ordered restore waves produced by
|
|
1271
|
+
:meth:`_build_restore_waves`.
|
|
1272
|
+
|
|
1273
|
+
Raises:
|
|
1274
|
+
RuntimeError: If the restore worker pool is unavailable.
|
|
1275
|
+
"""
|
|
1276
|
+
restore_executor = self._restore_executor
|
|
1277
|
+
if restore_executor is None:
|
|
1278
|
+
raise RuntimeError("DaxBackend restore executor is not available")
|
|
1279
|
+
|
|
1280
|
+
for wave in waves:
|
|
1281
|
+
futures = [
|
|
1282
|
+
restore_executor.submit(self._restore_region, region)
|
|
1283
|
+
for region in wave.regions
|
|
1284
|
+
if region.items
|
|
1285
|
+
]
|
|
1286
|
+
for future in futures:
|
|
1287
|
+
future.result()
|
|
1288
|
+
|
|
1289
|
+
def _cleanup_restore_outputs(self, reserved: Sequence[_RestoreItem]) -> None:
|
|
1290
|
+
"""Release any output buffers allocated for a failed restore batch.
|
|
1291
|
+
|
|
1292
|
+
Args:
|
|
1293
|
+
reserved: Restore items whose temporary output buffers should be
|
|
1294
|
+
decremented and cleared.
|
|
1295
|
+
"""
|
|
1296
|
+
for item in reserved:
|
|
1297
|
+
if item.memory_obj is not None:
|
|
1298
|
+
item.memory_obj.ref_count_down()
|
|
1299
|
+
item.memory_obj = None
|
|
1300
|
+
|
|
1301
|
+
def _finalize_reserved_items(
|
|
1302
|
+
self,
|
|
1303
|
+
reserved: Sequence[_RestoreItem],
|
|
1304
|
+
*,
|
|
1305
|
+
touched_keys: Optional[set[CacheEngineKey]] = None,
|
|
1306
|
+
) -> None:
|
|
1307
|
+
"""Release restore borrows and update post-restore slot state.
|
|
1308
|
+
|
|
1309
|
+
Args:
|
|
1310
|
+
reserved: Restore items previously reserved by
|
|
1311
|
+
:meth:`_reserve_restore_items`.
|
|
1312
|
+
touched_keys: Keys that completed successfully and should refresh
|
|
1313
|
+
their LRU state before borrow counts are dropped.
|
|
1314
|
+
"""
|
|
1315
|
+
if not reserved:
|
|
1316
|
+
return
|
|
1317
|
+
touched_keys = touched_keys or set()
|
|
1318
|
+
with self._state_lock:
|
|
1319
|
+
if self._active_ops > 0:
|
|
1320
|
+
self._active_ops -= 1
|
|
1321
|
+
else:
|
|
1322
|
+
logger.warning("DaxBackend active op count underflow during restore")
|
|
1323
|
+
|
|
1324
|
+
for item in reserved:
|
|
1325
|
+
state = self._slot_states.get(item.slot_id)
|
|
1326
|
+
if state is None or state.generation != item.generation:
|
|
1327
|
+
continue
|
|
1328
|
+
if state.borrow_count > 0:
|
|
1329
|
+
state.borrow_count -= 1
|
|
1330
|
+
|
|
1331
|
+
if item.key in touched_keys:
|
|
1332
|
+
current = self._index.get(item.key)
|
|
1333
|
+
if (
|
|
1334
|
+
current is not None
|
|
1335
|
+
and current.slot_id == item.slot_id
|
|
1336
|
+
and current.generation == item.generation
|
|
1337
|
+
):
|
|
1338
|
+
self._touch_locked(item.key)
|
|
1339
|
+
|
|
1340
|
+
if state.pending_free and state.borrow_count == 0:
|
|
1341
|
+
state.pending_free = False
|
|
1342
|
+
self._free_slot_locked(item.slot_id)
|
|
1343
|
+
|
|
1344
|
+
self._state_condition.notify_all()
|
|
1345
|
+
|
|
1346
|
+
def _restore_batch(
|
|
1347
|
+
self,
|
|
1348
|
+
keys: list[CacheEngineKey],
|
|
1349
|
+
prefix_only: bool,
|
|
1350
|
+
) -> list[Optional[MemoryObj]]:
|
|
1351
|
+
"""Restore one batch of keys through the staged DAX retrieve pipeline.
|
|
1352
|
+
|
|
1353
|
+
Args:
|
|
1354
|
+
keys: Ordered keys to restore.
|
|
1355
|
+
prefix_only: When ``True``, return only the consecutive readable
|
|
1356
|
+
prefix used by async-prefetch retrieval. When ``False``, return
|
|
1357
|
+
an input-aligned list and preserve ``None`` holes for misses.
|
|
1358
|
+
|
|
1359
|
+
Returns:
|
|
1360
|
+
Restored outputs for the batch. The returned list is input-aligned
|
|
1361
|
+
for blocking retrieval and prefix-compacted for async-prefetch.
|
|
1362
|
+
"""
|
|
1363
|
+
reserved, results = self._reserve_restore_items(keys, prefix_only=prefix_only)
|
|
1364
|
+
if not reserved:
|
|
1365
|
+
return [] if prefix_only else results
|
|
1366
|
+
|
|
1367
|
+
touched_keys: set[CacheEngineKey] = set()
|
|
1368
|
+
try:
|
|
1369
|
+
self._allocate_restore_outputs(reserved)
|
|
1370
|
+
waves = self._build_restore_waves(reserved)
|
|
1371
|
+
self._run_restore_waves(waves)
|
|
1372
|
+
for item in reserved:
|
|
1373
|
+
memory_obj = cast(MemoryObj, item.memory_obj)
|
|
1374
|
+
memory_obj.metadata.cached_positions = item.cached_positions
|
|
1375
|
+
results[item.result_index] = memory_obj
|
|
1376
|
+
touched_keys.add(item.key)
|
|
1377
|
+
except Exception:
|
|
1378
|
+
self._cleanup_restore_outputs(reserved)
|
|
1379
|
+
self._finalize_reserved_items(reserved)
|
|
1380
|
+
raise
|
|
1381
|
+
|
|
1382
|
+
self._finalize_reserved_items(reserved, touched_keys=touched_keys)
|
|
1383
|
+
if prefix_only:
|
|
1384
|
+
return cast(
|
|
1385
|
+
list[Optional[MemoryObj]],
|
|
1386
|
+
[cast(MemoryObj, results[item.result_index]) for item in reserved],
|
|
1387
|
+
)
|
|
1388
|
+
return results
|
|
1389
|
+
|
|
1390
|
+
def _do_write(self, offset: int, memory_obj: MemoryObj, size: int) -> None:
|
|
1391
|
+
ctypes.memmove(
|
|
1392
|
+
ctypes.c_void_p(self._base_ptr + offset),
|
|
1393
|
+
ctypes.c_void_p(memory_obj.data_ptr),
|
|
1394
|
+
size,
|
|
1395
|
+
)
|
|
1396
|
+
|
|
1397
|
+
def _do_read(self, offset: int, memory_obj: MemoryObj, size: int) -> None:
|
|
1398
|
+
ctypes.memmove(
|
|
1399
|
+
ctypes.c_void_p(memory_obj.data_ptr),
|
|
1400
|
+
ctypes.c_void_p(self._base_ptr + offset),
|
|
1401
|
+
size,
|
|
1402
|
+
)
|
|
1403
|
+
|
|
1404
|
+
async def _submit_write(
|
|
1405
|
+
self,
|
|
1406
|
+
key: CacheEngineKey,
|
|
1407
|
+
offset: int,
|
|
1408
|
+
size: int,
|
|
1409
|
+
memory_obj: MemoryObj,
|
|
1410
|
+
on_complete_callback: Optional[Callable[[CacheEngineKey], None]] = None,
|
|
1411
|
+
) -> None:
|
|
1412
|
+
write_error: Optional[Exception] = None
|
|
1413
|
+
should_invoke_callback = False
|
|
1414
|
+
try:
|
|
1415
|
+
try:
|
|
1416
|
+
await asyncio.to_thread(self._do_write, offset, memory_obj, size)
|
|
1417
|
+
except Exception as e:
|
|
1418
|
+
write_error = e
|
|
1419
|
+
logger.warning("Async DAX write failed for key %s: %s", key, e)
|
|
1420
|
+
finally:
|
|
1421
|
+
with self._state_lock:
|
|
1422
|
+
should_invoke_callback = self._finalize_inflight_locked(
|
|
1423
|
+
key,
|
|
1424
|
+
write_failed=write_error is not None,
|
|
1425
|
+
)
|
|
1426
|
+
|
|
1427
|
+
if write_error is not None:
|
|
1428
|
+
raise RuntimeError(
|
|
1429
|
+
f"DaxBackend write failed for key {key}: {write_error}"
|
|
1430
|
+
) from write_error
|
|
1431
|
+
|
|
1432
|
+
if should_invoke_callback:
|
|
1433
|
+
self._invoke_on_complete_callback(key, on_complete_callback)
|
|
1434
|
+
finally:
|
|
1435
|
+
memory_obj.ref_count_down()
|
|
1436
|
+
with self._state_lock:
|
|
1437
|
+
if self._active_puts > 0:
|
|
1438
|
+
self._active_puts -= 1
|
|
1439
|
+
else:
|
|
1440
|
+
logger.warning(
|
|
1441
|
+
"DaxBackend active put count underflow for key %s", key
|
|
1442
|
+
)
|
|
1443
|
+
self._state_condition.notify_all()
|