PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/cheroot/workers/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/cheroot/workers/threadpool.py |
"""A thread-based worker pool. .. spelling:: joinable """ import collections import threading import time import socket import warnings import queue from jaraco.functools import pass_none __all__ = ('WorkerThread', 'ThreadPool') class TrueyZero: """Object which equals and does math like the integer 0 but evals True.""" def __add__(self, other): return other def __radd__(self, other): return other trueyzero = TrueyZero() _SHUTDOWNREQUEST = None class WorkerThread(threading.Thread): """Thread which continuously polls a Queue for Connection objects. Due to the timing issues of polling a Queue, a WorkerThread does not check its own 'ready' flag after it has started. To stop the thread, it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue (one for each running WorkerThread). """ conn = None """The current connection pulled off the Queue, or None.""" server = None """The HTTP Server which spawned this thread, and which owns the Queue and is placing active connections into it.""" ready = False """A simple flag for the calling server to know when this thread has begun polling the Queue.""" def __init__(self, server): """Initialize WorkerThread instance. Args: server (cheroot.server.HTTPServer): web server object receiving this request """ self.ready = False self.server = server self.requests_seen = 0 self.bytes_read = 0 self.bytes_written = 0 self.start_time = None self.work_time = 0 self.stats = { 'Requests': lambda s: self.requests_seen + ( self.start_time is None and trueyzero or self.conn.requests_seen ), 'Bytes Read': lambda s: self.bytes_read + ( self.start_time is None and trueyzero or self.conn.rfile.bytes_read ), 'Bytes Written': lambda s: self.bytes_written + ( self.start_time is None and trueyzero or self.conn.wfile.bytes_written ), 'Work Time': lambda s: self.work_time + ( self.start_time is None and trueyzero or time.time() - self.start_time ), 'Read Throughput': lambda s: s['Bytes Read'](s) / ( s['Work Time'](s) or 1e-6 ), 'Write Throughput': lambda s: s['Bytes Written'](s) / ( s['Work Time'](s) or 1e-6 ), } threading.Thread.__init__(self) def run(self): """Process incoming HTTP connections. Retrieves incoming connections from thread pool. """ self.server.stats['Worker Threads'][self.name] = self.stats try: self.ready = True while True: conn = self.server.requests.get() if conn is _SHUTDOWNREQUEST: return self.conn = conn is_stats_enabled = self.server.stats['Enabled'] if is_stats_enabled: self.start_time = time.time() keep_conn_open = False try: keep_conn_open = conn.communicate() finally: if keep_conn_open: self.server.put_conn(conn) else: conn.close() if is_stats_enabled: self.requests_seen += self.conn.requests_seen self.bytes_read += self.conn.rfile.bytes_read self.bytes_written += self.conn.wfile.bytes_written self.work_time += time.time() - self.start_time self.start_time = None self.conn = None except (KeyboardInterrupt, SystemExit) as ex: self.server.interrupt = ex class ThreadPool: """A Request Queue for an HTTPServer which pools threads. ThreadPool objects must provide min, get(), put(obj), start() and stop(timeout) attributes. """ def __init__( self, server, min=10, max=-1, accepted_queue_size=-1, accepted_queue_timeout=10, ): """Initialize HTTP requests queue instance. Args: server (cheroot.server.HTTPServer): web server object receiving this request min (int): minimum number of worker threads max (int): maximum number of worker threads (-1/inf for no max) accepted_queue_size (int): maximum number of active requests in queue accepted_queue_timeout (int): timeout for putting request into queue :raises ValueError: if the min/max values are invalid :raises TypeError: if the max is not an integer or inf """ if min < 1: raise ValueError(f'min={min!s} must be > 0') if max == float('inf'): pass elif not isinstance(max, int) or max == 0: raise TypeError( 'Expected an integer or the infinity value for the `max` ' f'argument but got {max!r}.', ) elif max < 0: max = float('inf') if max < min: raise ValueError( f'max={max!s} must be > min={min!s} (or infinity for no max)', ) self.server = server self.min = min self.max = max self._threads = [] self._queue = queue.Queue(maxsize=accepted_queue_size) self._queue_put_timeout = accepted_queue_timeout self.get = self._queue.get self._pending_shutdowns = collections.deque() def start(self): """Start the pool of threads. :raises RuntimeError: if the pool is already started """ if self._threads: raise RuntimeError('Threadpools can only be started once.') self.grow(self.min) @property def idle(self): # noqa: D401; irrelevant for properties """Number of worker threads which are idle. Read-only.""" # noqa: D401 idles = len([t for t in self._threads if t.conn is None]) return max(idles - len(self._pending_shutdowns), 0) def put(self, obj): """Put request into queue. Args: obj (:py:class:`~cheroot.server.HTTPConnection`): HTTP connection waiting to be processed """ self._queue.put(obj, block=True, timeout=self._queue_put_timeout) def _clear_dead_threads(self): # Remove any dead threads from our list for t in [t for t in self._threads if not t.is_alive()]: self._threads.remove(t) try: self._pending_shutdowns.popleft() except IndexError: pass def grow(self, amount): """Spawn new worker threads (not above self.max).""" budget = max(self.max - len(self._threads), 0) n_new = min(amount, budget) workers = [self._spawn_worker() for i in range(n_new)] for worker in workers: while not worker.ready: time.sleep(.1) self._threads.extend(workers) def _spawn_worker(self): worker = WorkerThread(self.server) worker.name = ( 'CP Server {worker_name!s}'. format(worker_name=worker.name) ) worker.start() return worker def shrink(self, amount): """Kill off worker threads (not below self.min).""" # Grow/shrink the pool if necessary. # Remove any dead threads from our list amount -= len(self._pending_shutdowns) self._clear_dead_threads() if amount <= 0: return # calculate the number of threads above the minimum n_extra = max(len(self._threads) - self.min, 0) # don't remove more than amount n_to_remove = min(amount, n_extra) # put shutdown requests on the queue equal to the number of threads # to remove. As each request is processed by a worker, that worker # will terminate and be culled from the list. for _ in range(n_to_remove): self._pending_shutdowns.append(None) self._queue.put(_SHUTDOWNREQUEST) def stop(self, timeout=5): """Terminate all worker threads. Args: timeout (int): time to wait for threads to stop gracefully """ # for compatability, negative timeouts are treated like None # TODO: treat negative timeouts like already expired timeouts if timeout is not None and timeout < 0: timeout = None warnings.warning( 'In the future, negative timeouts to Server.stop() ' 'will be equivalent to a timeout of zero.', stacklevel=2, ) if timeout is not None: endtime = time.time() + timeout # Must shut down threads here so the code that calls # this method can know when all threads are stopped. for worker in self._threads: self._queue.put(_SHUTDOWNREQUEST) ignored_errors = ( # Raised when start_response called >1 time w/o exc_info or # wsgi write is called before start_response. See cheroot#261 RuntimeError, # Ignore repeated Ctrl-C. See cherrypy#691. KeyboardInterrupt, ) for worker in self._clear_threads(): remaining_time = timeout and endtime - time.time() try: worker.join(remaining_time) if worker.is_alive(): # Timeout exhausted; forcibly shut down the socket. self._force_close(worker.conn) worker.join() except ignored_errors: pass @staticmethod @pass_none def _force_close(conn): if conn.rfile.closed: return try: try: conn.socket.shutdown(socket.SHUT_RD) except TypeError: # pyOpenSSL sockets don't take an arg conn.socket.shutdown() except OSError: # shutdown sometimes fails (race with 'closed' check?) # ref #238 pass def _clear_threads(self): """Clear self._threads and yield all joinable threads.""" # threads = pop_all(self._threads) threads, self._threads[:] = self._threads[:], [] return ( thread for thread in threads if thread is not threading.current_thread() ) @property def qsize(self): """Return the queue size.""" return self._queue.qsize()