1616import threading
1717from asyncio import BaseEventLoop
1818from logging import LogRecord
19- from typing import Optional
19+ from typing import Optional , List
2020
2121import grpc
2222
@@ -79,8 +79,8 @@ def __init__(self, loop: BaseEventLoop, host: str, port: int,
7979 # PYTHON_THREADPOOL_THREAD_COUNT app setting. The default value is 1.
8080 self ._sync_tp_max_workers : int = self ._get_sync_tp_max_workers ()
8181 self ._sync_call_tp : concurrent .futures .Executor = (
82- concurrent . futures . ThreadPoolExecutor (
83- max_workers = self . _sync_tp_max_workers ) )
82+ self . _create_sync_call_tp ( self . _sync_tp_max_workers )
83+ )
8484
8585 self ._grpc_connect_timeout : float = grpc_connect_timeout
8686 # This is set to -1 by default to remove the limitation on msg size
@@ -97,9 +97,7 @@ async def connect(cls, host: str, port: int, worker_id: str,
9797 disp = cls (loop , host , port , worker_id , request_id , connect_timeout )
9898 disp ._grpc_thread .start ()
9999 await disp ._grpc_connected_fut
100- logger .info ('Successfully opened gRPC channel to %s:%s '
101- 'with sync threadpool max workers set to %s' ,
102- host , port , disp ._sync_tp_max_workers )
100+ logger .info ('Successfully opened gRPC channel to %s:%s ' , host , port )
103101 return disp
104102
105103 async def dispatch_forever (self ):
@@ -161,9 +159,7 @@ def stop(self) -> None:
161159 self ._grpc_thread .join ()
162160 self ._grpc_thread = None
163161
164- if self ._sync_call_tp is not None :
165- self ._sync_call_tp .shutdown ()
166- self ._sync_call_tp = None
162+ self ._stop_sync_call_tp ()
167163
168164 def on_logging (self , record : logging .LogRecord , formatted_msg : str ) -> None :
169165 if record .levelno >= logging .CRITICAL :
@@ -318,11 +314,19 @@ async def _handle__invocation_request(self, req):
318314 fi : functions .FunctionInfo = self ._functions .get_function (
319315 function_id )
320316
321- logger .info (f'Received FunctionInvocationRequest, '
322- f'request ID: { self .request_id } , '
323- f'function ID: { function_id } , '
324- f'invocation ID: { invocation_id } , '
325- f'function type: { "async" if fi .is_async else "sync" } ' )
317+ function_invocation_logs : List [str ] = [
318+ 'Received FunctionInvocationRequest' ,
319+ f'request ID: { self .request_id } ' ,
320+ f'function ID: { function_id } ' ,
321+ f'invocation ID: { invocation_id } ' ,
322+ f'function type: { "async" if fi .is_async else "sync" } '
323+ ]
324+ if not fi .is_async :
325+ function_invocation_logs .append (
326+ f'sync threadpool max workers: { self ._sync_tp_max_workers } '
327+ )
328+ logger .info (', ' .join (function_invocation_logs ))
329+
326330 args = {}
327331 for pb in invoc_request .input_data :
328332 pb_type_info = fi .input_types [pb .name ]
@@ -426,6 +430,13 @@ async def _handle__function_environment_reload_request(self, req):
426430 for var in env_vars :
427431 os .environ [var ] = env_vars [var ]
428432
433+ # Apply PYTHON_THREADPOOL_THREAD_COUNT
434+ self ._stop_sync_call_tp ()
435+ self ._sync_tp_max_workers = self ._get_sync_tp_max_workers ()
436+ self ._sync_call_tp = (
437+ self ._create_sync_call_tp (self ._sync_tp_max_workers )
438+ )
439+
429440 # Reload package namespaces for customer's libraries
430441 packages_to_reload = ['azure' , 'google' ]
431442 for p in packages_to_reload :
@@ -479,6 +490,15 @@ def _change_cwd(self, new_cwd: str):
479490 else :
480491 logger .warning ('Directory %s is not found when reloading' , new_cwd )
481492
493+ def _stop_sync_call_tp (self ):
494+ """Deallocate the current synchronous thread pool and assign
495+ self._sync_call_tp to None. If the thread pool does not exist,
496+ this will be a no op.
497+ """
498+ if getattr (self , '_sync_call_tp' , None ):
499+ self ._sync_call_tp .shutdown ()
500+ self ._sync_call_tp = None
501+
482502 def _get_sync_tp_max_workers (self ) -> int :
483503 def tp_max_workers_validator (value : str ) -> bool :
484504 try :
@@ -501,6 +521,17 @@ def tp_max_workers_validator(value: str) -> bool:
501521 default_value = f'{ PYTHON_THREADPOOL_THREAD_COUNT_DEFAULT } ' ,
502522 validator = tp_max_workers_validator ))
503523
524+ def _create_sync_call_tp (
525+ self , max_worker : int ) -> concurrent .futures .Executor :
526+ """Create a thread pool executor with max_worker. This is a wrapper
527+ over ThreadPoolExecutor constructor. Consider calling this method after
528+ _stop_sync_call_tp() to ensure only 1 synchronous thread pool is
529+ running.
530+ """
531+ return concurrent .futures .ThreadPoolExecutor (
532+ max_workers = max_worker
533+ )
534+
504535 def __run_sync_func (self , invocation_id , func , params ):
505536 # This helper exists because we need to access the current
506537 # invocation_id from ThreadPoolExecutor's threads.
0 commit comments