
    #j              	      J   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
mZ ddlmZ erddlmZ  ej        e          ZdZdZ e ej        d e ej                    d	z  d
z                                ZdZ G d d          ZddZddZd ZefddZdS )zc
Daemon mode support for Hindsight API.

Provides idle timeout for running as a background daemon.
    )annotationsN)Path)TYPE_CHECKING)IOi"  HINDSIGHT_API_DAEMON_LOGz
.hindsightz
daemon.log_HINDSIGHT_DAEMON_CHILDc                  0    e Zd ZdZefd	dZd Zd Zd ZdS )
IdleTimeoutMiddlewarezBASGI middleware that tracks activity and exits after idle timeout.idle_timeoutintc                `    || _         || _        t          j                    | _        d | _        d S N)appr   timelast_activity_checker_task)selfr   r   s      \/home/rurouni/.hermes/hermes-agent/venv/lib/python3.11/site-packages/hindsight_api/daemon.py__init__zIdleTimeoutMiddleware.__init__)   s-    (!Y[[!    c                t   K   t          j                     | _        |                     |||           d {V  d S r   )r   r   r   )r   scopereceivesends       r   __call__zIdleTimeoutMiddleware.__call__/   sB      !Y[[hhugt,,,,,,,,,,,r   c                \    t          j        |                                           | _        dS )z7Start the background task that checks for idle timeout.N)asynciocreate_task_check_idler   )r   s    r   start_idle_checkerz(IdleTimeoutMiddleware.start_idle_checker4   s'    $01A1A1C1CDDr   c                |  K   | j         dk    rdS 	 t          j        d           d{V  t          j                    | j        z
  }|| j         k    rlt
                              d| j          d           t          j        d           d{V  ddl}t          j	        t          j
                    |j                   )z:Background task that exits the process after idle timeout.r   NT   zIdle timeout reached (zs), shutting down daemon   )r   r   sleepr   r   loggerinfosignaloskillgetpidSIGTERM)r   	idle_timer'   s      r   r   z!IdleTimeoutMiddleware._check_idle8   s       !!F
	5-#########	d&88I4,,,`T5F```aaamA&&&&&&&&&	V^444
	5r   N)r   r   )	__name__
__module____qualname____doc__DEFAULT_IDLE_TIMEOUTr   r   r    r    r   r   r
   r
   &   sf        LL0D " " " " "- - -
E E E5 5 5 5 5r   r
   
log_handle'IO[bytes]'returndictc                    t          j                    dk    rKt          t          dd          }t          t          dd          }||z  t          j        | t          j        ddS dt          j        | | dS )an  Cross-platform kwargs to spawn a subprocess detached from the caller.

    On POSIX, ``start_new_session=True`` calls ``setsid(2)`` so the child
    survives the parent's terminal.  On Windows we use
    ``DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP``.

    ``log_handle`` receives the child's stdout/stderr so output never leaks
    into the parent's terminal.
    WindowsDETACHED_PROCESSr   CREATE_NEW_PROCESS_GROUPT)creationflagsstdinstdoutstderr	close_fds)start_new_sessionr<   r=   r>   )platformsystemgetattr
subprocessDEVNULLSTDOUT)r3   detached_processcreate_new_process_groups      r   _detach_popen_kwargsrI   K   s     I%%":/A1EE#*:7QST#U#U -0HH'  '
 
 	
 "#	  r   Nonec                    t           j                            dd           t          j                                         t          j                                         t          t          j	        d          5 } t          j
        |                                 t          j                                                   ddd           n# 1 swxY w Y   t          t           d          }t          j
        |                                t          j                                                   t          j
        |                                t          j                                                   dS )zpRedirect stdin/stdout/stderr to the daemon log file.

    Called in the daemon child process after re-exec.
    Tparentsexist_okrNa)DAEMON_LOG_PATHparentmkdirsysr=   flushr>   openr(   devnulldup2filenor<   )rW   log_fds     r   _redirect_stdio_to_logr[   g   s=   
    ===JJ	bj#		 6'
  #)"2"2"4"45556 6 6 6 6 6 6 6 6 6 6 6 6 6 6 /3''FGFMMOOSZ..00111GFMMOOSZ..0011111s   8ACCCc                    t           j        dk    r#t          j                            dd           dS t
          j                            t                    dk    rt                       dS d t           j
        dd         D             } t           j        dd	g| z   }t
          j                                        }d|t          <   t          t                    |d
<   t          j                            dd           t          t          d          5 }t          j        |fd|it#          |           ddd           n# 1 swxY w Y   t          j        d           dS )a`  Detach the current process into a background daemon.

    Uses ``subprocess.Popen`` (which maps to ``posix_spawn`` on macOS) to
    re-exec the current command in a detached session.  This replaces the
    traditional double-fork pattern because ``os.fork()`` without ``exec()``
    corrupts Apple framework state (XPC, Metal/MPS, ObjC runtime) on macOS,
    causing SIGBUS crashes when PyTorch uses the MPS backend.

    The function has two code paths controlled by the ``_HINDSIGHT_DAEMON_CHILD``
    environment variable:

    * **Parent** (env var not set): re-exec the same command via Popen with
      ``start_new_session=True``, stripping ``--daemon`` from argv and setting
      ``_HINDSIGHT_DAEMON_CHILD=1``.  Then ``sys.exit(0)``.
    * **Child** (env var set): redirect stdio to the daemon log file and return.
      No fork, no re-exec.

    On Windows there is no fork model: the spawning parent is expected to
    detach us via ``CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS`` and to
    redirect stdout/stderr to ``HINDSIGHT_API_DAEMON_LOG`` before exec.
    We still ensure the log directory exists.
    win32TrL   N1c                    g | ]
}|d k    |S )z--daemonr2   ).0rP   s     r   
<listcomp>zdaemonize.<locals>.<listcomp>   s    ===Q*__!___r   r#   z-mzhindsight_api.mainr   abenvr   )rT   rA   rQ   rR   rS   r(   environgetENV_DAEMON_CHILDr[   argv
executablecopystrrV   rD   PopenrI   exit)
child_argscmdrc   r3   s       r   	daemonizero   y   s   . |w$$TD$AAA
 
z~~&''3..    >=SXabb\===J>4!5
6
CC
*//

CC&)/&:&:C"#   ===	ot	$	$ K
JJ#J)=j)I)IJJJK K K K K K K K K K K K K K K HQKKKKKs   "D??EEportr   boolc                    ddl }	 |                     |j        |j                  }|                    d           |                    d| f          }|                                 |dk    S # t          $ r Y dS w xY w)z>Check if a daemon is running and responsive on the given port.r   Nr#   z	127.0.0.1F)socketAF_INETSOCK_STREAM
settimeout
connect_exclose	Exception)rp   rs   sockresults       r   check_daemon_runningr|      s    MMM}}V^V-?@@+t!455

{   uus   A%A, ,
A:9A:)r3   r4   r5   r6   )r5   rJ   )rp   r   r5   rq   )r0   
__future__r   r   loggingr(   rA   rD   rT   r   pathlibr   typingr   r   	getLoggerr-   r%   DEFAULT_DAEMON_PORTr1   getenvrj   homerQ   rf   r
   rI   r[   ro   r|   r2   r   r   <module>r      s    # " " " " "   				      



                    		8	$	$    $yry!;SS|A[^jAj=k=kllmm - "5 "5 "5 "5 "5 "5 "5 "5J   82 2 2 2$2 2 2j &9       r   