@@ -433,6 +433,8 @@ def __init__(self):
433433 # event loop is not running
434434 self ._thread_id = None
435435 self ._exception_handler = None
436+ self ._settings = events .EventLoopSettings .DEFAULT
437+ self ._eager_state = constants .EAGER_IDLE
436438 self .set_debug (coroutines ._is_debug_mode ())
437439 # The preserved state of async generator hooks.
438440 self ._old_agen_hooks = None
@@ -666,6 +668,7 @@ def _run_forever_cleanup(self):
666668 loop cleanup logic.
667669 """
668670 self ._stopping = False
671+ self ._eager_state = constants .EAGER_IDLE
669672 self ._thread_id = None
670673 events ._set_running_loop (None )
671674 self ._set_coroutine_origin_tracking (False )
@@ -681,6 +684,8 @@ def run_forever(self):
681684 while True :
682685 self ._run_once ()
683686 if self ._stopping :
687+ if self ._eager_state >= 0 :
688+ self ._run_once ()
684689 break
685690 finally :
686691 self ._run_forever_cleanup ()
@@ -2008,10 +2013,13 @@ def _timer_handle_cancelled(self, handle):
20082013 def _run_once (self ):
20092014 """Run one full iteration of the event loop.
20102015
2011- This calls all currently ready callbacks, polls for I/O,
2012- schedules the resulting callbacks, and finally schedules
2013- 'call_later' callbacks.
2016+ This polls for I/O, schedules the resulting callbacks and the
2017+ 'call_later' callbacks that are due, and calls all ready
2018+ callbacks. Then it calls the callbacks scheduled by them
2019+ eagerly, within the limits of the loop settings.
20142020 """
2021+ nleft = self ._eager_state
2022+ nready = len (self ._ready )
20152023
20162024 sched_count = len (self ._scheduled )
20172025 if (sched_count > _MIN_SCHEDULED_TIMER_HANDLES and
@@ -2067,11 +2075,59 @@ def _run_once(self):
20672075
20682076 # This is the only place where callbacks are actually *called*.
20692077 # All other places just add them to ready.
2070- # Note: We run all currently scheduled callbacks, but not any
2071- # callbacks scheduled by callbacks run this time around --
2072- # they will be run the next time (after another I/O poll).
2078+ # Note: We run all currently scheduled callbacks. Callbacks
2079+ # scheduled by them are run eagerly in batches, within the limits
2080+ # of the loop settings; the rest will be run the next time (after
2081+ # another I/O poll).
20732082 # Use an idiom that is thread-safe without using locks.
2083+ self ._eager_state = constants .EAGER_IDLE
2084+ settings = self ._settings
2085+ if nleft >= 0 and self ._stopping :
2086+ # The final iteration after stop() during eager processing: run
2087+ # what the loop would run without eager processing, and keep the
2088+ # callbacks scheduled for later iterations queued.
2089+ nleft = min (nleft , nready )
2090+ nnew = len (self ._ready ) - nready
2091+ self ._run_handles (nleft , eager = False )
2092+ later = [self ._ready .popleft () for i in range (nready - nleft )]
2093+ self ._run_handles (nnew , eager = False )
2094+ self ._ready .extendleft (reversed (later ))
2095+ return
20742096 ntodo = len (self ._ready )
2097+ if nleft > 0 :
2098+ nleft = min (nleft , ntodo )
2099+ self ._run_handles (nleft , eager = False )
2100+ if self ._stopping :
2101+ return
2102+ ntodo -= nleft
2103+ nleft = len (self ._ready ) - ntodo
2104+ self ._run_handles (ntodo , eager = False )
2105+ if (not self ._ready or self ._eager_state == constants .EAGER_BREAK
2106+ or self ._stopping or settings .eager_max_time <= 0
2107+ or not settings .eager_max_callbacks ):
2108+ if nleft > 0 :
2109+ self ._eager_state = nleft
2110+ return
2111+
2112+ # Eager processing: run the callbacks scheduled by the callbacks
2113+ # above, in batches, within the limits of the loop settings.
2114+ deadline = (self .time () + settings .eager_max_time
2115+ + constants .CLOCK_RESOLUTION )
2116+ nremaining = settings .eager_max_callbacks
2117+ while True :
2118+ if nleft <= 0 :
2119+ nleft = len (self ._ready )
2120+ ntodo = min (nleft , nremaining , constants .EAGER_BATCH_SIZE )
2121+ nremaining -= ntodo
2122+ nleft -= self ._run_handles (ntodo , eager = True )
2123+ if (not nremaining or not self ._ready
2124+ or self ._eager_state == constants .EAGER_BREAK
2125+ or self ._stopping or self .time () > deadline ):
2126+ break
2127+ if nleft > 0 or self ._stopping :
2128+ self ._eager_state = nleft
2129+
2130+ def _run_handles (self , ntodo , * , eager ):
20752131 for i in range (ntodo ):
20762132 handle = self ._ready .popleft ()
20772133 if handle ._cancelled :
@@ -2089,7 +2145,11 @@ def _run_once(self):
20892145 self ._current_handle = None
20902146 else :
20912147 handle ._run ()
2148+ if eager and self ._eager_state == constants .EAGER_BREAK :
2149+ ntodo = i + 1
2150+ break
20922151 handle = None # Needed to break cycles when an exception occurs.
2152+ return ntodo
20932153
20942154 def _set_coroutine_origin_tracking (self , enabled ):
20952155 if bool (enabled ) == bool (self ._coroutine_origin_tracking_enabled ):
@@ -2114,3 +2174,11 @@ def set_debug(self, enabled):
21142174
21152175 if self .is_running ():
21162176 self .call_soon_threadsafe (self ._set_coroutine_origin_tracking , enabled )
2177+
2178+ def get_settings (self ):
2179+ return self ._settings
2180+
2181+ def set_settings (self , settings ):
2182+ if not isinstance (settings , events .EventLoopSettings ):
2183+ raise TypeError ('settings must be EventLoopSettings instance' )
2184+ self ._settings = settings
0 commit comments