Repository: Azure/azure-functions-python-worker

Issue:
Allow specifying PYTHON_THREADPOOL_THREAD_COUNT larger than 32
Currently, the worker limits the maximum value of `PYTHON_THREADPOOL_THREAD_COUNT` to 32. Any value larger than that is ignored. There is no good reason for this in general case: if someone/something decided a larger number works better for a specific use case, this should be respected.

Comments:

Patch:
diff --git a/CODEOWNERS b/CODEOWNERS
index 8d5a9d3..9dd0e16 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -7,6 +7,7 @@
 
 #
 # AZURE FUNCTIONS TEAM
-# For all file changes, github would automatically include the following people in the PRs.
+# For all file changes, github would automatically
+# include the following people in the PRs.
 #
-* @anirudhgarg @Hazhzeng @vrdmr @AnatoliB
\ No newline at end of file
+* @vrdmr @AnatoliB
\ No newline at end of file
diff --git a/azure_functions_worker/constants.py b/azure_functions_worker/constants.py
index 96c96cb..bad0504 100644
--- a/azure_functions_worker/constants.py
+++ b/azure_functions_worker/constants.py
@@ -39,6 +39,8 @@ UNIX_SHARED_MEMORY_DIRECTORIES = "FUNCTIONS_UNIX_SHARED_MEMORY_DIRECTORIES"
 PYTHON_THREADPOOL_THREAD_COUNT_DEFAULT = 1
 PYTHON_THREADPOOL_THREAD_COUNT_MIN = 1
 PYTHON_THREADPOOL_THREAD_COUNT_MAX = sys.maxsize
+PYTHON_THREADPOOL_THREAD_COUNT_MAX_37 = 32
+
 PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT = False
 PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT_39 = False
 PYTHON_ENABLE_WORKER_EXTENSIONS_DEFAULT = False
diff --git a/azure_functions_worker/dispatcher.py b/azure_functions_worker/dispatcher.py
index e8a7b24..694e5fc 100644
--- a/azure_functions_worker/dispatcher.py
+++ b/azure_functions_worker/dispatcher.py
@@ -26,7 +26,7 @@ from . import loader
 from . import protos
 from .constants import (PYTHON_THREADPOOL_THREAD_COUNT,
                         PYTHON_THREADPOOL_THREAD_COUNT_DEFAULT,
-                        PYTHON_THREADPOOL_THREAD_COUNT_MAX,
+                        PYTHON_THREADPOOL_THREAD_COUNT_MAX_37,
                         PYTHON_THREADPOOL_THREAD_COUNT_MIN)
 from .logging import disable_console_logging, enable_console_logging
 from .logging import (logger, error_logger, is_system_log_category,
@@ -567,25 +567,28 @@ class Dispatcher(metaclass=DispatcherMeta):
                                'integer')
                 return False
 
-            if int_value < PYTHON_THREADPOOL_THREAD_COUNT_MIN or (
-                    int_value > PYTHON_THREADPOOL_THREAD_COUNT_MAX):
+            if int_value < PYTHON_THREADPOOL_THREAD_COUNT_MIN:
                 logger.warning(f'{PYTHON_THREADPOOL_THREAD_COUNT} must be set '
                                f'to a value between '
                                f'{PYTHON_THREADPOOL_THREAD_COUNT_MIN} and '
-                               f'{PYTHON_THREADPOOL_THREAD_COUNT_MAX}. '
-                               'Reverting to default value for max_workers')
+                               'sys.maxint. Reverting to default value for '
+                               'max_workers')
                 return False
-
             return True
 
         # Starting Python 3.9, worker won't be putting a limit on the
         # max_workers count in the created threadpool.
         default_value = None if sys.version_info.minor == 9 \
             else f'{PYTHON_THREADPOOL_THREAD_COUNT_DEFAULT}'
+
         max_workers = get_app_setting(setting=PYTHON_THREADPOOL_THREAD_COUNT,
                                       default_value=default_value,
                                       validator=tp_max_workers_validator)
 
+        if sys.version_info.minor <= 7:
+            max_workers = min(int(max_workers),
+                              PYTHON_THREADPOOL_THREAD_COUNT_MAX_37)
+
         # We can box the app setting as int for earlier python versions.
         return int(max_workers) if max_workers else None
 


Test patch:
diff --git a/tests/unittests/test_dispatcher.py b/tests/unittests/test_dispatcher.py
index cb90ab8..2d5960a 100644
--- a/tests/unittests/test_dispatcher.py
+++ b/tests/unittests/test_dispatcher.py
@@ -11,8 +11,7 @@ from azure_functions_worker import protos
 from azure_functions_worker import testutils
 from azure_functions_worker.constants import PYTHON_THREADPOOL_THREAD_COUNT, \
     PYTHON_THREADPOOL_THREAD_COUNT_DEFAULT, \
-    PYTHON_THREADPOOL_THREAD_COUNT_MAX, \
-    PYTHON_THREADPOOL_THREAD_COUNT_MIN
+    PYTHON_THREADPOOL_THREAD_COUNT_MAX_37, PYTHON_THREADPOOL_THREAD_COUNT_MIN
 
 SysVersionInfo = col.namedtuple("VersionInfo", ["major", "minor", "micro",
                                                 "releaselevel", "serial"])
@@ -37,7 +36,8 @@ class TestThreadPoolSettingsPython37(testutils.AsyncTestCase):
             script_root=DISPATCHER_FUNCTIONS_DIR)
         self._default_workers: Optional[
             int] = PYTHON_THREADPOOL_THREAD_COUNT_DEFAULT
-        self._allowed_max_workers: int = 100000
+        self._over_max_workers: int = 10000
+        self._allowed_max_workers: int = PYTHON_THREADPOOL_THREAD_COUNT_MAX_37
         self._pre_env = dict(os.environ)
         self.mock_version_info = patch(
             'azure_functions_worker.dispatcher.sys.version_info',
@@ -128,33 +128,26 @@ class TestThreadPoolSettingsPython37(testutils.AsyncTestCase):
                 await self._assert_workers_threadpool(self._ctrl, host,
                                                       self._default_workers)
             mock_logger.warning.assert_any_call(
-                f'{PYTHON_THREADPOOL_THREAD_COUNT} must be set to a value '
-                f'between {PYTHON_THREADPOOL_THREAD_COUNT_MIN} and '
-                f'{PYTHON_THREADPOOL_THREAD_COUNT_MAX}. Reverting to default '
-                f'value for max_workers')
+                f'{PYTHON_THREADPOOL_THREAD_COUNT} must be set '
+                f'to a value between '
+                f'{PYTHON_THREADPOOL_THREAD_COUNT_MIN} and '
+                'sys.maxint. Reverting to default value for '
+                'max_workers')
 
-    @unittest.skip("We no more check any max limit. This is up to the customer,"
-                   " how ever high int they want to set")
     async def test_dispatcher_sync_threadpool_exceed_max_setting(self):
-        """Test if the sync threadpool will pick up default value when the
+        """Test if the sync threadpool will pick up default max value when the
         setting is above maximum
         """
-        with patch('azure_functions_worker.dispatcher.logger') as mock_logger:
+        with patch('azure_functions_worker.dispatcher.logger'):
             # Configure thread pool max worker to an invalid value
             os.environ.update({PYTHON_THREADPOOL_THREAD_COUNT:
                               f'{self._over_max_workers}'})
             async with self._ctrl as host:
                 await self._check_if_function_is_ok(host)
 
-                # Ensure the dispatcher sync threadpool should fallback to 1
+                # Ensure the dispatcher sync threadpool should fallback to max
                 await self._assert_workers_threadpool(self._ctrl, host,
-                                                      self._default_workers)
-
-            mock_logger.warning.assert_any_call(
-                f'{PYTHON_THREADPOOL_THREAD_COUNT} must be set to a value '
-                f'between {PYTHON_THREADPOOL_THREAD_COUNT_MIN} and '
-                f'{PYTHON_THREADPOOL_THREAD_COUNT_MAX}. Reverting to default '
-                f'value for max_workers')
+                                                      self._allowed_max_workers)
 
     async def test_dispatcher_sync_threadpool_in_placeholder(self):
         """Test if the sync threadpool will pick up app setting in placeholder
@@ -189,13 +182,13 @@ class TestThreadPoolSettingsPython37(testutils.AsyncTestCase):
                 mock_logger.warning.assert_any_call(
                     f'{PYTHON_THREADPOOL_THREAD_COUNT} must be an integer')
 
-    @unittest.skip("We no more check any max limit. This is up to the customer,"
-                   " how ever high int they want to set")
     async def test_dispatcher_sync_threadpool_in_placeholder_above_max(self):
-        """Test if the sync threadpool will use the default setting when the
-        app setting is above maximum
+        """Test if the sync threadpool will use the default max setting when
+        the app setting is above maximum.
+
+        Note: This is designed for Linux Consumption.
         """
-        with patch('azure_functions_worker.dispatcher.logger') as mock_logger:
+        with patch('azure_functions_worker.dispatcher.logger'):
             async with self._ctrl as host:
                 await self._check_if_function_is_ok(host)
 
@@ -204,13 +197,7 @@ class TestThreadPoolSettingsPython37(testutils.AsyncTestCase):
                     PYTHON_THREADPOOL_THREAD_COUNT: f'{self._over_max_workers}'
                 })
                 await self._assert_workers_threadpool(self._ctrl, host,
-                                                      self._default_workers)
-
-                mock_logger.warning.assert_any_call(
-                    f'{PYTHON_THREADPOOL_THREAD_COUNT} must be set to a '
-                    f'value '
-                    'between 1 and 1024. '
-                    'Reverting to default value for max_workers')
+                                                      self._allowed_max_workers)
 
     async def test_dispatcher_sync_threadpool_in_placeholder_below_min(self):
         """Test if the sync threadpool will use the default setting when the
@@ -229,10 +216,11 @@ class TestThreadPoolSettingsPython37(testutils.AsyncTestCase):
                                                       self._default_workers)
 
                 mock_logger.warning.assert_any_call(
-                    f'{PYTHON_THREADPOOL_THREAD_COUNT} must be set to a value '
-                    f'between {PYTHON_THREADPOOL_THREAD_COUNT_MIN} and '
-                    f'{PYTHON_THREADPOOL_THREAD_COUNT_MAX}. Reverting to '
-                    f'default value for max_workers')
+                    f'{PYTHON_THREADPOOL_THREAD_COUNT} must be set '
+                    f'to a value between '
+                    f'{PYTHON_THREADPOOL_THREAD_COUNT_MIN} and '
+                    'sys.maxint. Reverting to default value for '
+                    'max_workers')
 
     async def test_sync_invocation_request_log(self):
         with patch('azure_functions_worker.dispatcher.logger') as mock_logger:
@@ -418,6 +406,8 @@ class TestThreadPoolSettingsPython38(TestThreadPoolSettingsPython37):
         self.mock_version_info = patch(
             'azure_functions_worker.dispatcher.sys.version_info',
             SysVersionInfo(3, 8, 0, 'final', 0))
+        self._over_max_workers: int = 10000
+        self._allowed_max_workers: int = self._over_max_workers
         self.mock_version_info.start()
 
     def tearDown(self):
@@ -425,25 +415,43 @@ class TestThreadPoolSettingsPython38(TestThreadPoolSettingsPython37):
         os.environ.update(self._pre_env)
         self.mock_version_info.stop()
 
+    async def test_dispatcher_sync_threadpool_in_placeholder_above_max(self):
+        """Test if the sync threadpool will use any value and there isn't any
+        artificial max value set.
+        """
+        with patch('azure_functions_worker.dispatcher.logger'):
+            async with self._ctrl as host:
+                await self._check_if_function_is_ok(host)
+
+                # Reload environment variable on specialization
+                await host.reload_environment(environment={
+                    PYTHON_THREADPOOL_THREAD_COUNT: f'{self._over_max_workers}'
+                })
+                await self._assert_workers_threadpool(self._ctrl, host,
+                                                      self._allowed_max_workers)
+                self.assertNotEqual(
+                    self._ctrl._worker.get_sync_tp_workers_set(),
+                    self._default_workers)
+
 
 @unittest.skipIf(sys.version_info.minor != 9,
                  "Run the tests only for Python 3.9. In other platforms, "
                  "as the default passed is None, the cpu_count determines the "
                  "number of max_workers and we cannot mock the os.cpu_count() "
                  "in the concurrent.futures.ThreadPoolExecutor")
-class TestThreadPoolSettingsPython39(TestThreadPoolSettingsPython37):
+class TestThreadPoolSettingsPython39(TestThreadPoolSettingsPython38):
     def setUp(self):
         super(TestThreadPoolSettingsPython39, self).setUp()
 
         self.mock_os_cpu = patch(
             'os.cpu_count', return_value=2)
-        self.mock_os_cpu.start()
         # 6 - based on 2 cores - min(32, (os.cpu_count() or 1) + 4) - 2 + 4
         self._default_workers: Optional[int] = 6
-
         self.mock_version_info = patch(
             'azure_functions_worker.dispatcher.sys.version_info',
             SysVersionInfo(3, 9, 0, 'final', 0))
+
+        self.mock_os_cpu.start()
         self.mock_version_info.start()
 
     def tearDown(self):