lmcache-cli 0.4.5.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lmcache/__init__.py +84 -0
- lmcache/_version.py +24 -0
- lmcache/cli/__init__.py +1 -0
- lmcache/cli/commands/__init__.py +34 -0
- lmcache/cli/commands/base.py +157 -0
- lmcache/cli/commands/bench/__init__.py +557 -0
- lmcache/cli/commands/bench/engine_bench/__init__.py +1 -0
- lmcache/cli/commands/bench/engine_bench/config.py +245 -0
- lmcache/cli/commands/bench/engine_bench/interactive/__init__.py +274 -0
- lmcache/cli/commands/bench/engine_bench/interactive/config.json +10 -0
- lmcache/cli/commands/bench/engine_bench/interactive/schema.py +352 -0
- lmcache/cli/commands/bench/engine_bench/interactive/state.py +327 -0
- lmcache/cli/commands/bench/engine_bench/interactive/terminal.py +291 -0
- lmcache/cli/commands/bench/engine_bench/progress.py +145 -0
- lmcache/cli/commands/bench/engine_bench/request_sender.py +232 -0
- lmcache/cli/commands/bench/engine_bench/stats.py +275 -0
- lmcache/cli/commands/bench/engine_bench/workloads/__init__.py +153 -0
- lmcache/cli/commands/bench/engine_bench/workloads/base.py +122 -0
- lmcache/cli/commands/bench/engine_bench/workloads/long_doc_permutator.py +435 -0
- lmcache/cli/commands/bench/engine_bench/workloads/long_doc_qa.py +281 -0
- lmcache/cli/commands/bench/engine_bench/workloads/multi_round_chat.py +337 -0
- lmcache/cli/commands/bench/engine_bench/workloads/random_prefill.py +178 -0
- lmcache/cli/commands/describe.py +310 -0
- lmcache/cli/commands/kvcache.py +133 -0
- lmcache/cli/commands/mock.py +75 -0
- lmcache/cli/commands/ping.py +113 -0
- lmcache/cli/commands/query/__init__.py +155 -0
- lmcache/cli/commands/query/prompt.py +134 -0
- lmcache/cli/commands/query/request.py +357 -0
- lmcache/cli/commands/server.py +99 -0
- lmcache/cli/commands/tool/__init__.py +63 -0
- lmcache/cli/commands/tool/cache_simulator.py +113 -0
- lmcache/cli/commands/trace/__init__.py +505 -0
- lmcache/cli/commands/trace/dispatch.py +249 -0
- lmcache/cli/commands/trace/driver.py +372 -0
- lmcache/cli/commands/trace/stats.py +289 -0
- lmcache/cli/documents/lmcache.txt +11 -0
- lmcache/cli/main.py +42 -0
- lmcache/cli/metrics/__init__.py +29 -0
- lmcache/cli/metrics/formatter.py +171 -0
- lmcache/cli/metrics/handler.py +94 -0
- lmcache/cli/metrics/metrics.py +161 -0
- lmcache/cli/metrics/section.py +77 -0
- lmcache/connections.py +173 -0
- lmcache/integration/__init__.py +2 -0
- lmcache/integration/base_service_factory.py +165 -0
- lmcache/integration/request_telemetry/__init__.py +1 -0
- lmcache/integration/request_telemetry/base.py +51 -0
- lmcache/integration/request_telemetry/factory.py +113 -0
- lmcache/integration/request_telemetry/fastapi.py +109 -0
- lmcache/integration/request_telemetry/noop.py +35 -0
- lmcache/integration/sglang/__init__.py +2 -0
- lmcache/integration/sglang/sglang_adapter.py +326 -0
- lmcache/integration/sglang/utils.py +39 -0
- lmcache/integration/vllm/__init__.py +1 -0
- lmcache/integration/vllm/lmcache_connector_v1.py +213 -0
- lmcache/integration/vllm/lmcache_connector_v1_085.py +150 -0
- lmcache/integration/vllm/lmcache_mp_connector_0180.py +1072 -0
- lmcache/integration/vllm/tests/test_mm_hash_utils.py +112 -0
- lmcache/integration/vllm/utils.py +433 -0
- lmcache/integration/vllm/vllm_multi_process_adapter.py +1090 -0
- lmcache/integration/vllm/vllm_service_factory.py +339 -0
- lmcache/integration/vllm/vllm_v1_adapter.py +1713 -0
- lmcache/logging.py +107 -0
- lmcache/native_storage_ops.pyi +230 -0
- lmcache/non_cuda_equivalents.py +1424 -0
- lmcache/observability.py +1958 -0
- lmcache/storage_backend/serde/__init__.py +1 -0
- lmcache/storage_backend/serde/cachegen_basics.py +210 -0
- lmcache/storage_backend/serde/cachegen_decoder.py +207 -0
- lmcache/storage_backend/serde/cachegen_encoder.py +394 -0
- lmcache/storage_backend/serde/serde.py +75 -0
- lmcache/tools/__init__.py +1 -0
- lmcache/tools/cache_simulator/README.md +392 -0
- lmcache/tools/cache_simulator/__init__.py +1 -0
- lmcache/tools/cache_simulator/docs/simulate_example.png +0 -0
- lmcache/tools/cache_simulator/docs/sweep_example.png +0 -0
- lmcache/tools/cache_simulator/gen_bench_dataset.py +360 -0
- lmcache/tools/cache_simulator/lru_cache.py +124 -0
- lmcache/tools/cache_simulator/plot_hit_rate.py +231 -0
- lmcache/tools/cache_simulator/simulator.py +795 -0
- lmcache/tools/controller_benchmark/README.md +161 -0
- lmcache/tools/controller_benchmark/__init__.py +1 -0
- lmcache/tools/controller_benchmark/__main__.py +331 -0
- lmcache/tools/controller_benchmark/benchmark.py +660 -0
- lmcache/tools/controller_benchmark/config.py +44 -0
- lmcache/tools/controller_benchmark/constants.py +10 -0
- lmcache/tools/controller_benchmark/handlers/__init__.py +46 -0
- lmcache/tools/controller_benchmark/handlers/admit.py +52 -0
- lmcache/tools/controller_benchmark/handlers/base.py +47 -0
- lmcache/tools/controller_benchmark/handlers/deregister.py +49 -0
- lmcache/tools/controller_benchmark/handlers/evict.py +52 -0
- lmcache/tools/controller_benchmark/handlers/heartbeat.py +56 -0
- lmcache/tools/controller_benchmark/handlers/p2p_lookup.py +47 -0
- lmcache/tools/controller_benchmark/handlers/register.py +56 -0
- lmcache/tools/mp_status_viewer/__init__.py +1 -0
- lmcache/tools/mp_status_viewer/__main__.py +95 -0
- lmcache/usage_context.py +417 -0
- lmcache/utils.py +665 -0
- lmcache/v1/__init__.py +2 -0
- lmcache/v1/api_server/__init__.py +2 -0
- lmcache/v1/api_server/__main__.py +537 -0
- lmcache/v1/basic_check.py +112 -0
- lmcache/v1/cache_controller/__init__.py +9 -0
- lmcache/v1/cache_controller/commands/__init__.py +15 -0
- lmcache/v1/cache_controller/commands/base.py +35 -0
- lmcache/v1/cache_controller/commands/full_sync.py +49 -0
- lmcache/v1/cache_controller/config.py +176 -0
- lmcache/v1/cache_controller/controller_manager.py +535 -0
- lmcache/v1/cache_controller/controllers/__init__.py +11 -0
- lmcache/v1/cache_controller/controllers/full_sync_tracker.py +473 -0
- lmcache/v1/cache_controller/controllers/kv_controller.py +439 -0
- lmcache/v1/cache_controller/controllers/registration_controller.py +282 -0
- lmcache/v1/cache_controller/executor.py +463 -0
- lmcache/v1/cache_controller/frontend/static/css/style.css +201 -0
- lmcache/v1/cache_controller/frontend/static/img/logo.png +0 -0
- lmcache/v1/cache_controller/frontend/static/index.html +234 -0
- lmcache/v1/cache_controller/frontend/static/js/controller_app.js +660 -0
- lmcache/v1/cache_controller/full_sync_sender.py +475 -0
- lmcache/v1/cache_controller/locks.py +149 -0
- lmcache/v1/cache_controller/message.py +828 -0
- lmcache/v1/cache_controller/observability.py +208 -0
- lmcache/v1/cache_controller/utils.py +679 -0
- lmcache/v1/cache_controller/worker.py +665 -0
- lmcache/v1/cache_engine.py +2058 -0
- lmcache/v1/cache_interface.py +19 -0
- lmcache/v1/check/__init__.py +74 -0
- lmcache/v1/check/check_mode_gen.py +86 -0
- lmcache/v1/check/check_mode_test_l2_adapter.py +284 -0
- lmcache/v1/check/check_mode_test_remote.py +155 -0
- lmcache/v1/check/check_mode_test_storage_manager.py +142 -0
- lmcache/v1/check/utils.py +571 -0
- lmcache/v1/compute/__init__.py +2 -0
- lmcache/v1/compute/attention/__init__.py +0 -0
- lmcache/v1/compute/attention/abstract.py +39 -0
- lmcache/v1/compute/attention/flash_attn.py +129 -0
- lmcache/v1/compute/attention/flash_infer_sparse.py +284 -0
- lmcache/v1/compute/attention/metadata.py +85 -0
- lmcache/v1/compute/attention/utils.py +14 -0
- lmcache/v1/compute/blend/__init__.py +7 -0
- lmcache/v1/compute/blend/blender.py +168 -0
- lmcache/v1/compute/blend/metadata.py +34 -0
- lmcache/v1/compute/blend/utils.py +63 -0
- lmcache/v1/compute/models/__init__.py +0 -0
- lmcache/v1/compute/models/base.py +141 -0
- lmcache/v1/compute/models/llama.py +9 -0
- lmcache/v1/compute/models/qwen3.py +24 -0
- lmcache/v1/compute/models/utils.py +68 -0
- lmcache/v1/compute/positional_encoding.py +199 -0
- lmcache/v1/config.py +848 -0
- lmcache/v1/config_base.py +848 -0
- lmcache/v1/distributed/api.py +248 -0
- lmcache/v1/distributed/config.py +321 -0
- lmcache/v1/distributed/error.py +64 -0
- lmcache/v1/distributed/eviction.py +192 -0
- lmcache/v1/distributed/eviction_policy/__init__.py +21 -0
- lmcache/v1/distributed/eviction_policy/factory.py +27 -0
- lmcache/v1/distributed/eviction_policy/lru.py +244 -0
- lmcache/v1/distributed/eviction_policy/noop.py +50 -0
- lmcache/v1/distributed/internal_api.py +170 -0
- lmcache/v1/distributed/l1_manager.py +835 -0
- lmcache/v1/distributed/l2_adapters/__init__.py +67 -0
- lmcache/v1/distributed/l2_adapters/base.py +360 -0
- lmcache/v1/distributed/l2_adapters/config.py +385 -0
- lmcache/v1/distributed/l2_adapters/factory.py +205 -0
- lmcache/v1/distributed/l2_adapters/fs_l2_adapter.py +747 -0
- lmcache/v1/distributed/l2_adapters/fs_native_l2_adapter.py +167 -0
- lmcache/v1/distributed/l2_adapters/mock_l2_adapter.py +516 -0
- lmcache/v1/distributed/l2_adapters/mooncake_store_l2_adapter.py +135 -0
- lmcache/v1/distributed/l2_adapters/native_connector_l2_adapter.py +468 -0
- lmcache/v1/distributed/l2_adapters/native_plugin_l2_adapter.py +199 -0
- lmcache/v1/distributed/l2_adapters/nixl_store_dynamic_l2_adapter.py +831 -0
- lmcache/v1/distributed/l2_adapters/nixl_store_l2_adapter.py +983 -0
- lmcache/v1/distributed/l2_adapters/plugin_l2_adapter.py +210 -0
- lmcache/v1/distributed/l2_adapters/resp_l2_adapter.py +176 -0
- lmcache/v1/distributed/memory_manager.py +179 -0
- lmcache/v1/distributed/storage_controller.py +39 -0
- lmcache/v1/distributed/storage_controllers/__init__.py +43 -0
- lmcache/v1/distributed/storage_controllers/eviction_controller.py +242 -0
- lmcache/v1/distributed/storage_controllers/prefetch_controller.py +830 -0
- lmcache/v1/distributed/storage_controllers/prefetch_policy.py +193 -0
- lmcache/v1/distributed/storage_controllers/store_controller.py +452 -0
- lmcache/v1/distributed/storage_controllers/store_policy.py +213 -0
- lmcache/v1/distributed/storage_manager.py +532 -0
- lmcache/v1/event_manager.py +145 -0
- lmcache/v1/exceptions/__init__.py +16 -0
- lmcache/v1/gpu_connector/__init__.py +126 -0
- lmcache/v1/gpu_connector/gpu_connectors.py +1906 -0
- lmcache/v1/gpu_connector/gpu_ops.py +85 -0
- lmcache/v1/gpu_connector/hpu_connector.py +326 -0
- lmcache/v1/gpu_connector/mock_gpu_connector.py +67 -0
- lmcache/v1/gpu_connector/utils.py +890 -0
- lmcache/v1/gpu_connector/xpu_connectors.py +916 -0
- lmcache/v1/health_monitor/__init__.py +1 -0
- lmcache/v1/health_monitor/base.py +587 -0
- lmcache/v1/health_monitor/checks/__init__.py +1 -0
- lmcache/v1/health_monitor/checks/remote_backend_check.py +304 -0
- lmcache/v1/health_monitor/constants.py +36 -0
- lmcache/v1/internal_api_server/__init__.py +0 -0
- lmcache/v1/internal_api_server/api_registry.py +59 -0
- lmcache/v1/internal_api_server/api_server.py +120 -0
- lmcache/v1/internal_api_server/common/__init__.py +1 -0
- lmcache/v1/internal_api_server/common/env_api.py +22 -0
- lmcache/v1/internal_api_server/common/loglevel_api.py +57 -0
- lmcache/v1/internal_api_server/common/metrics_api.py +29 -0
- lmcache/v1/internal_api_server/common/periodic_thread_api.py +138 -0
- lmcache/v1/internal_api_server/common/run_script_api.py +73 -0
- lmcache/v1/internal_api_server/common/thread_api.py +63 -0
- lmcache/v1/internal_api_server/controller/__init__.py +1 -0
- lmcache/v1/internal_api_server/controller/key_stats_api.py +81 -0
- lmcache/v1/internal_api_server/controller/worker_info_api.py +136 -0
- lmcache/v1/internal_api_server/utils.py +43 -0
- lmcache/v1/internal_api_server/vllm/__init__.py +1 -0
- lmcache/v1/internal_api_server/vllm/backend_api.py +221 -0
- lmcache/v1/internal_api_server/vllm/bypass_api.py +204 -0
- lmcache/v1/internal_api_server/vllm/cache_api.py +895 -0
- lmcache/v1/internal_api_server/vllm/chunk_statistics_api.py +141 -0
- lmcache/v1/internal_api_server/vllm/conf_api.py +147 -0
- lmcache/v1/internal_api_server/vllm/freeze_api.py +172 -0
- lmcache/v1/internal_api_server/vllm/hot_cache_api.py +184 -0
- lmcache/v1/internal_api_server/vllm/inference_api.py +65 -0
- lmcache/v1/internal_api_server/vllm/load_fs_chunks_api.py +320 -0
- lmcache/v1/internal_api_server/vllm/lookup_api.py +145 -0
- lmcache/v1/internal_api_server/vllm/version_api.py +25 -0
- lmcache/v1/kv_layer_groups.py +267 -0
- lmcache/v1/lazy_memory_allocator.py +284 -0
- lmcache/v1/lookup_client/__init__.py +25 -0
- lmcache/v1/lookup_client/abstract_client.py +77 -0
- lmcache/v1/lookup_client/async_lookup_message.py +50 -0
- lmcache/v1/lookup_client/chunk_statistics_lookup_client.py +200 -0
- lmcache/v1/lookup_client/factory.py +251 -0
- lmcache/v1/lookup_client/hit_limit_lookup_client.py +86 -0
- lmcache/v1/lookup_client/lmcache_async_lookup_client.py +407 -0
- lmcache/v1/lookup_client/lmcache_lookup_client.py +285 -0
- lmcache/v1/lookup_client/lmcache_lookup_client_bypass.py +99 -0
- lmcache/v1/lookup_client/mooncake_lookup_client.py +87 -0
- lmcache/v1/lookup_client/record_strategies/__init__.py +77 -0
- lmcache/v1/lookup_client/record_strategies/base.py +327 -0
- lmcache/v1/lookup_client/record_strategies/file_hash.py +130 -0
- lmcache/v1/lookup_client/record_strategies/memory_bloom_filter.py +81 -0
- lmcache/v1/manager.py +539 -0
- lmcache/v1/memory_management.py +2619 -0
- lmcache/v1/metadata.py +114 -0
- lmcache/v1/mp_observability/AGENTS.override.md +21 -0
- lmcache/v1/mp_observability/README.md +204 -0
- lmcache/v1/mp_observability/config.py +340 -0
- lmcache/v1/mp_observability/event.py +100 -0
- lmcache/v1/mp_observability/event_bus.py +313 -0
- lmcache/v1/mp_observability/otel_init.py +145 -0
- lmcache/v1/mp_observability/subscribers/__init__.py +28 -0
- lmcache/v1/mp_observability/subscribers/logging/__init__.py +19 -0
- lmcache/v1/mp_observability/subscribers/logging/l1.py +56 -0
- lmcache/v1/mp_observability/subscribers/logging/l2.py +73 -0
- lmcache/v1/mp_observability/subscribers/logging/lookup_hash.py +209 -0
- lmcache/v1/mp_observability/subscribers/logging/mp_server.py +90 -0
- lmcache/v1/mp_observability/subscribers/logging/sm.py +59 -0
- lmcache/v1/mp_observability/subscribers/metrics/__init__.py +20 -0
- lmcache/v1/mp_observability/subscribers/metrics/l0_lifecycle.py +290 -0
- lmcache/v1/mp_observability/subscribers/metrics/l1.py +55 -0
- lmcache/v1/mp_observability/subscribers/metrics/l1_lifecycle.py +166 -0
- lmcache/v1/mp_observability/subscribers/metrics/l2.py +121 -0
- lmcache/v1/mp_observability/subscribers/metrics/sm.py +69 -0
- lmcache/v1/mp_observability/subscribers/tracing/__init__.py +12 -0
- lmcache/v1/mp_observability/subscribers/tracing/mp_server.py +333 -0
- lmcache/v1/mp_observability/subscribers/tracing/span_registry.py +148 -0
- lmcache/v1/mp_observability/trace/__init__.py +50 -0
- lmcache/v1/mp_observability/trace/codecs.py +255 -0
- lmcache/v1/mp_observability/trace/decorator.py +147 -0
- lmcache/v1/mp_observability/trace/format.py +132 -0
- lmcache/v1/mp_observability/trace/lifecycle.py +83 -0
- lmcache/v1/mp_observability/trace/reader.py +167 -0
- lmcache/v1/mp_observability/trace/recorder.py +300 -0
- lmcache/v1/multiprocess/__init__.py +0 -0
- lmcache/v1/multiprocess/affinity_pool.py +102 -0
- lmcache/v1/multiprocess/blend_server_v2.py +891 -0
- lmcache/v1/multiprocess/config.py +253 -0
- lmcache/v1/multiprocess/custom_types.py +281 -0
- lmcache/v1/multiprocess/futures.py +194 -0
- lmcache/v1/multiprocess/gpu_context.py +511 -0
- lmcache/v1/multiprocess/http_server.py +235 -0
- lmcache/v1/multiprocess/mp_runtime_plugin_launcher.py +130 -0
- lmcache/v1/multiprocess/mq.py +732 -0
- lmcache/v1/multiprocess/protocol.py +86 -0
- lmcache/v1/multiprocess/protocols/README.md +213 -0
- lmcache/v1/multiprocess/protocols/__init__.py +127 -0
- lmcache/v1/multiprocess/protocols/base.py +89 -0
- lmcache/v1/multiprocess/protocols/blend.py +109 -0
- lmcache/v1/multiprocess/protocols/blend_v2.py +57 -0
- lmcache/v1/multiprocess/protocols/controller.py +53 -0
- lmcache/v1/multiprocess/protocols/debug.py +34 -0
- lmcache/v1/multiprocess/protocols/engine.py +146 -0
- lmcache/v1/multiprocess/protocols/observability.py +39 -0
- lmcache/v1/multiprocess/server.py +1134 -0
- lmcache/v1/multiprocess/session.py +190 -0
- lmcache/v1/multiprocess/token_hasher.py +441 -0
- lmcache/v1/offload_server/__init__.py +17 -0
- lmcache/v1/offload_server/abstract_server.py +37 -0
- lmcache/v1/offload_server/message.py +30 -0
- lmcache/v1/offload_server/zmq_server.py +122 -0
- lmcache/v1/periodic_thread.py +579 -0
- lmcache/v1/pin_monitor.py +246 -0
- lmcache/v1/plugin/__init__.py +0 -0
- lmcache/v1/plugin/runtime_plugin_launcher.py +211 -0
- lmcache/v1/protocol.py +317 -0
- lmcache/v1/rpc/__init__.py +17 -0
- lmcache/v1/rpc/transport.py +105 -0
- lmcache/v1/rpc/zmq_transport.py +213 -0
- lmcache/v1/rpc_utils.py +165 -0
- lmcache/v1/server/__init__.py +2 -0
- lmcache/v1/server/__main__.py +170 -0
- lmcache/v1/server/storage_backend/__init__.py +21 -0
- lmcache/v1/server/storage_backend/abstract_backend.py +80 -0
- lmcache/v1/server/storage_backend/local_backend.py +75 -0
- lmcache/v1/server/utils.py +21 -0
- lmcache/v1/standalone/__init__.py +1 -0
- lmcache/v1/standalone/__main__.py +583 -0
- lmcache/v1/standalone/manager.py +80 -0
- lmcache/v1/standalone/standalone_service_factory.py +86 -0
- lmcache/v1/storage_backend/__init__.py +313 -0
- lmcache/v1/storage_backend/abstract_backend.py +445 -0
- lmcache/v1/storage_backend/audit_backend.py +233 -0
- lmcache/v1/storage_backend/batched_message_sender.py +222 -0
- lmcache/v1/storage_backend/cache_policy/__init__.py +45 -0
- lmcache/v1/storage_backend/cache_policy/base_policy.py +87 -0
- lmcache/v1/storage_backend/cache_policy/fifo.py +58 -0
- lmcache/v1/storage_backend/cache_policy/lfu.py +105 -0
- lmcache/v1/storage_backend/cache_policy/lru.py +81 -0
- lmcache/v1/storage_backend/cache_policy/mru.py +61 -0
- lmcache/v1/storage_backend/connector/__init__.py +443 -0
- lmcache/v1/storage_backend/connector/audit_adapter.py +77 -0
- lmcache/v1/storage_backend/connector/audit_connector.py +320 -0
- lmcache/v1/storage_backend/connector/base_connector.py +379 -0
- lmcache/v1/storage_backend/connector/blackhole_adapter.py +21 -0
- lmcache/v1/storage_backend/connector/blackhole_connector.py +37 -0
- lmcache/v1/storage_backend/connector/eic_adapter.py +31 -0
- lmcache/v1/storage_backend/connector/eic_connector.py +757 -0
- lmcache/v1/storage_backend/connector/external_adapter.py +79 -0
- lmcache/v1/storage_backend/connector/fs_adapter.py +51 -0
- lmcache/v1/storage_backend/connector/fs_connector.py +403 -0
- lmcache/v1/storage_backend/connector/infinistore_adapter.py +56 -0
- lmcache/v1/storage_backend/connector/infinistore_connector.py +177 -0
- lmcache/v1/storage_backend/connector/instrumented_connector.py +219 -0
- lmcache/v1/storage_backend/connector/lm_adapter.py +31 -0
- lmcache/v1/storage_backend/connector/lm_connector.py +176 -0
- lmcache/v1/storage_backend/connector/mock_adapter.py +57 -0
- lmcache/v1/storage_backend/connector/mock_connector.py +349 -0
- lmcache/v1/storage_backend/connector/mooncakestore_adapter.py +43 -0
- lmcache/v1/storage_backend/connector/mooncakestore_connector.py +614 -0
- lmcache/v1/storage_backend/connector/redis_adapter.py +181 -0
- lmcache/v1/storage_backend/connector/redis_connector.py +828 -0
- lmcache/v1/storage_backend/connector/s3_adapter.py +59 -0
- lmcache/v1/storage_backend/connector/s3_connector.py +699 -0
- lmcache/v1/storage_backend/connector/sagemaker_hyperpod_adapter.py +233 -0
- lmcache/v1/storage_backend/connector/sagemaker_hyperpod_connector.py +987 -0
- lmcache/v1/storage_backend/connector/valkey_adapter.py +114 -0
- lmcache/v1/storage_backend/connector/valkey_connector.py +627 -0
- lmcache/v1/storage_backend/gds_backend.py +1199 -0
- lmcache/v1/storage_backend/job_executor/__init__.py +0 -0
- lmcache/v1/storage_backend/job_executor/base_executor.py +34 -0
- lmcache/v1/storage_backend/job_executor/pq_executor.py +235 -0
- lmcache/v1/storage_backend/local_cpu_backend.py +810 -0
- lmcache/v1/storage_backend/local_disk_backend.py +656 -0
- lmcache/v1/storage_backend/maru_backend.py +734 -0
- lmcache/v1/storage_backend/naive_serde/__init__.py +50 -0
- lmcache/v1/storage_backend/naive_serde/cachegen_basics.py +133 -0
- lmcache/v1/storage_backend/naive_serde/cachegen_decoder.py +135 -0
- lmcache/v1/storage_backend/naive_serde/cachegen_encoder.py +83 -0
- lmcache/v1/storage_backend/naive_serde/kivi_serde.py +22 -0
- lmcache/v1/storage_backend/naive_serde/naive_serde.py +18 -0
- lmcache/v1/storage_backend/naive_serde/serde.py +37 -0
- lmcache/v1/storage_backend/native_clients/connector_client_base.py +165 -0
- lmcache/v1/storage_backend/native_clients/resp_client.py +35 -0
- lmcache/v1/storage_backend/nixl_storage_backend.py +1400 -0
- lmcache/v1/storage_backend/p2p_backend.py +788 -0
- lmcache/v1/storage_backend/path_sharder.py +117 -0
- lmcache/v1/storage_backend/pd_backend.py +646 -0
- lmcache/v1/storage_backend/plugins/dax_backend.py +1443 -0
- lmcache/v1/storage_backend/plugins/rust_raw_block_backend.py +1361 -0
- lmcache/v1/storage_backend/remote_backend.py +624 -0
- lmcache/v1/storage_backend/resp_client.py +227 -0
- lmcache/v1/storage_backend/storage_backend_listener.py +19 -0
- lmcache/v1/storage_backend/storage_manager.py +1352 -0
- lmcache/v1/system_detection.py +110 -0
- lmcache/v1/token_database.py +551 -0
- lmcache/v1/transfer_channel/__init__.py +83 -0
- lmcache/v1/transfer_channel/abstract.py +285 -0
- lmcache/v1/transfer_channel/mock_memory_channel.py +156 -0
- lmcache/v1/transfer_channel/nixl_channel.py +639 -0
- lmcache/v1/transfer_channel/py_socket_channel.py +260 -0
- lmcache/v1/transfer_channel/transfer_utils.py +63 -0
- lmcache/v1/utils/__init__.py +1 -0
- lmcache/v1/utils/bloom_filter.py +109 -0
- lmcache/v1/utils/cache_utils.py +125 -0
- lmcache_cli-0.4.5.dev0.dist-info/METADATA +185 -0
- lmcache_cli-0.4.5.dev0.dist-info/RECORD +399 -0
- lmcache_cli-0.4.5.dev0.dist-info/WHEEL +5 -0
- lmcache_cli-0.4.5.dev0.dist-info/entry_points.txt +2 -0
- lmcache_cli-0.4.5.dev0.dist-info/licenses/LICENSE +201 -0
- lmcache_cli-0.4.5.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Standard
|
|
3
|
+
from contextlib import nullcontext
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
# First Party
|
|
9
|
+
from lmcache.logging import init_logger
|
|
10
|
+
from lmcache.observability import LMCStatsMonitor, PrometheusLogger
|
|
11
|
+
from lmcache.v1.periodic_thread import (
|
|
12
|
+
PeriodicThread,
|
|
13
|
+
PeriodicThreadRegistry,
|
|
14
|
+
ThreadLevel,
|
|
15
|
+
ThreadRunSummary,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
# First Party
|
|
20
|
+
from lmcache.v1.config import LMCacheEngineConfig
|
|
21
|
+
from lmcache.v1.memory_management import MemoryObj
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
logger = init_logger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PinMonitor(PeriodicThread):
|
|
28
|
+
"""
|
|
29
|
+
Global monitor (singleton per process, shared across all cache engines)
|
|
30
|
+
for pinned TensorMemoryObj instances to handle timeout detection.
|
|
31
|
+
This class runs a background thread that periodically checks for pinned objects
|
|
32
|
+
that have exceeded their timeout duration.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
_instance = None
|
|
36
|
+
_instance_lock = threading.Lock() # Class-level lock for singleton pattern
|
|
37
|
+
|
|
38
|
+
def __init__(self, config: "LMCacheEngineConfig"):
|
|
39
|
+
# Initialize PeriodicThread base class
|
|
40
|
+
super().__init__(
|
|
41
|
+
name="PinMonitor-thread",
|
|
42
|
+
interval=config.pin_check_interval_sec,
|
|
43
|
+
level=ThreadLevel.CRITICAL,
|
|
44
|
+
init_wait=0.0,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# obj_id is the virtual memory address given by Python's id() function
|
|
48
|
+
self._pinned_objects: dict[
|
|
49
|
+
int, tuple["MemoryObj", float]
|
|
50
|
+
] = {} # {obj_id: (memory_obj, register_time)}
|
|
51
|
+
self._objects_lock = threading.Lock()
|
|
52
|
+
self._check_interval = config.pin_check_interval_sec
|
|
53
|
+
self._pin_timeout_sec = config.pin_timeout_sec
|
|
54
|
+
|
|
55
|
+
# Register with the global registry
|
|
56
|
+
PeriodicThreadRegistry.get_instance().register(self)
|
|
57
|
+
|
|
58
|
+
# Auto-start the monitor on first instance creation
|
|
59
|
+
self.start_monitoring()
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def GetOrCreate(config: Optional["LMCacheEngineConfig"] = None) -> "PinMonitor":
|
|
63
|
+
"""Get or create the singleton instance.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
config: Required for first-time initialization.
|
|
67
|
+
Optional for subsequent calls.
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
ValueError: If config is None when creating the instance
|
|
71
|
+
for the first time.
|
|
72
|
+
"""
|
|
73
|
+
if PinMonitor._instance is None:
|
|
74
|
+
with PinMonitor._instance_lock:
|
|
75
|
+
if PinMonitor._instance is None:
|
|
76
|
+
assert config is not None, "config is required"
|
|
77
|
+
PinMonitor._instance = PinMonitor(config)
|
|
78
|
+
return PinMonitor._instance
|
|
79
|
+
|
|
80
|
+
def on_pin(self, memory_obj: "MemoryObj"):
|
|
81
|
+
"""Register a pinned memory object for timeout monitoring.
|
|
82
|
+
|
|
83
|
+
Note: The same memory_obj can be pinned multiple times, so this
|
|
84
|
+
function may be called multiple times with the same object.
|
|
85
|
+
Each call updates the register time, effectively resetting the
|
|
86
|
+
timeout countdown.
|
|
87
|
+
"""
|
|
88
|
+
obj_id = id(memory_obj)
|
|
89
|
+
with self._objects_lock:
|
|
90
|
+
current_time = time.time()
|
|
91
|
+
self._pinned_objects[obj_id] = (memory_obj, current_time)
|
|
92
|
+
logger.debug(
|
|
93
|
+
"Registered pinned object %s for timeout monitoring at time %.2f",
|
|
94
|
+
obj_id,
|
|
95
|
+
current_time,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def on_unpin(self, memory_obj: "MemoryObj"):
|
|
99
|
+
"""Unregister a memory object from timeout monitoring."""
|
|
100
|
+
obj_id = id(memory_obj)
|
|
101
|
+
with self._objects_lock:
|
|
102
|
+
if obj_id in self._pinned_objects:
|
|
103
|
+
del self._pinned_objects[obj_id]
|
|
104
|
+
logger.debug(
|
|
105
|
+
"Unregistered pinned object %s from timeout monitoring",
|
|
106
|
+
obj_id,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def _check_timeouts(self) -> tuple[int, int, int]:
|
|
110
|
+
"""Check all registered pinned objects for timeout.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
tuple: (pinned_count, timeout_count, force_unpin_success_count)
|
|
114
|
+
"""
|
|
115
|
+
current_time = time.time()
|
|
116
|
+
timeout_objects = []
|
|
117
|
+
|
|
118
|
+
with self._objects_lock:
|
|
119
|
+
pinned_count = len(self._pinned_objects)
|
|
120
|
+
for obj_id, (memory_obj, register_time) in list(
|
|
121
|
+
self._pinned_objects.items()
|
|
122
|
+
):
|
|
123
|
+
# Check if object is still pinned and has exceeded timeout
|
|
124
|
+
if memory_obj.meta.pin_count > 0:
|
|
125
|
+
elapsed_time = current_time - register_time
|
|
126
|
+
if elapsed_time > self._pin_timeout_sec:
|
|
127
|
+
timeout_objects.append((memory_obj, elapsed_time))
|
|
128
|
+
|
|
129
|
+
# Force unpin timeout objects outside the lock to avoid deadlocks
|
|
130
|
+
force_unpin_success_count = 0
|
|
131
|
+
for memory_obj, elapsed_time in timeout_objects:
|
|
132
|
+
try:
|
|
133
|
+
self._force_unpin_timeout_object(memory_obj, elapsed_time)
|
|
134
|
+
force_unpin_success_count += 1
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.error(
|
|
137
|
+
"Error forcing unpin for timeout object %s: %s", id(memory_obj), e
|
|
138
|
+
)
|
|
139
|
+
if force_unpin_success_count > 0:
|
|
140
|
+
logger.warning(
|
|
141
|
+
"Force unpinned %d timeout objects in %d pinned_objects "
|
|
142
|
+
"within %d seconds",
|
|
143
|
+
force_unpin_success_count,
|
|
144
|
+
pinned_count,
|
|
145
|
+
self._pin_timeout_sec,
|
|
146
|
+
)
|
|
147
|
+
else:
|
|
148
|
+
logger.debug(
|
|
149
|
+
"PinMonitor check: pinned_objects=%d, timeout_objects=%d, "
|
|
150
|
+
"force_unpin_success=%d",
|
|
151
|
+
pinned_count,
|
|
152
|
+
len(timeout_objects),
|
|
153
|
+
force_unpin_success_count,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return pinned_count, len(timeout_objects), force_unpin_success_count
|
|
157
|
+
|
|
158
|
+
def _force_unpin_timeout_object(self, memory_obj: "MemoryObj", elapsed_time: float):
|
|
159
|
+
"""Force unpin a timeout object and log the event."""
|
|
160
|
+
# Get current pin_count without holding the lock for unpin calls
|
|
161
|
+
# Use nullcontext if memory_obj doesn't have a lock attribute
|
|
162
|
+
obj_lock = getattr(memory_obj, "lock", None) or nullcontext()
|
|
163
|
+
with obj_lock:
|
|
164
|
+
current_pin_count = memory_obj.meta.pin_count
|
|
165
|
+
if current_pin_count <= 0:
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
logger.warning(
|
|
169
|
+
"Pin timeout detected for MemoryObj %s. "
|
|
170
|
+
"Pin count: %s, Elapsed time: %.2fs. Forcing unpin to 0.",
|
|
171
|
+
memory_obj.meta.address,
|
|
172
|
+
current_pin_count,
|
|
173
|
+
elapsed_time,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Update forced unpin statistics
|
|
177
|
+
LMCStatsMonitor.GetOrCreate().update_forced_unpin_count(1)
|
|
178
|
+
|
|
179
|
+
# Call unpin() while pin_count > 0 to properly release resources
|
|
180
|
+
while memory_obj.meta.pin_count > 0:
|
|
181
|
+
memory_obj.unpin()
|
|
182
|
+
|
|
183
|
+
def _execute(self) -> ThreadRunSummary:
|
|
184
|
+
"""
|
|
185
|
+
Execute one pin monitor check cycle.
|
|
186
|
+
|
|
187
|
+
This method is called by the PeriodicThread base class.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
ThreadRunSummary: Summary of the check cycle
|
|
191
|
+
"""
|
|
192
|
+
pinned_count, timeout_count, force_unpin_count = self._check_timeouts()
|
|
193
|
+
|
|
194
|
+
return ThreadRunSummary(
|
|
195
|
+
success=True,
|
|
196
|
+
message=f"Checked {pinned_count} objects, {timeout_count} timeouts, "
|
|
197
|
+
f"{force_unpin_count} force unpinned",
|
|
198
|
+
extra_info={
|
|
199
|
+
"pinned_count": str(pinned_count),
|
|
200
|
+
"timeout_count": str(timeout_count),
|
|
201
|
+
"force_unpin_count": str(force_unpin_count),
|
|
202
|
+
},
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
def start_monitoring(self):
|
|
206
|
+
"""Start the background monitoring thread."""
|
|
207
|
+
if self.is_running:
|
|
208
|
+
return
|
|
209
|
+
|
|
210
|
+
# Use base class start method
|
|
211
|
+
self.start()
|
|
212
|
+
logger.info("PinMonitor started")
|
|
213
|
+
|
|
214
|
+
# Setup metrics callback
|
|
215
|
+
prometheus_logger = PrometheusLogger.GetInstanceOrNone()
|
|
216
|
+
if prometheus_logger is not None:
|
|
217
|
+
prometheus_logger.pin_monitor_pinned_objects_count.set_function(
|
|
218
|
+
lambda: len(self._pinned_objects)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def stop_monitoring(self):
|
|
222
|
+
"""Stop the background monitoring thread."""
|
|
223
|
+
if not self.is_running:
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
# Unregister from the global registry
|
|
227
|
+
PeriodicThreadRegistry.get_instance().unregister(self.name)
|
|
228
|
+
|
|
229
|
+
# Use base class stop method
|
|
230
|
+
self.stop()
|
|
231
|
+
logger.info("PinMonitor stopped")
|
|
232
|
+
|
|
233
|
+
def get_monitored_count(self) -> int:
|
|
234
|
+
"""Get the number of currently monitored pinned objects."""
|
|
235
|
+
with self._objects_lock:
|
|
236
|
+
return len(self._pinned_objects)
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def DestroyInstance():
|
|
240
|
+
"""Destroy the singleton instance and stop monitoring.
|
|
241
|
+
This is mainly used for testing to ensure clean state between tests.
|
|
242
|
+
"""
|
|
243
|
+
with PinMonitor._instance_lock:
|
|
244
|
+
if PinMonitor._instance is not None:
|
|
245
|
+
PinMonitor._instance.stop_monitoring()
|
|
246
|
+
PinMonitor._instance = None
|
|
File without changes
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# Standard
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import atexit
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import threading
|
|
9
|
+
|
|
10
|
+
# First Party
|
|
11
|
+
from lmcache.logging import init_logger
|
|
12
|
+
|
|
13
|
+
logger = init_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RuntimePluginLauncher:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
config,
|
|
20
|
+
role: str | None,
|
|
21
|
+
worker_count: int,
|
|
22
|
+
worker_id: int,
|
|
23
|
+
):
|
|
24
|
+
self.config = config
|
|
25
|
+
self.role = role
|
|
26
|
+
self.worker_count = worker_count
|
|
27
|
+
self.worker_id = worker_id
|
|
28
|
+
self.plugin_processes: list[subprocess.Popen] = []
|
|
29
|
+
# Register cleanup handler
|
|
30
|
+
atexit.register(self.stop_plugins)
|
|
31
|
+
|
|
32
|
+
def launch_plugins(self):
|
|
33
|
+
"""Launch all configured plugins"""
|
|
34
|
+
if not self.config.runtime_plugin_locations:
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
for loc in self.config.runtime_plugin_locations:
|
|
38
|
+
self._launch_plugins(loc)
|
|
39
|
+
|
|
40
|
+
def _launch_plugins(self, loc: str):
|
|
41
|
+
"""Launch plugins from specified location"""
|
|
42
|
+
path = Path(loc)
|
|
43
|
+
if not path.exists():
|
|
44
|
+
logger.warning(f"Runtime plugin location {loc} does not exist")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
files = []
|
|
48
|
+
if path.is_file():
|
|
49
|
+
files = [path]
|
|
50
|
+
elif path.is_dir():
|
|
51
|
+
# Recursively find all .py and .sh files
|
|
52
|
+
for ext in ["*.py", "*.sh"]:
|
|
53
|
+
files.extend(path.rglob(ext))
|
|
54
|
+
|
|
55
|
+
for file in files:
|
|
56
|
+
self._launch_plugin(file)
|
|
57
|
+
|
|
58
|
+
def _should_skip_plugin(self, file: Path, parts: list[str]) -> bool:
|
|
59
|
+
"""Determine if plugin should be skipped based on role/worker ID.
|
|
60
|
+
|
|
61
|
+
The naming convention is ROLE_WORKERID_NAME (e.g. server_0_foo.py).
|
|
62
|
+
When role is None (MP mode), this convention does not apply,
|
|
63
|
+
so both role and worker ID filtering are skipped entirely.
|
|
64
|
+
"""
|
|
65
|
+
if len(parts) < 2:
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
# When role is None, skip all role/worker-based filtering
|
|
69
|
+
# (e.g. MP mode has no role concept)
|
|
70
|
+
if self.role is None:
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
plugin_role = parts[0].upper()
|
|
74
|
+
if plugin_role != "ALL" and plugin_role != self.role.upper():
|
|
75
|
+
logger.info(
|
|
76
|
+
"Skipping %s: requires role %s",
|
|
77
|
+
file,
|
|
78
|
+
plugin_role,
|
|
79
|
+
)
|
|
80
|
+
return True
|
|
81
|
+
|
|
82
|
+
# Check worker ID match (parts[1] in ROLE_WORKERID_NAME)
|
|
83
|
+
if len(parts) > 2 and parts[1].isdigit():
|
|
84
|
+
plugin_worker_id = int(parts[1])
|
|
85
|
+
if plugin_worker_id != self.worker_id:
|
|
86
|
+
logger.info(
|
|
87
|
+
"worker %d is skipping plugin %s, which is only for worker ID %d",
|
|
88
|
+
self.worker_id,
|
|
89
|
+
file,
|
|
90
|
+
plugin_worker_id,
|
|
91
|
+
)
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
def _launch_plugin(self, file: Path):
|
|
97
|
+
"""Launch a plugin"""
|
|
98
|
+
try:
|
|
99
|
+
filename = file.stem.lower()
|
|
100
|
+
parts = filename.split("_")
|
|
101
|
+
|
|
102
|
+
if self._should_skip_plugin(file, parts):
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
# Get interpreter from first line (shebang)
|
|
106
|
+
interpreter = self._get_interpreter(file)
|
|
107
|
+
|
|
108
|
+
# Pass role and config as environment variables
|
|
109
|
+
env = os.environ.copy()
|
|
110
|
+
role_str = self.role or ""
|
|
111
|
+
env["LMCACHE_RUNTIME_PLUGIN_ROLE"] = role_str
|
|
112
|
+
env["LMCACHE_RUNTIME_PLUGIN_CONFIG"] = self.config.to_json()
|
|
113
|
+
env["LMCACHE_RUNTIME_PLUGIN_WORKER_COUNT"] = str(self.worker_count)
|
|
114
|
+
env["LMCACHE_RUNTIME_PLUGIN_WORKER_ID"] = str(self.worker_id)
|
|
115
|
+
|
|
116
|
+
# TODO: For backwards compatibility, remove when applicable
|
|
117
|
+
env["LMCACHE_PLUGIN_ROLE"] = role_str
|
|
118
|
+
env["LMCACHE_PLUGIN_CONFIG"] = self.config.to_json()
|
|
119
|
+
env["LMCACHE_PLUGIN_WORKER_COUNT"] = str(self.worker_count)
|
|
120
|
+
env["LMCACHE_PLUGIN_WORKER_ID"] = str(self.worker_id)
|
|
121
|
+
|
|
122
|
+
# Force line-buffered stdout for Python sub-processes
|
|
123
|
+
# so that output is captured in real-time rather than
|
|
124
|
+
# being held in the block buffer until exit.
|
|
125
|
+
env["PYTHONUNBUFFERED"] = "1"
|
|
126
|
+
|
|
127
|
+
proc = subprocess.Popen(
|
|
128
|
+
[interpreter, str(file)],
|
|
129
|
+
env=env,
|
|
130
|
+
stdout=subprocess.PIPE,
|
|
131
|
+
stderr=subprocess.STDOUT,
|
|
132
|
+
text=True,
|
|
133
|
+
)
|
|
134
|
+
self.plugin_processes.append(proc)
|
|
135
|
+
logger.info(f"Launched runtime plugin: {file} with {interpreter}")
|
|
136
|
+
|
|
137
|
+
# Start thread to capture output continuously
|
|
138
|
+
threading.Thread(
|
|
139
|
+
target=self._capture_plugin_output, args=(proc, str(file)), daemon=True
|
|
140
|
+
).start()
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.error(f"Failed to launch plugin {file}: {e}")
|
|
143
|
+
|
|
144
|
+
def _get_interpreter(self, file: Path) -> str:
|
|
145
|
+
"""Get interpreter from first line comment"""
|
|
146
|
+
interpreters = []
|
|
147
|
+
try:
|
|
148
|
+
with open(file, "r", encoding="utf-8") as f:
|
|
149
|
+
first_line = f.readline().strip()
|
|
150
|
+
if first_line.startswith("#!"):
|
|
151
|
+
# Extract interpreter
|
|
152
|
+
interpreter_str = first_line[2:].strip()
|
|
153
|
+
interpreters.append(interpreter_str)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.error(
|
|
156
|
+
f"Error reading interpreter from runtime plugin file {file} - "
|
|
157
|
+
f"using default interpreters: {e}"
|
|
158
|
+
)
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
# Fallback to default interpreters
|
|
162
|
+
if file.suffix == ".py":
|
|
163
|
+
interpreters.append("python")
|
|
164
|
+
interpreters.append("python3")
|
|
165
|
+
elif file.suffix == ".sh":
|
|
166
|
+
interpreters.append("bash")
|
|
167
|
+
else:
|
|
168
|
+
raise ValueError(f"Plugin type {file.suffix} not supported ")
|
|
169
|
+
|
|
170
|
+
# Try each interpreter until we find one that exists
|
|
171
|
+
for interpreter in interpreters:
|
|
172
|
+
interpreter = interpreter.strip()
|
|
173
|
+
resolved_interpreter = shutil.which(interpreter)
|
|
174
|
+
if resolved_interpreter:
|
|
175
|
+
return resolved_interpreter
|
|
176
|
+
|
|
177
|
+
raise ValueError(f"No valid interpreter found for {file} from {interpreters}")
|
|
178
|
+
|
|
179
|
+
def _capture_plugin_output(self, proc: subprocess.Popen, plugin_name: str):
|
|
180
|
+
"""Continuously capture and log plugin output"""
|
|
181
|
+
try:
|
|
182
|
+
assert proc.stdout is not None, (
|
|
183
|
+
"The runtime plugin subprocess does not have stdout"
|
|
184
|
+
)
|
|
185
|
+
while True:
|
|
186
|
+
line = proc.stdout.readline()
|
|
187
|
+
if not line:
|
|
188
|
+
break
|
|
189
|
+
logger.info(f"[{plugin_name}] {line.strip()}")
|
|
190
|
+
|
|
191
|
+
proc.wait()
|
|
192
|
+
logger.info(
|
|
193
|
+
f"Runtime plugin {plugin_name} exited with code {proc.returncode}"
|
|
194
|
+
)
|
|
195
|
+
except Exception as e:
|
|
196
|
+
logger.error(f"Error capturing output for {plugin_name}: {e}")
|
|
197
|
+
|
|
198
|
+
def stop_plugins(self):
|
|
199
|
+
"""Terminate all plugin processes.
|
|
200
|
+
|
|
201
|
+
Note: This method may be called via atexit during interpreter shutdown,
|
|
202
|
+
when the logging system may already be closed. We avoid using logger
|
|
203
|
+
here to prevent "I/O operation on closed file" errors.
|
|
204
|
+
"""
|
|
205
|
+
for proc in self.plugin_processes:
|
|
206
|
+
try:
|
|
207
|
+
if proc.poll() is None:
|
|
208
|
+
proc.terminate()
|
|
209
|
+
except Exception:
|
|
210
|
+
# Silently ignore errors during shutdown
|
|
211
|
+
pass
|