class LambdaFunctionObserver_stop(TestCase): @patch("samcli.lib.utils.file_observer.FileObserver") @patch("samcli.lib.utils.file_observer.ImageObserver") def setUp(self, ImageObserverMock, FileObserverMock): self.on_change = Mock() self.image_observer_mock = Mock() ImageObserverMock.return_value = self.image_observer_mock self.file_observer_mock = Mock() FileObserverMock.return_value = self.file_observer_mock self.lambda_function_observer = LambdaFunctionObserver(self.on_change) def test_successfully_start_observing(self): self.lambda_function_observer.stop() self.file_observer_mock.stop.assert_called_once_with() self.image_observer_mock.stop.assert_called_once_with()
class WarmLambdaRuntime(LambdaRuntime): """ This class extends the LambdaRuntime class to add the Warm containers feature. This class handles the warm containers life cycle. """ def __init__(self, container_manager, image_builder): """ Initialize the Local Lambda runtime Parameters ---------- container_manager samcli.local.docker.manager.ContainerManager Instance of the ContainerManager class that can run a local Docker container image_builder samcli.local.docker.lambda_image.LambdaImage Instance of the LambdaImage class that can create am image warm_containers bool Determines if the warm containers is enabled or not. """ self._containers = {} self._observer = LambdaFunctionObserver(self._on_code_change) super().__init__(container_manager, image_builder) def create(self, function_config, debug_context=None, container_host=None, container_host_interface=None): """ Create a new Container for the passed function, then store it in a dictionary using the function name, so it can be retrieved later and used in the other functions. Make sure to use the debug_context only if the function_config.name equals debug_context.debug-function or the warm_containers option is disabled Parameters ---------- function_config FunctionConfig Configuration of the function to create a new Container for it. debug_context DebugContext Debugging context for the function (includes port, args, and path) container_host string Host of locally emulated Lambda container container_host_interface string Interface that Docker host binds ports to Returns ------- Container the created container """ # reuse the cached container if it is created container = self._containers.get(function_config.name, None) if container and container.is_created(): LOG.info( "Reuse the created warm container for Lambda function '%s'", function_config.name) return container # debug_context should be used only if the function name is the one defined # in debug-function option if debug_context and debug_context.debug_function != function_config.name: LOG.debug( "Disable the debugging for Lambda Function %s, as the passed debug function is %s", function_config.name, debug_context.debug_function, ) debug_context = None container = super().create(function_config, debug_context, container_host, container_host_interface) self._containers[function_config.name] = container self._observer.watch(function_config) self._observer.start() return container def _on_invoke_done(self, container): """ Cleanup the created resources, just before the invoke function ends. In warm containers, the running containers will be closed just before the end of te command execution, so no action is done here Parameters ---------- container: Container The current running container """ def _configure_interrupt(self, function_name, timeout, container, is_debugging): """ When a Lambda function is executing, we setup certain interrupt handlers to stop the execution. Usually, we setup a function timeout interrupt to kill the container after timeout expires. If debugging though, we don't enforce a timeout. But we setup a SIGINT interrupt to catch Ctrl+C and terminate the container. Parameters ---------- function_name: str Name of the function we are running timeout: int Timeout in seconds container: samcli.local.docker.container.Container Instance of a container to terminate is_debugging: bool Are we debugging? Returns ------- threading.Timer Timer object, if we setup a timer. None otherwise """ def timer_handler(): # NOTE: This handler runs in a separate thread. So don't try to mutate any non-thread-safe data structures LOG.info("Function '%s' timed out after %d seconds", function_name, timeout) def signal_handler(sig, frame): # NOTE: This handler runs in a separate thread. So don't try to mutate any non-thread-safe data structures LOG.info("Execution of function %s was interrupted", function_name) if is_debugging: LOG.debug("Setting up SIGTERM interrupt handler") signal.signal(signal.SIGTERM, signal_handler) return None # Start a timer, we'll use this to abort the function if it runs beyond the specified timeout LOG.debug("Starting a timer for %s seconds for function '%s'", timeout, function_name) timer = threading.Timer(timeout, timer_handler, ()) timer.start() return timer def clean_running_containers_and_related_resources(self): """ Clean the running containers, the decompressed code dirs, and stop the created observer """ LOG.debug("Terminating all running warm containers") for function_name, container in self._containers.items(): LOG.debug( "Terminate running warm container for Lambda Function '%s'", function_name) self._container_manager.stop(container) self._clean_decompressed_paths() self._observer.stop() def _on_code_change(self, functions): """ Handles the lambda function code change event. it determines if there is a real change in the code by comparing the checksum of the code path before and after the event. Parameters ---------- functions: list [FunctionConfig] the lambda functions that their source code or images got changed """ for function_config in functions: function_name = function_config.name resource = "source code" if function_config.packagetype == ZIP else f"{function_config.imageuri} image" LOG.info( "Lambda Function '%s' %s has been changed, terminate its warm container. " "The new container will be created in lazy mode", function_name, resource, ) container = self._containers.get(function_name, None) if container: self._container_manager.stop(container) self._containers.pop(function_name, None) self._observer.unwatch(function_config)