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,167 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ """Streaming reader for trace files.
4
+
5
+ The reader yields ``(Header, Iterator[Record])`` pairs. Records are
6
+ yielded lazily so that arbitrarily large traces can be inspected
7
+ without loading the whole file into memory.
8
+
9
+ Trailing partial frames (truncated by SIGKILL or filesystem buffering)
10
+ are detected and the iterator stops cleanly with a WARNING log.
11
+ """
12
+
13
+ # Future
14
+ from __future__ import annotations
15
+
16
+ # Standard
17
+ from typing import BinaryIO, Iterator
18
+ import struct
19
+
20
+ # First Party
21
+ from lmcache.logging import init_logger
22
+ from lmcache.v1.mp_observability.trace.format import (
23
+ FORMAT_VERSION,
24
+ MAGIC,
25
+ TRACE_SCHEMA_VERSION,
26
+ Header,
27
+ Record,
28
+ decode_header,
29
+ decode_record,
30
+ )
31
+
32
+ logger = init_logger(__name__)
33
+
34
+ _LEN_STRUCT = struct.Struct(">I")
35
+ _LEN_PREFIX = _LEN_STRUCT.size
36
+
37
+
38
+ class TraceReader:
39
+ """Streaming reader for a binary trace file.
40
+
41
+ Usage::
42
+
43
+ with TraceReader("/tmp/run.lct") as r:
44
+ header = r.header
45
+ for record in r.records():
46
+ ...
47
+
48
+ Closing the reader closes the underlying file handle.
49
+ """
50
+
51
+ def __init__(self, path: str) -> None:
52
+ self._path = path
53
+ self._fh: BinaryIO | None = open(path, "rb")
54
+ try:
55
+ self._header = self._read_header()
56
+ except Exception:
57
+ self._fh.close()
58
+ self._fh = None
59
+ raise
60
+
61
+ def __enter__(self) -> TraceReader:
62
+ return self
63
+
64
+ def __exit__(self, *_exc: object) -> None:
65
+ self.close()
66
+
67
+ @property
68
+ def header(self) -> Header:
69
+ """Return the file header. Always present; populated by
70
+ ``__init__``."""
71
+ return self._header
72
+
73
+ @property
74
+ def path(self) -> str:
75
+ """Path of the trace file."""
76
+ return self._path
77
+
78
+ def records(self) -> Iterator[Record]:
79
+ """Yield every record in the file in order.
80
+
81
+ Yields each :class:`Record` as it is read. When the file ends
82
+ cleanly (boundary aligned to a frame), iteration stops without
83
+ error. When a partial trailing frame is detected, a warning
84
+ is logged and iteration stops.
85
+ """
86
+ if self._fh is None:
87
+ raise RuntimeError("TraceReader is closed")
88
+ while True:
89
+ frame = self._read_frame(strict=False)
90
+ if frame is None:
91
+ return
92
+ try:
93
+ yield decode_record(frame)
94
+ except Exception as e:
95
+ logger.warning(
96
+ "TraceReader: skipping malformed record at offset %d: %s",
97
+ self._fh.tell(),
98
+ e,
99
+ )
100
+ continue
101
+
102
+ def close(self) -> None:
103
+ """Close the underlying file. Idempotent."""
104
+ if self._fh is not None:
105
+ self._fh.close()
106
+ self._fh = None
107
+
108
+ # ---- internal -----------------------------------------------------
109
+
110
+ def _read_header(self) -> Header:
111
+ frame = self._read_frame(strict=True)
112
+ if frame is None:
113
+ raise ValueError(f"trace file {self._path!r} is empty")
114
+ header = decode_header(frame)
115
+ if header.magic != MAGIC:
116
+ raise ValueError(
117
+ f"trace file {self._path!r}: bad magic "
118
+ f"(got {header.magic!r}, expected {MAGIC!r})"
119
+ )
120
+ if header.format_version != FORMAT_VERSION:
121
+ raise ValueError(
122
+ f"trace file {self._path!r}: unsupported format_version "
123
+ f"{header.format_version} (this build expects {FORMAT_VERSION})"
124
+ )
125
+ if header.trace_schema_version != TRACE_SCHEMA_VERSION:
126
+ raise ValueError(
127
+ f"trace file {self._path!r}: unsupported trace_schema_version "
128
+ f"{header.trace_schema_version} "
129
+ f"(this build expects {TRACE_SCHEMA_VERSION})"
130
+ )
131
+ return header
132
+
133
+ def _read_frame(self, strict: bool) -> bytes | None:
134
+ """Read one length-prefixed frame.
135
+
136
+ Returns ``None`` on clean EOF (when ``strict=False``). On
137
+ truncation in the middle of a frame, logs a WARNING and
138
+ returns ``None``. In ``strict=True`` mode, both partial and
139
+ empty reads raise.
140
+ """
141
+ assert self._fh is not None
142
+ prefix = self._fh.read(_LEN_PREFIX)
143
+ if not prefix:
144
+ if strict:
145
+ raise ValueError("unexpected EOF reading frame length")
146
+ return None
147
+ if len(prefix) < _LEN_PREFIX:
148
+ msg = (
149
+ f"truncated frame length prefix at offset "
150
+ f"{self._fh.tell() - len(prefix)}"
151
+ )
152
+ if strict:
153
+ raise ValueError(msg)
154
+ logger.warning("TraceReader: %s", msg)
155
+ return None
156
+ (length,) = _LEN_STRUCT.unpack(prefix)
157
+ body = self._fh.read(length)
158
+ if len(body) < length:
159
+ msg = (
160
+ f"truncated frame body at offset "
161
+ f"{self._fh.tell() - len(body)} (got {len(body)} of {length})"
162
+ )
163
+ if strict:
164
+ raise ValueError(msg)
165
+ logger.warning("TraceReader: %s", msg)
166
+ return None
167
+ return body
@@ -0,0 +1,300 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ """Trace recorder — writes TRACE_CALL events to a binary file.
4
+
5
+ Architecture:
6
+
7
+ * The recorder is an :class:`EventSubscriber` registered on the global
8
+ EventBus. Subscriber callbacks run on the EventBus drain thread, so
9
+ they are already off the request path.
10
+ * Encoding (codec + msgpack) and disk I/O happen synchronously inside
11
+ the callback. Adding a second worker thread would be premature
12
+ optimization; the EventBus drain thread already serves that role.
13
+ * Length-prefixed framing: each frame is written as a 4-byte
14
+ big-endian length followed by msgpack bytes. This keeps the reader
15
+ simple and tolerates partial-write tail truncation gracefully.
16
+ """
17
+
18
+ # Future
19
+ from __future__ import annotations
20
+
21
+ # Standard
22
+ from abc import ABC, abstractmethod
23
+ from dataclasses import asdict, is_dataclass
24
+ from typing import Any
25
+ import hashlib
26
+ import json
27
+ import os
28
+ import struct
29
+ import threading
30
+ import time
31
+
32
+ # First Party
33
+ from lmcache.logging import init_logger
34
+ from lmcache.v1.distributed.config import StorageManagerConfig
35
+ from lmcache.v1.mp_observability.event import Event, EventType
36
+ from lmcache.v1.mp_observability.event_bus import EventCallback, EventSubscriber
37
+ from lmcache.v1.mp_observability.trace import codecs
38
+ from lmcache.v1.mp_observability.trace.decorator import set_tracing_enabled
39
+ from lmcache.v1.mp_observability.trace.format import (
40
+ FORMAT_VERSION,
41
+ MAGIC,
42
+ TRACE_SCHEMA_VERSION,
43
+ Header,
44
+ Record,
45
+ encode_header,
46
+ encode_record,
47
+ )
48
+
49
+ logger = init_logger(__name__)
50
+
51
+ #: Frame length prefix size (bytes). Big-endian uint32 — 4 GiB cap
52
+ #: per frame which is far above any expected record size.
53
+ _LEN_PREFIX = 4
54
+ _LEN_STRUCT = struct.Struct(">I")
55
+
56
+
57
+ class TraceRecorder(EventSubscriber, ABC):
58
+ """Base class for trace recorders.
59
+
60
+ Concrete subclasses select which events they care about. This
61
+ base provides:
62
+
63
+ * file management (open / write / fsync / close)
64
+ * header emission
65
+ * the trace-gate flip (on at construction, off at ``close()``)
66
+
67
+ Subclasses implement :meth:`get_subscriptions`.
68
+ """
69
+
70
+ def __init__(self, output_path: str, level: str) -> None:
71
+ self._output_path = output_path
72
+ self._level = level
73
+ self._fd = open(output_path, "wb", buffering=0)
74
+ self._lock = threading.Lock()
75
+ self._closed = False
76
+ self._dropped_count = 0
77
+ self._header_written = False
78
+ self._t_mono_start = time.monotonic()
79
+ self._t_wall_start = time.time()
80
+
81
+ # The header is written lazily on the first of: a successful
82
+ # ``attach_storage_config`` call, the first record, or
83
+ # ``close()``. Deferring avoids the in-place rewrite problem
84
+ # that would otherwise corrupt the file when the placeholder
85
+ # header and the final header have different byte lengths.
86
+ # Flip the trace gate AFTER the file is open so a racing publish
87
+ # cannot land on a half-initialized recorder.
88
+ set_tracing_enabled(True)
89
+ logger.info("trace recorder writing to %s (level=%s)", output_path, level)
90
+
91
+ # ---- subclass extension points ------------------------------------
92
+
93
+ @abstractmethod
94
+ def get_subscriptions(self) -> dict[EventType, EventCallback]: ...
95
+
96
+ # ---- public API ---------------------------------------------------
97
+
98
+ @property
99
+ def output_path(self) -> str:
100
+ """Path of the trace file on disk."""
101
+ return self._output_path
102
+
103
+ @property
104
+ def dropped_count(self) -> int:
105
+ """Number of records that failed to encode/write."""
106
+ return self._dropped_count
107
+
108
+ def attach_storage_config(self, config: StorageManagerConfig) -> None:
109
+ """Write the header populated from the StorageManagerConfig.
110
+
111
+ Must be called before any records are written; the
112
+ server lifecycle does this immediately after construction.
113
+ Subsequent calls are silently ignored — the header is written
114
+ once for the lifetime of the file.
115
+
116
+ Args:
117
+ config: The StorageManagerConfig in use. Its dataclass
118
+ form is JSON-serialized and SHA-256 hashed for the
119
+ header digest, so a replay driver can detect
120
+ mismatched configurations.
121
+ """
122
+ with self._lock:
123
+ if self._closed or self._header_written:
124
+ return
125
+ sm_json = json.dumps(self._safe_config_dict(config), sort_keys=True)
126
+ digest = hashlib.sha256(sm_json.encode("utf-8")).hexdigest()
127
+ self._write_header(sm_json, digest)
128
+ self._header_written = True
129
+
130
+ def shutdown(self) -> None:
131
+ """:class:`EventBus` shutdown hook — close the recorder."""
132
+ self.close()
133
+
134
+ def close(self) -> None:
135
+ """Flush, fsync, and close the trace file.
136
+
137
+ Idempotent. Flips the trace gate off so any straggler
138
+ publishes after this point are no-ops. Writes a fallback
139
+ empty-config header if neither ``attach_storage_config`` nor
140
+ any record was written, so the resulting file is always
141
+ readable.
142
+ """
143
+ with self._lock:
144
+ if self._closed:
145
+ return
146
+ self._closed = True
147
+ set_tracing_enabled(False)
148
+ try:
149
+ if not self._header_written:
150
+ self._write_header(sm_config_json="", sm_config_digest="")
151
+ self._header_written = True
152
+ self._fd.flush()
153
+ os.fsync(self._fd.fileno())
154
+ except OSError:
155
+ logger.exception("trace recorder: fsync failed")
156
+ finally:
157
+ self._fd.close()
158
+ if self._dropped_count:
159
+ logger.warning(
160
+ "trace recorder closed; %d record(s) dropped", self._dropped_count
161
+ )
162
+ else:
163
+ logger.info("trace recorder closed cleanly: %s", self._output_path)
164
+
165
+ # ---- internal -----------------------------------------------------
166
+
167
+ def _write_header(self, sm_config_json: str, sm_config_digest: str) -> None:
168
+ header = Header(
169
+ magic=MAGIC,
170
+ format_version=FORMAT_VERSION,
171
+ level=self._level,
172
+ trace_schema_version=TRACE_SCHEMA_VERSION,
173
+ t_mono_start=self._t_mono_start,
174
+ t_wall_start=self._t_wall_start,
175
+ sm_config_json=sm_config_json,
176
+ sm_config_digest=sm_config_digest,
177
+ )
178
+ self._write_frame(encode_header(header))
179
+
180
+ def _write_frame(self, frame: bytes) -> None:
181
+ # Single write so the prefix and body land atomically for frames
182
+ # below PIPE_BUF; two writes would let a concurrent appender
183
+ # interleave between them and would also double the syscall
184
+ # count on an unbuffered fd. Caller holds ``self._lock`` (or is
185
+ # in __init__ before the gate flips on).
186
+ self._fd.write(_LEN_STRUCT.pack(len(frame)) + frame)
187
+
188
+ def _on_trace_call(self, event: Event) -> None:
189
+ """Encode and append one TRACE_CALL event.
190
+
191
+ Errors are logged at WARNING and counted, but do not propagate
192
+ — losing a record is preferable to taking down the EventBus
193
+ drain thread.
194
+ """
195
+ try:
196
+ qualname = event.metadata["qualname"]
197
+ args = event.metadata["args"]
198
+ # ``t_mono`` is stamped in the metadata at publish time
199
+ # (see ``publish_call_event``) so the recorded value is
200
+ # co-temporal with ``event.timestamp`` (wall-clock) instead
201
+ # of picking up the drain-thread delay.
202
+ publish_t_mono = event.metadata["t_mono"]
203
+ encoded_args = codecs.encode_args(args)
204
+ t_mono = max(0.0, publish_t_mono - self._t_mono_start)
205
+ record = Record(
206
+ t_mono=t_mono,
207
+ t_wall=event.timestamp,
208
+ qualname=qualname,
209
+ args=encoded_args,
210
+ )
211
+ frame = encode_record(record)
212
+ except Exception:
213
+ self._dropped_count += 1
214
+ logger.warning(
215
+ "trace recorder: failed to encode TRACE_CALL event "
216
+ "(qualname=%s); dropping",
217
+ event.metadata.get("qualname", "<unknown>"),
218
+ exc_info=True,
219
+ )
220
+ return
221
+
222
+ with self._lock:
223
+ if self._closed:
224
+ self._dropped_count += 1
225
+ return
226
+ try:
227
+ # Write a placeholder header on first record if the
228
+ # caller never invoked ``attach_storage_config``.
229
+ # Ensures the file is always readable.
230
+ if not self._header_written:
231
+ self._write_header(sm_config_json="", sm_config_digest="")
232
+ self._header_written = True
233
+ self._write_frame(frame)
234
+ except OSError:
235
+ self._dropped_count += 1
236
+ logger.warning(
237
+ "trace recorder: write failed; dropping record",
238
+ exc_info=True,
239
+ )
240
+
241
+ @staticmethod
242
+ def _safe_config_dict(config: StorageManagerConfig) -> dict[str, Any]:
243
+ """Best-effort conversion of a StorageManagerConfig to a JSON
244
+ dict.
245
+
246
+ Delegates to the module-level
247
+ :func:`safe_storage_config_dict` so the replay driver can
248
+ reproduce the exact digest the recorder writes. Kept as a
249
+ staticmethod for backwards-compatible access.
250
+ """
251
+ return safe_storage_config_dict(config)
252
+
253
+
254
+ def safe_storage_config_dict(config: StorageManagerConfig) -> dict[str, Any]:
255
+ """Best-effort JSON-serializable dict view of a StorageManagerConfig.
256
+
257
+ Falls back to ``str(config)`` for fields that are not directly
258
+ serializable. The result is used only for the header digest and
259
+ human inspection — it does not need to be replay-faithful.
260
+
261
+ Exposed publicly so that the replay driver can compute a digest
262
+ of its *own* config using the same algorithm the recorder used,
263
+ for mismatch detection.
264
+
265
+ Args:
266
+ config: The StorageManagerConfig to serialize.
267
+
268
+ Returns:
269
+ A JSON-friendly dict.
270
+ """
271
+ if is_dataclass(config):
272
+ try:
273
+ return _coerce_jsonable(asdict(config))
274
+ except Exception:
275
+ pass
276
+ return {"repr": str(config)}
277
+
278
+
279
+ def _coerce_jsonable(obj: Any) -> Any:
280
+ """Recursively coerce ``obj`` into a JSON-friendly value.
281
+
282
+ Anything not natively serializable falls through to ``str(obj)``.
283
+ """
284
+ if isinstance(obj, (str, int, float, bool)) or obj is None:
285
+ return obj
286
+ if isinstance(obj, (list, tuple)):
287
+ return [_coerce_jsonable(x) for x in obj]
288
+ if isinstance(obj, dict):
289
+ return {str(k): _coerce_jsonable(v) for k, v in obj.items()}
290
+ return str(obj)
291
+
292
+
293
+ class StorageTraceRecorder(TraceRecorder):
294
+ """Records every ``TRACE_CALL`` event into a ``"storage"``-level file."""
295
+
296
+ def __init__(self, output_path: str) -> None:
297
+ super().__init__(output_path=output_path, level="storage")
298
+
299
+ def get_subscriptions(self) -> dict[EventType, EventCallback]:
300
+ return {EventType.TRACE_CALL: self._on_trace_call}
File without changes
@@ -0,0 +1,102 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """
3
+ Thread pool with affinity routing.
4
+
5
+ Tasks submitted with the same ``affinity_key`` always execute on the same
6
+ worker thread (determined by ``affinity_key % num_workers``). Within each
7
+ worker, tasks execute sequentially in FIFO order.
8
+
9
+ This is used for GPU-bound request handlers (STORE / RETRIEVE) so that all
10
+ operations for a given vLLM instance land on one thread, eliminating the need
11
+ for per-instance locks on the shared temporary GPU buffer.
12
+ """
13
+
14
+ # Standard
15
+ from concurrent.futures import Future
16
+ import queue
17
+ import threading
18
+
19
+ # First Party
20
+ from lmcache.logging import init_logger
21
+
22
+ logger = init_logger(__name__)
23
+
24
+ # Sentinel object to signal worker shutdown
25
+ _SHUTDOWN = object()
26
+
27
+
28
+ class AffinityThreadPool:
29
+ """Thread pool that routes tasks to workers by affinity key.
30
+
31
+ Args:
32
+ max_workers: Number of worker threads.
33
+ thread_name_prefix: Prefix for worker thread names.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ max_workers: int,
39
+ thread_name_prefix: str = "affinity",
40
+ ) -> None:
41
+ self._num_workers = max_workers
42
+ self._queues: list[queue.Queue] = [queue.Queue() for _ in range(max_workers)]
43
+ self._threads: list[threading.Thread] = []
44
+ for i in range(max_workers):
45
+ t = threading.Thread(
46
+ target=self._worker,
47
+ args=(self._queues[i],),
48
+ daemon=True,
49
+ name=f"{thread_name_prefix}-{i}",
50
+ )
51
+ t.start()
52
+ self._threads.append(t)
53
+
54
+ logger.debug(
55
+ "Created AffinityThreadPool with %d workers (prefix=%s)",
56
+ max_workers,
57
+ thread_name_prefix,
58
+ )
59
+
60
+ # ------------------------------------------------------------------
61
+ # Worker loop
62
+ # ------------------------------------------------------------------
63
+
64
+ @staticmethod
65
+ def _worker(q: queue.Queue) -> None:
66
+ while True:
67
+ item = q.get()
68
+ if item is _SHUTDOWN:
69
+ break
70
+ future, fn, args, kwargs = item
71
+ if future.set_running_or_notify_cancel():
72
+ try:
73
+ result = fn(*args, **kwargs)
74
+ future.set_result(result)
75
+ except BaseException as exc:
76
+ future.set_exception(exc)
77
+
78
+ # ------------------------------------------------------------------
79
+ # Public API
80
+ # ------------------------------------------------------------------
81
+
82
+ def submit(self, fn, *args, affinity_key: int = 0, **kwargs) -> Future:
83
+ """Submit *fn* for execution on the worker determined by *affinity_key*.
84
+
85
+ Returns a :class:`concurrent.futures.Future`.
86
+ """
87
+ future: Future = Future()
88
+ slot = affinity_key % self._num_workers
89
+ self._queues[slot].put((future, fn, args, kwargs))
90
+ return future
91
+
92
+ def shutdown(self, wait: bool = True) -> None:
93
+ """Shut down the pool.
94
+
95
+ Sends a shutdown sentinel to every worker. If *wait* is true, blocks
96
+ until all workers have exited.
97
+ """
98
+ for q in self._queues:
99
+ q.put(_SHUTDOWN)
100
+ if wait:
101
+ for t in self._threads:
102
+ t.join()