Skip to content

Commit 81b8410

Browse files
authored
Supporting PyStein programming model in the Worker (#965)
1 parent 1312797 commit 81b8410

File tree

47 files changed

+1800
-370
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1800
-370
lines changed

azure_functions_worker/constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,8 @@
5050

5151
# External Site URLs
5252
MODULE_NOT_FOUND_TS_URL = "https://aka.ms/functions-modulenotfound"
53+
54+
# new programming model script file name
55+
SCRIPT_FILE_NAME = "function_app.py"
56+
57+
PYTHON_LANGUAGE_RUNTIME = "python"

azure_functions_worker/dispatcher.py

Lines changed: 88 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,13 @@
1818

1919
import grpc
2020

21-
from . import bindings
22-
from . import constants
23-
from . import functions
24-
from . import loader
25-
from . import protos
21+
from . import bindings, constants, functions, loader, protos
2622
from .bindings.shared_memory_data_transfer import SharedMemoryManager
2723
from .constants import (PYTHON_THREADPOOL_THREAD_COUNT,
2824
PYTHON_THREADPOOL_THREAD_COUNT_DEFAULT,
2925
PYTHON_THREADPOOL_THREAD_COUNT_MAX_37,
3026
PYTHON_THREADPOOL_THREAD_COUNT_MIN,
31-
PYTHON_ENABLE_DEBUG_LOGGING)
27+
PYTHON_ENABLE_DEBUG_LOGGING, SCRIPT_FILE_NAME)
3228
from .extension import ExtensionManager
3329
from .logging import disable_console_logging, enable_console_logging
3430
from .logging import enable_debug_logging_recommendation
@@ -40,7 +36,6 @@
4036
from .utils.wrappers import disable_feature_by
4137
from .version import VERSION
4238

43-
4439
_TRUE = "true"
4540

4641
"""In Python 3.6, the current_task method was in the Task class, but got moved
@@ -260,13 +255,13 @@ async def _dispatch_grpc_request(self, request):
260255
resp = await request_handler(request)
261256
self._grpc_resp_queue.put_nowait(resp)
262257

263-
async def _handle__worker_init_request(self, req):
258+
async def _handle__worker_init_request(self, request):
264259
logger.info('Received WorkerInitRequest, '
265260
'python version %s, worker version %s, request ID %s',
266261
sys.version, VERSION, self.request_id)
267262
enable_debug_logging_recommendation()
268263

269-
worker_init_request = req.worker_init_request
264+
worker_init_request = request.worker_init_request
270265
host_capabilities = worker_init_request.capabilities
271266
if constants.FUNCTION_DATA_CACHE in host_capabilities:
272267
val = host_capabilities[constants.FUNCTION_DATA_CACHE]
@@ -294,42 +289,93 @@ async def _handle__worker_init_request(self, req):
294289
result=protos.StatusResult(
295290
status=protos.StatusResult.Success)))
296291

297-
async def _handle__worker_status_request(self, req):
292+
async def _handle__worker_status_request(self, request):
298293
# Logging is not necessary in this request since the response is used
299294
# for host to judge scale decisions of out-of-proc languages.
300295
# Having log here will reduce the responsiveness of the worker.
301296
return protos.StreamingMessage(
302-
request_id=req.request_id,
297+
request_id=request.request_id,
303298
worker_status_response=protos.WorkerStatusResponse())
304299

305-
async def _handle__function_load_request(self, req):
306-
func_request = req.function_load_request
300+
async def _handle__functions_metadata_request(self, request):
301+
metadata_request = request.functions_metadata_request
302+
directory = metadata_request.function_app_directory
303+
function_path = os.path.join(directory, SCRIPT_FILE_NAME)
304+
305+
if not os.path.exists(function_path):
306+
# Fallback to legacy model
307+
logger.info(f"{SCRIPT_FILE_NAME} does not exist. "
308+
"Switching to host indexing.")
309+
return protos.StreamingMessage(
310+
request_id=request.request_id,
311+
function_metadata_response=protos.FunctionMetadataResponse(
312+
use_default_metadata_indexing=True,
313+
result=protos.StatusResult(
314+
status=protos.StatusResult.Success)))
315+
316+
try:
317+
fx_metadata_results = []
318+
indexed_functions = loader.index_function_app(function_path)
319+
if indexed_functions:
320+
indexed_function_logs: List[str] = []
321+
for func in indexed_functions:
322+
function_log = \
323+
f"Function Name: {func.get_function_name()} " \
324+
"Function Binding: " \
325+
f"{[binding.name for binding in func.get_bindings()]}"
326+
indexed_function_logs.append(function_log)
327+
328+
logger.info(
329+
f'Successfully processed FunctionMetadataRequest for '
330+
f'functions: {" ".join(indexed_function_logs)}')
331+
332+
fx_metadata_results = loader.process_indexed_function(
333+
self._functions,
334+
indexed_functions)
335+
else:
336+
logger.warning("No functions indexed. Please refer to the "
337+
"documentation.")
338+
339+
return protos.StreamingMessage(
340+
request_id=request.request_id,
341+
function_metadata_response=protos.FunctionMetadataResponse(
342+
function_metadata_results=fx_metadata_results,
343+
result=protos.StatusResult(
344+
status=protos.StatusResult.Success)))
345+
346+
except Exception as ex:
347+
return protos.StreamingMessage(
348+
request_id=self.request_id,
349+
function_metadata_response=protos.FunctionMetadataResponse(
350+
result=protos.StatusResult(
351+
status=protos.StatusResult.Failure,
352+
exception=self._serialize_exception(ex))))
353+
354+
async def _handle__function_load_request(self, request):
355+
func_request = request.function_load_request
307356
function_id = func_request.function_id
308357
function_name = func_request.metadata.name
309358

310-
logger.info(f'Received FunctionLoadRequest, '
311-
f'request ID: {self.request_id}, '
312-
f'function ID: {function_id}'
313-
f'function Name: {function_name}')
314359
try:
315-
func = loader.load_function(
316-
func_request.metadata.name,
317-
func_request.metadata.directory,
318-
func_request.metadata.script_file,
319-
func_request.metadata.entry_point)
320-
321-
self._functions.add_function(
322-
function_id, func, func_request.metadata)
323-
324-
ExtensionManager.function_load_extension(
325-
function_name,
326-
func_request.metadata.directory
327-
)
360+
if not self._functions.get_function(function_id):
361+
func = loader.load_function(
362+
func_request.metadata.name,
363+
func_request.metadata.directory,
364+
func_request.metadata.script_file,
365+
func_request.metadata.entry_point)
366+
367+
self._functions.add_function(
368+
function_id, func, func_request.metadata)
369+
370+
ExtensionManager.function_load_extension(
371+
function_name,
372+
func_request.metadata.directory
373+
)
328374

329-
logger.info('Successfully processed FunctionLoadRequest, '
330-
f'request ID: {self.request_id}, '
331-
f'function ID: {function_id},'
332-
f'function Name: {function_name}')
375+
logger.info('Successfully processed FunctionLoadRequest, '
376+
f'request ID: {self.request_id}, '
377+
f'function ID: {function_id},'
378+
f'function Name: {function_name}')
333379

334380
return protos.StreamingMessage(
335381
request_id=self.request_id,
@@ -347,8 +393,8 @@ async def _handle__function_load_request(self, req):
347393
status=protos.StatusResult.Failure,
348394
exception=self._serialize_exception(ex))))
349395

350-
async def _handle__invocation_request(self, req):
351-
invoc_request = req.invocation_request
396+
async def _handle__invocation_request(self, request):
397+
invoc_request = request.invocation_request
352398
invocation_id = invoc_request.invocation_id
353399
function_id = invoc_request.function_id
354400

@@ -361,6 +407,7 @@ async def _handle__invocation_request(self, req):
361407
try:
362408
fi: functions.FunctionInfo = self._functions.get_function(
363409
function_id)
410+
assert fi is not None
364411

365412
function_invocation_logs: List[str] = [
366413
'Received FunctionInvocationRequest',
@@ -456,15 +503,16 @@ async def _handle__invocation_request(self, req):
456503
status=protos.StatusResult.Failure,
457504
exception=self._serialize_exception(ex))))
458505

459-
async def _handle__function_environment_reload_request(self, req):
506+
async def _handle__function_environment_reload_request(self, request):
460507
"""Only runs on Linux Consumption placeholder specialization.
461508
"""
462509
try:
463510
logger.info('Received FunctionEnvironmentReloadRequest, '
464511
'request ID: %s', self.request_id)
465512
enable_debug_logging_recommendation()
466513

467-
func_env_reload_request = req.function_environment_reload_request
514+
func_env_reload_request = \
515+
request.function_environment_reload_request
468516

469517
# Import before clearing path cache so that the default
470518
# azure.functions modules is available in sys.modules for
@@ -523,7 +571,7 @@ async def _handle__function_environment_reload_request(self, req):
523571
request_id=self.request_id,
524572
function_environment_reload_response=failure_response)
525573

526-
async def _handle__close_shared_memory_resources_request(self, req):
574+
async def _handle__close_shared_memory_resources_request(self, request):
527575
"""
528576
Frees any memory maps that were produced as output for a given
529577
invocation.
@@ -534,7 +582,7 @@ async def _handle__close_shared_memory_resources_request(self, req):
534582
If the cache is not enabled, the worker should free the resources as at
535583
this point the host has read the memory maps and does not need them.
536584
"""
537-
close_request = req.close_shared_memory_resources_request
585+
close_request = request.close_shared_memory_resources_request
538586
map_names = close_request.map_names
539587
# Assign default value of False to all result values.
540588
# If we are successfully able to close a memory map, its result will be

azure_functions_worker/extension.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
33

4+
import functools
5+
import logging
46
from types import ModuleType
57
from typing import Any, Callable, List, Optional
6-
import logging
7-
import functools
8-
from .utils.common import (
9-
is_python_version,
10-
get_sdk_from_sys_path,
11-
get_sdk_version
12-
)
13-
from .utils.wrappers import enable_feature_by
8+
149
from .constants import (
1510
PYTHON_ISOLATE_WORKER_DEPENDENCIES,
1611
PYTHON_ENABLE_WORKER_EXTENSIONS,
1712
PYTHON_ENABLE_WORKER_EXTENSIONS_DEFAULT,
1813
PYTHON_ENABLE_WORKER_EXTENSIONS_DEFAULT_39
1914
)
2015
from .logging import logger, SYSTEM_LOG_PREFIX
21-
16+
from .utils.common import (
17+
is_python_version,
18+
get_sdk_from_sys_path,
19+
get_sdk_version
20+
)
21+
from .utils.wrappers import enable_feature_by
2222

2323
# Extension Hooks
2424
FUNC_EXT_POST_FUNCTION_LOAD = "post_function_load"

0 commit comments

Comments
 (0)