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.
Files changed (399) hide show
  1. lmcache/__init__.py +84 -0
  2. lmcache/_version.py +24 -0
  3. lmcache/cli/__init__.py +1 -0
  4. lmcache/cli/commands/__init__.py +34 -0
  5. lmcache/cli/commands/base.py +157 -0
  6. lmcache/cli/commands/bench/__init__.py +557 -0
  7. lmcache/cli/commands/bench/engine_bench/__init__.py +1 -0
  8. lmcache/cli/commands/bench/engine_bench/config.py +245 -0
  9. lmcache/cli/commands/bench/engine_bench/interactive/__init__.py +274 -0
  10. lmcache/cli/commands/bench/engine_bench/interactive/config.json +10 -0
  11. lmcache/cli/commands/bench/engine_bench/interactive/schema.py +352 -0
  12. lmcache/cli/commands/bench/engine_bench/interactive/state.py +327 -0
  13. lmcache/cli/commands/bench/engine_bench/interactive/terminal.py +291 -0
  14. lmcache/cli/commands/bench/engine_bench/progress.py +145 -0
  15. lmcache/cli/commands/bench/engine_bench/request_sender.py +232 -0
  16. lmcache/cli/commands/bench/engine_bench/stats.py +275 -0
  17. lmcache/cli/commands/bench/engine_bench/workloads/__init__.py +153 -0
  18. lmcache/cli/commands/bench/engine_bench/workloads/base.py +122 -0
  19. lmcache/cli/commands/bench/engine_bench/workloads/long_doc_permutator.py +435 -0
  20. lmcache/cli/commands/bench/engine_bench/workloads/long_doc_qa.py +281 -0
  21. lmcache/cli/commands/bench/engine_bench/workloads/multi_round_chat.py +337 -0
  22. lmcache/cli/commands/bench/engine_bench/workloads/random_prefill.py +178 -0
  23. lmcache/cli/commands/describe.py +310 -0
  24. lmcache/cli/commands/kvcache.py +133 -0
  25. lmcache/cli/commands/mock.py +75 -0
  26. lmcache/cli/commands/ping.py +113 -0
  27. lmcache/cli/commands/query/__init__.py +155 -0
  28. lmcache/cli/commands/query/prompt.py +134 -0
  29. lmcache/cli/commands/query/request.py +357 -0
  30. lmcache/cli/commands/server.py +99 -0
  31. lmcache/cli/commands/tool/__init__.py +63 -0
  32. lmcache/cli/commands/tool/cache_simulator.py +113 -0
  33. lmcache/cli/commands/trace/__init__.py +505 -0
  34. lmcache/cli/commands/trace/dispatch.py +249 -0
  35. lmcache/cli/commands/trace/driver.py +372 -0
  36. lmcache/cli/commands/trace/stats.py +289 -0
  37. lmcache/cli/documents/lmcache.txt +11 -0
  38. lmcache/cli/main.py +42 -0
  39. lmcache/cli/metrics/__init__.py +29 -0
  40. lmcache/cli/metrics/formatter.py +171 -0
  41. lmcache/cli/metrics/handler.py +94 -0
  42. lmcache/cli/metrics/metrics.py +161 -0
  43. lmcache/cli/metrics/section.py +77 -0
  44. lmcache/connections.py +173 -0
  45. lmcache/integration/__init__.py +2 -0
  46. lmcache/integration/base_service_factory.py +165 -0
  47. lmcache/integration/request_telemetry/__init__.py +1 -0
  48. lmcache/integration/request_telemetry/base.py +51 -0
  49. lmcache/integration/request_telemetry/factory.py +113 -0
  50. lmcache/integration/request_telemetry/fastapi.py +109 -0
  51. lmcache/integration/request_telemetry/noop.py +35 -0
  52. lmcache/integration/sglang/__init__.py +2 -0
  53. lmcache/integration/sglang/sglang_adapter.py +326 -0
  54. lmcache/integration/sglang/utils.py +39 -0
  55. lmcache/integration/vllm/__init__.py +1 -0
  56. lmcache/integration/vllm/lmcache_connector_v1.py +213 -0
  57. lmcache/integration/vllm/lmcache_connector_v1_085.py +150 -0
  58. lmcache/integration/vllm/lmcache_mp_connector_0180.py +1072 -0
  59. lmcache/integration/vllm/tests/test_mm_hash_utils.py +112 -0
  60. lmcache/integration/vllm/utils.py +433 -0
  61. lmcache/integration/vllm/vllm_multi_process_adapter.py +1090 -0
  62. lmcache/integration/vllm/vllm_service_factory.py +339 -0
  63. lmcache/integration/vllm/vllm_v1_adapter.py +1713 -0
  64. lmcache/logging.py +107 -0
  65. lmcache/native_storage_ops.pyi +230 -0
  66. lmcache/non_cuda_equivalents.py +1424 -0
  67. lmcache/observability.py +1958 -0
  68. lmcache/storage_backend/serde/__init__.py +1 -0
  69. lmcache/storage_backend/serde/cachegen_basics.py +210 -0
  70. lmcache/storage_backend/serde/cachegen_decoder.py +207 -0
  71. lmcache/storage_backend/serde/cachegen_encoder.py +394 -0
  72. lmcache/storage_backend/serde/serde.py +75 -0
  73. lmcache/tools/__init__.py +1 -0
  74. lmcache/tools/cache_simulator/README.md +392 -0
  75. lmcache/tools/cache_simulator/__init__.py +1 -0
  76. lmcache/tools/cache_simulator/docs/simulate_example.png +0 -0
  77. lmcache/tools/cache_simulator/docs/sweep_example.png +0 -0
  78. lmcache/tools/cache_simulator/gen_bench_dataset.py +360 -0
  79. lmcache/tools/cache_simulator/lru_cache.py +124 -0
  80. lmcache/tools/cache_simulator/plot_hit_rate.py +231 -0
  81. lmcache/tools/cache_simulator/simulator.py +795 -0
  82. lmcache/tools/controller_benchmark/README.md +161 -0
  83. lmcache/tools/controller_benchmark/__init__.py +1 -0
  84. lmcache/tools/controller_benchmark/__main__.py +331 -0
  85. lmcache/tools/controller_benchmark/benchmark.py +660 -0
  86. lmcache/tools/controller_benchmark/config.py +44 -0
  87. lmcache/tools/controller_benchmark/constants.py +10 -0
  88. lmcache/tools/controller_benchmark/handlers/__init__.py +46 -0
  89. lmcache/tools/controller_benchmark/handlers/admit.py +52 -0
  90. lmcache/tools/controller_benchmark/handlers/base.py +47 -0
  91. lmcache/tools/controller_benchmark/handlers/deregister.py +49 -0
  92. lmcache/tools/controller_benchmark/handlers/evict.py +52 -0
  93. lmcache/tools/controller_benchmark/handlers/heartbeat.py +56 -0
  94. lmcache/tools/controller_benchmark/handlers/p2p_lookup.py +47 -0
  95. lmcache/tools/controller_benchmark/handlers/register.py +56 -0
  96. lmcache/tools/mp_status_viewer/__init__.py +1 -0
  97. lmcache/tools/mp_status_viewer/__main__.py +95 -0
  98. lmcache/usage_context.py +417 -0
  99. lmcache/utils.py +665 -0
  100. lmcache/v1/__init__.py +2 -0
  101. lmcache/v1/api_server/__init__.py +2 -0
  102. lmcache/v1/api_server/__main__.py +537 -0
  103. lmcache/v1/basic_check.py +112 -0
  104. lmcache/v1/cache_controller/__init__.py +9 -0
  105. lmcache/v1/cache_controller/commands/__init__.py +15 -0
  106. lmcache/v1/cache_controller/commands/base.py +35 -0
  107. lmcache/v1/cache_controller/commands/full_sync.py +49 -0
  108. lmcache/v1/cache_controller/config.py +176 -0
  109. lmcache/v1/cache_controller/controller_manager.py +535 -0
  110. lmcache/v1/cache_controller/controllers/__init__.py +11 -0
  111. lmcache/v1/cache_controller/controllers/full_sync_tracker.py +473 -0
  112. lmcache/v1/cache_controller/controllers/kv_controller.py +439 -0
  113. lmcache/v1/cache_controller/controllers/registration_controller.py +282 -0
  114. lmcache/v1/cache_controller/executor.py +463 -0
  115. lmcache/v1/cache_controller/frontend/static/css/style.css +201 -0
  116. lmcache/v1/cache_controller/frontend/static/img/logo.png +0 -0
  117. lmcache/v1/cache_controller/frontend/static/index.html +234 -0
  118. lmcache/v1/cache_controller/frontend/static/js/controller_app.js +660 -0
  119. lmcache/v1/cache_controller/full_sync_sender.py +475 -0
  120. lmcache/v1/cache_controller/locks.py +149 -0
  121. lmcache/v1/cache_controller/message.py +828 -0
  122. lmcache/v1/cache_controller/observability.py +208 -0
  123. lmcache/v1/cache_controller/utils.py +679 -0
  124. lmcache/v1/cache_controller/worker.py +665 -0
  125. lmcache/v1/cache_engine.py +2058 -0
  126. lmcache/v1/cache_interface.py +19 -0
  127. lmcache/v1/check/__init__.py +74 -0
  128. lmcache/v1/check/check_mode_gen.py +86 -0
  129. lmcache/v1/check/check_mode_test_l2_adapter.py +284 -0
  130. lmcache/v1/check/check_mode_test_remote.py +155 -0
  131. lmcache/v1/check/check_mode_test_storage_manager.py +142 -0
  132. lmcache/v1/check/utils.py +571 -0
  133. lmcache/v1/compute/__init__.py +2 -0
  134. lmcache/v1/compute/attention/__init__.py +0 -0
  135. lmcache/v1/compute/attention/abstract.py +39 -0
  136. lmcache/v1/compute/attention/flash_attn.py +129 -0
  137. lmcache/v1/compute/attention/flash_infer_sparse.py +284 -0
  138. lmcache/v1/compute/attention/metadata.py +85 -0
  139. lmcache/v1/compute/attention/utils.py +14 -0
  140. lmcache/v1/compute/blend/__init__.py +7 -0
  141. lmcache/v1/compute/blend/blender.py +168 -0
  142. lmcache/v1/compute/blend/metadata.py +34 -0
  143. lmcache/v1/compute/blend/utils.py +63 -0
  144. lmcache/v1/compute/models/__init__.py +0 -0
  145. lmcache/v1/compute/models/base.py +141 -0
  146. lmcache/v1/compute/models/llama.py +9 -0
  147. lmcache/v1/compute/models/qwen3.py +24 -0
  148. lmcache/v1/compute/models/utils.py +68 -0
  149. lmcache/v1/compute/positional_encoding.py +199 -0
  150. lmcache/v1/config.py +848 -0
  151. lmcache/v1/config_base.py +848 -0
  152. lmcache/v1/distributed/api.py +248 -0
  153. lmcache/v1/distributed/config.py +321 -0
  154. lmcache/v1/distributed/error.py +64 -0
  155. lmcache/v1/distributed/eviction.py +192 -0
  156. lmcache/v1/distributed/eviction_policy/__init__.py +21 -0
  157. lmcache/v1/distributed/eviction_policy/factory.py +27 -0
  158. lmcache/v1/distributed/eviction_policy/lru.py +244 -0
  159. lmcache/v1/distributed/eviction_policy/noop.py +50 -0
  160. lmcache/v1/distributed/internal_api.py +170 -0
  161. lmcache/v1/distributed/l1_manager.py +835 -0
  162. lmcache/v1/distributed/l2_adapters/__init__.py +67 -0
  163. lmcache/v1/distributed/l2_adapters/base.py +360 -0
  164. lmcache/v1/distributed/l2_adapters/config.py +385 -0
  165. lmcache/v1/distributed/l2_adapters/factory.py +205 -0
  166. lmcache/v1/distributed/l2_adapters/fs_l2_adapter.py +747 -0
  167. lmcache/v1/distributed/l2_adapters/fs_native_l2_adapter.py +167 -0
  168. lmcache/v1/distributed/l2_adapters/mock_l2_adapter.py +516 -0
  169. lmcache/v1/distributed/l2_adapters/mooncake_store_l2_adapter.py +135 -0
  170. lmcache/v1/distributed/l2_adapters/native_connector_l2_adapter.py +468 -0
  171. lmcache/v1/distributed/l2_adapters/native_plugin_l2_adapter.py +199 -0
  172. lmcache/v1/distributed/l2_adapters/nixl_store_dynamic_l2_adapter.py +831 -0
  173. lmcache/v1/distributed/l2_adapters/nixl_store_l2_adapter.py +983 -0
  174. lmcache/v1/distributed/l2_adapters/plugin_l2_adapter.py +210 -0
  175. lmcache/v1/distributed/l2_adapters/resp_l2_adapter.py +176 -0
  176. lmcache/v1/distributed/memory_manager.py +179 -0
  177. lmcache/v1/distributed/storage_controller.py +39 -0
  178. lmcache/v1/distributed/storage_controllers/__init__.py +43 -0
  179. lmcache/v1/distributed/storage_controllers/eviction_controller.py +242 -0
  180. lmcache/v1/distributed/storage_controllers/prefetch_controller.py +830 -0
  181. lmcache/v1/distributed/storage_controllers/prefetch_policy.py +193 -0
  182. lmcache/v1/distributed/storage_controllers/store_controller.py +452 -0
  183. lmcache/v1/distributed/storage_controllers/store_policy.py +213 -0
  184. lmcache/v1/distributed/storage_manager.py +532 -0
  185. lmcache/v1/event_manager.py +145 -0
  186. lmcache/v1/exceptions/__init__.py +16 -0
  187. lmcache/v1/gpu_connector/__init__.py +126 -0
  188. lmcache/v1/gpu_connector/gpu_connectors.py +1906 -0
  189. lmcache/v1/gpu_connector/gpu_ops.py +85 -0
  190. lmcache/v1/gpu_connector/hpu_connector.py +326 -0
  191. lmcache/v1/gpu_connector/mock_gpu_connector.py +67 -0
  192. lmcache/v1/gpu_connector/utils.py +890 -0
  193. lmcache/v1/gpu_connector/xpu_connectors.py +916 -0
  194. lmcache/v1/health_monitor/__init__.py +1 -0
  195. lmcache/v1/health_monitor/base.py +587 -0
  196. lmcache/v1/health_monitor/checks/__init__.py +1 -0
  197. lmcache/v1/health_monitor/checks/remote_backend_check.py +304 -0
  198. lmcache/v1/health_monitor/constants.py +36 -0
  199. lmcache/v1/internal_api_server/__init__.py +0 -0
  200. lmcache/v1/internal_api_server/api_registry.py +59 -0
  201. lmcache/v1/internal_api_server/api_server.py +120 -0
  202. lmcache/v1/internal_api_server/common/__init__.py +1 -0
  203. lmcache/v1/internal_api_server/common/env_api.py +22 -0
  204. lmcache/v1/internal_api_server/common/loglevel_api.py +57 -0
  205. lmcache/v1/internal_api_server/common/metrics_api.py +29 -0
  206. lmcache/v1/internal_api_server/common/periodic_thread_api.py +138 -0
  207. lmcache/v1/internal_api_server/common/run_script_api.py +73 -0
  208. lmcache/v1/internal_api_server/common/thread_api.py +63 -0
  209. lmcache/v1/internal_api_server/controller/__init__.py +1 -0
  210. lmcache/v1/internal_api_server/controller/key_stats_api.py +81 -0
  211. lmcache/v1/internal_api_server/controller/worker_info_api.py +136 -0
  212. lmcache/v1/internal_api_server/utils.py +43 -0
  213. lmcache/v1/internal_api_server/vllm/__init__.py +1 -0
  214. lmcache/v1/internal_api_server/vllm/backend_api.py +221 -0
  215. lmcache/v1/internal_api_server/vllm/bypass_api.py +204 -0
  216. lmcache/v1/internal_api_server/vllm/cache_api.py +895 -0
  217. lmcache/v1/internal_api_server/vllm/chunk_statistics_api.py +141 -0
  218. lmcache/v1/internal_api_server/vllm/conf_api.py +147 -0
  219. lmcache/v1/internal_api_server/vllm/freeze_api.py +172 -0
  220. lmcache/v1/internal_api_server/vllm/hot_cache_api.py +184 -0
  221. lmcache/v1/internal_api_server/vllm/inference_api.py +65 -0
  222. lmcache/v1/internal_api_server/vllm/load_fs_chunks_api.py +320 -0
  223. lmcache/v1/internal_api_server/vllm/lookup_api.py +145 -0
  224. lmcache/v1/internal_api_server/vllm/version_api.py +25 -0
  225. lmcache/v1/kv_layer_groups.py +267 -0
  226. lmcache/v1/lazy_memory_allocator.py +284 -0
  227. lmcache/v1/lookup_client/__init__.py +25 -0
  228. lmcache/v1/lookup_client/abstract_client.py +77 -0
  229. lmcache/v1/lookup_client/async_lookup_message.py +50 -0
  230. lmcache/v1/lookup_client/chunk_statistics_lookup_client.py +200 -0
  231. lmcache/v1/lookup_client/factory.py +251 -0
  232. lmcache/v1/lookup_client/hit_limit_lookup_client.py +86 -0
  233. lmcache/v1/lookup_client/lmcache_async_lookup_client.py +407 -0
  234. lmcache/v1/lookup_client/lmcache_lookup_client.py +285 -0
  235. lmcache/v1/lookup_client/lmcache_lookup_client_bypass.py +99 -0
  236. lmcache/v1/lookup_client/mooncake_lookup_client.py +87 -0
  237. lmcache/v1/lookup_client/record_strategies/__init__.py +77 -0
  238. lmcache/v1/lookup_client/record_strategies/base.py +327 -0
  239. lmcache/v1/lookup_client/record_strategies/file_hash.py +130 -0
  240. lmcache/v1/lookup_client/record_strategies/memory_bloom_filter.py +81 -0
  241. lmcache/v1/manager.py +539 -0
  242. lmcache/v1/memory_management.py +2619 -0
  243. lmcache/v1/metadata.py +114 -0
  244. lmcache/v1/mp_observability/AGENTS.override.md +21 -0
  245. lmcache/v1/mp_observability/README.md +204 -0
  246. lmcache/v1/mp_observability/config.py +340 -0
  247. lmcache/v1/mp_observability/event.py +100 -0
  248. lmcache/v1/mp_observability/event_bus.py +313 -0
  249. lmcache/v1/mp_observability/otel_init.py +145 -0
  250. lmcache/v1/mp_observability/subscribers/__init__.py +28 -0
  251. lmcache/v1/mp_observability/subscribers/logging/__init__.py +19 -0
  252. lmcache/v1/mp_observability/subscribers/logging/l1.py +56 -0
  253. lmcache/v1/mp_observability/subscribers/logging/l2.py +73 -0
  254. lmcache/v1/mp_observability/subscribers/logging/lookup_hash.py +209 -0
  255. lmcache/v1/mp_observability/subscribers/logging/mp_server.py +90 -0
  256. lmcache/v1/mp_observability/subscribers/logging/sm.py +59 -0
  257. lmcache/v1/mp_observability/subscribers/metrics/__init__.py +20 -0
  258. lmcache/v1/mp_observability/subscribers/metrics/l0_lifecycle.py +290 -0
  259. lmcache/v1/mp_observability/subscribers/metrics/l1.py +55 -0
  260. lmcache/v1/mp_observability/subscribers/metrics/l1_lifecycle.py +166 -0
  261. lmcache/v1/mp_observability/subscribers/metrics/l2.py +121 -0
  262. lmcache/v1/mp_observability/subscribers/metrics/sm.py +69 -0
  263. lmcache/v1/mp_observability/subscribers/tracing/__init__.py +12 -0
  264. lmcache/v1/mp_observability/subscribers/tracing/mp_server.py +333 -0
  265. lmcache/v1/mp_observability/subscribers/tracing/span_registry.py +148 -0
  266. lmcache/v1/mp_observability/trace/__init__.py +50 -0
  267. lmcache/v1/mp_observability/trace/codecs.py +255 -0
  268. lmcache/v1/mp_observability/trace/decorator.py +147 -0
  269. lmcache/v1/mp_observability/trace/format.py +132 -0
  270. lmcache/v1/mp_observability/trace/lifecycle.py +83 -0
  271. lmcache/v1/mp_observability/trace/reader.py +167 -0
  272. lmcache/v1/mp_observability/trace/recorder.py +300 -0
  273. lmcache/v1/multiprocess/__init__.py +0 -0
  274. lmcache/v1/multiprocess/affinity_pool.py +102 -0
  275. lmcache/v1/multiprocess/blend_server_v2.py +891 -0
  276. lmcache/v1/multiprocess/config.py +253 -0
  277. lmcache/v1/multiprocess/custom_types.py +281 -0
  278. lmcache/v1/multiprocess/futures.py +194 -0
  279. lmcache/v1/multiprocess/gpu_context.py +511 -0
  280. lmcache/v1/multiprocess/http_server.py +235 -0
  281. lmcache/v1/multiprocess/mp_runtime_plugin_launcher.py +130 -0
  282. lmcache/v1/multiprocess/mq.py +732 -0
  283. lmcache/v1/multiprocess/protocol.py +86 -0
  284. lmcache/v1/multiprocess/protocols/README.md +213 -0
  285. lmcache/v1/multiprocess/protocols/__init__.py +127 -0
  286. lmcache/v1/multiprocess/protocols/base.py +89 -0
  287. lmcache/v1/multiprocess/protocols/blend.py +109 -0
  288. lmcache/v1/multiprocess/protocols/blend_v2.py +57 -0
  289. lmcache/v1/multiprocess/protocols/controller.py +53 -0
  290. lmcache/v1/multiprocess/protocols/debug.py +34 -0
  291. lmcache/v1/multiprocess/protocols/engine.py +146 -0
  292. lmcache/v1/multiprocess/protocols/observability.py +39 -0
  293. lmcache/v1/multiprocess/server.py +1134 -0
  294. lmcache/v1/multiprocess/session.py +190 -0
  295. lmcache/v1/multiprocess/token_hasher.py +441 -0
  296. lmcache/v1/offload_server/__init__.py +17 -0
  297. lmcache/v1/offload_server/abstract_server.py +37 -0
  298. lmcache/v1/offload_server/message.py +30 -0
  299. lmcache/v1/offload_server/zmq_server.py +122 -0
  300. lmcache/v1/periodic_thread.py +579 -0
  301. lmcache/v1/pin_monitor.py +246 -0
  302. lmcache/v1/plugin/__init__.py +0 -0
  303. lmcache/v1/plugin/runtime_plugin_launcher.py +211 -0
  304. lmcache/v1/protocol.py +317 -0
  305. lmcache/v1/rpc/__init__.py +17 -0
  306. lmcache/v1/rpc/transport.py +105 -0
  307. lmcache/v1/rpc/zmq_transport.py +213 -0
  308. lmcache/v1/rpc_utils.py +165 -0
  309. lmcache/v1/server/__init__.py +2 -0
  310. lmcache/v1/server/__main__.py +170 -0
  311. lmcache/v1/server/storage_backend/__init__.py +21 -0
  312. lmcache/v1/server/storage_backend/abstract_backend.py +80 -0
  313. lmcache/v1/server/storage_backend/local_backend.py +75 -0
  314. lmcache/v1/server/utils.py +21 -0
  315. lmcache/v1/standalone/__init__.py +1 -0
  316. lmcache/v1/standalone/__main__.py +583 -0
  317. lmcache/v1/standalone/manager.py +80 -0
  318. lmcache/v1/standalone/standalone_service_factory.py +86 -0
  319. lmcache/v1/storage_backend/__init__.py +313 -0
  320. lmcache/v1/storage_backend/abstract_backend.py +445 -0
  321. lmcache/v1/storage_backend/audit_backend.py +233 -0
  322. lmcache/v1/storage_backend/batched_message_sender.py +222 -0
  323. lmcache/v1/storage_backend/cache_policy/__init__.py +45 -0
  324. lmcache/v1/storage_backend/cache_policy/base_policy.py +87 -0
  325. lmcache/v1/storage_backend/cache_policy/fifo.py +58 -0
  326. lmcache/v1/storage_backend/cache_policy/lfu.py +105 -0
  327. lmcache/v1/storage_backend/cache_policy/lru.py +81 -0
  328. lmcache/v1/storage_backend/cache_policy/mru.py +61 -0
  329. lmcache/v1/storage_backend/connector/__init__.py +443 -0
  330. lmcache/v1/storage_backend/connector/audit_adapter.py +77 -0
  331. lmcache/v1/storage_backend/connector/audit_connector.py +320 -0
  332. lmcache/v1/storage_backend/connector/base_connector.py +379 -0
  333. lmcache/v1/storage_backend/connector/blackhole_adapter.py +21 -0
  334. lmcache/v1/storage_backend/connector/blackhole_connector.py +37 -0
  335. lmcache/v1/storage_backend/connector/eic_adapter.py +31 -0
  336. lmcache/v1/storage_backend/connector/eic_connector.py +757 -0
  337. lmcache/v1/storage_backend/connector/external_adapter.py +79 -0
  338. lmcache/v1/storage_backend/connector/fs_adapter.py +51 -0
  339. lmcache/v1/storage_backend/connector/fs_connector.py +403 -0
  340. lmcache/v1/storage_backend/connector/infinistore_adapter.py +56 -0
  341. lmcache/v1/storage_backend/connector/infinistore_connector.py +177 -0
  342. lmcache/v1/storage_backend/connector/instrumented_connector.py +219 -0
  343. lmcache/v1/storage_backend/connector/lm_adapter.py +31 -0
  344. lmcache/v1/storage_backend/connector/lm_connector.py +176 -0
  345. lmcache/v1/storage_backend/connector/mock_adapter.py +57 -0
  346. lmcache/v1/storage_backend/connector/mock_connector.py +349 -0
  347. lmcache/v1/storage_backend/connector/mooncakestore_adapter.py +43 -0
  348. lmcache/v1/storage_backend/connector/mooncakestore_connector.py +614 -0
  349. lmcache/v1/storage_backend/connector/redis_adapter.py +181 -0
  350. lmcache/v1/storage_backend/connector/redis_connector.py +828 -0
  351. lmcache/v1/storage_backend/connector/s3_adapter.py +59 -0
  352. lmcache/v1/storage_backend/connector/s3_connector.py +699 -0
  353. lmcache/v1/storage_backend/connector/sagemaker_hyperpod_adapter.py +233 -0
  354. lmcache/v1/storage_backend/connector/sagemaker_hyperpod_connector.py +987 -0
  355. lmcache/v1/storage_backend/connector/valkey_adapter.py +114 -0
  356. lmcache/v1/storage_backend/connector/valkey_connector.py +627 -0
  357. lmcache/v1/storage_backend/gds_backend.py +1199 -0
  358. lmcache/v1/storage_backend/job_executor/__init__.py +0 -0
  359. lmcache/v1/storage_backend/job_executor/base_executor.py +34 -0
  360. lmcache/v1/storage_backend/job_executor/pq_executor.py +235 -0
  361. lmcache/v1/storage_backend/local_cpu_backend.py +810 -0
  362. lmcache/v1/storage_backend/local_disk_backend.py +656 -0
  363. lmcache/v1/storage_backend/maru_backend.py +734 -0
  364. lmcache/v1/storage_backend/naive_serde/__init__.py +50 -0
  365. lmcache/v1/storage_backend/naive_serde/cachegen_basics.py +133 -0
  366. lmcache/v1/storage_backend/naive_serde/cachegen_decoder.py +135 -0
  367. lmcache/v1/storage_backend/naive_serde/cachegen_encoder.py +83 -0
  368. lmcache/v1/storage_backend/naive_serde/kivi_serde.py +22 -0
  369. lmcache/v1/storage_backend/naive_serde/naive_serde.py +18 -0
  370. lmcache/v1/storage_backend/naive_serde/serde.py +37 -0
  371. lmcache/v1/storage_backend/native_clients/connector_client_base.py +165 -0
  372. lmcache/v1/storage_backend/native_clients/resp_client.py +35 -0
  373. lmcache/v1/storage_backend/nixl_storage_backend.py +1400 -0
  374. lmcache/v1/storage_backend/p2p_backend.py +788 -0
  375. lmcache/v1/storage_backend/path_sharder.py +117 -0
  376. lmcache/v1/storage_backend/pd_backend.py +646 -0
  377. lmcache/v1/storage_backend/plugins/dax_backend.py +1443 -0
  378. lmcache/v1/storage_backend/plugins/rust_raw_block_backend.py +1361 -0
  379. lmcache/v1/storage_backend/remote_backend.py +624 -0
  380. lmcache/v1/storage_backend/resp_client.py +227 -0
  381. lmcache/v1/storage_backend/storage_backend_listener.py +19 -0
  382. lmcache/v1/storage_backend/storage_manager.py +1352 -0
  383. lmcache/v1/system_detection.py +110 -0
  384. lmcache/v1/token_database.py +551 -0
  385. lmcache/v1/transfer_channel/__init__.py +83 -0
  386. lmcache/v1/transfer_channel/abstract.py +285 -0
  387. lmcache/v1/transfer_channel/mock_memory_channel.py +156 -0
  388. lmcache/v1/transfer_channel/nixl_channel.py +639 -0
  389. lmcache/v1/transfer_channel/py_socket_channel.py +260 -0
  390. lmcache/v1/transfer_channel/transfer_utils.py +63 -0
  391. lmcache/v1/utils/__init__.py +1 -0
  392. lmcache/v1/utils/bloom_filter.py +109 -0
  393. lmcache/v1/utils/cache_utils.py +125 -0
  394. lmcache_cli-0.4.5.dev0.dist-info/METADATA +185 -0
  395. lmcache_cli-0.4.5.dev0.dist-info/RECORD +399 -0
  396. lmcache_cli-0.4.5.dev0.dist-info/WHEEL +5 -0
  397. lmcache_cli-0.4.5.dev0.dist-info/entry_points.txt +2 -0
  398. lmcache_cli-0.4.5.dev0.dist-info/licenses/LICENSE +201 -0
  399. lmcache_cli-0.4.5.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,193 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """
3
+ Prefetch policy interface and default implementation for L2-to-L1 load decisions.
4
+
5
+ The prefetch policy decides which L2 adapter should load each key when multiple
6
+ adapters have the same key. It receives lookup results (bitmaps) from all adapters
7
+ and produces a load plan mapping each adapter to the key indices it should load.
8
+ """
9
+
10
+ # Standard
11
+ from abc import ABC, abstractmethod
12
+
13
+ # First Party
14
+ from lmcache.native_storage_ops import Bitmap
15
+ from lmcache.v1.distributed.api import ObjectKey
16
+ from lmcache.v1.distributed.storage_controllers.store_policy import (
17
+ AdapterDescriptor,
18
+ )
19
+
20
+
21
+ class PrefetchPolicy(ABC):
22
+ """
23
+ Abstract interface for prefetch load-plan decisions.
24
+
25
+ The prefetch policy is called by the PrefetchController after all L2
26
+ adapters have completed their lookup_and_lock operations. Given the
27
+ lookup results, it decides which adapter should load which keys.
28
+ """
29
+
30
+ @abstractmethod
31
+ def select_load_plan(
32
+ self,
33
+ keys: list[ObjectKey],
34
+ lookup_results: dict[int, Bitmap],
35
+ adapters: list[AdapterDescriptor],
36
+ ) -> dict[int, Bitmap]:
37
+ """
38
+ Decide which adapter loads which keys.
39
+
40
+ Args:
41
+ keys: Full list of keys being prefetched from L2.
42
+ lookup_results: Mapping from adapter index to Bitmap.
43
+ A set bit at position i means the adapter has keys[i].
44
+ adapters: Descriptors of available L2 adapters.
45
+
46
+ Returns:
47
+ Mapping from adapter index to a bitmap telling which keys that
48
+ the adapter should load. The returned bitmaps should not
49
+ overlap, and the union of all returned bitmaps should be a subset
50
+ of the union of the input bitmaps.
51
+ """
52
+
53
+ def select_l1_retentions(
54
+ self,
55
+ keys: list[ObjectKey],
56
+ ) -> list[bool]:
57
+ """Determine which keys to retain in L1 after prefetched
58
+ objects are consumed.
59
+
60
+ Called by PrefetchController just before
61
+ ``l1_mgr.reserve_write`` to build the ``is_temporary``
62
+ flags. A ``True`` value means the key is retained
63
+ (permanent); ``False`` means it is temporary and will be
64
+ deleted after the reader finishes.
65
+
66
+ The default implementation marks all keys as temporary
67
+ (not retained). Override in subclasses to implement
68
+ hot-cache or selective-retention strategies.
69
+
70
+ Args:
71
+ keys: Keys about to be written into L1.
72
+
73
+ Returns:
74
+ A list of bools with the same length as *keys*.
75
+ ``True`` = retain (permanent), ``False`` = temporary.
76
+ """
77
+ return [False] * len(keys)
78
+
79
+
80
+ # -----------------------------------------------------------------------------
81
+ # Registry: prefetch policy name -> policy class
82
+ # -----------------------------------------------------------------------------
83
+
84
+ _PREFETCH_POLICY_REGISTRY: dict[str, type[PrefetchPolicy]] = {}
85
+
86
+
87
+ def register_prefetch_policy(
88
+ name: str,
89
+ policy_cls: type[PrefetchPolicy],
90
+ ) -> None:
91
+ """
92
+ Register a prefetch policy class under a name.
93
+
94
+ Each policy module should call this at import time.
95
+
96
+ Args:
97
+ name: Policy name (e.g. "default").
98
+ policy_cls: A concrete PrefetchPolicy subclass.
99
+ """
100
+ if name in _PREFETCH_POLICY_REGISTRY:
101
+ raise ValueError(f"Prefetch policy already registered: {name!r}")
102
+ _PREFETCH_POLICY_REGISTRY[name] = policy_cls
103
+
104
+
105
+ def get_registered_prefetch_policies() -> list[str]:
106
+ """Return the list of registered prefetch policy names."""
107
+ return list(_PREFETCH_POLICY_REGISTRY)
108
+
109
+
110
+ def create_prefetch_policy(name: str) -> PrefetchPolicy:
111
+ """
112
+ Create a prefetch policy instance by name.
113
+
114
+ Args:
115
+ name: Registered policy name.
116
+
117
+ Returns:
118
+ A new PrefetchPolicy instance.
119
+
120
+ Raises:
121
+ ValueError: If no policy is registered under the given name.
122
+ """
123
+ if name not in _PREFETCH_POLICY_REGISTRY:
124
+ known = ", ".join(sorted(_PREFETCH_POLICY_REGISTRY)) or "(none)"
125
+ raise ValueError(f"Unknown prefetch policy {name!r}. Known: {known}")
126
+ return _PREFETCH_POLICY_REGISTRY[name]()
127
+
128
+
129
+ class DefaultPrefetchPolicy(PrefetchPolicy):
130
+ """
131
+ Default prefetch policy: for each key, pick the first adapter
132
+ (lowest index) that has it.
133
+ """
134
+
135
+ def select_load_plan(
136
+ self,
137
+ keys: list[ObjectKey],
138
+ lookup_results: dict[int, Bitmap],
139
+ adapters: list[AdapterDescriptor],
140
+ ) -> dict[int, Bitmap]:
141
+ """
142
+ Assign each key to the first adapter (by index) that has it.
143
+
144
+ Args:
145
+ keys: Full list of keys being prefetched.
146
+ lookup_results: Adapter index -> Bitmap of lookup hits.
147
+ adapters: Descriptors of available L2 adapters.
148
+
149
+ Returns:
150
+ Mapping from adapter index to key bitmaps. Each key goes
151
+ to the lowest-indexed adapter that reported having it.
152
+ """
153
+ plan: dict[int, Bitmap] = {}
154
+ global_bitmap = Bitmap(len(keys))
155
+ for bitmap in lookup_results.values():
156
+ global_bitmap |= bitmap
157
+
158
+ for ad in sorted(adapters, key=lambda a: a.index):
159
+ curr_bitmap = lookup_results.get(ad.index)
160
+ if curr_bitmap is None:
161
+ continue
162
+
163
+ local_bitmap = global_bitmap & curr_bitmap
164
+ global_bitmap &= ~local_bitmap
165
+ if local_bitmap.popcount() == 0:
166
+ continue
167
+
168
+ plan[ad.index] = local_bitmap
169
+
170
+ return plan
171
+
172
+
173
+ class RetainPrefetchPolicy(DefaultPrefetchPolicy):
174
+ """Prefetch policy that retains all prefetched keys in L1.
175
+
176
+ Inherits ``select_load_plan`` from ``DefaultPrefetchPolicy``
177
+ (first-adapter-wins) and only overrides the L1 retention
178
+ decision: all prefetched keys become permanent.
179
+
180
+ Use this when prefetched data is likely to be reused by
181
+ subsequent requests (e.g. shared system-prompt chunks).
182
+ """
183
+
184
+ def select_l1_retentions(
185
+ self,
186
+ keys: list[ObjectKey],
187
+ ) -> list[bool]:
188
+ """Retain all prefetched keys permanently in L1."""
189
+ return [True] * len(keys)
190
+
191
+
192
+ register_prefetch_policy("default", DefaultPrefetchPolicy)
193
+ register_prefetch_policy("retain", RetainPrefetchPolicy)
@@ -0,0 +1,452 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """
3
+ Store Controller: asynchronously copies data from L1 to L2 after writes complete.
4
+
5
+ The controller runs a background thread with an event-driven loop that:
6
+ 1. Listens for L1 write-completion events via StoreListener.
7
+ 2. Submits store tasks to L2 adapters based on StorePolicy decisions.
8
+ 3. Monitors L2 task completion via event fds.
9
+ 4. Releases L1 read locks and optionally deletes keys from L1.
10
+ """
11
+
12
+ # Standard
13
+ from dataclasses import dataclass
14
+ import os
15
+ import select
16
+ import threading
17
+
18
+ # First Party
19
+ from lmcache.logging import init_logger
20
+ from lmcache.v1.distributed.api import ObjectKey
21
+ from lmcache.v1.distributed.error import L1Error
22
+ from lmcache.v1.distributed.internal_api import L1ManagerListener
23
+ from lmcache.v1.distributed.l1_manager import L1Manager
24
+ from lmcache.v1.distributed.l2_adapters.base import L2AdapterInterface, L2TaskId
25
+ from lmcache.v1.distributed.storage_controller import StorageControllerInterface
26
+ from lmcache.v1.distributed.storage_controllers.store_policy import (
27
+ AdapterDescriptor,
28
+ StorePolicy,
29
+ )
30
+ from lmcache.v1.mp_observability.event import Event, EventType
31
+ from lmcache.v1.mp_observability.event_bus import get_event_bus
32
+
33
+ logger = init_logger(__name__)
34
+
35
+ # Poll timeout in milliseconds for the store loop
36
+ STORE_LOOP_POLL_TIMEOUT_MS = 500
37
+
38
+
39
+ # Helper classes (module-level, before main class)
40
+
41
+
42
+ class StoreListener(L1ManagerListener):
43
+ """
44
+ Listener that receives L1 write-completion callbacks and enqueues
45
+ keys for the StoreController's background loop.
46
+
47
+ The ``on_keys_write_finished`` callback is invoked inside L1Manager's
48
+ lock, so it must be non-blocking. It appends keys to an internal list
49
+ and signals an eventfd to wake up the controller's select.poll().
50
+ """
51
+
52
+ def __init__(self) -> None:
53
+ self._pending_keys: list[ObjectKey] = []
54
+ self._lock = threading.Lock()
55
+ self._event_fd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
56
+
57
+ def get_event_fd(self) -> int:
58
+ """
59
+ Return the eventfd that is signaled when new keys are available.
60
+
61
+ Returns:
62
+ int: The eventfd file descriptor.
63
+ """
64
+ return self._event_fd
65
+
66
+ def pop_pending_keys(self) -> list[ObjectKey]:
67
+ """
68
+ Pop all pending keys from the queue.
69
+
70
+ This is non-blocking and should be called by the StoreController's
71
+ main loop after select.poll() indicates the eventfd is ready.
72
+
73
+ Returns:
74
+ list[ObjectKey]: All keys enqueued since the last pop.
75
+ """
76
+ with self._lock:
77
+ keys = self._pending_keys
78
+ self._pending_keys = []
79
+ return keys
80
+
81
+ def pending_count(self) -> int:
82
+ """Return the number of pending keys waiting to be processed."""
83
+ with self._lock:
84
+ return len(self._pending_keys)
85
+
86
+ # L1ManagerListener implementation
87
+
88
+ def on_l1_keys_write_finished(self, keys: list[ObjectKey]) -> None:
89
+ """
90
+ Enqueue keys and signal the eventfd.
91
+
92
+ Called inside L1Manager's lock. Must be fast and must not
93
+ call any L1Manager methods (would deadlock).
94
+
95
+ Args:
96
+ keys (list[ObjectKey]): Keys that finished writing.
97
+ """
98
+ with self._lock:
99
+ self._pending_keys.extend(keys)
100
+ os.eventfd_write(self._event_fd, 1)
101
+
102
+ def on_l1_keys_reserved_read(self, keys: list[ObjectKey]) -> None:
103
+ pass
104
+
105
+ def on_l1_keys_read_finished(self, keys: list[ObjectKey]) -> None:
106
+ pass
107
+
108
+ def on_l1_keys_reserved_write(self, keys: list[ObjectKey]) -> None:
109
+ pass
110
+
111
+ def on_l1_keys_deleted_by_manager(self, keys: list[ObjectKey]) -> None:
112
+ pass
113
+
114
+ def on_l1_keys_finish_write_and_reserve_read(self, keys: list[ObjectKey]) -> None:
115
+ # No op here because we don't want to trigger store when the
116
+ # objects are prefetched to L1.
117
+ pass
118
+
119
+ def on_l1_keys_accessed(self, keys: list[ObjectKey]) -> None:
120
+ pass
121
+
122
+ def close(self) -> None:
123
+ """Close the eventfd."""
124
+ os.close(self._event_fd)
125
+
126
+
127
+ @dataclass
128
+ class InFlightStoreTask:
129
+ """
130
+ Tracks a single submitted L2 store task so the controller can
131
+ release L1 read locks and perform cleanup when it completes.
132
+ """
133
+
134
+ adapter_index: int
135
+ """Which L2 adapter this task was submitted to."""
136
+
137
+ keys: list[ObjectKey]
138
+ """All keys that were submitted in this store task."""
139
+
140
+ read_locked_keys: list[ObjectKey]
141
+ """The subset of keys for which reserve_read succeeded
142
+ (i.e., keys holding an L1 read lock that must be released)."""
143
+
144
+
145
+ # Main class
146
+
147
+
148
+ class StoreController(StorageControllerInterface):
149
+ """
150
+ Asynchronously stores L1 data to L2 adapters after write completion.
151
+
152
+ The controller:
153
+ 1. Registers a StoreListener with L1Manager to receive
154
+ on_keys_write_finished callbacks.
155
+ 2. Runs a background thread with an event-driven loop using
156
+ select.poll() on the listener eventfd and all L2 adapter
157
+ store eventfds.
158
+ 3. On new keys: consults StorePolicy to decide targets,
159
+ calls reserve_read to get MemoryObjs, and submits store
160
+ tasks to L2 adapters.
161
+ 4. On L2 task completion: releases read locks, optionally
162
+ deletes keys from L1 per policy.
163
+
164
+ Args:
165
+ l1_manager: The L1 manager instance.
166
+ l2_adapters: List of L2 adapter instances.
167
+ adapter_descriptors: Descriptors for each L2 adapter (same order).
168
+ policy: The store policy for deciding targets and deletions.
169
+ """
170
+
171
+ def __init__(
172
+ self,
173
+ l1_manager: L1Manager,
174
+ l2_adapters: list[L2AdapterInterface],
175
+ adapter_descriptors: list[AdapterDescriptor],
176
+ policy: StorePolicy,
177
+ ) -> None:
178
+ self._l1_manager = l1_manager
179
+ self._l2_adapters = l2_adapters
180
+ self._adapter_descriptors = adapter_descriptors
181
+ self._policy = policy
182
+
183
+ self._listener = StoreListener()
184
+ self._l1_manager.register_listener(self._listener)
185
+ self._event_bus = get_event_bus()
186
+
187
+ # (adapter_index, task_id) -> InFlightStoreTask
188
+ # Composite key is needed because task IDs are only unique
189
+ # within a single adapter, not across adapters.
190
+ self._in_flight_tasks: dict[tuple[int, L2TaskId], InFlightStoreTask] = {}
191
+
192
+ # Shadow counter for status reporting (updated in background loop)
193
+ self._status_in_flight_count: int = 0
194
+
195
+ # Map eventfd -> adapter index for quick lookup in poll results
196
+ self._efd_to_adapter_index: dict[int, int] = {}
197
+ for i, adapter in enumerate(self._l2_adapters):
198
+ efd = adapter.get_store_event_fd()
199
+ self._efd_to_adapter_index[efd] = i
200
+
201
+ self._stop_flag = threading.Event()
202
+ self._thread = threading.Thread(
203
+ target=self._store_loop,
204
+ daemon=True,
205
+ )
206
+
207
+ def start(self) -> None:
208
+ """Start the background store loop thread."""
209
+ logger.info("Starting StoreController...")
210
+ self._thread.start()
211
+
212
+ def stop(self) -> None:
213
+ """
214
+ Signal the loop to stop, wait for the thread to join.
215
+
216
+ Releases all in-flight read locks on shutdown so that
217
+ L1 objects are not permanently locked.
218
+ """
219
+ self._stop_flag.set()
220
+ # Wake up the poll loop so it can exit promptly
221
+ os.eventfd_write(self._listener.get_event_fd(), 1)
222
+ self._thread.join()
223
+ self._cleanup_in_flight_tasks()
224
+ self._listener.close()
225
+
226
+ def report_status(self) -> dict:
227
+ """Return a status dict for the store controller."""
228
+ is_healthy = self._thread.is_alive()
229
+ return {
230
+ "is_healthy": is_healthy,
231
+ "thread_alive": is_healthy,
232
+ "pending_keys_count": self._listener.pending_count(),
233
+ "in_flight_task_count": self._status_in_flight_count,
234
+ "num_l2_adapters": len(self._l2_adapters),
235
+ }
236
+
237
+ # Private methods
238
+
239
+ def _store_loop(self) -> None:
240
+ """
241
+ Main event-driven loop running in a background thread.
242
+
243
+ Uses select.poll() to wait on:
244
+ - The StoreListener's eventfd (new keys from L1 writes).
245
+ - Each L2 adapter's store eventfd (completed store tasks).
246
+
247
+ Exits when the stop flag is set.
248
+ """
249
+ poller = select.poll()
250
+
251
+ listener_efd = self._listener.get_event_fd()
252
+ poller.register(listener_efd, select.POLLIN)
253
+
254
+ for efd in self._efd_to_adapter_index:
255
+ poller.register(efd, select.POLLIN)
256
+
257
+ while not self._stop_flag.is_set():
258
+ ready = poller.poll(STORE_LOOP_POLL_TIMEOUT_MS)
259
+
260
+ for fd, events in ready:
261
+ if not (events & select.POLLIN):
262
+ continue
263
+
264
+ # Consume the eventfd value
265
+ try:
266
+ os.eventfd_read(fd)
267
+ except (OSError, BlockingIOError):
268
+ pass
269
+
270
+ try:
271
+ if fd == listener_efd:
272
+ keys = self._listener.pop_pending_keys()
273
+ if keys:
274
+ self._process_new_keys(keys)
275
+ elif fd in self._efd_to_adapter_index:
276
+ adapter_index = self._efd_to_adapter_index[fd]
277
+ self._process_completed_tasks(adapter_index)
278
+ except Exception:
279
+ logger.exception(
280
+ "Unexpected error in store loop while processing fd %d",
281
+ fd,
282
+ )
283
+
284
+ def _process_new_keys(self, keys: list[ObjectKey]) -> None:
285
+ """
286
+ Process a batch of newly written keys.
287
+
288
+ 1. Ask the policy which adapters each key should go to.
289
+ 2. For each adapter target, reserve read access on L1 to get
290
+ MemoryObj references (skip keys that fail — best-effort).
291
+ 3. Submit store tasks to L2 adapters.
292
+ 4. Track in-flight tasks for later cleanup.
293
+
294
+ Args:
295
+ keys (list[ObjectKey]): Keys that finished writing to L1.
296
+ """
297
+ plan = self._policy.select_store_targets(keys, self._adapter_descriptors)
298
+
299
+ l1_mgr = self._l1_manager
300
+
301
+ for adapter_index, target_keys in plan.items():
302
+ if not target_keys:
303
+ continue
304
+
305
+ if adapter_index >= len(self._l2_adapters):
306
+ logger.error(
307
+ "StorePolicy returned invalid adapter index %d "
308
+ "(only %d adapters available). Skipping.",
309
+ adapter_index,
310
+ len(self._l2_adapters),
311
+ )
312
+ continue
313
+
314
+ # Reserve read to get MemoryObj references and hold read locks
315
+ read_results = l1_mgr.reserve_read(target_keys)
316
+
317
+ successful_keys = []
318
+ successful_objs = []
319
+ for key in target_keys:
320
+ result = read_results.get(key)
321
+ if result is None:
322
+ continue
323
+ err, obj = result
324
+ if err != L1Error.SUCCESS or obj is None:
325
+ logger.debug(
326
+ "Skipping key %s for L2 store (adapter %d): %s",
327
+ key,
328
+ adapter_index,
329
+ err,
330
+ )
331
+ continue
332
+ successful_keys.append(key)
333
+ successful_objs.append(obj)
334
+
335
+ if not successful_keys:
336
+ continue
337
+
338
+ adapter = self._l2_adapters[adapter_index]
339
+ task_id = adapter.submit_store_task(successful_keys, successful_objs)
340
+
341
+ self._in_flight_tasks[(adapter_index, task_id)] = InFlightStoreTask(
342
+ adapter_index=adapter_index,
343
+ keys=successful_keys,
344
+ read_locked_keys=list(successful_keys),
345
+ )
346
+ self._status_in_flight_count += 1
347
+
348
+ self._event_bus.publish(
349
+ Event(
350
+ event_type=EventType.L2_STORE_SUBMITTED,
351
+ metadata={
352
+ "adapter_index": adapter_index,
353
+ "key_count": len(successful_keys),
354
+ },
355
+ )
356
+ )
357
+
358
+ logger.debug(
359
+ "Submitted store task %d to adapter %d with %d keys.",
360
+ task_id,
361
+ adapter_index,
362
+ len(successful_keys),
363
+ )
364
+
365
+ def _process_completed_tasks(self, adapter_index: int) -> None:
366
+ """
367
+ Process completed store tasks for a given adapter.
368
+
369
+ 1. Pop all completed tasks from the adapter.
370
+ 2. Release L1 read locks for each task.
371
+ 3. If the task succeeded, ask the policy which keys to
372
+ delete from L1 and delete them.
373
+ 4. Remove the task from in-flight tracking.
374
+
375
+ Args:
376
+ adapter_index (int): Index of the adapter whose eventfd
377
+ was signaled.
378
+ """
379
+ adapter = self._l2_adapters[adapter_index]
380
+ completed = adapter.pop_completed_store_tasks()
381
+
382
+ l1_mgr = self._l1_manager
383
+
384
+ for task_id, success in completed.items():
385
+ composite_key = (adapter_index, task_id)
386
+ task = self._in_flight_tasks.pop(composite_key, None)
387
+ if task is not None:
388
+ self._status_in_flight_count -= 1
389
+ if task is None:
390
+ logger.warning(
391
+ "Completed store task %d (adapter %d) not found in tracking.",
392
+ task_id,
393
+ adapter_index,
394
+ )
395
+ continue
396
+
397
+ # Always release read locks
398
+ l1_mgr.finish_read(task.read_locked_keys)
399
+
400
+ if success:
401
+ self._event_bus.publish(
402
+ Event(
403
+ event_type=EventType.L2_STORE_COMPLETED,
404
+ metadata={
405
+ "adapter_index": adapter_index,
406
+ "succeeded_count": len(task.keys),
407
+ "failed_count": 0,
408
+ },
409
+ )
410
+ )
411
+ logger.debug(
412
+ "L2 store task %d completed: adapter %d, %d keys.",
413
+ task_id,
414
+ adapter_index,
415
+ len(task.keys),
416
+ )
417
+ delete_keys = self._policy.select_l1_deletions(task.keys)
418
+ if delete_keys:
419
+ l1_mgr.delete(delete_keys)
420
+ else:
421
+ self._event_bus.publish(
422
+ Event(
423
+ event_type=EventType.L2_STORE_COMPLETED,
424
+ metadata={
425
+ "adapter_index": adapter_index,
426
+ "succeeded_count": 0,
427
+ "failed_count": len(task.keys),
428
+ },
429
+ )
430
+ )
431
+ logger.warning(
432
+ "Store task %d to adapter %d failed for keys: %s",
433
+ task_id,
434
+ adapter_index,
435
+ task.keys,
436
+ )
437
+
438
+ def _cleanup_in_flight_tasks(self) -> None:
439
+ """
440
+ Release all held read locks for any in-flight tasks that
441
+ haven't completed. Called during stop().
442
+ """
443
+ l1_mgr = self._l1_manager
444
+ for (adapter_index, task_id), task in self._in_flight_tasks.items():
445
+ logger.warning(
446
+ "Cleaning up in-flight store task %d (adapter %d, %d keys).",
447
+ task_id,
448
+ adapter_index,
449
+ len(task.read_locked_keys),
450
+ )
451
+ l1_mgr.finish_read(task.read_locked_keys)
452
+ self._in_flight_tasks.clear()