@@ -434,6 +434,8 @@ def __init__(self):
434434 self ._thread_id = None
435435 self ._clock_resolution = time .get_clock_info ('monotonic' ).resolution
436436 self ._exception_handler = None
437+ self ._settings = events .EventLoopSettings .DEFAULT
438+ self ._eager_state = constants .EAGER_IDLE
437439 self .set_debug (coroutines ._is_debug_mode ())
438440 # The preserved state of async generator hooks.
439441 self ._old_agen_hooks = None
@@ -667,6 +669,7 @@ def _run_forever_cleanup(self):
667669 loop cleanup logic.
668670 """
669671 self ._stopping = False
672+ self ._eager_state = constants .EAGER_IDLE
670673 self ._thread_id = None
671674 events ._set_running_loop (None )
672675 self ._set_coroutine_origin_tracking (False )
@@ -682,6 +685,8 @@ def run_forever(self):
682685 while True :
683686 self ._run_once ()
684687 if self ._stopping :
688+ if self ._eager_state >= 0 :
689+ self ._run_once ()
685690 break
686691 finally :
687692 self ._run_forever_cleanup ()
@@ -2009,10 +2014,14 @@ def _timer_handle_cancelled(self, handle):
20092014 def _run_once (self ):
20102015 """Run one full iteration of the event loop.
20112016
2012- This calls all currently ready callbacks, polls for I/O,
2013- schedules the resulting callbacks, and finally schedules
2014- 'call_later' callbacks.
2017+ This polls for I/O, schedules the resulting callbacks and the
2018+ 'call_later' callbacks that are due, and calls all ready
2019+ callbacks. Then it calls the callbacks scheduled by them
2020+ eagerly, within the limits of the loop settings.
20152021 """
2022+ left = self ._eager_state
2023+ if left >= 0 and self ._stopping :
2024+ nready = len (self ._ready )
20162025
20172026 sched_count = len (self ._scheduled )
20182027 if (sched_count > _MIN_SCHEDULED_TIMER_HANDLES and
@@ -2068,11 +2077,59 @@ def _run_once(self):
20682077
20692078 # This is the only place where callbacks are actually *called*.
20702079 # All other places just add them to ready.
2071- # Note: We run all currently scheduled callbacks, but not any
2072- # callbacks scheduled by callbacks run this time around --
2073- # they will be run the next time (after another I/O poll).
2080+ # Note: We run all currently scheduled callbacks. Callbacks
2081+ # scheduled by them are run eagerly in batches, within the limits
2082+ # of the loop settings; the rest will be run the next time (after
2083+ # another I/O poll).
20742084 # Use an idiom that is thread-safe without using locks.
2085+ self ._eager_state = constants .EAGER_IDLE
2086+ settings = self ._settings
2087+ if left >= 0 and self ._stopping :
2088+ # The final iteration after stop() during eager processing: run
2089+ # what the loop would run without eager processing, and keep the
2090+ # callbacks scheduled for later iterations queued.
2091+ left = min (left , nready )
2092+ nnew = len (self ._ready ) - nready
2093+ self ._run_handles (left )
2094+ later = [self ._ready .popleft () for i in range (nready - left )]
2095+ self ._run_handles (nnew )
2096+ self ._ready .extendleft (reversed (later ))
2097+ return
20752098 ntodo = len (self ._ready )
2099+ if left > 0 :
2100+ left = min (left , ntodo )
2101+ self ._run_handles (left )
2102+ if self ._stopping :
2103+ return
2104+ ntodo -= left
2105+ left = len (self ._ready ) - ntodo
2106+ self ._run_handles (ntodo )
2107+ if (not self ._ready or self ._eager_state == constants .EAGER_BREAK
2108+ or self ._stopping or settings .eager_max_time <= 0
2109+ or not settings .eager_max_callbacks ):
2110+ if left > 0 :
2111+ self ._eager_state = left
2112+ return
2113+
2114+ # Eager processing: run the callbacks scheduled by the callbacks
2115+ # above, in batches, within the limits of the loop settings.
2116+ deadline = (self .time () + settings .eager_max_time
2117+ + self ._clock_resolution )
2118+ remaining = settings .eager_max_callbacks
2119+ while True :
2120+ if left <= 0 :
2121+ left = len (self ._ready )
2122+ ntodo = min (left , remaining , constants .EAGER_BATCH_SIZE )
2123+ remaining -= ntodo
2124+ left -= self ._run_handles (ntodo , True )
2125+ if (not remaining or not self ._ready
2126+ or self ._eager_state == constants .EAGER_BREAK
2127+ or self ._stopping or self .time () > deadline ):
2128+ break
2129+ if left > 0 or self ._stopping :
2130+ self ._eager_state = left
2131+
2132+ def _run_handles (self , ntodo , eager = False ):
20762133 for i in range (ntodo ):
20772134 handle = self ._ready .popleft ()
20782135 if handle ._cancelled :
@@ -2090,7 +2147,11 @@ def _run_once(self):
20902147 self ._current_handle = None
20912148 else :
20922149 handle ._run ()
2150+ if eager and self ._eager_state == constants .EAGER_BREAK :
2151+ ntodo = i + 1
2152+ break
20932153 handle = None # Needed to break cycles when an exception occurs.
2154+ return ntodo
20942155
20952156 def _set_coroutine_origin_tracking (self , enabled ):
20962157 if bool (enabled ) == bool (self ._coroutine_origin_tracking_enabled ):
@@ -2115,3 +2176,11 @@ def set_debug(self, enabled):
21152176
21162177 if self .is_running ():
21172178 self .call_soon_threadsafe (self ._set_coroutine_origin_tracking , enabled )
2179+
2180+ def get_settings (self ):
2181+ return self ._settings
2182+
2183+ def set_settings (self , settings ):
2184+ if not isinstance (settings , events .EventLoopSettings ):
2185+ raise TypeError ('settings must be EventLoopSettings instance' )
2186+ self ._settings = settings
0 commit comments