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,135 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """
3
+ Mooncake Store native L2 adapter config and factory.
4
+ """
5
+
6
+ # Future
7
+ from __future__ import annotations
8
+
9
+ # Standard
10
+ from typing import (
11
+ TYPE_CHECKING,
12
+ Dict,
13
+ Optional,
14
+ )
15
+
16
+ if TYPE_CHECKING:
17
+ from lmcache.v1.distributed.internal_api import (
18
+ L1MemoryDesc,
19
+ )
20
+
21
+ # First Party
22
+ from lmcache.logging import init_logger
23
+ from lmcache.v1.distributed.l2_adapters.base import (
24
+ L2AdapterInterface,
25
+ )
26
+ from lmcache.v1.distributed.l2_adapters.config import (
27
+ L2AdapterConfigBase,
28
+ register_l2_adapter_type,
29
+ )
30
+ from lmcache.v1.distributed.l2_adapters.factory import (
31
+ register_l2_adapter_factory,
32
+ )
33
+
34
+ logger = init_logger(__name__)
35
+
36
+ # Keys consumed only by LMCache (never sent to mooncake).
37
+ _LMCACHE_ONLY_KEYS = {"type", "num_workers", "eviction"}
38
+
39
+
40
+ class MooncakeStoreL2AdapterConfig(L2AdapterConfigBase):
41
+ """Config for an L2 adapter backed by the native
42
+ C++ Mooncake Store connector.
43
+
44
+ ``setup_config`` is a string-to-string dict that is
45
+ forwarded **as-is** to mooncake's
46
+ ``RealClient::setup_internal(ConfigDict)``.
47
+ LMCache does NOT interpret, validate, or fill in
48
+ defaults for any mooncake keys — that is mooncake's
49
+ responsibility.
50
+
51
+ ``num_workers`` is the only LMCache-specific knob.
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ setup_config: Dict[str, str],
57
+ num_workers: int = 4,
58
+ ):
59
+ super().__init__()
60
+ self.setup_config: Dict[str, str] = dict(setup_config)
61
+ self.num_workers = num_workers
62
+
63
+ @classmethod
64
+ def from_dict(cls, d: dict) -> "MooncakeStoreL2AdapterConfig":
65
+ num_workers = d.get("num_workers", 4)
66
+ if not isinstance(num_workers, int) or num_workers <= 0:
67
+ raise ValueError("num_workers must be a positive integer")
68
+
69
+ # Everything except LMCache-only keys is
70
+ # forwarded to mooncake as str values.
71
+ setup: Dict[str, str] = {}
72
+ for k, v in d.items():
73
+ if k in _LMCACHE_ONLY_KEYS:
74
+ continue
75
+ if v is not None:
76
+ setup[k] = str(v)
77
+
78
+ return cls(
79
+ setup_config=setup,
80
+ num_workers=num_workers,
81
+ )
82
+
83
+ @classmethod
84
+ def help(cls) -> str:
85
+ return (
86
+ "Mooncake Store L2 adapter config.\n"
87
+ "All keys except LMCache-only keys are "
88
+ "forwarded as-is to mooncake's "
89
+ "setup_internal(ConfigDict).\n"
90
+ "Refer to mooncake documentation for "
91
+ "available setup keys.\n"
92
+ "- num_workers (int): C++ worker threads "
93
+ "(default 4, >0)"
94
+ )
95
+
96
+
97
+ def _create_mooncake_store_l2_adapter(
98
+ config: L2AdapterConfigBase,
99
+ l1_memory_desc: "Optional[L1MemoryDesc]" = None,
100
+ ) -> L2AdapterInterface:
101
+ """Create a NativeConnectorL2Adapter backed by the
102
+ C++ Mooncake Store connector."""
103
+ try:
104
+ # First Party
105
+ from lmcache.lmcache_mooncake import (
106
+ LMCacheMooncakeClient,
107
+ )
108
+ except ImportError as e:
109
+ raise RuntimeError(
110
+ "Mooncake Store L2 adapter requires the "
111
+ "C++ Mooncake extension. Build with: "
112
+ "MOONCAKE_INCLUDE_DIR=/path/to/mooncake-"
113
+ "store/include pip install -e ."
114
+ ) from e
115
+
116
+ # First Party
117
+ from lmcache.v1.distributed.l2_adapters.native_connector_l2_adapter import ( # noqa: E501
118
+ NativeConnectorL2Adapter,
119
+ )
120
+
121
+ assert isinstance(config, MooncakeStoreL2AdapterConfig)
122
+ native_client = LMCacheMooncakeClient(
123
+ config=config.setup_config,
124
+ num_workers=config.num_workers,
125
+ )
126
+ logger.info(
127
+ "Created Mooncake Store L2 adapter (workers=%d)",
128
+ config.num_workers,
129
+ )
130
+ return NativeConnectorL2Adapter(native_client)
131
+
132
+
133
+ # Self-register config type and adapter factory
134
+ register_l2_adapter_type("mooncake_store", MooncakeStoreL2AdapterConfig)
135
+ register_l2_adapter_factory("mooncake_store", _create_mooncake_store_l2_adapter)
@@ -0,0 +1,468 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """
3
+ L2 adapter that wraps any pybind-wrapped C++ IStorageConnector (native client).
4
+
5
+ This bridge lets any native storage connector (Redis, RDMA, Mooncake, etc.)
6
+ serve as an MP-mode L2 adapter. The same C++ connector implementation is
7
+ also usable in non-MP mode via ConnectorClientBase.
8
+
9
+ Architecture:
10
+ - The native client has 1 eventfd + drain_completions() for all operations.
11
+ - This adapter creates 3 Python eventfds (store, lookup, load) and runs a
12
+ background demux thread that routes native completions to the right
13
+ category based on a future_id → op_type mapping.
14
+ - ObjectKey serialization and MemoryObj buffer extraction happen at the
15
+ submit call boundary.
16
+ - Locking is client-side (refcount dict) since remote backends don't have
17
+ our eviction concept.
18
+ """
19
+
20
+ # Future
21
+ from __future__ import annotations
22
+
23
+ # Standard
24
+ from collections import defaultdict
25
+ import os
26
+ import select
27
+ import threading
28
+
29
+ # First Party
30
+ from lmcache.logging import init_logger
31
+ from lmcache.native_storage_ops import Bitmap
32
+ from lmcache.v1.distributed.api import ObjectKey
33
+ from lmcache.v1.distributed.l2_adapters.base import (
34
+ L2AdapterInterface,
35
+ L2TaskId,
36
+ )
37
+ from lmcache.v1.memory_management import MemoryObj
38
+
39
+ logger = init_logger(__name__)
40
+
41
+
42
+ # Key separator — kept in sync with fs_l2_adapter.py and
43
+ # csrc/storage_backends/fs/connector.cpp. Both ``@`` in ``model_name``
44
+ # and ``@`` in ``cache_salt`` are rejected by ObjectKey.__post_init__
45
+ # so splitting on ``@`` is unambiguous.
46
+ _KEY_SEP = "@"
47
+
48
+
49
+ def _object_key_to_string(key: ObjectKey) -> str:
50
+ """Serialize an ObjectKey to the native-connector wire format.
51
+
52
+ Unsalted::
53
+
54
+ <model_name>@<kv_rank_hex>@<chunk_hash_hex>
55
+
56
+ Salted (trailing ``cache_salt``)::
57
+
58
+ <model_name>@<kv_rank_hex>@<chunk_hash_hex>@<cache_salt>
59
+
60
+ Keys with ``cache_salt=""`` produce the 3-field shape, which is
61
+ bit-identical to the format used before ``cache_salt`` existed —
62
+ so existing un-salted caches remain valid with no migration.
63
+ """
64
+ base = (
65
+ f"{key.model_name}{_KEY_SEP}{key.kv_rank:08x}{_KEY_SEP}{key.chunk_hash.hex()}"
66
+ )
67
+ if key.cache_salt:
68
+ return f"{base}{_KEY_SEP}{key.cache_salt}"
69
+ return base
70
+
71
+
72
+ def _obj_to_memoryview(
73
+ obj: MemoryObj,
74
+ ) -> memoryview: # type: ignore[type-arg]
75
+ """
76
+ Extract a byte-oriented memoryview from a MemoryObj.
77
+
78
+ Uses the MemoryObj's byte_array property which returns
79
+ a ctypes-backed memoryview with itemsize=1, so pybind's
80
+ buffer_info.size == num_bytes.
81
+ """
82
+ return obj.byte_array # type: ignore[return-value]
83
+
84
+
85
+ class NativeConnectorL2Adapter(L2AdapterInterface):
86
+ """
87
+ Wraps a pybind-wrapped C++ IStorageConnector to
88
+ implement L2AdapterInterface.
89
+
90
+ The native_client must expose:
91
+ - event_fd() -> int
92
+ - submit_batch_get(keys, memoryviews) -> int
93
+ - submit_batch_set(keys, memoryviews) -> int
94
+ - submit_batch_exists(keys) -> int
95
+ - drain_completions()
96
+ -> list[tuple[int, bool, str, list[bool]|None]]
97
+ - close()
98
+ """
99
+
100
+ # Operation type tags for the pending-ops map
101
+ _OP_STORE = "store"
102
+ _OP_LOOKUP = "lookup"
103
+ _OP_LOAD = "load"
104
+ _OP_DELETE = "delete"
105
+
106
+ def __init__(self, native_client, max_capacity_gb: float = 0):
107
+ super().__init__()
108
+ self._client = native_client
109
+ self._client_fd: int = int(native_client.event_fd())
110
+
111
+ # 3 distinct Python eventfds for the L2 adapter
112
+ # interface
113
+ self._store_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
114
+ self._lookup_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
115
+ self._load_efd = os.eventfd(0, os.EFD_NONBLOCK | os.EFD_CLOEXEC)
116
+
117
+ # Pending ops: native future_id →
118
+ # (op_type, task_id, num_keys, keys_for_locking)
119
+ # keys_for_locking is only set for lookup ops so
120
+ # we can apply locks
121
+ self._pending_ops: dict[
122
+ int,
123
+ tuple[str, L2TaskId, int, list[ObjectKey] | None],
124
+ ] = {}
125
+
126
+ # Completed results (same pattern as MockL2Adapter)
127
+ self._completed_stores: dict[L2TaskId, bool] = {}
128
+ self._completed_lookups: dict[L2TaskId, Bitmap] = {}
129
+ self._completed_loads: dict[L2TaskId, Bitmap] = {}
130
+
131
+ # Client-side lock tracking (refcount per key)
132
+ self._locked_keys: dict[ObjectKey, int] = defaultdict(int)
133
+
134
+ # Delete capability detection
135
+ self._has_delete = callable(getattr(native_client, "submit_batch_delete", None))
136
+
137
+ # Pending delete events for synchronous delete() calls
138
+ self._pending_delete_events: dict[L2TaskId, threading.Event] = {}
139
+
140
+ # Client-side size tracking for get_usage()
141
+ self._max_capacity_bytes = int(max_capacity_gb * (1024**3))
142
+ self._current_size_bytes: int = 0
143
+ self._key_sizes: dict[ObjectKey, int] = {}
144
+ # Pending store sizes: native future_id -> (keys, per_key_sizes)
145
+ self._pending_store_sizes: dict[int, tuple[list[ObjectKey], list[int]]] = {}
146
+
147
+ # Task ID counter
148
+ self._next_task_id: L2TaskId = 0
149
+
150
+ # Lock for all shared state above
151
+ self._lock = threading.Lock()
152
+
153
+ # Background demux thread
154
+ self._stop = threading.Event()
155
+ self._demux_thread = threading.Thread(
156
+ target=self._demux_loop,
157
+ daemon=True,
158
+ name="l2-adapter-demux",
159
+ )
160
+ self._demux_thread.start()
161
+
162
+ # ---------------------------------------------------------------
163
+ # Event Fd Interface
164
+ # ---------------------------------------------------------------
165
+
166
+ def get_store_event_fd(self) -> int:
167
+ return self._store_efd
168
+
169
+ def get_lookup_and_lock_event_fd(self) -> int:
170
+ return self._lookup_efd
171
+
172
+ def get_load_event_fd(self) -> int:
173
+ return self._load_efd
174
+
175
+ # ---------------------------------------------------------------
176
+ # Store Interface
177
+ # ---------------------------------------------------------------
178
+
179
+ def submit_store_task(
180
+ self,
181
+ keys: list[ObjectKey],
182
+ objects: list[MemoryObj],
183
+ ) -> L2TaskId:
184
+ key_strings = [_object_key_to_string(k) for k in keys]
185
+ memviews = [_obj_to_memoryview(obj) for obj in objects]
186
+ per_key_sizes = [obj.get_size() for obj in objects]
187
+
188
+ # Register pending op BEFORE submit to avoid race
189
+ # with demux thread. The native submit is
190
+ # non-blocking so holding the lock is brief.
191
+ with self._lock:
192
+ task_id = self._get_next_task_id()
193
+ future_id = int(self._client.submit_batch_set(key_strings, memviews))
194
+ self._pending_ops[future_id] = (
195
+ self._OP_STORE,
196
+ task_id,
197
+ len(keys),
198
+ None,
199
+ )
200
+ self._pending_store_sizes[future_id] = (list(keys), per_key_sizes)
201
+
202
+ return task_id
203
+
204
+ def pop_completed_store_tasks(
205
+ self,
206
+ ) -> dict[L2TaskId, bool]:
207
+ with self._lock:
208
+ completed = self._completed_stores
209
+ self._completed_stores = {}
210
+ return completed
211
+
212
+ # ---------------------------------------------------------------
213
+ # Lookup and Lock Interface
214
+ # ---------------------------------------------------------------
215
+
216
+ def submit_lookup_and_lock_task(
217
+ self,
218
+ keys: list[ObjectKey],
219
+ ) -> L2TaskId:
220
+ key_strings = [_object_key_to_string(k) for k in keys]
221
+
222
+ with self._lock:
223
+ task_id = self._get_next_task_id()
224
+ future_id = int(self._client.submit_batch_exists(key_strings))
225
+ self._pending_ops[future_id] = (
226
+ self._OP_LOOKUP,
227
+ task_id,
228
+ len(keys),
229
+ list(keys),
230
+ )
231
+
232
+ return task_id
233
+
234
+ def query_lookup_and_lock_result(self, task_id: L2TaskId) -> Bitmap | None:
235
+ with self._lock:
236
+ return self._completed_lookups.pop(task_id, None)
237
+
238
+ def submit_unlock(self, keys: list[ObjectKey]) -> None:
239
+ with self._lock:
240
+ for key in keys:
241
+ if key not in self._locked_keys:
242
+ continue
243
+ if self._locked_keys[key] <= 1:
244
+ del self._locked_keys[key]
245
+ else:
246
+ self._locked_keys[key] -= 1
247
+
248
+ # ---------------------------------------------------------------
249
+ # Load Interface
250
+ # ---------------------------------------------------------------
251
+
252
+ def submit_load_task(
253
+ self,
254
+ keys: list[ObjectKey],
255
+ objects: list[MemoryObj],
256
+ ) -> L2TaskId:
257
+ key_strings = [_object_key_to_string(k) for k in keys]
258
+ memviews = [_obj_to_memoryview(obj) for obj in objects]
259
+
260
+ with self._lock:
261
+ task_id = self._get_next_task_id()
262
+ future_id = int(self._client.submit_batch_get(key_strings, memviews))
263
+ self._pending_ops[future_id] = (
264
+ self._OP_LOAD,
265
+ task_id,
266
+ len(keys),
267
+ list(keys),
268
+ )
269
+
270
+ return task_id
271
+
272
+ def query_load_result(self, task_id: L2TaskId) -> Bitmap | None:
273
+ with self._lock:
274
+ return self._completed_loads.pop(task_id, None)
275
+
276
+ # ---------------------------------------------------------------
277
+ # Eviction Interface
278
+ # ---------------------------------------------------------------
279
+
280
+ def delete(self, keys: list[ObjectKey]) -> None:
281
+ """Delete a batch of keys from the remote backend.
282
+
283
+ Submits a batch delete to the native connector and blocks
284
+ until the demux thread signals completion (up to 30s timeout).
285
+ Fires ``_notify_keys_deleted`` on success so eviction policy
286
+ tracking stays in sync.
287
+
288
+ No-op if the connector does not expose ``submit_batch_delete``
289
+ or if the key list is empty.
290
+ """
291
+ if not keys or not self._has_delete:
292
+ return
293
+
294
+ key_strings = [_object_key_to_string(k) for k in keys]
295
+ done_event = threading.Event()
296
+
297
+ with self._lock:
298
+ task_id = self._get_next_task_id()
299
+ future_id = int(self._client.submit_batch_delete(key_strings))
300
+ self._pending_ops[future_id] = (
301
+ self._OP_DELETE,
302
+ task_id,
303
+ len(keys),
304
+ list(keys),
305
+ )
306
+ self._pending_delete_events[task_id] = done_event
307
+
308
+ # Block until demux thread signals completion
309
+ if not done_event.wait(timeout=30.0):
310
+ with self._lock:
311
+ self._pending_delete_events.pop(task_id, None)
312
+ # Note: _pending_ops entry may already be consumed
313
+ # by the demux thread; pop is safe either way.
314
+ for fid, entry in list(self._pending_ops.items()):
315
+ if entry[1] == task_id:
316
+ self._pending_ops.pop(fid, None)
317
+ break
318
+ logger.warning(
319
+ "delete() timed out after 30s for %d keys",
320
+ len(keys),
321
+ )
322
+ return
323
+
324
+ self._notify_keys_deleted(keys)
325
+
326
+ def get_usage(self) -> tuple[float, float]:
327
+ if self._max_capacity_bytes <= 0:
328
+ return (-1.0, -1.0)
329
+ with self._lock:
330
+ usage = self._current_size_bytes / self._max_capacity_bytes
331
+ return (usage, usage)
332
+
333
+ # ---------------------------------------------------------------
334
+ # Cleanup
335
+ # ---------------------------------------------------------------
336
+
337
+ def close(self) -> None:
338
+ self._stop.set()
339
+ self._demux_thread.join(timeout=5)
340
+
341
+ self._client.close()
342
+
343
+ os.close(self._store_efd)
344
+ os.close(self._lookup_efd)
345
+ os.close(self._load_efd)
346
+
347
+ # ---------------------------------------------------------------
348
+ # Internal helpers
349
+ # ---------------------------------------------------------------
350
+
351
+ def _get_next_task_id(self) -> L2TaskId:
352
+ """Increment and return the next task ID.
353
+ Must be called under _lock."""
354
+ task_id = self._next_task_id
355
+ self._next_task_id += 1
356
+ return task_id
357
+
358
+ def _demux_loop(self) -> None:
359
+ """Background thread that polls the native
360
+ connector's eventfd, drains completions, and
361
+ routes them to the correct L2 result category.
362
+ """
363
+ poller = select.poll()
364
+ poller.register(self._client_fd, select.POLLIN)
365
+
366
+ while not self._stop.is_set():
367
+ events = poller.poll(500)
368
+ if not events:
369
+ continue
370
+
371
+ try:
372
+ completions = self._client.drain_completions()
373
+ except Exception:
374
+ logger.exception("drain_completions failed")
375
+ continue
376
+
377
+ if not completions:
378
+ continue
379
+
380
+ # Collect listener notifications to fire after
381
+ # releasing the lock.
382
+ keys_stored: list[ObjectKey] = []
383
+ keys_accessed: list[ObjectKey] = []
384
+
385
+ with self._lock:
386
+ for (
387
+ future_id,
388
+ ok,
389
+ error,
390
+ result_bools,
391
+ ) in completions:
392
+ fid = int(future_id)
393
+ entry = self._pending_ops.pop(fid, None)
394
+ if entry is None:
395
+ logger.warning(
396
+ "Received completion for unknown future_id=%d",
397
+ fid,
398
+ )
399
+ continue
400
+
401
+ (
402
+ op_type,
403
+ task_id,
404
+ num_keys,
405
+ lookup_keys,
406
+ ) = entry
407
+
408
+ if op_type == self._OP_STORE:
409
+ self._completed_stores[task_id] = ok
410
+ # Update size tracking on success
411
+ store_info = self._pending_store_sizes.pop(fid, None)
412
+ if ok and store_info is not None:
413
+ store_keys, sizes = store_info
414
+ for key, size in zip(store_keys, sizes, strict=False):
415
+ if key not in self._key_sizes:
416
+ self._key_sizes[key] = size
417
+ self._current_size_bytes += size
418
+ keys_stored.extend(store_keys)
419
+ os.eventfd_write(self._store_efd, 1)
420
+
421
+ elif op_type == self._OP_LOOKUP:
422
+ bitmap = Bitmap(num_keys)
423
+ if ok and result_bools is not None:
424
+ for i, found in enumerate(result_bools):
425
+ if found:
426
+ bitmap.set(i)
427
+ if lookup_keys is not None:
428
+ self._locked_keys[lookup_keys[i]] += 1
429
+ self._completed_lookups[task_id] = bitmap
430
+ os.eventfd_write(self._lookup_efd, 1)
431
+
432
+ elif op_type == self._OP_LOAD:
433
+ bitmap = Bitmap(num_keys)
434
+ loaded_keys: list[ObjectKey] = []
435
+ if result_bools is not None:
436
+ for i, loaded in enumerate(result_bools):
437
+ if loaded:
438
+ bitmap.set(i)
439
+ if lookup_keys is not None:
440
+ loaded_keys.append(lookup_keys[i])
441
+ elif ok:
442
+ # Fallback for connectors that
443
+ # do not report per-key results
444
+ for i in range(num_keys):
445
+ bitmap.set(i)
446
+ if lookup_keys is not None:
447
+ loaded_keys.extend(lookup_keys)
448
+ keys_accessed.extend(loaded_keys)
449
+ self._completed_loads[task_id] = bitmap
450
+ os.eventfd_write(self._load_efd, 1)
451
+
452
+ elif op_type == self._OP_DELETE:
453
+ # Decrement sizes for successfully deleted keys
454
+ if result_bools is not None and lookup_keys is not None:
455
+ for i, deleted in enumerate(result_bools):
456
+ if deleted:
457
+ key = lookup_keys[i]
458
+ size = self._key_sizes.pop(key, 0)
459
+ self._current_size_bytes -= size
460
+ evt = self._pending_delete_events.pop(task_id, None)
461
+ if evt is not None:
462
+ evt.set()
463
+
464
+ # Fire listener notifications outside the lock
465
+ if keys_stored:
466
+ self._notify_keys_stored(keys_stored)
467
+ if keys_accessed:
468
+ self._notify_keys_accessed(keys_accessed)