Skip to content

Commit cdef72f

Browse files
committed
Moved indexing to init
1 parent b97a4ad commit cdef72f

File tree

5 files changed

+78
-40
lines changed

5 files changed

+78
-40
lines changed

azure_functions_worker/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,6 @@
5555

5656
# Paths
5757
CUSTOMER_PACKAGES_PATH = "/home/site/wwwroot/.python_packages/lib/site-packages"
58+
59+
# Flag to index functions in handle init request
60+
INIT_INDEXING = "INIT_INDEXING"

azure_functions_worker/dispatcher.py

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
PYTHON_ENABLE_DEBUG_LOGGING,
3131
PYTHON_SCRIPT_FILE_NAME,
3232
PYTHON_SCRIPT_FILE_NAME_DEFAULT,
33-
PYTHON_LANGUAGE_RUNTIME)
33+
PYTHON_LANGUAGE_RUNTIME, INIT_INDEXING)
3434
from .extension import ExtensionManager
3535
from .logging import disable_console_logging, enable_console_logging
3636
from .logging import (logger, error_logger, is_system_log_category,
@@ -72,9 +72,12 @@ def __init__(self, loop: BaseEventLoop, host: str, port: int,
7272
self._function_data_cache_enabled = False
7373
self._functions = functions.Registry()
7474
self._shmem_mgr = SharedMemoryManager()
75-
7675
self._old_task_factory = None
7776

77+
# Used to store metadata returns
78+
self.function_metadata_result = None
79+
self.function_metadata_exception = None
80+
7881
# We allow the customer to change synchronous thread pool max worker
7982
# count by setting the PYTHON_THREADPOOL_THREAD_COUNT app setting.
8083
# For 3.[6|7|8] The default value is 1.
@@ -297,6 +300,9 @@ async def _handle__worker_init_request(self, request):
297300
# dictionary which will be later used in the invocation request
298301
bindings.load_binding_registry()
299302

303+
if is_envvar_true(INIT_INDEXING):
304+
self.get_function_metadata(worker_init_request)
305+
300306
return protos.StreamingMessage(
301307
request_id=self.request_id,
302308
worker_init_response=protos.WorkerInitResponse(
@@ -313,52 +319,57 @@ async def _handle__worker_status_request(self, request):
313319
request_id=request.request_id,
314320
worker_status_response=protos.WorkerStatusResponse())
315321

316-
async def _handle__functions_metadata_request(self, request):
317-
metadata_request = request.functions_metadata_request
318-
directory = metadata_request.function_app_directory
322+
def get_function_metadata(self, directory, caller_info):
319323
script_file_name = get_app_setting(
320324
setting=PYTHON_SCRIPT_FILE_NAME,
321325
default_value=f'{PYTHON_SCRIPT_FILE_NAME_DEFAULT}')
322-
function_path = os.path.join(directory, script_file_name)
323326

324327
logger.info(
325-
'Received WorkerMetadataRequest, request ID %s, function_path: %s',
326-
self.request_id, function_path)
328+
'Received WorkerMetadataRequest from %s, request ID %s, script_file_name: %s',
329+
caller_info, self.request_id, script_file_name)
327330

328331
try:
329332
validate_script_file_name(script_file_name)
333+
function_path = os.path.join(directory, script_file_name)
330334

331-
if not os.path.exists(function_path):
332-
# Fallback to legacy model
333-
return protos.StreamingMessage(
334-
request_id=request.request_id,
335-
function_metadata_response=protos.FunctionMetadataResponse(
336-
use_default_metadata_indexing=True,
337-
result=protos.StatusResult(
338-
status=protos.StatusResult.Success)))
335+
self.function_metadata_result = self.index_functions(function_path) \
336+
if os.path.exists(function_path) else None
339337

340-
fx_metadata_results = self.index_functions(function_path)
338+
except Exception as ex:
339+
self.function_metadata_exception = self._serialize_exception(ex)
341340

341+
async def _handle__functions_metadata_request(self, request):
342+
metadata_request = request.functions_metadata_request
343+
directory = metadata_request.function_app_directory
344+
345+
if not is_envvar_true(INIT_INDEXING):
346+
self.get_function_metadata(directory,
347+
caller_info=sys._getframe().f_code.co_name)
348+
349+
if self.function_metadata_exception:
342350
return protos.StreamingMessage(
343-
request_id=request.request_id,
351+
request_id=self.request_id,
344352
function_metadata_response=protos.FunctionMetadataResponse(
345-
function_metadata_results=fx_metadata_results,
346353
result=protos.StatusResult(
347-
status=protos.StatusResult.Success)))
354+
status=protos.StatusResult.Failure,
355+
exception=self.function_metadata_exception)))
356+
else:
357+
fmr = self.function_metadata_result
348358

349-
except Exception as ex:
350359
return protos.StreamingMessage(
351-
request_id=self.request_id,
360+
request_id=request.request_id,
352361
function_metadata_response=protos.FunctionMetadataResponse(
362+
use_default_metadata_indexing=False if fmr else True,
363+
function_metadata_results=fmr,
353364
result=protos.StatusResult(
354-
status=protos.StatusResult.Failure,
355-
exception=self._serialize_exception(ex))))
365+
status=protos.StatusResult.Success)))
356366

357367
async def _handle__function_load_request(self, request):
358368
func_request = request.function_load_request
359369
function_id = func_request.function_id
360370
function_metadata = func_request.metadata
361371
function_name = function_metadata.name
372+
directory = function_metadata.directory
362373

363374
logger.info(
364375
'Received WorkerLoadRequest, request ID %s, function_id: %s,'
@@ -367,28 +378,23 @@ async def _handle__function_load_request(self, request):
367378
programming_model = "V2"
368379
try:
369380
if not self._functions.get_function(function_id):
370-
script_file_name = get_app_setting(
371-
setting=PYTHON_SCRIPT_FILE_NAME,
372-
default_value=f'{PYTHON_SCRIPT_FILE_NAME_DEFAULT}')
373-
validate_script_file_name(script_file_name)
374-
function_path = os.path.join(
375-
function_metadata.directory,
376-
script_file_name)
377-
378-
if function_metadata.properties.get("worker_indexed", False) \
379-
or os.path.exists(function_path):
381+
382+
if function_metadata.properties.get("worker_indexed", False)\
383+
and not is_envvar_true(INIT_INDEXING):
380384
# This is for the second worker and above where the worker
381385
# indexing is enabled and load request is called without
382386
# calling the metadata request. In this case we index the
383387
# function and update the workers registry
384-
_ = self.index_functions(function_path)
388+
389+
self.get_function_metadata(directory,
390+
caller_info=sys._getframe().f_code.co_name)
385391
else:
386392
# legacy function
387393
programming_model = "V1"
388394

389395
func = loader.load_function(
390-
func_request.metadata.name,
391-
func_request.metadata.directory,
396+
function_name,
397+
directory,
392398
func_request.metadata.script_file,
393399
func_request.metadata.entry_point)
394400

azure_functions_worker/utils/common.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,3 @@ def validate_script_file_name(file_name: str):
153153
pattern = re.compile(r'^[a-zA-Z0-9_][a-zA-Z0-9_\-]*\.py$')
154154
if not pattern.match(file_name):
155155
raise InvalidFileNameError(file_name)
156-
return True

tests/endtoend/test_http_functions.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,37 @@ def get_script_dir(cls):
205205
'common_libs_functions_stein'
206206

207207

208+
class TestHttpFunctionsWithInitIndexing(testutils.WebHostTestCase):
209+
210+
@classmethod
211+
def setUpClass(cls):
212+
os.environ["INIT_INDEXING"] = "1"
213+
super().setUpClass()
214+
215+
@classmethod
216+
def tearDownClass(cls):
217+
# Remove the PYTHON_SCRIPT_FILE_NAME environment variable
218+
os.environ.pop('INIT_INDEXING')
219+
super().tearDownClass()
220+
221+
@classmethod
222+
def get_script_dir(cls):
223+
return testutils.E2E_TESTS_FOLDER / 'http_functions'
224+
225+
def test_default_http_template_should_accept_body(self):
226+
"""Test if the azure.functions SDK is able to deserialize http body
227+
and pass it to default template
228+
"""
229+
r = self.webhost.request('POST', 'default_template',
230+
data='{ "name": "body" }'.encode('utf-8'),
231+
timeout=REQUEST_TIMEOUT_SEC)
232+
self.assertTrue(r.ok)
233+
self.assertEqual(
234+
r.content,
235+
b'Hello, body. This HTTP triggered function executed successfully.'
236+
)
237+
238+
208239
class TestUserThreadLoggingHttpFunctions(testutils.WebHostTestCase):
209240
"""Test the Http trigger that contains logging with user threads.
210241

tests/unittests/test_utilities.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,7 @@ def test_get_sdk_dummy_version_with_flag_enabled(self):
374374

375375
def test_valid_script_file_name(self):
376376
file_name = 'test.py'
377-
valid_name = common.validate_script_file_name(file_name)
378-
self.assertTrue(valid_name)
377+
common.validate_script_file_name(file_name)
379378

380379
def test_invalid_script_file_name(self):
381380
file_name = 'test'

0 commit comments

Comments
 (0)