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,830 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""
|
|
3
|
+
Prefetch Controller: asynchronously prefetches data from L2 adapters into L1.
|
|
4
|
+
|
|
5
|
+
The controller runs a background thread with an event-driven loop that:
|
|
6
|
+
1. Accepts prefetch requests from external threads via submit_prefetch_request.
|
|
7
|
+
2. Submits lookup_and_lock tasks to all L2 adapters.
|
|
8
|
+
3. Computes a load plan using the PrefetchPolicy, trimmed to the contiguous
|
|
9
|
+
prefix of found keys.
|
|
10
|
+
4. Reserves L1 write buffers and submits load tasks to L2 adapters.
|
|
11
|
+
5. On load completion, transitions L1 entries from write-locked to read-locked.
|
|
12
|
+
6. Reports prefix hit count.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# Standard
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from typing import Iterable
|
|
18
|
+
import enum
|
|
19
|
+
import os
|
|
20
|
+
import select
|
|
21
|
+
import threading
|
|
22
|
+
|
|
23
|
+
# First Party
|
|
24
|
+
from lmcache.logging import init_logger
|
|
25
|
+
from lmcache.native_storage_ops import Bitmap
|
|
26
|
+
from lmcache.v1.distributed.api import MemoryLayoutDesc, ObjectKey
|
|
27
|
+
from lmcache.v1.distributed.error import L1Error
|
|
28
|
+
from lmcache.v1.distributed.l1_manager import L1Manager
|
|
29
|
+
from lmcache.v1.distributed.l2_adapters.base import L2AdapterInterface, L2TaskId
|
|
30
|
+
from lmcache.v1.distributed.storage_controller import StorageControllerInterface
|
|
31
|
+
from lmcache.v1.distributed.storage_controllers.prefetch_policy import (
|
|
32
|
+
PrefetchPolicy,
|
|
33
|
+
)
|
|
34
|
+
from lmcache.v1.distributed.storage_controllers.store_policy import (
|
|
35
|
+
AdapterDescriptor,
|
|
36
|
+
)
|
|
37
|
+
from lmcache.v1.memory_management import MemoryObj
|
|
38
|
+
from lmcache.v1.mp_observability.event import Event, EventType
|
|
39
|
+
from lmcache.v1.mp_observability.event_bus import get_event_bus
|
|
40
|
+
|
|
41
|
+
logger = init_logger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# HELPER FUNCTIONS
|
|
45
|
+
def trim_load_plan_to_first_n_keys(
|
|
46
|
+
load_plan: dict[int, Bitmap],
|
|
47
|
+
num_keys: int,
|
|
48
|
+
n: int,
|
|
49
|
+
) -> dict[int, Bitmap]:
|
|
50
|
+
"""
|
|
51
|
+
Trim the load plan to only include keys with indices < n.
|
|
52
|
+
|
|
53
|
+
For example, if n=3 and the combined load plan has keys
|
|
54
|
+
{0, 1, 3}, the trimmed plan will only include key indices [0, 1]
|
|
55
|
+
and exclude index 3.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
load_plan: Mapping from adapter index to Bitmap of key indices.
|
|
59
|
+
num_keys: Total number of keys (bitmap size).
|
|
60
|
+
n: Number of keys to include in the trimmed plan (prefix length).
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Trimmed load plan with only key indices < n.
|
|
64
|
+
|
|
65
|
+
Note:
|
|
66
|
+
the adapter index will not appear in the return dict if
|
|
67
|
+
it has no keys in the prefix.
|
|
68
|
+
"""
|
|
69
|
+
if n <= 0:
|
|
70
|
+
return {}
|
|
71
|
+
|
|
72
|
+
trimmed_plan: dict[int, Bitmap] = {}
|
|
73
|
+
mask_bitmap = Bitmap(num_keys, n)
|
|
74
|
+
for adapter_idx, bitmap in load_plan.items():
|
|
75
|
+
new_bitmap = bitmap & mask_bitmap
|
|
76
|
+
if new_bitmap.popcount() == 0:
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
trimmed_plan[adapter_idx] = new_bitmap
|
|
80
|
+
|
|
81
|
+
return trimmed_plan
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def trim_load_plan_to_prefix(
|
|
85
|
+
load_plan: dict[int, Bitmap],
|
|
86
|
+
num_keys: int,
|
|
87
|
+
) -> dict[int, Bitmap]:
|
|
88
|
+
"""
|
|
89
|
+
Trim the load plan to the longest contiguous prefix of keys.
|
|
90
|
+
|
|
91
|
+
For example, if num_keys=5 and the combined load plan has keys
|
|
92
|
+
{0, 1, 3}, the prefix is 2 (keys 0 and 1), so the trimmed plan
|
|
93
|
+
will only include key indices [0, 1] and exclude index 3.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
load_plan: Mapping from adapter index to Bitmap of key indices.
|
|
97
|
+
num_keys: Total number of keys in the request.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Trimmed load plan with only prefix key indices.
|
|
101
|
+
|
|
102
|
+
Note:
|
|
103
|
+
the adapter index will not appear in the return dict if
|
|
104
|
+
it has no keys in the prefix.
|
|
105
|
+
"""
|
|
106
|
+
merged_plan = Bitmap(num_keys)
|
|
107
|
+
for bitmap in load_plan.values():
|
|
108
|
+
merged_plan = merged_plan | bitmap
|
|
109
|
+
|
|
110
|
+
prefix_length = merged_plan.count_leading_ones()
|
|
111
|
+
return trim_load_plan_to_first_n_keys(load_plan, num_keys, prefix_length)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def merge_bitmaps(bitmaps: Iterable[Bitmap], num_keys: int) -> Bitmap:
|
|
115
|
+
"""Merge multiple bitmaps with a bitwise OR."""
|
|
116
|
+
if not bitmaps:
|
|
117
|
+
return Bitmap(0)
|
|
118
|
+
merged = Bitmap(num_keys)
|
|
119
|
+
for bm in bitmaps:
|
|
120
|
+
merged = merged | bm
|
|
121
|
+
return merged
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# Poll timeout in milliseconds for the prefetch loop
|
|
125
|
+
PREFETCH_LOOP_POLL_TIMEOUT_MS = 500
|
|
126
|
+
|
|
127
|
+
PrefetchRequestId = int
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class PrefetchPhase(enum.Enum):
|
|
131
|
+
LOOKUP = enum.auto()
|
|
132
|
+
PLAN_AND_LOAD = enum.auto()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass
|
|
136
|
+
class InFlightPrefetchRequest:
|
|
137
|
+
"""Tracks a single prefetch request across its lifecycle phases."""
|
|
138
|
+
|
|
139
|
+
request_id: PrefetchRequestId
|
|
140
|
+
keys: list[ObjectKey]
|
|
141
|
+
layout_desc: MemoryLayoutDesc
|
|
142
|
+
phase: PrefetchPhase
|
|
143
|
+
extra_count: int = 0
|
|
144
|
+
"""Extra read locks per key (on top of the default 1) to acquire when
|
|
145
|
+
transitioning from write-locked to read-locked. Must match the
|
|
146
|
+
``extra_count`` used in the corresponding ``submit_prefetch_task`` call."""
|
|
147
|
+
|
|
148
|
+
# Lookup phase: adapter_idx -> task_id (removed as results arrive)
|
|
149
|
+
pending_lookup_tasks: dict[int, L2TaskId] = field(default_factory=dict)
|
|
150
|
+
# Lookup phase: adapter_idx -> bitmap (populated as results arrive)
|
|
151
|
+
lookup_results: dict[int, Bitmap] = field(default_factory=dict)
|
|
152
|
+
|
|
153
|
+
# Load phase: adapter_idx -> bitmap of key indices to load
|
|
154
|
+
load_plan: dict[int, Bitmap] = field(default_factory=dict)
|
|
155
|
+
# Load phase: adapter_idx -> task_id (removed as results arrive)
|
|
156
|
+
pending_load_tasks: dict[int, L2TaskId] = field(default_factory=dict)
|
|
157
|
+
# Load phase: adapter_idx -> bitmap (populated as results arrive)
|
|
158
|
+
load_results: dict[int, Bitmap] = field(default_factory=dict)
|
|
159
|
+
# Load phase: keys that were write-reserved in L1
|
|
160
|
+
write_reserved_keys: list[ObjectKey] = field(default_factory=list)
|
|
161
|
+
write_reserved_objs: dict[ObjectKey, MemoryObj] = field(default_factory=dict)
|
|
162
|
+
|
|
163
|
+
def all_lookups_done(self) -> bool:
|
|
164
|
+
return len(self.pending_lookup_tasks) == 0
|
|
165
|
+
|
|
166
|
+
def all_loads_done(self) -> bool:
|
|
167
|
+
return len(self.pending_load_tasks) == 0
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class PrefetchController(StorageControllerInterface):
|
|
171
|
+
"""
|
|
172
|
+
Asynchronously prefetches data from L2 adapters into L1 memory.
|
|
173
|
+
|
|
174
|
+
The controller:
|
|
175
|
+
1. Accepts prefetch requests via submit_prefetch_request (thread-safe).
|
|
176
|
+
2. Runs a background thread that submits lookup_and_lock to all adapters.
|
|
177
|
+
3. Uses PrefetchPolicy to compute a load plan from lookup results.
|
|
178
|
+
4. Reserves L1 write buffers and submits load tasks to adapters.
|
|
179
|
+
5. On completion, transitions loaded keys to read-locked state.
|
|
180
|
+
6. Reports the number of prefix hits via query_prefetch_result.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
l1_manager: The L1 manager instance.
|
|
184
|
+
l2_adapters: List of L2 adapter instances.
|
|
185
|
+
adapter_descriptors: Descriptors for each L2 adapter (same order).
|
|
186
|
+
policy: The prefetch policy for load plan decisions.
|
|
187
|
+
max_in_flight: Maximum number of concurrent prefetch requests.
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
def __init__(
|
|
191
|
+
self,
|
|
192
|
+
l1_manager: L1Manager,
|
|
193
|
+
l2_adapters: list[L2AdapterInterface],
|
|
194
|
+
adapter_descriptors: list[AdapterDescriptor],
|
|
195
|
+
policy: PrefetchPolicy,
|
|
196
|
+
max_in_flight: int = 8,
|
|
197
|
+
) -> None:
|
|
198
|
+
self._l1_manager = l1_manager
|
|
199
|
+
self._l2_adapters = l2_adapters
|
|
200
|
+
self._adapter_descriptors = adapter_descriptors
|
|
201
|
+
self._policy = policy
|
|
202
|
+
self._max_in_flight = max_in_flight
|
|
203
|
+
|
|
204
|
+
# In-flight request tracking (background thread only)
|
|
205
|
+
self._in_flight_requests: dict[PrefetchRequestId, InFlightPrefetchRequest] = {}
|
|
206
|
+
self._pending_queue: list[
|
|
207
|
+
tuple[PrefetchRequestId, list[ObjectKey], MemoryLayoutDesc, int]
|
|
208
|
+
] = []
|
|
209
|
+
|
|
210
|
+
# Shadow counters for status reporting (updated in background loop)
|
|
211
|
+
self._status_in_flight_count: int = 0
|
|
212
|
+
self._status_pending_count: int = 0
|
|
213
|
+
self._status_lookup_phase_count: int = 0
|
|
214
|
+
self._status_load_phase_count: int = 0
|
|
215
|
+
|
|
216
|
+
# Thread-safe submission queue (external -> background)
|
|
217
|
+
self._submission_lock = threading.Lock()
|
|
218
|
+
self._submission_queue: list[
|
|
219
|
+
tuple[PrefetchRequestId, list[ObjectKey], MemoryLayoutDesc, int]
|
|
220
|
+
] = []
|
|
221
|
+
self._next_request_id: PrefetchRequestId = 0
|
|
222
|
+
self._submission_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
|
|
223
|
+
|
|
224
|
+
# Thread-safe lookup results (background -> external)
|
|
225
|
+
self._lookup_results_lock = threading.Lock()
|
|
226
|
+
self._completed_lookups: dict[PrefetchRequestId, int] = {}
|
|
227
|
+
|
|
228
|
+
# Thread-safe prefetch results (background -> external)
|
|
229
|
+
self._prefetch_results_lock = threading.Lock()
|
|
230
|
+
self._completed_results: dict[PrefetchRequestId, int] = {}
|
|
231
|
+
|
|
232
|
+
# Map eventfds to adapter indices for quick lookup in poll.
|
|
233
|
+
# Relies on the L2AdapterInterface contract that every adapter
|
|
234
|
+
# returns distinct fds for store/lookup/load, and no two adapters
|
|
235
|
+
# share an fd. See the docstrings in L2AdapterInterface.
|
|
236
|
+
self._lookup_efd_to_adapter: dict[int, int] = {}
|
|
237
|
+
self._load_efd_to_adapter: dict[int, int] = {}
|
|
238
|
+
for i, adapter in enumerate(self._l2_adapters):
|
|
239
|
+
self._lookup_efd_to_adapter[adapter.get_lookup_and_lock_event_fd()] = i
|
|
240
|
+
self._load_efd_to_adapter[adapter.get_load_event_fd()] = i
|
|
241
|
+
|
|
242
|
+
self._event_bus = get_event_bus()
|
|
243
|
+
|
|
244
|
+
self._stop_flag = threading.Event()
|
|
245
|
+
self._thread = threading.Thread(
|
|
246
|
+
target=self._prefetch_loop,
|
|
247
|
+
daemon=True,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# =========================================================================
|
|
251
|
+
# External API (thread-safe)
|
|
252
|
+
# =========================================================================
|
|
253
|
+
|
|
254
|
+
def submit_prefetch_request(
|
|
255
|
+
self,
|
|
256
|
+
keys: list[ObjectKey],
|
|
257
|
+
layout_desc: MemoryLayoutDesc,
|
|
258
|
+
extra_count: int = 0,
|
|
259
|
+
) -> PrefetchRequestId:
|
|
260
|
+
"""
|
|
261
|
+
Submit a prefetch request for the given keys.
|
|
262
|
+
|
|
263
|
+
Thread-safe. Can be called from any thread.
|
|
264
|
+
|
|
265
|
+
Only the **contiguous prefix** of found keys is loaded from L2.
|
|
266
|
+
If L2 has keys {0, 1, 3, 4} but not key 2, only keys {0, 1} are
|
|
267
|
+
loaded because the gap at index 2 breaks the prefix. Keys beyond
|
|
268
|
+
the prefix are never transferred, saving I/O bandwidth and L1
|
|
269
|
+
memory. Use :meth:`query_prefetch_result` to retrieve the number
|
|
270
|
+
of prefix hits once the request completes.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
keys: List of object keys to prefetch from L2 into L1.
|
|
274
|
+
The ordering defines the prefix: index 0 is the first key.
|
|
275
|
+
layout_desc: Memory layout for L1 write buffer allocation.
|
|
276
|
+
extra_count: Extra read locks per key (on top of the default 1)
|
|
277
|
+
to acquire when transitioning loaded keys from write-locked
|
|
278
|
+
to read-locked. Must match the ``extra_count`` used in the
|
|
279
|
+
corresponding ``submit_prefetch_task`` call so that all TP
|
|
280
|
+
workers can each consume one read lock.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
A request ID for tracking via query_prefetch_result.
|
|
284
|
+
"""
|
|
285
|
+
with self._submission_lock:
|
|
286
|
+
request_id = self._next_request_id
|
|
287
|
+
self._next_request_id += 1
|
|
288
|
+
self._submission_queue.append((request_id, keys, layout_desc, extra_count))
|
|
289
|
+
os.eventfd_write(self._submission_efd, 1)
|
|
290
|
+
return request_id
|
|
291
|
+
|
|
292
|
+
def query_lookup_result(self, request_id: PrefetchRequestId) -> int | None:
|
|
293
|
+
"""
|
|
294
|
+
Query the number of prefix hits from the lookup phase.
|
|
295
|
+
|
|
296
|
+
Thread-safe. Returns the number of prefix hits if the lookup phase
|
|
297
|
+
has completed, None if still in progress, or the prefetch request
|
|
298
|
+
has already been consumed by query_prefetch_result.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
request_id: The request ID from submit_prefetch_request.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Number of prefix hits from the lookup phase, or None if not yet complete
|
|
305
|
+
or if the request has already been consumed by a previous call to this
|
|
306
|
+
method.
|
|
307
|
+
|
|
308
|
+
Note:
|
|
309
|
+
This function does not pop the result. The caller need to make sure to call
|
|
310
|
+
the query_prefetch_result after calling this function, otherwise nobody
|
|
311
|
+
will clean up the completed lookups dictionary, causing memory leak.
|
|
312
|
+
"""
|
|
313
|
+
with self._lookup_results_lock:
|
|
314
|
+
return self._completed_lookups.get(request_id, None)
|
|
315
|
+
|
|
316
|
+
def query_prefetch_result(self, request_id: PrefetchRequestId) -> int | None:
|
|
317
|
+
"""
|
|
318
|
+
Query the result of a prefetch request.
|
|
319
|
+
|
|
320
|
+
Thread-safe. Returns the number of prefix hits if the request
|
|
321
|
+
has completed, None if still in progress. Each result can only
|
|
322
|
+
be retrieved once (subsequent calls return None).
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
request_id: The request ID from submit_prefetch_request.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Number of prefix hits, or None if not yet complete.
|
|
329
|
+
|
|
330
|
+
Note:
|
|
331
|
+
This function will pop the completed lookup results as well.
|
|
332
|
+
Therefore, the caller need to make sure that never call
|
|
333
|
+
query_lookup_result after calling this function, otherwise it will
|
|
334
|
+
get None forever.
|
|
335
|
+
"""
|
|
336
|
+
with self._prefetch_results_lock:
|
|
337
|
+
result = self._completed_results.pop(request_id, None)
|
|
338
|
+
if result is not None:
|
|
339
|
+
with self._lookup_results_lock:
|
|
340
|
+
self._completed_lookups.pop(request_id, None)
|
|
341
|
+
return result
|
|
342
|
+
|
|
343
|
+
def report_status(self) -> dict:
|
|
344
|
+
"""Return a status dict for the prefetch controller."""
|
|
345
|
+
is_healthy = self._thread.is_alive()
|
|
346
|
+
with self._submission_lock:
|
|
347
|
+
submission_queue_size = len(self._submission_queue)
|
|
348
|
+
with self._prefetch_results_lock:
|
|
349
|
+
completed_results_count = len(self._completed_results)
|
|
350
|
+
return {
|
|
351
|
+
"is_healthy": is_healthy,
|
|
352
|
+
"thread_alive": is_healthy,
|
|
353
|
+
"max_in_flight": self._max_in_flight,
|
|
354
|
+
"submission_queue_size": submission_queue_size,
|
|
355
|
+
"pending_queue_size": self._status_pending_count,
|
|
356
|
+
"in_flight_request_count": self._status_in_flight_count,
|
|
357
|
+
"lookup_phase_count": self._status_lookup_phase_count,
|
|
358
|
+
"load_phase_count": self._status_load_phase_count,
|
|
359
|
+
"completed_results_count": completed_results_count,
|
|
360
|
+
"num_l2_adapters": len(self._l2_adapters),
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
# =========================================================================
|
|
364
|
+
# Lifecycle
|
|
365
|
+
# =========================================================================
|
|
366
|
+
|
|
367
|
+
def start(self) -> None:
|
|
368
|
+
"""Start the background prefetch loop thread."""
|
|
369
|
+
logger.info("Starting PrefetchController...")
|
|
370
|
+
self._thread.start()
|
|
371
|
+
|
|
372
|
+
def stop(self) -> None:
|
|
373
|
+
"""
|
|
374
|
+
Signal the loop to stop and wait for the thread to join.
|
|
375
|
+
|
|
376
|
+
Cleans up any in-flight requests (releases L1 write locks,
|
|
377
|
+
L2 locks) before returning.
|
|
378
|
+
"""
|
|
379
|
+
self._stop_flag.set()
|
|
380
|
+
os.eventfd_write(self._submission_efd, 1)
|
|
381
|
+
self._thread.join()
|
|
382
|
+
self._cleanup_in_flight_requests()
|
|
383
|
+
os.close(self._submission_efd)
|
|
384
|
+
|
|
385
|
+
# =========================================================================
|
|
386
|
+
# Background loop
|
|
387
|
+
# =========================================================================
|
|
388
|
+
|
|
389
|
+
def _prefetch_loop(self) -> None:
|
|
390
|
+
"""
|
|
391
|
+
Main event-driven loop running in a background thread.
|
|
392
|
+
|
|
393
|
+
Uses select.poll() to wait on:
|
|
394
|
+
- The submission eventfd (new prefetch requests).
|
|
395
|
+
- Each L2 adapter's lookup eventfd (completed lookups).
|
|
396
|
+
- Each L2 adapter's load eventfd (completed loads).
|
|
397
|
+
"""
|
|
398
|
+
poller = select.poll()
|
|
399
|
+
poller.register(self._submission_efd, select.POLLIN)
|
|
400
|
+
for efd in self._lookup_efd_to_adapter:
|
|
401
|
+
poller.register(efd, select.POLLIN)
|
|
402
|
+
for efd in self._load_efd_to_adapter:
|
|
403
|
+
poller.register(efd, select.POLLIN)
|
|
404
|
+
|
|
405
|
+
while not self._stop_flag.is_set():
|
|
406
|
+
ready = poller.poll(PREFETCH_LOOP_POLL_TIMEOUT_MS)
|
|
407
|
+
|
|
408
|
+
for fd, events in ready:
|
|
409
|
+
if not (events & select.POLLIN):
|
|
410
|
+
continue
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
os.eventfd_read(fd)
|
|
414
|
+
except (OSError, BlockingIOError):
|
|
415
|
+
pass
|
|
416
|
+
|
|
417
|
+
try:
|
|
418
|
+
if fd == self._submission_efd:
|
|
419
|
+
self._drain_submission_queue()
|
|
420
|
+
elif fd in self._lookup_efd_to_adapter:
|
|
421
|
+
self._process_lookup_completions(
|
|
422
|
+
self._lookup_efd_to_adapter[fd]
|
|
423
|
+
)
|
|
424
|
+
elif fd in self._load_efd_to_adapter:
|
|
425
|
+
self._process_load_completions(self._load_efd_to_adapter[fd])
|
|
426
|
+
except Exception:
|
|
427
|
+
logger.exception(
|
|
428
|
+
"Unexpected error in prefetch loop while processing fd %d",
|
|
429
|
+
fd,
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
try:
|
|
433
|
+
self._start_pending_requests()
|
|
434
|
+
except Exception:
|
|
435
|
+
logger.exception(
|
|
436
|
+
"Unexpected error in prefetch loop while starting pending requests"
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
def _drain_submission_queue(self) -> None:
|
|
440
|
+
"""Move items from the thread-safe submission queue to the
|
|
441
|
+
pending queue."""
|
|
442
|
+
with self._submission_lock:
|
|
443
|
+
items = self._submission_queue
|
|
444
|
+
self._submission_queue = []
|
|
445
|
+
self._pending_queue.extend(items)
|
|
446
|
+
self._status_pending_count += len(items)
|
|
447
|
+
|
|
448
|
+
def _start_pending_requests(self) -> None:
|
|
449
|
+
"""Start pending requests up to the max in-flight limit."""
|
|
450
|
+
while (
|
|
451
|
+
self._pending_queue and len(self._in_flight_requests) < self._max_in_flight
|
|
452
|
+
):
|
|
453
|
+
request_id, keys, layout_desc, extra_count = self._pending_queue.pop(0)
|
|
454
|
+
self._status_pending_count -= 1
|
|
455
|
+
self._start_lookup_phase(request_id, keys, layout_desc, extra_count)
|
|
456
|
+
|
|
457
|
+
# =========================================================================
|
|
458
|
+
# Lookup phase
|
|
459
|
+
# =========================================================================
|
|
460
|
+
|
|
461
|
+
def _start_lookup_phase(
|
|
462
|
+
self,
|
|
463
|
+
request_id: PrefetchRequestId,
|
|
464
|
+
keys: list[ObjectKey],
|
|
465
|
+
layout_desc: MemoryLayoutDesc,
|
|
466
|
+
extra_count: int = 0,
|
|
467
|
+
) -> None:
|
|
468
|
+
"""Submit lookup_and_lock to all adapters for a new request."""
|
|
469
|
+
if not self._l2_adapters:
|
|
470
|
+
self._complete_request(request_id, 0)
|
|
471
|
+
return
|
|
472
|
+
|
|
473
|
+
pending_lookup_tasks: dict[int, L2TaskId] = {}
|
|
474
|
+
for i, adapter in enumerate(self._l2_adapters):
|
|
475
|
+
task_id = adapter.submit_lookup_and_lock_task(keys)
|
|
476
|
+
pending_lookup_tasks[i] = task_id
|
|
477
|
+
|
|
478
|
+
request = InFlightPrefetchRequest(
|
|
479
|
+
request_id=request_id,
|
|
480
|
+
keys=keys,
|
|
481
|
+
layout_desc=layout_desc,
|
|
482
|
+
phase=PrefetchPhase.LOOKUP,
|
|
483
|
+
extra_count=extra_count,
|
|
484
|
+
pending_lookup_tasks=pending_lookup_tasks,
|
|
485
|
+
)
|
|
486
|
+
self._in_flight_requests[request_id] = request
|
|
487
|
+
self._status_in_flight_count += 1
|
|
488
|
+
self._status_lookup_phase_count += 1
|
|
489
|
+
|
|
490
|
+
self._event_bus.publish(
|
|
491
|
+
Event(
|
|
492
|
+
event_type=EventType.L2_PREFETCH_LOOKUP_SUBMITTED,
|
|
493
|
+
metadata={
|
|
494
|
+
"request_id": request_id,
|
|
495
|
+
"key_count": len(keys),
|
|
496
|
+
"adapter_count": len(pending_lookup_tasks),
|
|
497
|
+
},
|
|
498
|
+
)
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
def _process_lookup_completions(self, adapter_index: int) -> None:
|
|
502
|
+
"""Check all LOOKUP-phase requests for completed lookups from
|
|
503
|
+
this adapter."""
|
|
504
|
+
ready_to_transition: list[InFlightPrefetchRequest] = []
|
|
505
|
+
|
|
506
|
+
for request in list(self._in_flight_requests.values()):
|
|
507
|
+
if request.phase != PrefetchPhase.LOOKUP:
|
|
508
|
+
continue
|
|
509
|
+
if adapter_index not in request.pending_lookup_tasks:
|
|
510
|
+
continue
|
|
511
|
+
|
|
512
|
+
task_id = request.pending_lookup_tasks[adapter_index]
|
|
513
|
+
result = self._l2_adapters[adapter_index].query_lookup_and_lock_result(
|
|
514
|
+
task_id
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
if result is not None:
|
|
518
|
+
request.lookup_results[adapter_index] = result
|
|
519
|
+
del request.pending_lookup_tasks[adapter_index]
|
|
520
|
+
|
|
521
|
+
if request.all_lookups_done():
|
|
522
|
+
ready_to_transition.append(request)
|
|
523
|
+
|
|
524
|
+
for request in ready_to_transition:
|
|
525
|
+
self._transition_to_load_phase(request)
|
|
526
|
+
|
|
527
|
+
# =========================================================================
|
|
528
|
+
# Load phase
|
|
529
|
+
# =========================================================================
|
|
530
|
+
def _transition_to_load_phase(self, request: InFlightPrefetchRequest) -> None:
|
|
531
|
+
"""Compute load plan, reserve L1 buffers, and submit load tasks."""
|
|
532
|
+
request.phase = PrefetchPhase.PLAN_AND_LOAD
|
|
533
|
+
self._status_lookup_phase_count -= 1
|
|
534
|
+
self._status_load_phase_count += 1
|
|
535
|
+
|
|
536
|
+
# Step 1: get load plan from policy
|
|
537
|
+
load_plan = self._policy.select_load_plan(
|
|
538
|
+
request.keys,
|
|
539
|
+
request.lookup_results,
|
|
540
|
+
self._adapter_descriptors,
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
# Step 2: trim the load plan to only prefix
|
|
544
|
+
trimmed_plan = trim_load_plan_to_prefix(load_plan, len(request.keys))
|
|
545
|
+
|
|
546
|
+
if not trimmed_plan:
|
|
547
|
+
# Nothing to load after trimming to prefix. Unlock all lookup locks
|
|
548
|
+
# and complete with 0 hits.
|
|
549
|
+
self._unlock_all_lookups(request)
|
|
550
|
+
self._update_lookup_results(request.request_id, 0)
|
|
551
|
+
self._event_bus.publish(
|
|
552
|
+
Event(
|
|
553
|
+
event_type=EventType.L2_PREFETCH_LOOKUP_COMPLETED,
|
|
554
|
+
metadata={
|
|
555
|
+
"request_id": request.request_id,
|
|
556
|
+
"prefix_hit_count": 0,
|
|
557
|
+
},
|
|
558
|
+
)
|
|
559
|
+
)
|
|
560
|
+
self._complete_request(request.request_id, 0)
|
|
561
|
+
return
|
|
562
|
+
|
|
563
|
+
# Step 3: reserve L1 write buffers
|
|
564
|
+
merged_bitmap = merge_bitmaps(trimmed_plan.values(), len(request.keys))
|
|
565
|
+
keys_to_reserve = merged_bitmap.gather(request.keys)
|
|
566
|
+
l1_mgr = self._l1_manager
|
|
567
|
+
|
|
568
|
+
retentions = self._policy.select_l1_retentions(
|
|
569
|
+
keys_to_reserve,
|
|
570
|
+
)
|
|
571
|
+
write_results = l1_mgr.reserve_write(
|
|
572
|
+
keys=keys_to_reserve,
|
|
573
|
+
is_temporary=[not r for r in retentions],
|
|
574
|
+
layout_desc=request.layout_desc,
|
|
575
|
+
mode="new",
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
# Step 4: filter to successfully reserved keys
|
|
579
|
+
reserved_key_set: set[ObjectKey] = set()
|
|
580
|
+
for key, (err, mem_obj) in write_results.items():
|
|
581
|
+
if err == L1Error.SUCCESS and mem_obj is not None:
|
|
582
|
+
request.write_reserved_keys.append(key)
|
|
583
|
+
request.write_reserved_objs[key] = mem_obj
|
|
584
|
+
reserved_key_set.add(key)
|
|
585
|
+
else:
|
|
586
|
+
logger.debug(
|
|
587
|
+
"Prefetch request %d: reserve write failed for %s: %s",
|
|
588
|
+
request.request_id,
|
|
589
|
+
key,
|
|
590
|
+
err,
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
# Step 5: recompute load plan excluding failed reservations
|
|
594
|
+
reserved_bitmap = Bitmap(len(request.keys))
|
|
595
|
+
for i, key in enumerate(request.keys):
|
|
596
|
+
if key in reserved_key_set:
|
|
597
|
+
reserved_bitmap.set(i)
|
|
598
|
+
|
|
599
|
+
prefix_length = reserved_bitmap.count_leading_ones()
|
|
600
|
+
trimmed_plan = trim_load_plan_to_first_n_keys(
|
|
601
|
+
load_plan, len(request.keys), prefix_length
|
|
602
|
+
)
|
|
603
|
+
request.load_plan = trimmed_plan
|
|
604
|
+
|
|
605
|
+
## Step 6: phase 1 unlock — keys locked in lookup but not in plan
|
|
606
|
+
self._unlock_unneeded_keys(request)
|
|
607
|
+
|
|
608
|
+
if not trimmed_plan:
|
|
609
|
+
# Nothing loadable after filtering
|
|
610
|
+
if request.write_reserved_keys:
|
|
611
|
+
l1_mgr.finish_write(request.write_reserved_keys)
|
|
612
|
+
l1_mgr.delete(request.write_reserved_keys)
|
|
613
|
+
self._update_lookup_results(request.request_id, 0)
|
|
614
|
+
self._event_bus.publish(
|
|
615
|
+
Event(
|
|
616
|
+
event_type=EventType.L2_PREFETCH_LOOKUP_COMPLETED,
|
|
617
|
+
metadata={
|
|
618
|
+
"request_id": request.request_id,
|
|
619
|
+
"prefix_hit_count": 0,
|
|
620
|
+
},
|
|
621
|
+
)
|
|
622
|
+
)
|
|
623
|
+
self._complete_request(request.request_id, 0)
|
|
624
|
+
return
|
|
625
|
+
|
|
626
|
+
## Step 7: submit load tasks per adapter
|
|
627
|
+
for adapter_idx, bitmap in trimmed_plan.items():
|
|
628
|
+
per_adapter_keys = bitmap.gather(request.keys)
|
|
629
|
+
per_adapter_objs = [
|
|
630
|
+
request.write_reserved_objs[key] for key in per_adapter_keys
|
|
631
|
+
]
|
|
632
|
+
task_id = self._l2_adapters[adapter_idx].submit_load_task(
|
|
633
|
+
per_adapter_keys, per_adapter_objs
|
|
634
|
+
)
|
|
635
|
+
request.pending_load_tasks[adapter_idx] = task_id
|
|
636
|
+
|
|
637
|
+
## Step 8: update the lookup result based on the final load plan
|
|
638
|
+
self._update_lookup_results(request.request_id, prefix_length)
|
|
639
|
+
|
|
640
|
+
self._event_bus.publish(
|
|
641
|
+
Event(
|
|
642
|
+
event_type=EventType.L2_PREFETCH_LOOKUP_COMPLETED,
|
|
643
|
+
metadata={
|
|
644
|
+
"request_id": request.request_id,
|
|
645
|
+
"prefix_hit_count": prefix_length,
|
|
646
|
+
},
|
|
647
|
+
)
|
|
648
|
+
)
|
|
649
|
+
self._event_bus.publish(
|
|
650
|
+
Event(
|
|
651
|
+
event_type=EventType.L2_PREFETCH_LOAD_SUBMITTED,
|
|
652
|
+
metadata={
|
|
653
|
+
"request_id": request.request_id,
|
|
654
|
+
"key_count": len(reserved_key_set),
|
|
655
|
+
"adapter_count": len(trimmed_plan),
|
|
656
|
+
},
|
|
657
|
+
)
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
logger.debug(
|
|
661
|
+
"Prefetch request %d: submitted load tasks to %d adapters for %d keys",
|
|
662
|
+
request.request_id,
|
|
663
|
+
len(trimmed_plan),
|
|
664
|
+
len(reserved_key_set),
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
def _update_lookup_results(
|
|
668
|
+
self, request_id: PrefetchRequestId, hit_chunks: int
|
|
669
|
+
) -> None:
|
|
670
|
+
"""Update the completed lookups dict with the number of prefix hits."""
|
|
671
|
+
with self._lookup_results_lock:
|
|
672
|
+
self._completed_lookups[request_id] = hit_chunks
|
|
673
|
+
|
|
674
|
+
def _process_load_completions(self, adapter_index: int) -> None:
|
|
675
|
+
"""Check all PLAN_AND_LOAD-phase requests for completed loads."""
|
|
676
|
+
ready_to_finalize: list[InFlightPrefetchRequest] = []
|
|
677
|
+
|
|
678
|
+
for request in list(self._in_flight_requests.values()):
|
|
679
|
+
if request.phase != PrefetchPhase.PLAN_AND_LOAD:
|
|
680
|
+
continue
|
|
681
|
+
if adapter_index not in request.pending_load_tasks:
|
|
682
|
+
continue
|
|
683
|
+
|
|
684
|
+
task_id = request.pending_load_tasks[adapter_index]
|
|
685
|
+
result = self._l2_adapters[adapter_index].query_load_result(task_id)
|
|
686
|
+
|
|
687
|
+
if result is not None:
|
|
688
|
+
request.load_results[adapter_index] = result
|
|
689
|
+
del request.pending_load_tasks[adapter_index]
|
|
690
|
+
|
|
691
|
+
if request.all_loads_done():
|
|
692
|
+
ready_to_finalize.append(request)
|
|
693
|
+
|
|
694
|
+
for request in ready_to_finalize:
|
|
695
|
+
self._finalize_load(request)
|
|
696
|
+
|
|
697
|
+
def _finalize_load(self, request: InFlightPrefetchRequest) -> None:
|
|
698
|
+
"""
|
|
699
|
+
Finalize a completed load: build result bitmap, transition L1
|
|
700
|
+
state, release non-prefix read locks, and report prefix hits.
|
|
701
|
+
|
|
702
|
+
Only prefix keys are submitted for loading, but partial load
|
|
703
|
+
failures can create gaps. Keys beyond the gap that were
|
|
704
|
+
successfully loaded still need their read locks released.
|
|
705
|
+
"""
|
|
706
|
+
num_keys = len(request.keys)
|
|
707
|
+
|
|
708
|
+
# Scatter per-adapter local load results into global positions.
|
|
709
|
+
# Each adapter's load bitmap is locally indexed (size == adapter's
|
|
710
|
+
# key count). The plan bitmap maps local → global indices via
|
|
711
|
+
# get_indices_list().
|
|
712
|
+
result_bitmap = Bitmap(num_keys)
|
|
713
|
+
for adapter_idx, plan_bitmap in request.load_plan.items():
|
|
714
|
+
load_bitmap = request.load_results.get(adapter_idx)
|
|
715
|
+
if load_bitmap is None:
|
|
716
|
+
continue
|
|
717
|
+
plan_indices = plan_bitmap.get_indices_list()
|
|
718
|
+
for global_i in load_bitmap.gather(plan_indices):
|
|
719
|
+
result_bitmap.set(global_i)
|
|
720
|
+
|
|
721
|
+
# Separate loaded vs. failed among write-reserved keys
|
|
722
|
+
loaded_keys: list[ObjectKey] = result_bitmap.gather(request.keys)
|
|
723
|
+
loaded_set = set(loaded_keys)
|
|
724
|
+
failed_keys = [k for k in request.write_reserved_keys if k not in loaded_set]
|
|
725
|
+
|
|
726
|
+
# Phase 2 unlock: release L2 locks for all keys in the load plan
|
|
727
|
+
self._unlock_all_plan_keys(request)
|
|
728
|
+
|
|
729
|
+
l1_mgr = self._l1_manager
|
|
730
|
+
|
|
731
|
+
# Transition loaded keys: write-locked -> read-locked
|
|
732
|
+
# Use extra_count so that all TP workers each get their own read lock.
|
|
733
|
+
if loaded_keys:
|
|
734
|
+
l1_mgr.finish_write_and_reserve_read(
|
|
735
|
+
loaded_keys, extra_count=request.extra_count
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
# Clean up failed keys
|
|
739
|
+
if failed_keys:
|
|
740
|
+
l1_mgr.finish_write(failed_keys)
|
|
741
|
+
l1_mgr.delete(failed_keys)
|
|
742
|
+
|
|
743
|
+
self._event_bus.publish(
|
|
744
|
+
Event(
|
|
745
|
+
event_type=EventType.L2_PREFETCH_LOAD_COMPLETED,
|
|
746
|
+
metadata={
|
|
747
|
+
"request_id": request.request_id,
|
|
748
|
+
"loaded_count": len(loaded_keys),
|
|
749
|
+
"failed_count": len(failed_keys),
|
|
750
|
+
},
|
|
751
|
+
)
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
# Partial load failures can create gaps in the prefix.
|
|
755
|
+
# Release read locks for loaded keys beyond the prefix.
|
|
756
|
+
prefix_hits = result_bitmap.count_leading_ones()
|
|
757
|
+
prefix_mask = Bitmap(num_keys, prefix_hits)
|
|
758
|
+
non_prefix_loaded_bitmap = result_bitmap & (~prefix_mask)
|
|
759
|
+
non_prefix_loaded = non_prefix_loaded_bitmap.gather(request.keys)
|
|
760
|
+
if non_prefix_loaded:
|
|
761
|
+
l1_mgr.finish_read(non_prefix_loaded, extra_count=request.extra_count)
|
|
762
|
+
|
|
763
|
+
self._complete_request(request.request_id, prefix_hits)
|
|
764
|
+
|
|
765
|
+
# =========================================================================
|
|
766
|
+
# Unlock helpers
|
|
767
|
+
# =========================================================================
|
|
768
|
+
|
|
769
|
+
def _unlock_unneeded_keys(self, request: InFlightPrefetchRequest) -> None:
|
|
770
|
+
"""Phase 1 unlock: keys locked in lookup but not in the load plan."""
|
|
771
|
+
for adapter_idx, lookup_bitmap in request.lookup_results.items():
|
|
772
|
+
plan_bitmap = request.load_plan.get(adapter_idx, Bitmap(len(request.keys)))
|
|
773
|
+
to_unlock_bitmap = lookup_bitmap & (~plan_bitmap)
|
|
774
|
+
unlock_keys = to_unlock_bitmap.gather(request.keys)
|
|
775
|
+
if unlock_keys:
|
|
776
|
+
self._l2_adapters[adapter_idx].submit_unlock(unlock_keys)
|
|
777
|
+
|
|
778
|
+
def _unlock_all_plan_keys(self, request: InFlightPrefetchRequest) -> None:
|
|
779
|
+
"""Phase 2 unlock: release L2 locks for all keys in the load plan."""
|
|
780
|
+
for adapter_idx, load_bitmap in request.load_plan.items():
|
|
781
|
+
unlock_keys = load_bitmap.gather(request.keys)
|
|
782
|
+
self._l2_adapters[adapter_idx].submit_unlock(unlock_keys)
|
|
783
|
+
|
|
784
|
+
def _unlock_all_lookups(self, request: InFlightPrefetchRequest) -> None:
|
|
785
|
+
"""Unlock all keys locked during lookup (nothing to load case)."""
|
|
786
|
+
for adapter_idx, lookup_bitmap in request.lookup_results.items():
|
|
787
|
+
unlock_keys = lookup_bitmap.gather(request.keys)
|
|
788
|
+
if unlock_keys:
|
|
789
|
+
self._l2_adapters[adapter_idx].submit_unlock(unlock_keys)
|
|
790
|
+
|
|
791
|
+
# =========================================================================
|
|
792
|
+
# Completion and cleanup
|
|
793
|
+
# =========================================================================
|
|
794
|
+
|
|
795
|
+
def _complete_request(
|
|
796
|
+
self, request_id: PrefetchRequestId, prefix_hits: int
|
|
797
|
+
) -> None:
|
|
798
|
+
"""Store the result and remove from in-flight tracking."""
|
|
799
|
+
with self._prefetch_results_lock:
|
|
800
|
+
self._completed_results[request_id] = prefix_hits
|
|
801
|
+
removed = self._in_flight_requests.pop(request_id, None)
|
|
802
|
+
if removed is not None:
|
|
803
|
+
self._status_in_flight_count -= 1
|
|
804
|
+
if removed.phase == PrefetchPhase.LOOKUP:
|
|
805
|
+
self._status_lookup_phase_count -= 1
|
|
806
|
+
elif removed.phase == PrefetchPhase.PLAN_AND_LOAD:
|
|
807
|
+
self._status_load_phase_count -= 1
|
|
808
|
+
logger.debug(
|
|
809
|
+
"Prefetch request %d completed: %d prefix hits",
|
|
810
|
+
request_id,
|
|
811
|
+
prefix_hits,
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
def _cleanup_in_flight_requests(self) -> None:
|
|
815
|
+
"""Release resources for any in-flight requests during shutdown."""
|
|
816
|
+
l1_mgr = self._l1_manager
|
|
817
|
+
for request in self._in_flight_requests.values():
|
|
818
|
+
if request.phase == PrefetchPhase.PLAN_AND_LOAD:
|
|
819
|
+
if request.write_reserved_keys:
|
|
820
|
+
l1_mgr.finish_write(request.write_reserved_keys)
|
|
821
|
+
l1_mgr.delete(request.write_reserved_keys)
|
|
822
|
+
self._unlock_all_plan_keys(request)
|
|
823
|
+
elif request.phase == PrefetchPhase.LOOKUP:
|
|
824
|
+
self._unlock_all_lookups(request)
|
|
825
|
+
logger.warning(
|
|
826
|
+
"Cleaning up in-flight prefetch request %d (%d keys).",
|
|
827
|
+
request.request_id,
|
|
828
|
+
len(request.keys),
|
|
829
|
+
)
|
|
830
|
+
self._in_flight_requests.clear()
|