
    `gj5%                       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j0                  e      Zd	Zd
ZdZdZddZdddZ dd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!dZ%edddd	 	 	 	 	 	 	 	 	 d"dZ&y)#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.logc                     t         j                  j                  dd      j                         } | rt	        |       S t               S )zHHERMES_HOME for process-level identity files (ignore profile overrides).HERMES_HOME )osenvirongetstripr   r   )vals    L/root/.hermes/venv/lib/python3.12/site-packages/gateway/shutdown_watchdog.py_process_hermes_homer   /   s5    
**..
+
1
1
3C
Cy    c                D    | | n	t               } |j                  t         S )z1Return ``<HERMES_HOME>/state/gateway.heartbeat``.)r   joinpath_HEARTBEAT_RELATIVEhomebases     r   get_loop_heartbeat_pathr    7   s$    #4)=)?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$    #4)=)?D4==122r   )pid
start_timer   extrac                   t        |      }t        | | nt        j                               t	        j
                  t        j                        j                         t        j                         d}|t        |      |d<   |r|j                  |       	 t        ||d       |S # t        $ r t        j!                  dd       Y |S w xY w)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#/3ryy{;ll8<<0::<^^%G
  %j 1uN$5 K  N=MKNs   B& & C
	C
)grace_sc                   	 t        t        |       d      }	 t        t        |      d      }||z   S # t        t        f$ r d}Y .w xY w# t        t        f$ r t        }Y ||z   S w xY w)z=Return the wall-clock leash for the shutdown watchdog thread.        )maxr3   	TypeError
ValueError!DEFAULT_SHUTDOWN_WATCHDOG_GRACE_S)drain_timeoutr;   draingraces       r   resolve_shutdown_watchdog_delayrE   `   sx    E-(#.2E'NC( 5= z"  z" 215=2s    2 A	 AA	A%$A%c                  	 | j                   j                  dd       dt        j                         |t        j                  t        j                        j                         |xs i d}	 t        | dd      5 }|j                  t        j                  |t        	      d
z          |j                  d       |j                          	 t!        j"                  |d       |j                  d       |j                          ddd       	 t&        j(                  j                  d|ddt        j                          d       t&        j(                  j                          t!        j"                  d       y# t        $ r Y yw xY w# t$        $ r |j                  d       Y w xY w# 1 sw Y   xY w# t$        $ r Y w xY w# t$        $ r Y yw xY w)z:Best-effort faulthandler + metadata dump before hard-exit.T)parentsexist_okNshutdown_watchdog_fired)eventr$   delay_sfired_atsnapshot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.
)rS   )parentmkdirOSErrorr   r.   r   r/   r   r0   r1   openwritejsondumpsstrflushfaulthandlerdump_tracebackr5   sysstderr)	dump_pathrK   rM   headerfhs        r   _write_watchdog_dumprd   q   s   td;
 +yy{LL.88:NF)S73 		rHHTZZ4t;<HH@AHHJC++F HH)*HHJ		

4WSM BIIK= ?A	
 	

##5A  "  CABC		 		    ss   E: -F5 ;AF)F	"!F)F5 A-G :	FF	F&#F)%F&&F))F2.F5 5	G G	GG   zgateway-shutdown-watchdog)
done_eventsnapshot_fn	exit_codera   namec               `   ||nt        j                         	 t        t        |       d      dk  rS dfd}	 t        j                  |d|      j                          S # t        t
        f$ r	 t        Y Qw xY w# t        $ r t        j                  dd       Y S w xY w)	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.
    r=   r   c                 >   t        j                         	z   } t        j                         | k  rL| t        j                         z
  }
j                  t        |d            ry t        j                         | k  rL
j	                         ry d }	         }n	t               }t        |	|       	 t        j                  d	|       t        j                  t        j                  fD ]  }	 |j                           	 ddlm}m}  |         |        	 ddlm}  |d       t)        j*                         y # t
        $ r}dt        |      i}Y d }~d }~ww xY w# t
        $ r Y w xY w# t
        $ r Y w xY w# t
        $ r Y rw xY w# t
        $ r Y qw xY w)	N      ?)timeoutsnapshot_error)rK   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)r2   r)   waitminis_setr5   reprr#   rd   r6   criticalr_   stdoutr`   r\   gateway.statusro   rp   hermes_loggingrq   r   _exit)deadline	remainingrM   exctargetstreamro   rp   rq   delaydonera   rh   rg   s            r   	_watchdogz(arm_shutdown_watchdog.<locals>._watchdog   s    >>#e+nn) 4>>#33IyyY!4y5 nn) ;;=-1"9&= (39X9ZVUXF	OO>	 zz3::. 	F		T(*	6C( 	I  9,d3i89  		    		
  		s`   E  6E# 1E2F F  	E 	EE #	E/.E/2	E>=E>	FF	FFT)r~   daemonri   zFailed to arm shutdown watchdogr+   )returnNone)	threadingEventr>   r3   r?   r@   rA   Threadstartr5   r6   r7   )	rK   rf   rg   rh   ra   ri   r   r   r   s	     ```  @@r   arm_shutdown_watchdogr      s    $ $/:Y__5FD2E'NC( z3 3jG	$TBHHJ K z" 212z  G6FKGs#   A. &B	 .BB	 B-,B-)
interval_sr%   r   should_continuec                
  K   	 t        t        |       d      }t        ||       	 | |       syt        j                  |       d{    | |       syt        ||       ?# t        t        f$ r	 t        }Y dw xY w7 7w)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.
    rl   )r%   r   N)r>   r3   r?   r@   DEFAULT_HEARTBEAT_INTERVAL_Sr:   asynciosleep)r   r%   r   r   intervals        r   loop_heartbeat_foreverr      s     0uZ(#. JT:
&/@mmH%%%&/@
>  z" 0/0 	&s3   BA& 0B	B
B&A>;B=A>>B)r   r   )N)r   Optional[Path]r   r   )
r$   zOptional[int]r%   Optional[float]r   r   r&   Optional[Dict[str, Any]]r   r   )rB   r3   r;   r3   r   r3   )ra   r   rK   r3   rM   r   r   r   )rK   r3   rf   zOptional[threading.Event]rg   z&Optional[Callable[[], Dict[str, Any]]]rh   r-   ra   r   ri   r[   r   zthreading.Event)
r   r3   r%   r   r   r   r   zOptional[Callable[[], bool]]r   r   )'__doc__
__future__r   r   r]   rY   loggingr   r_   r   r2   r   r   pathlibr   typingr   r   r	   r
   hermes_constantsr   utilsr   	getLogger__name__r6   rA   r   r   r"   r   r    r#   r:   rE   rd   r   r    r   r   <module>r      s  & #     	 
   '  0 0 , #			8	$ %) !# 4 C /3 "&&*	   	
 $ 
@ 7  	"++ + '	+
 
+b -1:> $+TT *T 8	T
 T T T Tr 5"&48??  ? 	?
 2? 
?r   