
    [j5%                    B   d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlmZmZ ddlmZ ddlmZmZmZmZ ddlmZ ddlmZ  ej        e          Zd	Zd
ZdZdZd8dZd9d:dZ d9d:dZ!dddddd;dZ"edd<d Z#d=d%Z$ddd&dd'd(d>d2Z%edddd3d?d7Z&dS )@u  Out-of-loop shutdown backstop + event-loop liveness heartbeat (#66892).

When the asyncio loop freezes mid-drain, every asyncio-based recovery path is
structurally unable to fire: the drain deadline, status rewrites, and forensics
all need the same loop that is stuck. launchd/systemd KeepAlive only restarts a
*dead* process, so a wedged-but-alive gateway sits as a zombie until manual
SIGKILL.

This module provides:

1. A plain OS-thread shutdown watchdog armed at ``stop()``. If shutdown has not
   completed within ``restart_drain_timeout + grace``, it dumps all-thread
   stacks via ``faulthandler`` plus a metadata snapshot, then ``os._exit`` so
   the service manager can revive the process.
2. An event-loop heartbeat file at ``<HERMES_HOME>/state/gateway.heartbeat`` so
   external supervision can distinguish "process alive" from "loop frozen"
   (``gateway_state.json`` alone can't — it only rewrites on transitions/turns).
    )annotationsN)datetimetimezone)Path)AnyCallableDictOptional)get_hermes_home)atomic_json_writeg      N@g      >@)statezgateway.heartbeat)logszgateway-shutdown-watchdog.logreturnr   c                     t           j                            dd                                          } | rt	          |           S t                      S )zHHERMES_HOME for process-level identity files (ignore profile overrides).HERMES_HOME )osenvirongetstripr   r   )vals    ;/home/ice/.hermes/hermes-agent/gateway/shutdown_watchdog.py_process_hermes_homer   /   sC    
*..
+
+
1
1
3
3C
 Cyy    homeOptional[Path]c                D    | | nt                      } |j        t           S )z1Return ``<HERMES_HOME>/state/gateway.heartbeat``.)r   joinpath_HEARTBEAT_RELATIVEr   bases     r   get_loop_heartbeat_pathr"   7   s'    #44)=)?)?D4=-..r   c                D    | | nt                      } |j        t           S )zBReturn the faulthandler / metadata dump path for a fired watchdog.)r   r   _WATCHDOG_DUMP_RELATIVEr    s     r   get_shutdown_watchdog_dump_pathr%   =   s'    #44)=)?)?D4=122r   )pid
start_timer   extrar&   Optional[int]r'   Optional[float]r(   Optional[Dict[str, Any]]c                   t          |          }t          | | nt          j                              t	          j        t          j                                                  t          j
                    d}|t          |          |d<   |r|                    |           	 t          ||d           n,# t          $ r t                              dd           Y nw xY w|S )u   Atomically rewrite the loop-liveness heartbeat file.

    ``start_time`` is the gateway process start (``time.time()`` epoch seconds)
    so supervisors can detect PID reuse. Best-effort — never raises.
    N)r&   
updated_at	monotonicr'   )indentz&Failed to write gateway loop heartbeatTexc_info)r"   intr   getpidr   nowr   utc	isoformattimer.   floatupdater   	Exceptionloggerdebug)r&   r'   r   r(   pathpayloads         r   write_loop_heartbeatr?   C   s     #4((D#/33ry{{;;l8<00::<<^%% G
  %j 1 1 uN$55555 N N N=MMMMMNKs   #B6 6&CC)grace_sdrain_timeoutr8   r@   c                   	 t          t          |           d          }n# t          t          f$ r d}Y nw xY w	 t          t          |          d          }n# t          t          f$ r
 t          }Y nw xY w||z   S )z=Return the wall-clock leash for the shutdown watchdog thread.        )maxr8   	TypeError
ValueError!DEFAULT_SHUTDOWN_WATCHDOG_GRACE_S)rA   r@   draingraces       r   resolve_shutdown_watchdog_delayrJ   `   s    E-((#..z"   2E'NNC((z" 2 2 2125=s     66A A32A3	dump_pathdelay_ssnapshotNonec                  	 | j                             dd           n# t          $ r Y dS w xY wdt          j                    |t          j        t          j                  	                                |pi d}	 t          | dd          5 }|                    t          j        |t          	          d
z              |                    d           |                                 	 t!          j        |d           n%# t$          $ r |                    d           Y nw xY w|                    d           |                                 ddd           n# 1 swxY w Y   n# t$          $ r Y nw xY w	 t&          j                            d|ddt          j                     d           t&          j                                         t!          j        d           dS # t$          $ r Y dS w xY w)z:Best-effort faulthandler + metadata dump before hard-exit.T)parentsexist_okNshutdown_watchdog_fired)eventr&   rL   fired_atrM   azutf-8)encoding)default
z(--- faulthandler dump (all threads) ---
)fileall_threadsz%(faulthandler.dump_traceback failed)
z--- end dump ---
z&Gateway shutdown watchdog fired after z.0fzs (pid=z); dumping all thread stacks.
)rZ   )parentmkdirOSErrorr   r3   r   r4   r   r5   r6   openwritejsondumpsstrflushfaulthandlerdump_tracebackr:   sysstderr)rK   rL   rM   headerfhs        r   _write_watchdog_dumprj   q   sv   td;;;;    +y{{L..88::N F)S7333 		rHHTZ444t;<<<HH@AAAHHJJJC+FFFFF C C CABBBBBCHH)***HHJJJ		 		 		 		 		 		 		 		 		 		 		 		 		 		 		    

AWD A AIKKA A A	
 	
 	
 	
#555555   sz    
--:E% AE(C?>E?D!E D!!,EE% EE%  E!E% %
E21E26A+G# #
G10G1   zgateway-shutdown-watchdog)
done_eventsnapshot_fn	exit_coderK   namerl   Optional[threading.Event]rm   &Optional[Callable[[], Dict[str, Any]]]rn   r2   ro   rb   threading.Eventc                  ||nt          j                    	 t          t          |           d          n# t          t
          f$ r
 t          Y nw xY wdk    rS dfd}	 t          j        |d|                                           n,# t          $ r t                              d	d
           Y nw xY wS )a  Arm a daemon-thread hard-exit backstop for a wedged shutdown path.

    If ``done_event`` is set before ``delay_s`` elapses, the thread exits
    quietly (normal / progressing shutdown completed). Otherwise it dumps
    diagnostics and calls ``os._exit(exit_code)``.

    Never raises. Returns the ``done_event`` (creating one when omitted) so
    the caller can disarm on successful completion.
    NrC   r   r   rN   c                    t          j                    	z   } t          j                    | k     rS| t          j                    z
  }
                    t          |d                    rd S t          j                    | k     S
                                rd S d }4	              }n(# t
          $ r}dt          |          i}Y d }~nd }~ww xY wnt                      }t          |	|           	 t          
                    d	|           n# t
          $ r Y nw xY wt          j        t          j        fD ]'}	 |                                 # t
          $ r Y $w xY w	 ddlm}m}  |              |             n# t
          $ r Y nw xY w	 ddlm}  |d           n# t
          $ r Y nw xY wt)          j                   d S )	N      ?)timeoutsnapshot_error)rL   rM   uh   Shutdown watchdog fired after %.0fs — forcing process exit (asyncio drain path appears wedged; see %s)r   )remove_pid_filerelease_gateway_runtime_lock)drain_log_queue)r7   r.   waitminis_setr:   reprr%   rj   r;   criticalrf   stdoutrg   rc   gateway.statusrx   ry   hermes_loggingrz   r   _exit)deadline	remainingrM   exctargetstreamrx   ry   rz   delaydonerK   rn   rm   s            r   	_watchdogz(arm_shutdown_watchdog.<locals>._watchdog   sj    >##e+n)) 4>#3#33IyyY!4!4y55  n)) ;;== 	F-1"9&;== 9 9 9,d3ii89 (39X9Z9ZVUXFFFF	OO>	     	 	 	D	 z3:. 	 	F   	TTTTTTTTO((**** 	 	 	D		666666OC((((( 	 	 	D	
s`   
B( (
C2CC5D 
DD=E
EE#F   
FFF$ $
F10F1T)r   daemonro   zFailed to arm shutdown watchdogr0   )r   rN   )	threadingEventrD   r8   rE   rF   rG   Threadstartr:   r;   r<   )	rL   rl   rm   rn   rK   ro   r   r   r   s	     ```  @@r   arm_shutdown_watchdogr      s   $ $/::Y_5F5FD2E'NNC((z" 2 2 212 zz3 3 3 3 3 3 3 3 3 3jG	$TBBBHHJJJJ G G G6FFFFFGKs!   < AA-)B &C ?C )
interval_sr'   r   should_continuer   r   Optional[Callable[[], bool]]c                4  K   	 t          t          |           d          }n# t          t          f$ r
 t          }Y nw xY wt          ||           	 | |            sdS t          j        |           d{V  | |            sdS t          ||           H)u   Rewrite the loop heartbeat file on a cadence until cancelled / gated off.

    Runs as an asyncio task on the gateway loop — if the loop freezes, this
    task stops and the file mtime/updated_at goes stale for external monitors.
    ru   )r'   r   TN)rD   r8   rE   rF   DEFAULT_HEARTBEAT_INTERVAL_Sr?   asynciosleep)r   r'   r   r   intervals        r   loop_heartbeat_foreverr      s      0uZ((#..z" 0 0 0/0
 JT::::?&/@/@&FmH%%%%%%%%%&/@/@&F
>>>>?s   " ==)r   r   )N)r   r   r   r   )
r&   r)   r'   r*   r   r   r(   r+   r   r   )rA   r8   r@   r8   r   r8   )rK   r   rL   r8   rM   r+   r   rN   )rL   r8   rl   rp   rm   rq   rn   r2   rK   r   ro   rb   r   rr   )
r   r8   r'   r*   r   r   r   r   r   rN   )'__doc__
__future__r   r   rd   r`   loggingr   rf   r   r7   r   r   pathlibr   typingr   r   r	   r
   hermes_constantsr   utilsr   	getLogger__name__r;   rG   r   r   r$   r   r"   r%   r?   rJ   rj   r   r    r   r   <module>r      s&   & # " " " " "        				 



      ' ' ' ' ' ' ' '       0 0 0 0 0 0 0 0 0 0 0 0 , , , , , , # # # # # #		8	$	$ %) !# 4 C    / / / / /3 3 3 3 3 "&&*     @ 7     "+ + + +b -1:> $+T T T T T Tr 5"&48? ? ? ? ? ? ? ?r   