diff --git a/azure_functions_worker/constants.py b/azure_functions_worker/constants.py index 86a9dd59b..a0606d1f0 100644 --- a/azure_functions_worker/constants.py +++ b/azure_functions_worker/constants.py @@ -30,6 +30,9 @@ """ UNIX_SHARED_MEMORY_DIRECTORIES = "FUNCTIONS_UNIX_SHARED_MEMORY_DIRECTORIES" +# Flag to enable loading functions at init request +PYTHON_LOAD_FUNCTIONS_INIT = "PYTHON_LOAD_FUNCTIONS_INIT" + # Setting Defaults PYTHON_THREADPOOL_THREAD_COUNT_DEFAULT = 1 PYTHON_THREADPOOL_THREAD_COUNT_MIN = 1 diff --git a/azure_functions_worker/dispatcher.py b/azure_functions_worker/dispatcher.py index 1e60917aa..44ea4ce85 100644 --- a/azure_functions_worker/dispatcher.py +++ b/azure_functions_worker/dispatcher.py @@ -26,7 +26,7 @@ PYTHON_THREADPOOL_THREAD_COUNT_MAX_37, PYTHON_THREADPOOL_THREAD_COUNT_MIN, PYTHON_ENABLE_DEBUG_LOGGING, SCRIPT_FILE_NAME, - PYTHON_LANGUAGE_RUNTIME) + PYTHON_LANGUAGE_RUNTIME, PYTHON_LOAD_FUNCTIONS_INIT) from .extension import ExtensionManager from .logging import disable_console_logging, enable_console_logging from .logging import enable_debug_logging_recommendation @@ -288,6 +288,10 @@ async def _handle__worker_init_request(self, request): if not DependencyManager.is_in_linux_consumption(): DependencyManager.prioritize_customer_dependencies() + if DependencyManager.is_in_linux_consumption() \ + and is_envvar_true(PYTHON_LOAD_FUNCTIONS_INIT): + import azure.functions # NoQA + return protos.StreamingMessage( request_id=self.request_id, worker_init_response=protos.WorkerInitResponse( @@ -525,10 +529,11 @@ async def _handle__function_environment_reload_request(self, request): func_env_reload_request = \ request.function_environment_reload_request - # Import before clearing path cache so that the default - # azure.functions modules is available in sys.modules for - # customer use - import azure.functions # NoQA + if not is_envvar_true(PYTHON_LOAD_FUNCTIONS_INIT): + # Import before clearing path cache so that the default + # azure.functions modules is available in sys.modules for + # customer use + import azure.functions # NoQA # Append function project root to module finding sys.path if func_env_reload_request.function_app_directory: diff --git a/tests/unittests/test_dispatcher.py b/tests/unittests/test_dispatcher.py index 4c8b058fc..02f4a31e3 100644 --- a/tests/unittests/test_dispatcher.py +++ b/tests/unittests/test_dispatcher.py @@ -599,3 +599,36 @@ async def test_dispatcher_functions_metadata_request_legacy_fallback(self): self.assertTrue(r.response.use_default_metadata_indexing) self.assertEqual(r.response.result.status, protos.StatusResult.Success) + + +class TestDispatcherLoadFunctionInInitRequest(testutils.AsyncTestCase): + + def setUp(self): + self._ctrl = testutils.start_mockhost( + script_root=DISPATCHER_FUNCTIONS_DIR) + self._pre_env = dict(os.environ) + self.mock_version_info = patch( + 'azure_functions_worker.dispatcher.sys.version_info', + SysVersionInfo(3, 9, 0, 'final', 0)) + self.mock_version_info.start() + + def tearDown(self): + os.environ.clear() + os.environ.update(self._pre_env) + self.mock_version_info.stop() + + async def test_dispatcher_load_azfunc_in_init(self): + """Test if the dispatcher's log can be flushed out during worker + initialization + """ + os.environ.update({"CONTAINER_NAME": 'test', + "PYTHON_LOAD_FUNCTIONS_INIT": "1"}) + async with self._ctrl as host: + r = await host.init_worker('4.15.1') + self.assertEqual( + len([log for log in r.logs if log.message.startswith( + 'Received WorkerInitRequest' + )]), + 1 + ) + self.assertIn("azure.functions", sys.modules)