Ë
    âß²j0  ã                   óÀ  — U d 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
mZmZ e
rddlmZ  ej                  d«      ZdZdZd	Zd
ZdZ ej,                  e«      Zda ej2                  «       Zdaded<   deddfd„Zdee   fd„Zdee   fd„Z de!fd„Z"de!fd„Z# G d„ d«      Z$ ejJ                  «       Z&ded<    ej2                  «       Z'de(fd„Z)de$fd„Z*y)a"  Read-connection budgeting for SessionDB's WAL read path: per-file and
process-wide permit ceilings plus a descriptor-headroom gate, so N handles on
one state.db cannot walk the process into EMFILE. Idle pooled connections keep
their permit and are reclaimable across peers on the same path.é    N)ÚPath)ÚTYPE_CHECKINGÚIteratorÚOptional)Ú	SessionDBÚhermes_stateé   é   é   é@   g      Ð?)g        Nztuple[float, Optional[int]]Ú_fd_usage_cacheÚpidÚreturnzIterator[tuple[str, str]]c              #   ó¬   K  — d| › d�}t        j                  |«      D ]#  }|› d|› �}	 t        j                  |«      |f–— Œ% y# t        $ r Y Œ2w xY w­w)z¤Yield ``(readlink target, fd path)`` for every entry in /proc/<pid>/fd (unreadable
    links skipped). Raises OSError when the fd directory itself cannot be listed.z/proc/z/fdú/N)ÚosÚlistdirÚreadlinkÚOSError)r   Úfd_dirÚfdÚfd_paths       ú:/home/ubuntu/.hermes/hermes-agent/hermes_state_readpool.pyÚ_proc_fd_targetsr   H   sj   è ø€ ð �c�U˜#Ð€FÜ�j‰j˜Ó ò ˆØ�H˜A˜b˜TÐ"ˆð	Ü—+‘+˜gÓ&¨Ð/Ó/ñøô ò 	Ùð	üs(   ‚&A©AÁAÁ	AÁAÁAÁAc                  óâ   — dD ]"  } 	 t        t        j                  | «      «      c S  y# t        $ r<}|j                  t        j
                  t        j                  fv rY d}~ yY d}~Œfd}~ww xY w)z�Open descriptors in THIS process; None when unmeasurable (Windows: no fd
    dir, correctly inert); -1 when the probe itself hit EMFILE/ENFILE (no headroom).)z/proc/self/fdz/dev/fdNéÿÿÿÿ)Úlenr   r   r   ÚerrnoÚEMFILEÚENFILE)r   Úexcs     r   Ú_open_fd_countr"   T   sd   € ð /ò ˆð	Ü”r—z‘z &Ó)Ó*Ò*ðð øô ò 	Ø�y‰yœUŸ\™\¬5¯<©<Ð8Ñ8Ýô 9ûð	ús   ‡)©	A.²,A)Á)A.c                  óÔ   — 	 ddl } 	 | j                  | j                  «      \  }}|| j                  dfv ryt        |«      S # t        $ r Y yw xY w# t        t
        t        f$ r Y yw xY w)zFThe process's soft RLIMIT_NOFILE, or None when there is no usable one.r   Nr   )	ÚresourceÚImportErrorÚ	getrlimitÚRLIMIT_NOFILEÚAttributeErrorr   Ú
ValueErrorÚRLIM_INFINITYÚint)r$   ÚsoftÚ_hards      r   Ú_fd_soft_limitr.   `   sy   € ðÛðØ×(Ñ(¨×)?Ñ)?Ó@‰ˆˆeð �×&Ñ&¨Ð+Ñ+ØÜˆt‹9Ðøô ò Ùðûô œG¤ZÐ0ò Ùðús"   ‚A ‡A Á	AÁAÁA'Á&A'c                  ó2  — t        «       } | €yt        j                  «       }t        5  t        \  }}|duxr ||z
  t
        k  }ddd«       st        «       }t        5  ||faddd«       €y|dk\  xr | |z
  t        kD  S # 1 sw Y   Œ>xY w# 1 sw Y   Œ+xY w)záCan the process spare a descriptor for a new read connection? Fails OPEN
    when unmeasurable (refusing every read would be a self-inflicted convoy);
    fails CLOSED only on evidence (measured shortfall or a starved probe).NTr   )r.   ÚtimeÚ	monotonicÚ_fd_usage_lockr   Ú_FD_USAGE_CACHE_SECONDSr"   Ú_FD_HEADROOM_RESERVE)r,   ÚnowÚstampÚcachedÚfreshs        r   Ú_fd_headroom_okr9   o   s­   € ô Ó€DØ€|Øä
�.‰.Ó
€CÜ	ñ OÜ'‰ˆˆvØ˜dÐ"ÒN¨¨e©Ô7NÑ'Nˆ÷Oñ ÜÓ!ˆÜñ 	,Ø" F˜mˆO÷	,à€~ØØ�Q‰;ÒA˜D 6™MÔ-AÑAÐA÷Oð Oú÷
	,ð 	,ús   ¨BÁBÂB
ÂBc                  ó–   — t         5  t        t        j                  «       «      } ddd«       t	        d„  D «       «      S # 1 sw Y   ŒxY w)zšClose one idle read connection on ANY path: the process ceiling is shared
    across files, so a quiet profile must not hold descriptors a busy one needs.Nc              3   ó<   K  — | ]  }|j                  «       –— Œ y ­w©N)Úreclaim_idle)Ú.0Úbudgets     r   ú	<genexpr>z3_reclaim_idle_read_conn_anywhere.<locals>.<genexpr>‰   s   è ø€ Ò;¨ˆv×"Ñ"×$Ñ;ùó   ‚)Ú_read_budgets_lockÚlistÚ_read_budgetsÚvaluesÚany)Úbudgetss    r   Ú _reclaim_idle_read_conn_anywhererH   „   s@   € ô 
ñ /Ü”}×+Ñ+Ó-Ó.ˆ÷/äÑ;°7Ô;Ó;Ð;÷/ð /ús	   ‡?¿Ac                   ón   — e Zd ZdZdd„Zdd„Zdd„Zdddefd	„Zdd
„Z	defd„Z
dddefd„Zddddefd„Zy)Ú_PathReadBudgetu   Read-connection permits for ONE database file, shared process-wide
    (per-instance semaphores let N SessionDBs peak at N x (1 + MAX)). An idle
    pooled connection keeps its permit, so a permit miss first reclaims an IDLE
    connection from a peer on the same path.

    ``_READ_POOL_MAX`` used to be enforced by a ``BoundedSemaphore`` owned by each SessionDB, which bounded
    the wrong noun: the descriptors are spent on a *file*, so N SessionDB objects on one state.db each got
    their own allowance and peak scaled as ``N x (1 + _READ_POOL_MAX)``. A long-lived gateway holds at least
    two (``SessionStore`` and ``GatewayRunner`` open independent handles per profile path) and the count
    grows with the profile count, which is how a healthy process walked into EMFILE â€” #98573.
    r   Nc                 ó²   — t        j                  t        «      | _        t        j                  «       | _        t        j                  «       | _        d| _	        y )NF)
Ú	threadingÚBoundedSemaphoreÚ_READ_POOL_MAXÚpermitsÚLockÚ_lockÚweakrefÚWeakSetÚ_membersÚ_duplicate_handles_warned©Úselfs    r   Ú__init__z_PathReadBudget.__init__™   s8   € Ü ×1Ñ1´.ÓAˆŒÜ—^‘^Ó%ˆŒ
ä6=·o±oÓ6GˆŒØ).ˆÕ&ó    r   c                 ó   — | j                   5  | j                  j                  |«       t        d„ | j                  D «       «      }|t        kD  xr | j
                   }|r2d| _        dj                  t        d„ | j                  D «       «      «      }d d d «       r(t        j                  d|j                  t        «       y y # 1 sw Y   Œ4xY w)Nc              3   óP   K  — | ]  }|j                   s|j                  �d–— Œ  y ­w)Né   )Ú	read_onlyÚ_conn©r>   Úmembers     r   r@   z+_PathReadBudget.register.<locals>.<genexpr>¦   s*   è ø€ ò ØØ×'Ò'¨F¯L©LÐ,Dô ñùs   ‚$&Tz, c              3   óN   K  — | ]  }|j                   rŒ|j                  –— Œ y ­wr<   )r]   Ú_creation_siter_   s     r   r@   z+_PathReadBudget.register.<locals>.<genexpr>¯   s#   è ø€ ò 2Ø.4ÈV×M]ÓM]�F×)Õ)ñ2ùs   ‚%”%zÍ%d live SessionDB handles on %s in this process; each holds its own writer connection (read connections are capped at %d for the file). A long-lived process should share one handle per path. Created at: %s)rQ   rT   ÚaddÚsumÚ_HANDLES_PER_PATH_WARNrU   ÚjoinÚsortedÚloggerÚwarningÚdb_pathrN   )rW   ÚdbÚhandlesÚwarnÚcreation_sitess        r   Úregisterz_PathReadBudget.register    sÇ   € Ø�Z‰Zñ 	Ø�M‰M×Ñ˜bÔ!ô ñ Ø $§¡ôó ˆGð Ô4Ñ4Ò[¸T×=[Ñ=[Ð9[ˆDÙØ15�Ô.ð "&§¡¬6ñ 2Ø8<¿¹ô2ó ,ó "�÷	ñ" ô �N‰Nð
!ð ˜Ÿ™¤^°^õ
ð ÷#	ð 	ús   �BCÃCc                 ó|   — | j                   5  | j                  j                  |«       ddd«       y# 1 sw Y   yxY w)zERemove a closed writer from duplicate-handle diagnostics immediately.N)rQ   rT   Údiscard)rW   rk   s     r   Ú
unregisterz_PathReadBudget.unregisterÁ   s0   € à�Z‰Zñ 	&Ø�M‰M×!Ñ! "Ô%÷	&÷ 	&ñ 	&ús   �2²;Ú	requesterc                 óÐ   — t        «       st        5  t        dz  addd«       y| j                  «       sy| j	                  |«      ryt
        j                  «        y# 1 sw Y   yxY w)z¿Take a permit for a new read connection, or refuse (caller degrades to the
        locked writer connection). Gates, broadest first: fd headroom, process
        ceiling, this file's ceiling.r\   NFT)r9   rB   Ú_read_open_denied_fd_headroomÚ_acquire_process_permitÚ_acquire_path_permitÚ_process_read_permitsÚrelease©rW   rs   s     r   Úacquirez_PathReadBudget.acquireÆ   s]   € ô Ô ä#ñ 3Ü-°Ñ2Ð-÷3àØ×+Ñ+Ô-ØØ×$Ñ$ YÔ/ØÜ×%Ñ%Ô'Ø÷3àús   ‘
AÁA%c                 ó`   — | j                   j                  «        t        j                  «        y)zCReturn one connection's permits. Pairs with a successful acquire().N)rO   ry   rx   rV   s    r   ry   z_PathReadBudget.releaseÖ   s   € à�‰×ÑÔÜ×%Ñ%Õ'rY   c                 óv   — t         j                  d¬«      xs" t        «       xr t         j                  d¬«      S )NF©Úblocking)rx   r{   rH   rV   s    r   rv   z'_PathReadBudget._acquire_process_permitÛ   s8   € ä$×,Ñ,°eÐ,Ó<ò 
Ü,Ó.Ò`Ô3H×3PÑ3PÐZ_Ð3PÓ3`ð	
rY   c                 óž   — | j                   j                  d¬«      xs0 | j                  |¬«      xr | j                   j                  d¬«      S )NFr~   )Úexclude)rO   r{   r=   rz   s     r   rw   z$_PathReadBudget._acquire_path_permitá   sJ   € Ø�|‰|×#Ñ#¨UÐ#Ó3ò 
Ø×Ñ iÐÓ0ÒY°T·\±\×5IÑ5IÐSXÐ5IÓ5Yð	
rY   r�   zOptional[SessionDB]c                 ó®   — | j                   5  | j                  D �cg c]	  }||usŒ|‘Œ }}ddd«       t        d„ D «       «      S c c}w # 1 sw Y   Œ xY w)z—Close one idle pooled connection held by a member; True if one went.
        Its release() returns both permits, so both ceilings reclaim through here.Nc              3   ó<   K  — | ]  }|j                  «       –— Œ y ­wr<   )Ú_evict_one_idle_read_connr_   s     r   r@   z/_PathReadBudget.reclaim_idle.<locals>.<genexpr>ë   s   è ø€ ÒL¸&�6×3Ñ3×5ÑLùrA   )rQ   rT   rF   )rW   r�   rk   Úmemberss       r   r=   z_PathReadBudget.reclaim_idleæ   sY   € ð �Z‰Zñ 	HØ$(§M¡MÖG˜b°R¸wÒ5F’rÐGˆGÐG÷	HäÑLÀGÔLÓLÐLùò H÷	Hð 	Hús    �Aœ	A¦AªAÁAÁA)r   N)rk   r   r   Nr<   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__rX   ro   rr   Úboolr{   ry   rv   rw   r=   © rY   r   rJ   rJ   Œ   se   „ ñ
ó/óóB&ð
 ð °ó ó (ð

¨ó 
ð
¨kð 
¸dó 
ñ
MÐ$9ð MÀTô MrY   rJ   z1weakref.WeakValueDictionary[str, _PathReadBudget]rD   c                 ó|   — 	 t        t        | «      j                  «       «      S # t        $ r t        | «      cY S w xY w)z9Canonicalise a db path so two spellings share one budget.)Ústrr   Úresolver   )rj   s    r   Ú_read_budget_keyr�   ô   s8   € ðÜ”4˜“=×(Ñ(Ó*Ó+Ð+øÜò Ü�7‹|Òðús   ‚!$ ¤;º;c                 ó¨   — t        | «      }t        5  t        j                  |«      }|€t	        «       }|t        |<   |cd d d «       S # 1 sw Y   y xY wr<   )r�   rB   rD   ÚgetrJ   )rj   Úkeyr?   s      r   Ú_read_budget_forr“   ü   sN   € Ü
˜7Ó
#€CÜ	ñ Ü×"Ñ" 3Ó'ˆØˆ>Ü$Ó&ˆFØ!'ŒM˜#ÑØ÷÷ ò ús   ’,AÁA)+r‰   r   Úloggingr   rL   r0   rR   Úpathlibr   Útypingr   r   r   r   r   Ú	getLoggerrh   rN   Ú_READ_POOL_PROCESS_MAXre   r4   r3   rM   rx   ru   rP   r2   r   Ú__annotations__r+   r   r"   r.   rŠ   r9   rH   rJ   ÚWeakValueDictionaryrD   rB   r�   r�   r“   r‹   rY   r   ú<module>r›      s<  ðòCó
 Û Û 	Û Û Û Ý ß 4Ñ 4áÝ&ð 
ˆ×	Ñ	˜>Ó	*€ð  €ð Ð ð Ð ð Ð ð Ð à2˜	×2Ñ2Ð3IÓJÐ ð !"Ð à�—‘Ó!€Ø1<€Ð.Ó <ð	˜#ð 	Ð"=ó 	ð	˜ ™ó 	ð˜ ™ó ðB˜ó Bð*<¨$ó <÷_Mñ _MðH GbÀg×FaÑFaÓFc€ÐBÓ dØ#�Y—^‘^Ó%Ð ð ó ð ô rY   