o
    ih                     @   s   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Zddlm	Z	 ddl
mZmZ ddlmZmZmZmZ ddlmZmZmZmZmZmZmZmZmZmZmZmZmZm Z m!Z! ddl"m#Z# G d	d
 d
Z$dS )z
Dirty Arbiter Process

Asyncio-based arbiter that manages the dirty worker pool and routes
requests from HTTP workers to available dirty workers.
    N)util   )get_app_workers_attributeparse_dirty_app_spec)
DirtyErrorDirtyNoWorkersAvailableErrorDirtyTimeoutErrorDirtyWorkerError)DirtyProtocolmake_error_responsemake_responseSTASH_OP_PUTSTASH_OP_GETSTASH_OP_DELETESTASH_OP_KEYSSTASH_OP_CLEARSTASH_OP_INFOSTASH_OP_ENSURESTASH_OP_DELETE_TABLESTASH_OP_TABLESSTASH_OP_EXISTSMANAGE_OP_ADDMANAGE_OP_REMOVE)DirtyWorkerc                   @   s.  e Zd ZdZdd d D ZdZdIddZd	d
 Zdd Z	dd Z
dd Zdd Zdd Zdd Zdd Zdd Zdd Zdd Zdd  Zd!d" Zd#d$ Zd%d& Zd'd( ZdJd)d*Zd+d, Zd-d. Zd/d0 Zd1d2 Zd3d4 Zd5d6 ZdKd8d9Zd:d; Z d<d= Z!d>d? Z"d@dA Z#dBdC Z$dLdEdFZ%dGdH Z&dS )MDirtyArbitera4  
    Dirty arbiter that manages the dirty worker pool.

    The arbiter runs an asyncio event loop and handles:
    - Spawning and managing dirty worker processes
    - Accepting connections from HTTP workers
    - Routing requests to available dirty workers
    - Monitoring worker health via heartbeat
    c                 C   s   g | ]	}t td | qS )zSIG%s)getattrsignal).0x r   J/home/ubuntu/.local/lib/python3.10/site-packages/gunicorn/dirty/arbiter.py
<listcomp><   s    zDirtyArbiter.<listcomp>z*HUP QUIT INT TERM TTIN TTOU USR1 USR2 CHLD   Nc                 C   s   || _ || _d| _t | _|| _tjdd| _	|p!tj
| j	d| _i | _i | _i | _i | _i | _d| _d| _d| _| j j| _d| _d| _i | _i | _i | _i | _i | _g | _i | _|    dS )z
        Initialize the dirty arbiter.

        Args:
            cfg: Gunicorn config
            log: Logger
            socket_path: Path to the arbiter's Unix socket
            pidfile: Well-known PID file location for orphan detection
        Nzgunicorn-dirty-)prefixzarbiter.sockr   T)!cfglogpidosgetpidppidpidfiletempfilemkdtemptmpdirpathjoinsocket_pathworkersworker_socketsworker_connectionsworker_queuesworker_consumers_worker_rr_index
worker_agealivedirty_workersnum_workers_server_loop_pending_requests	app_specsapp_worker_mapworker_app_map_app_rr_indices_pending_respawnsstash_tables_parse_app_specs)selfr$   r%   r0   r*   r   r   r    __init__B   s8   



zDirtyArbiter.__init__c                 C   s   | j jD ]<}t|\}}|du r1zt|}W n ty0 } z| jd|| W Y d}~nd}~ww |||d| j|< t | j	|< qdS )a  
        Parse all app specifications from config.

        Populates self.app_specs with parsed information about each app,
        including the import path and worker count limits.

        Worker count priority:
        1. Config override (e.g., "module:Class:2") - highest priority
        2. Class attribute (e.g., workers = 2 on the class)
        3. None (all workers) - default
        Nz,Could not read workers attribute from %s: %s)import_pathworker_countoriginal_spec)
r$   
dirty_appsr   r   	Exceptionr%   warningr>   setr?   )rE   specrG   rH   er   r   r    rD   y   s$   zDirtyArbiter._parse_app_specsc                 C   s2   d}| j  D ]}|d }|durt||}q|S )a  
        Calculate minimum number of workers required by app specs.

        Returns the maximum worker_count across all apps that have limits.
        Apps with worker_count=None don't impose a minimum.

        Returns:
            int: Minimum workers required (at least 1)
        r   rH   N)r>   valuesmax)rE   min_requiredrN   rH   r   r   r    _get_minimum_workers   s   

z!DirtyArbiter._get_minimum_workersc                 C   s^   g }| j  D ]%\}}|d }t| j|t }|du r#|| q||k r,|| q|S )a  
        Determine which apps a new worker should load.

        Returns a list of import paths for apps that need more workers.
        Apps with workers=None (all workers) are always included.
        Apps with worker limits are included only if they haven't
        reached their limit yet.

        Returns:
            List of import paths to load, or empty list if no apps need workers
        rH   N)r>   itemslenr?   getrM   append)rE   	app_pathsrG   rN   rH   current_workersr   r   r    _get_apps_for_new_worker   s   
z%DirtyArbiter._get_apps_for_new_workerc                 C   sB   t || j|< |D ]}|| jvrt | j|< | j| | q	dS )a?  
        Register which apps a worker has loaded.

        Updates both app_worker_map and worker_app_map to track the
        bidirectional relationship between workers and apps.

        Args:
            worker_pid: The PID of the worker
            app_paths: List of app import paths loaded by this worker
        N)listr@   r?   rM   addrE   
worker_pidrX   app_pathr   r   r    _register_worker_apps   s   
z"DirtyArbiter._register_worker_appsc                 C   s6   | j |g }|D ]}|| jv r| j| | q	dS )z
        Unregister a worker's apps when it exits.

        Removes the worker from all tracking maps.

        Args:
            worker_pid: The PID of the worker to unregister
        N)r@   popr?   discardr]   r   r   r    _unregister_worker   s   

zDirtyArbiter._unregister_workerc              
   C   s  t  | _| jd| j | jrJz t| jd}|t| j W d   n1 s*w   Y  W n t	yI } z| j
d| W Y d}~nd}~ww | jt jd< | j|  |   td z!z	t|   W n	 tyr   Y nw W |   dS W |   dS |   w )z&Run the dirty arbiter (blocking call).z Dirty arbiter starting (pid: %s)wNzFailed to write PID file: %sGUNICORN_DIRTY_SOCKETzdirty-arbiter)r'   r(   r&   r%   infor*   openwritestrIOErrorrL   r0   environr$   on_dirty_startinginit_signalsr   _setproctitleasynciorun
_run_asyncKeyboardInterrupt_cleanup_sync)rE   frO   r   r   r    rp      s2   

zDirtyArbiter.runc                 C   s   | j D ]	}t|tj qttj| j ttj| j ttj| j ttj| j ttj| j ttj	| j ttj
| j ttj| j dS )zSet up signal handlers.N)SIGNALSr   SIG_DFLSIGTERM_signal_handlerSIGQUITSIGINTSIGHUPSIGUSR1SIGCHLDSIGTTINSIGTTOU)rE   sigr   r   r    rm   
  s   
zDirtyArbiter.init_signalsc                    s:  |t jkr jr j fdd dS |t jkr  j  dS |t jkr4 jr2 j fdd dS |t jkrW  j	d7  _	 j
d j	  jrU j fdd dS |t jkr  } j	|krn jd| dS   j	d8  _	 j
d	 j	  jr j fd
d dS d _ jr j j dS dS )zHandle signals.c                         t   S N)ro   create_task_handle_sigchldr   rE   r   r    <lambda>      z.DirtyArbiter._signal_handler.<locals>.<lambda>Nc                      r   r   )ro   r   reloadr   r   r   r    r   +  r   r   z'SIGTTIN: Increasing dirty workers to %sc                      r   r   ro   r   manage_workersr   r   r   r    r   6  r   zASIGTTOU: Cannot decrease below %s workers (required by app specs)z'SIGTTOU: Decreasing dirty workers to %sc                      r   r   r   r   r   r   r    r   I  r   F)r   r}   r<   call_soon_threadsafer|   r%   reopen_filesr{   r~   r:   rf   r   rS   rL   r8   	_shutdown)rE   r   framemin_workersr   r   r    rx     s\   










zDirtyArbiter._signal_handlerc                 C   s   | j r
| j   dS dS )zInitiate async shutdown.N)r;   closer   r   r   r    r   R  s   zDirtyArbiter._shutdownc                    s`  t  | _tj| jrt| j t j| j	| jdI dH | _
t| jd | jd| j |  I dH  t |  }zTz'| j
4 I dH  | j
 I dH  W d  I dH  n1 I dH s_w   Y  W n t jtfyq   Y nw W |  z|I dH  W n
 t jy   Y nw |  I dH  dS |  z|I dH  W n
 t jy   Y nw |  I dH  w )z/Main async loop - start server, manage workers.)r.   Ni  zDirty arbiter listening on %s)ro   get_running_loopr<   r'   r.   existsr0   unlinkstart_unix_serverhandle_clientr;   chmodr%   rf   r   r   _worker_monitorserve_foreverCancelledErrorRuntimeErrorcancelstop)rE   monitor_taskr   r   r    rq   W  sF   
(zDirtyArbiter._run_asyncc                    sn   | j r5tdI dH  t | jkr"| jd d| _ |   dS | 	 I dH  | 
 I dH  | j sdS dS )z1Periodically check worker health and manage pool.g      ?Nz+Parent changed, shutting down dirty arbiterF)r8   ro   sleepr'   getppidr)   r%   rL   r   murder_workersr   r   r   r   r    r     s   zDirtyArbiter._worker_monitorc                    s&   |    | jr|  I dH  dS dS )z#Handle SIGCHLD - reap dead workers.N)reap_workersr8   r   r   r   r   r    r     s
   zDirtyArbiter._handle_sigchldc                    sV  | j d zzX| jr_z
t|I dH }W n
 tjy    Y n?w |d}|tjkr5| 	||I dH  n'|tj
krD| ||I dH  n|tjkrS| ||I dH  n	| ||I dH  | jsW n tyy } z| j d| W Y d}~nd}~ww W |  z
| I dH  W dS  ty   Y dS w |  z	| I dH  W w  ty   Y w w )a
  
        Handle a connection from an HTTP worker.

        Routes requests to available dirty workers and returns responses.
        Supports both regular responses and streaming (chunk-based) responses.
        Also handles stash (shared state) operations.
        z&New client connection from HTTP workerNtypezClient connection error: %s)r%   debugr8   r
   read_message_asyncro   IncompleteReadErrorrV   MSG_TYPE_STASHhandle_stash_requestMSG_TYPE_STATUShandle_status_requestMSG_TYPE_MANAGEhandle_manage_requestroute_requestrK   errorr   wait_closed)rE   readerwritermessagemsg_typerO   r   r   r    r     sH   



zDirtyArbiter.handle_clientc              
      s$  | dd}| d}| |I dH }|du r>| js td}n|r*| jr*t|}ntd}t||}t||I dH  dS || j	vrK| 
|I dH  | j	| }t  }	||||	fI dH  z|	I dH  W dS  ty }
 zt|td|
 |d}t||I dH  W Y d}
~
dS d}
~
ww )a  
        Route a request to an available dirty worker via queue.

        Each worker has a dedicated queue and consumer task. Requests are
        submitted to the queue and processed sequentially by the consumer.

        For streaming responses, messages (chunks) are forwarded directly
        to the client_writer as they arrive from the worker.

        Args:
            request: Request message dict
            client_writer: StreamWriter to send responses to client
        idunknownr_   NzNo dirty workers availablezRequest failed: 	worker_id)rV   _get_available_workerr1   r   r>   r   r   r
   write_message_asyncr4   _start_worker_consumerro   r   create_futureputrK   r	   )rE   requestclient_writer
request_idr_   r^   r   responsequeuefuturerO   r   r   r    r     s8   






 zDirtyArbiter.route_requestc                    s>   t    j<  fdd}t | }|j< dS )z3Start a consumer task for a worker's request queue.c               
      s   j r^zI  I d H \} }}z7z| |I d H  | s$|d  W n ty@ } z| s6|| W Y d }~nd }~ww W    n   w W n tj	yX   Y d S w j sd S d S r   )
r8   rV   _execute_on_workerdone
set_resultrK   set_exception	task_donero   r   )r   r   r   rO   r   rE   r^   r   r    consumer  s,   


z5DirtyArbiter._start_worker_consumer.<locals>.consumerN)ro   Queuer4   r   r5   )rE   r^   r   taskr   r   r    r     s   
z#DirtyArbiter._start_worker_consumerc              
      s  | dd}z| |I dH \}}t||I dH  	 ztjt|| jjdI dH }W n tj	yL   t
|td| jj}t||I dH  Y W dS w | d}	|	tjkrat||I dH  q|	tjkrrt||I dH  W dS |	tjtjfv rt||I dH  W dS | jd|	 q ty }
 z)| jd	||
 | | t
|td
|
 |d}t||I dH  W Y d}
~
dS d}
~
ww )a  
        Execute request on a specific worker (called by consumer).

        Handles both regular responses and streaming (chunk-based) responses.
        For streaming, chunk and end messages are forwarded directly to the
        client_writer as they arrive from the worker.
        r   r   NT)timeoutzWorker timeoutr   z$Unknown message type from worker: %sz Error executing on worker %s: %szWorker communication failed: r   )rV   _get_worker_connectionr
   r   ro   wait_forr   r$   dirty_timeoutTimeoutErrorr   r   MSG_TYPE_CHUNKMSG_TYPE_ENDMSG_TYPE_RESPONSEMSG_TYPE_ERRORr%   rL   rK   r   _close_worker_connectionr	   )rE   r^   r   r   r   r   r   r   r   r   rO   r   r   r    r     sZ   


#

 zDirtyArbiter._execute_on_workerc                    s   |r| j r|| jv rt| j| }n	dS t| j }|s dS |r8| j r8| j|d}|d t| | j|< n| j}|d t| | _||t|  S )a  
        Get an available worker PID using round-robin selection.

        If app_path is provided, only returns workers that have loaded
        that specific app. Uses per-app round-robin to ensure fair
        distribution among eligible workers.

        Args:
            app_path: Optional import path of the target app. If None,
                     returns any worker using global round-robin.

        Returns:
            Worker PID or None if no eligible workers are available.
        Nr   r   )	r>   r?   r[   r1   keysrA   rV   rU   r6   )rE   r_   eligible_pidsidxr   r   r    r   G  s   


z"DirtyArbiter._get_available_workerc                    s   || j v r| j | S | j|}|std| tdD ]}tj|r( nt	dI dH  qtd| t
|I dH \}}||f| j |< ||fS )z%Get or create connection to a worker.zNo socket for worker 2   皙?NzWorker socket not ready: )r3   r2   rV   r   ranger'   r.   r   ro   r   open_unix_connection)rE   r^   r0   _r   r   r   r   r    r   q  s   

z#DirtyArbiter._get_worker_connectionc                 C   s*   || j v r| j |\}}|  dS dS )zClose connection to a worker.N)r3   ra   r   )rE   r^   _readerr   r   r   r    r     s   
z%DirtyArbiter._close_worker_connectionc                    s   | dd}t }g }| j D ]5\}}z|j }t|| d}	W n tt	t
fy2   d}	Y nw |||jt|dg t|dd|	d q|jd	d
 d | j|t|| jr`t| j ng d}
t||
}t||I dH  dS )z
        Handle a status query request.

        Returns information about the dirty arbiter and its workers.

        Args:
            message: Status request message
            client_writer: StreamWriter to send response to client
        r   r      NrX   bootedF)r&   ageappsr   last_heartbeatc                 S   s   | d S )Nr   r   )rd   r   r   r    r     s    z4DirtyArbiter.handle_status_request.<locals>.<lambda>key)arbiter_pidr1   rH   r   )rV   time	monotonicr1   rT   tmplast_updateroundOSError
ValueErrorAttributeErrorrW   r   r   sortr&   rU   r>   r[   r   r   r
   r   )rE   r   r   r   nowworkers_infor&   workerr   r   resultr   r   r   r    r     s4   





z"DirtyArbiter.handle_status_requestc                    sH  | dd}| d}tdt| dd}z|tkrad}t|D ]}  }|dur7  jd7  _|d7 }tdI dH  q"|dkrSd	d
|ddt	 j
 jd}n|d	d
||t	 j
 jd}nn|tkr  }	d}
t|D ]9} j|	krx n1t	 j
dkr n(  jd8  _t j
  fddd} |tj |
d7 }
tdI dH  qod	d||
t	 j
 jd}ntd| }t||}t||I dH  W dS  jd|tkrd
nd|| d| dd t||}t||I dH  W dS  ty# } z  jd| t|tt|}t||I dH  W Y d}~dS d}~ww )z
        Handle a worker management request.

        Supports adding or removing dirty workers via protocol messages.

        Args:
            message: Manage request message
            client_writer: StreamWriter to send response to client
        r   r   opr   countr   Nr   Tr\   z)All apps have reached their worker limits)success	operation	requestedspawnedreasontotal_workerstarget_workers)r   r   r   r   r   r   c                        j |  jS r   r1   r   pr   r   r    r         z4DirtyArbiter.handle_manage_request.<locals>.<lambda>r   remove)r   r   r   removedr   r   zUnknown manage operation: z6Worker management: %s %d workers (spawned/removed: %d)r   r  zManage operation error: %s)rV   rQ   intr   r   spawn_workerr:   ro   r   rU   r1   r   rS   minr   kill_workerr   rw   r   r   r
   r   r%   rf   r   rK   r   ri   )rE   r   r   r   r   r   r   r   r   r   r  
oldest_pidr   r   rO   r   r   r    r     s   

	





 z"DirtyArbiter.handle_manage_requestc              
      s`  | dd}| d}| dd}| d}| d}| d z`d	}|tkr=|| jvr3i | j|< || j| |< d
}n|tkr_|| jvrKddi}n|| j| vrWddi}n| j| | }n|tkr{|| jv rx|| j| v rx| j| |= d
}nd}n|tkr|| jvrg }nt| j|  }	 r fdd|	D }	|	}n|tkr|| jv r| j| 	  d
}n|t
kr|| jvrddi}nqt| j| |d}nf|tkr|| jvri | j|< d
}nU|tkr|| jv r| j|= d
}nEd}nB|tkrt| j }n6|tkr|| jvrd}n(|d	u rd
}n || j| v }ntd| }
t||
}t||I d	H  W d	S t|trrd|v rr|d }|dkrLtd| }
n|dkrYtd| }
ntt|}
d| dd d|
_t||
}nt||}t||I d	H  W d	S  ty } z | jd| t|tt|}t||I d	H  W Y d	}~d	S d	}~ww )a!  
        Handle a stash operation directly in the arbiter.

        All stash tables are stored in arbiter memory for simplicity
        and fast access.

        Args:
            message: Stash operation message
            client_writer: StreamWriter to send response to client
        r   r   r   table r   valuepatternNTr   key_not_foundFc                    s    g | ]}t  t| r|qS r   )fnmatchri   )r   kr  r   r    r!   H  s    
z5DirtyArbiter.handle_stash_request.<locals>.<listcomp>table_not_found)sizer  zUnknown stash operation: zTable not found: zKey not found: Stashr   ErrorzStash operation error: %s)rV   r   rC   r   r   r   r[   r   r   clearr   rU   r   r   r   r   r   r   r
   r   
isinstancedictri   titlereplace
error_typer   rK   r%   r   )rE   r   r   r   r   r  r   r
  r   all_keysr   r   r  rO   r   r  r    r     s   





















 z!DirtyArbiter.handle_stash_requestc                    s    j sdS  j} j r.t j|k r.  }|du rntdI dH   j r.t j|k st j|krZt j  fddd} 	|t
j tdI dH  t j|ks5dS dS )z%Maintain the number of dirty workers.Nr   c                    r   r   r   r   r   r   r    r     r   z-DirtyArbiter.manage_workers.<locals>.<lambda>r   )r8   r:   rU   r1   r  ro   r   r  r   r  r   rw   )rE   r:   r   r  r   r   r    r     s"   

zDirtyArbiter.manage_workersFc              
   C   s  | j r
| j d}n|rt| j }n|  }|s"| jd dS |  jd7  _t	j
| jd| j d}t| j| j|| j| j|d}t	 }|dkrp||_|| j|< || j|< | || | j| | | jd|| |S t	 |_ztd	| jj d
 |  t	d W dS  ty } zt	|jdur|jnd W Y d}~dS d}~w ty   | jd |j st	| j! t	d Y dS w )a  
        Spawn a new dirty worker.

        Worker app assignment follows these priorities:
        1. If there are pending respawns (from dead workers), use those apps
        2. Otherwise, determine apps for a new worker based on allocation
        3. If force_all_apps=True, spawn with all apps regardless of limits

        Args:
            force_all_apps: If True, spawn worker with all apps ignoring limits

        Returns:
            Worker PID in parent process, or None if no apps need workers
        r   z)No apps need more workers, skipping spawnNr   zworker-z.sock)r   r)   rX   r$   r%   r0   z,Spawned dirty worker (pid: %s) with apps: %szdirty-worker []z!Exception in dirty worker process)"rB   ra   r[   r>   r   rZ   r%   r   r7   r'   r.   r/   r-   r   r&   r$   forkr1   r2   r`   dirty_post_forkrf   r(   r   rn   	proc_nameinit_process_exit
SystemExitcoderK   	exceptionr   WORKER_BOOT_ERROR)rE   force_all_appsrX   r0   r   r&   rO   r   r   r    r    sZ   	


(zDirtyArbiter.spawn_workerc              
   C   s^   z	t || W dS  ty. } z|jtjkr#| | W Y d}~dS W Y d}~dS d}~ww )zKill a worker by PID.N)r'   killr   errnoESRCH_cleanup_worker)rE   r&   r   rO   r   r   r    r    s   zDirtyArbiter.kill_workerc                 C   s   |  | || jv r| j|   | j|= | j|d || jv r0t| j| }|r0| j| | 	| | j
|d}|rE| j| | | j|d}|rgtj|rizt| W dS  tyf   Y dS w dS dS )z
        Clean up after a worker exits.

        Saves the dead worker's app list to pending respawns so the
        replacement worker gets the same apps.
        N)r   r5   r   r4   ra   r@   r[   rB   rW   rc   r1   r$   dirty_worker_exitr2   r'   r.   r   r   r   )rE   r&   	dead_appsr   r0   r   r   r    r)    s*   



zDirtyArbiter._cleanup_workerc              
      s   | j jsdS t| j D ]=\}}zt |j  | j jkr"W qW n t	t
fy.   Y qw |jsD| jd| d|_| |tj q| |tj qdS )z!Kill workers that have timed out.NzDIRTY WORKER TIMEOUT (pid:%s)T)r$   r   r[   r1   rT   r   r   r   r   r   r   abortedr%   criticalr  r   SIGABRTSIGKILL)rE   r&   r   r   r   r    r     s"   zDirtyArbiter.murder_workersc              
   C   s   zG	 t dt j\}}|sW dS d}t |rt |}nt |r/t |}| jd|| || j	kr;| j
d| | | | jd| q tya } z|jtjkrV W Y d}~dS d}~ww )zReap dead worker processes.TNz)Dirty worker (pid:%s) killed by signal %sz$Dirty worker failed to boot (pid:%s)zDirty worker exited (pid:%s))r'   waitpidWNOHANG	WIFEXITEDWEXITSTATUSWIFSIGNALEDWTERMSIGr%   rL   r$  r   r)  rf   r   r'  ECHILD)rE   wpidstatusexitcoder   rO   r   r   r    r   *  s0   




zDirtyArbiter.reap_workersc                    sn   | j d t| jjD ]}|   tdI dH  qt| j	
 }|| jjd D ]	}| |tj q+dS )z!Reload workers (SIGHUP handling).zReloading dirty workersr   N)r%   rf   r   r$   r9   r  ro   r   r[   r1   r   r  r   rw   )rE   r   old_workersr&   r   r   r    r   C  s   zDirtyArbiter.reloadTc                    s   | j  D ]}|  q|rtjntj}t | jj }t	| j
 D ]}| || q$| j
rKt |k rK|   tdI dH  | j
rKt |k s6t	| j
 D ]	}| |tj qR|   dS )zStop all workers.r   N)r5   rP   r   r   rw   ry   r   r$   dirty_graceful_timeoutr[   r1   r   r  r   ro   r   r/  )rE   gracefulr   r   limitr&   r   r   r    r   Q  s   
zDirtyArbiter.stopc                 C   s   | j rtj| j rzt| j  W n	 ty   Y nw tj| jr5zt| j W n	 ty4   Y nw zt| jD ]}ttj	| j| q<t
| j W n	 tyZ   Y nw | jd| j dS )zSynchronous cleanup on exit.zDirty arbiter exiting (pid: %s)N)r*   r'   r.   r   r   r   r0   listdirr-   r/   rmdirr%   rf   r&   )rE   rt   r   r   r    rs   h  s(   zDirtyArbiter._cleanup_sync)NNr   )F)T)'__name__
__module____qualname____doc__splitru   r$  rF   rD   rS   rZ   r`   rc   rp   rm   rx   r   rq   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r)  r   r   r   r   rs   r   r   r   r    r   1   sL    

7" :((4
<*
)^u
H$
r   )%rD  ro   r'  r  r'   r   r+   r   gunicornr   appr   r   errorsr   r   r   r	   protocolr
   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r    <module>   s   D