Skip to content

Commit 8404b64

Browse files
authored
Implemented capability to isolate worker dependencies (#794)
1 parent 9a8acb8 commit 8404b64

File tree

25 files changed

+1132
-38
lines changed

25 files changed

+1132
-38
lines changed

azure_functions_worker/constants.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,20 @@
1313
# Debug Flags
1414
PYAZURE_WEBHOST_DEBUG = "PYAZURE_WEBHOST_DEBUG"
1515

16+
# Platform Environment Variables
17+
AZURE_WEBJOBS_SCRIPT_ROOT = "AzureWebJobsScriptRoot"
18+
1619
# Python Specific Feature Flags and App Settings
1720
PYTHON_ROLLBACK_CWD_PATH = "PYTHON_ROLLBACK_CWD_PATH"
1821
PYTHON_THREADPOOL_THREAD_COUNT = "PYTHON_THREADPOOL_THREAD_COUNT"
22+
PYTHON_ISOLATE_WORKER_DEPENDENCIES = "PYTHON_ISOLATE_WORKER_DEPENDENCIES"
1923

2024
# Setting Defaults
2125
PYTHON_THREADPOOL_THREAD_COUNT_DEFAULT = 1
2226
PYTHON_THREADPOOL_THREAD_COUNT_MIN = 1
2327
PYTHON_THREADPOOL_THREAD_COUNT_MAX = 32
28+
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT = False
29+
PYTHON_ISOLATE_WORKER_DEPENDENCIES_DEFAULT_39 = True
2430

2531
# External Site URLs
2632
MODULE_NOT_FOUND_TS_URL = "https://aka.ms/functions-modulenotfound"

azure_functions_worker/dispatcher.py

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
import asyncio
99
import concurrent.futures
10-
import importlib
11-
import inspect
1210
import logging
1311
import os
1412
import queue
@@ -33,6 +31,7 @@
3331
from .logging import error_logger, is_system_log_category, logger
3432
from .utils.common import get_app_setting
3533
from .utils.tracing import marshall_exception_trace
34+
from .utils.dependency import DependencyManager
3635
from .utils.wrappers import disable_feature_by
3736

3837
_TRUE = "true"
@@ -264,6 +263,9 @@ async def _handle__worker_init_request(self, req):
264263
constants.RPC_HTTP_TRIGGER_METADATA_REMOVED: _TRUE,
265264
}
266265

266+
# Can detech worker packages
267+
DependencyManager.use_customer_dependencies()
268+
267269
return protos.StreamingMessage(
268270
request_id=self.request_id,
269271
worker_init_response=protos.WorkerInitResponse(
@@ -450,26 +452,10 @@ async def _handle__function_environment_reload_request(self, req):
450452
self._create_sync_call_tp(self._get_sync_tp_max_workers())
451453
)
452454

453-
# Reload package namespaces for customer's libraries
454-
packages_to_reload = ['azure', 'google']
455-
for p in packages_to_reload:
456-
try:
457-
logger.info(f'Reloading {p} module')
458-
importlib.reload(sys.modules[p])
459-
except Exception as ex:
460-
logger.info('Unable to reload {}: \n{}'.format(p, ex))
461-
logger.info(f'Reloaded {p} module')
462-
463-
# Reload azure.functions to give user package precedence
464-
logger.info('Reloading azure.functions module at %s',
465-
inspect.getfile(sys.modules['azure.functions']))
466-
try:
467-
importlib.reload(sys.modules['azure.functions'])
468-
logger.info('Reloaded azure.functions module now at %s',
469-
inspect.getfile(sys.modules['azure.functions']))
470-
except Exception as ex:
471-
logger.info('Unable to reload azure.functions. '
472-
'Using default. Exception:\n{}'.format(ex))
455+
# Reload azure google namespaces
456+
DependencyManager.reload_azure_google_namespace(
457+
func_env_reload_request.function_app_directory
458+
)
473459

474460
# Change function app directory
475461
if getattr(func_env_reload_request,

azure_functions_worker/main.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@
55

66
import argparse
77

8-
from . import dispatcher
9-
from . import logging
10-
from ._thirdparty import aio_compat
11-
from .logging import error_logger, logger
12-
138

149
def parse_args():
1510
parser = argparse.ArgumentParser(
@@ -36,6 +31,14 @@ def parse_args():
3631

3732

3833
def main():
34+
from .utils.dependency import DependencyManager
35+
DependencyManager.initialize()
36+
DependencyManager.use_worker_dependencies()
37+
38+
from . import logging
39+
from ._thirdparty import aio_compat
40+
from .logging import error_logger, logger
41+
3942
args = parse_args()
4043
logging.setup(log_level=args.log_level, log_destination=args.log_to)
4144

@@ -52,6 +55,8 @@ def main():
5255

5356

5457
async def start_async(host, port, worker_id, request_id):
58+
from . import dispatcher
59+
5560
disp = await dispatcher.Dispatcher.connect(host=host, port=port,
5661
worker_id=worker_id,
5762
request_id=request_id,

azure_functions_worker/testutils.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import unittest
3030
import uuid
3131

32-
import asynctest as asynctest
3332
import grpc
3433
import requests
3534

@@ -123,7 +122,7 @@ def wrapper(*args, **kwargs):
123122
return wrapper
124123

125124

126-
class AsyncTestCase(asynctest.TestCase, metaclass=AsyncTestCaseMeta):
125+
class AsyncTestCase(unittest.TestCase, metaclass=AsyncTestCaseMeta):
127126
pass
128127

129128

azure_functions_worker/utils/common.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Licensed under the MIT License.
33
from typing import Optional, Callable
44
import os
5+
import sys
56

67

78
def is_true_like(setting: str) -> bool:
@@ -11,13 +12,32 @@ def is_true_like(setting: str) -> bool:
1112
return setting.lower().strip() in ['1', 'true', 't', 'yes', 'y']
1213

1314

15+
def is_false_like(setting: str) -> bool:
16+
if setting is None:
17+
return False
18+
19+
return setting.lower().strip() in ['0', 'false', 'f', 'no', 'n']
20+
21+
1422
def is_envvar_true(env_key: str) -> bool:
1523
if os.getenv(env_key) is None:
1624
return False
1725

1826
return is_true_like(os.environ[env_key])
1927

2028

29+
def is_envvar_false(env_key: str) -> bool:
30+
if os.getenv(env_key) is None:
31+
return False
32+
33+
return is_false_like(os.environ[env_key])
34+
35+
36+
def is_python_version(version: str) -> bool:
37+
current_version = f'{sys.version_info.major}.{sys.version_info.minor}'
38+
return current_version == version
39+
40+
2141
def get_app_setting(
2242
setting: str,
2343
default_value: Optional[str] = None,

0 commit comments

Comments
 (0)