-
Notifications
You must be signed in to change notification settings - Fork 11
Update lambda handler function extraction to work with python 3.9 #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,34 +1,24 @@ | ||
| import os | ||
| import logging | ||
| import importlib | ||
| from codeguru_profiler_agent.aws_lambda.profiler_decorator import with_lambda_profiler | ||
| from codeguru_profiler_agent.agent_metadata.aws_lambda import HANDLER_ENV_NAME_FOR_CODEGURU_KEY | ||
| HANDLER_ENV_NAME = "_HANDLER" | ||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def restore_handler_env(original_handler, env=os.environ): | ||
| env[HANDLER_ENV_NAME] = original_handler | ||
|
|
||
|
|
||
| def load_handler(bootstrap_module, env=os.environ, original_handler_env_key=HANDLER_ENV_NAME_FOR_CODEGURU_KEY): | ||
| def load_handler(handler_extractor, env=os.environ, original_handler_env_key=HANDLER_ENV_NAME_FOR_CODEGURU_KEY): | ||
| try: | ||
| original_handler_name = env.get(original_handler_env_key) | ||
| if not original_handler_name: | ||
| raise ValueError("Could not find module and function name from " + HANDLER_ENV_NAME_FOR_CODEGURU_KEY | ||
| + " environment variable") | ||
|
|
||
| # Delegate to the lambda code to load the customer's module. | ||
| if hasattr(bootstrap_module, '_get_handler'): | ||
| customer_handler_function = bootstrap_module._get_handler(original_handler_name) | ||
| else: | ||
| # TODO FIXME Review if the support for python 3.6 bootstrap can be improved. | ||
| # This returns both a init_handler and the function, we apply the init right away as we are in init process | ||
| init_handler, customer_handler_function = bootstrap_module._get_handlers( | ||
| handler=original_handler_name, | ||
| mode='event', # with 'event' it will return the function as is (handlerfn in the lambda code) | ||
| # 'http' would return wsgi.handle_one(sockfd, ('localhost', 80), handlerfn) instead | ||
| invokeid='unknown_id') # FIXME invokeid is used for error handling, need to see if we can get it | ||
| init_handler() | ||
| # Delegate to the lambda code to load the customer's module and function. | ||
| customer_handler_function = handler_extractor(original_handler_name) | ||
|
|
||
| restore_handler_env(original_handler_name, env) | ||
| return customer_handler_function | ||
| except: | ||
|
|
@@ -39,10 +29,60 @@ def load_handler(bootstrap_module, env=os.environ, original_handler_env_key=HAND | |
| raise | ||
|
|
||
|
|
||
| # Load the customer's handler, this should be done at import time which means it is done when lambda frameworks | ||
| # loads our module. We load the bootstrap module by string name so that IDE does not complain | ||
| lambda_bootstrap_module = __import__("bootstrap") | ||
| handler_function = load_handler(lambda_bootstrap_module) | ||
| def _python36_extractor(bootstrap_module, original_handler_name): | ||
| """ | ||
| The lambda bootstrap code for python 3.6 was different than for later versions, instead of the _get_handler | ||
| function there was a more complex _get_handlers function with more parameters | ||
| """ | ||
| # TODO FIXME Review if the support for python 3.6 bootstrap can be improved. | ||
| # This returns both a init_handler and the function, we apply the init right away as we are in init process | ||
| init_handler, customer_handler_function = bootstrap_module._get_handlers( | ||
| handler=original_handler_name, | ||
| mode='event', # with 'event' it will return the function as is (handlerfn in the lambda code) | ||
| # 'http' would return wsgi.handle_one(sockfd, ('localhost', 80), handlerfn) instead | ||
| invokeid='unknown_id') # FIXME invokeid is used for error handling, need to see if we can get it | ||
| init_handler() | ||
| return customer_handler_function | ||
|
|
||
|
|
||
| def get_lambda_handler_extractor(): | ||
| """ | ||
| This loads and returns a function from lambda or RIC source code that is able to load the customer's | ||
| handler function. | ||
| WARNING !! This is a bit dangerous since we are calling internal functions from other modules that we do not | ||
| officially depend on. The idea is that this code should run only in a lambda function environment where we can know | ||
| what is available. However if lambda developers decide to change their internal code it could impact this ! | ||
| """ | ||
| # First try to load the lambda RIC if it is available (i.e. python 3.9) | ||
| # See https://github.com/aws/aws-lambda-python-runtime-interface-client | ||
| ric_bootstrap_module = _try_to_load_module("awslambdaric.bootstrap") | ||
| if ric_bootstrap_module is not None and hasattr(ric_bootstrap_module, '_get_handler'): | ||
| return ric_bootstrap_module._get_handler | ||
|
|
||
| # If no RIC module is available there should be a bootstrap module available | ||
| # do not catch ModuleNotFoundError exceptions here as we cannot do anything if this fails. | ||
| bootstrap_module = importlib.import_module("bootstrap") | ||
| if hasattr(bootstrap_module, '_get_handler'): | ||
| return bootstrap_module._get_handler | ||
| else: | ||
| return lambda handler_name: _python36_extractor(bootstrap_module, handler_name) | ||
|
|
||
|
|
||
| def _try_to_load_module(module_name): | ||
| try: | ||
| return importlib.import_module(module_name) | ||
| except ModuleNotFoundError: | ||
| return None | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we log the error at least at
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could but I do not want every customer using python <3.9 (most customers) to see a log about not being able to find a module. Also I removed logger which was not being used in this code, originally I was afraid to use the logger here as I was not sure the lambda framework is already setup enough at this point in the process. |
||
|
|
||
|
|
||
| # We need to load the customer's handler function since the lambda framework loaded our function instead. | ||
| # We want to delegate this work to the lambda framework so we need to find the appropriate method that does it | ||
| # (depends on python versions) so we can call it. | ||
| # This should be done at import time which means it is done when lambda frameworks loads our module | ||
| handler_extractor = get_lambda_handler_extractor() | ||
|
|
||
| # Now load the actual customer's handler function. | ||
| handler_function = load_handler(handler_extractor) | ||
|
|
||
|
|
||
| # WARNING: Do not rename this file, this function or HANDLER_ENV_NAME_FOR_CODEGURU without changing the bootstrap script | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this comment and its place here. Should this be moved in the
_try_to_load_modulemethod?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
basically I first try to load module
awslambdaric.bootstrapand if that is not available I try to loadbootstrap.While the first one is never available for lambdas with runtime < 3.9 (at the moment), the other should always be there. So I use
_try_to_load_moduleto get the first one but for the second I doimport_moduledirectly, not trying to catch any exception if it fails.