• Stefy Lanza (nextime / spora )'s avatar
    runpod: a registry entry must stop vouching for a pod when its owner dies (v0.3.56) · 509a221d
    Stefy Lanza (nextime / spora ) authored
    A qwen38 pool pod (A40, $0.59/hr) was found running at 21:30, outside its
    08:00-20:00 schedule window, hours after it should have been down. Its config
    was correct and the pool said so every pass — "outside its warm-pod window —
    warm floor 0" — while the pod billed on.
    
    Two mechanisms each did their job and left a gap between them. The 21:17
    unattended upgrade restarted coderai; the pod survived it. With a warm floor of
    0 nothing provisioned, and adoption (`find_shared_pod`) only runs when a pool
    actually needs a pod — so no pool tracked it, and no pool applied
    `idle_timeout_s` to it. Meanwhile the reaper skips everything in
    `_known_pod_ids()`, and the dead process's registry entry was still in there,
    because `registered_pod_ids()` returned every key in the file regardless of who
    wrote it or whether that process still existed. The pod was simultaneously
    unmanaged and shielded. Nothing in the registry could tell the two apart: `at`
    was written once at registration and never updated.
    
    `at` is now a heartbeat. `touch_registered_pods()` refreshes this process's own
    entries from the scaler loop (every fourth 15 s pass), `live_registered_pod_ids()`
    counts ours unconditionally and a sibling's only while its heartbeat is fresh,
    and `_known_pod_ids()` uses that. An entry whose owner has been silent for
    REAP_GRACE_SECONDS stops shielding its pod, and the reaper treats it like any
    other leftover. The grace is 1200 s against a 60 s heartbeat, so a sibling
    between passes is never at risk.
    
    The trade this accepts: a process still running but whose maintenance loop has
    died stops heartbeating, and a sibling will reap its pods after the grace.
    Twenty minutes of silence from the loop that is supposed to manage a pod is
    itself the failure.
    
    Also: a pod the reaper kills is now recorded in the ledger, computed from a new
    `created` field (distinct from the heartbeat, which moves). Those hours were
    invisible before — only `_terminate` recorded spend, and that only runs for a
    pod a pool was tracking, which is exactly not this case. And the reaper
    unregisters what it terminates, so it cannot try twice.
    
    The pod itself was terminated by hand and its stale entry removed; the account
    is back to zero pods.
    
    11 tests in tests/test_runpod_registry_heartbeat.py, including the whole bug in
    one assertion: an orphan id is in `registered_pod_ids()` and not in
    `_known_pod_ids()`.
    Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01PxYHWbCAjjjD7A1rhFKtaW
    509a221d
Name
Last commit
Last update
..
conftest.py Loading commit data...
ocr_lane_stub_worker.py Loading commit data...
test_admin_auth.py Loading commit data...
test_admin_forward_auth.py Loading commit data...
test_admin_remote_ui.py Loading commit data...
test_audio_ml_backends.py Loading commit data...
test_audio_ml_endpoints.py Loading commit data...
test_autoupgrade.py Loading commit data...
test_autoupgrade_recovers_a_down_server.py Loading commit data...
test_autoupgrade_sees_training.py Loading commit data...
test_broker_config.py Loading commit data...
test_broker_dispatch.py Loading commit data...
test_broker_protocol.py Loading commit data...
test_broker_shutdown.py Loading commit data...
test_broker_streaming.py Loading commit data...
test_burst_placement.py Loading commit data...
test_character_studio_fetch.py Loading commit data...
test_chat_passthrough.py Loading commit data...
test_cluster.py Loading commit data...
test_compose.py Loading commit data...
test_forward_auth_peer_trust.py Loading commit data...
test_front_api_auth.py Loading commit data...
test_front_inflight_leak.py Loading commit data...
test_front_ocr_swap_gate.py Loading commit data...
test_image_has_c_compiler.py Loading commit data...
test_longcat_avatar_layout.py Loading commit data...
test_longcat_bsa.py Loading commit data...
test_longcat_cancellable.py Loading commit data...
test_longcat_checkpoint_resolve.py Loading commit data...
test_longcat_component_quant.py Loading commit data...
test_longcat_config_ui.py Loading commit data...
test_longcat_external_training.py Loading commit data...
test_longcat_int8_load.py Loading commit data...
test_longcat_lora.py Loading commit data...
test_longcat_orchestration.py Loading commit data...
test_longcat_packaging.py Loading commit data...
test_longcat_placement.py Loading commit data...
test_longcat_progress.py Loading commit data...
test_longcat_prompt_cache.py Loading commit data...
test_longcat_quantize.py Loading commit data...
test_longcat_scale.py Loading commit data...
test_longcat_tasks.py Loading commit data...
test_longcat_variants.py Loading commit data...
test_longcat_vendored_source.py Loading commit data...
test_longcat_venv_env.py Loading commit data...
test_longcat_vram_estimate.py Loading commit data...
test_longcat_worker.py Loading commit data...
test_manual_multimodal_test_client.py Loading commit data...
test_model_scan_cache.py Loading commit data...
test_models_context_window.py Loading commit data...
test_ocr_accent_repair.py Loading commit data...
test_ocr_blank_page.py Loading commit data...
test_ocr_chandra.py Loading commit data...
test_ocr_confidence.py Loading commit data...
test_ocr_fanout.py Loading commit data...
test_ocr_language.py Loading commit data...
test_ocr_olmocr.py Loading commit data...
test_ocr_request_budgets.py Loading commit data...
test_ocr_served_model.py Loading commit data...
test_ocr_token_trace.py Loading commit data...
test_ocr_unreadable_and_dpi.py Loading commit data...
test_ocr_vram_share_and_fallback.py Loading commit data...
test_ocr_worker_lanes.py Loading commit data...
test_olmocr_pod_tls.py Loading commit data...
test_oom_priority.py Loading commit data...
test_pod_log_drain.py Loading commit data...
test_proxy_logprobs.py Loading commit data...
test_remote_execution.py Loading commit data...
test_runpod_client_scale.py Loading commit data...
test_runpod_fleet_status.py Loading commit data...
test_runpod_global_volume.py Loading commit data...
test_runpod_ocr_remotization.py Loading commit data...
test_runpod_placement.py Loading commit data...
test_runpod_pod_extra_args.py Loading commit data...
test_runpod_reaper_grace.py Loading commit data...
test_runpod_regional_volumes.py Loading commit data...
test_runpod_registry_heartbeat.py Loading commit data...
test_runpod_request_gpu_choice.py Loading commit data...
test_runpod_scale_down.py Loading commit data...
test_runpod_scale_out.py Loading commit data...
test_runpod_schedule_region_spend.py Loading commit data...
test_runpod_warm_floor_rerent.py Loading commit data...
test_runpod_warmup.py Loading commit data...
test_studio_composed_surfaces.py Loading commit data...
test_surya_retry_budget.py Loading commit data...
test_surya_runpod_fixes.py Loading commit data...
test_text_lora_to_pod.py Loading commit data...
test_township_api_key.py Loading commit data...
test_township_clip_geometry.py Loading commit data...
test_township_longcat.py Loading commit data...
test_township_page_js.py Loading commit data...
test_township_templates.py Loading commit data...
test_township_video_dataset.py Loading commit data...
test_upgrade_launcher.py Loading commit data...
test_video_as_b64_scope.py Loading commit data...
test_video_geometry.py Loading commit data...
test_vllm_share_self_tuning.py Loading commit data...
test_vram_releasers.py Loading commit data...
test_weight_stores.py Loading commit data...
test_whisper_server_local_models.py Loading commit data...