Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions samcli/commands/local/cli_common/invoke_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def __init__(
invoke_images: Optional[str] = None,
mount_symlinks: Optional[bool] = False,
no_mem_limit: Optional[bool] = False,
no_watch: Optional[bool] = False,
) -> None:
"""
Initialize the context
Expand Down Expand Up @@ -204,6 +205,7 @@ def __init__(

self._mount_symlinks: Optional[bool] = mount_symlinks
self._no_mem_limit = no_mem_limit
self._no_watch = no_watch

# Note(xinhol): despite self._function_provider and self._stacks are initialized as None
# they will be assigned with a non-None value in __enter__() and
Expand Down Expand Up @@ -234,16 +236,18 @@ def __enter__(self) -> "InvokeContext":
ContainersMode.WARM: RefreshableSamFunctionProvider,
ContainersMode.COLD: SamFunctionProvider,
}

_function_providers_args: Dict[ContainersMode, List[Any]] = {
ContainersMode.WARM: [self._stacks, self._parameter_overrides, self._global_parameter_overrides],
ContainersMode.COLD: [self._stacks],
}

# don't resolve the code URI immediately if we passed in docker vol by passing True for use_raw_codeuri
# this way at the end the code URI will get resolved against the basedir option
if self._docker_volume_basedir:
_function_providers_args[self._containers_mode].append(True)
if self._no_watch:
_function_providers_args[self._containers_mode].extend([False, True])
elif self._no_watch:
_function_providers_args[self._containers_mode].extend([False, False, True])
Comment thread
roger-zhangg marked this conversation as resolved.
Outdated

self._function_provider = _function_providers_class[self._containers_mode](
*_function_providers_args[self._containers_mode]
Expand Down Expand Up @@ -415,6 +419,7 @@ def lambda_runtime(self) -> LambdaRuntime:
image_builder,
mount_symlinks=self._mount_symlinks,
no_mem_limit=self._no_mem_limit,
no_watch=self._no_watch,
),
ContainersMode.COLD: LambdaRuntime(
self._container_manager,
Expand Down
10 changes: 10 additions & 0 deletions samcli/commands/local/start_api/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@
required_param_lists=[["ssl_cert_file"]],
help="Path to SSL key file (default: None)",
)
@click.option(
"--no-watch",
is_flag=True,
default=False,
help="Disable file watching. Local code changes will not reset running docker container.",
Comment thread
roger-zhangg marked this conversation as resolved.
Outdated
)
@invoke_common_options
@warm_containers_common_options
@local_common_options
Expand Down Expand Up @@ -141,6 +147,7 @@ def cli(
terraform_plan_file,
ssl_cert_file,
ssl_key_file,
no_watch,
no_memory_limit,
):
"""
Expand Down Expand Up @@ -178,6 +185,7 @@ def cli(
ssl_cert_file,
ssl_key_file,
no_memory_limit,
no_watch,
) # pragma: no cover


Expand Down Expand Up @@ -211,6 +219,7 @@ def do_cli( # pylint: disable=R0914
ssl_cert_file,
ssl_key_file,
no_mem_limit,
no_watch,
):
"""
Implementation of the ``cli`` method, just separated out for unit testing purposes
Expand Down Expand Up @@ -257,6 +266,7 @@ def do_cli( # pylint: disable=R0914
invoke_images=processed_invoke_images,
add_host=add_host,
no_mem_limit=no_mem_limit,
no_watch=no_watch,
) as invoke_context:
ssl_context = (ssl_cert_file, ssl_key_file) if ssl_cert_file else None
service = LocalApiService(
Expand Down
1 change: 1 addition & 0 deletions samcli/commands/local/start_api/core/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"add_host",
"invoke_image",
"disable_authorizer",
"no_watch",
]

CONFIGURATION_OPTION_NAMES: List[str] = ["config_env", "config_file"] + SAVE_PARAMS_OPTIONS
Expand Down
10 changes: 10 additions & 0 deletions samcli/commands/local/start_lambda/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@
)
@skip_prepare_infra_option
@service_common_options(3001)
@click.option(
"--no-watch",
is_flag=True,
default=False,
help="Disable file watching. Local code changes will not reset running docker container.",
)
@invoke_common_options
@warm_containers_common_options
@local_common_options
Expand Down Expand Up @@ -101,6 +107,7 @@ def cli(
hook_name,
skip_prepare_infra,
terraform_plan_file,
no_watch,
no_memory_limit,
):
"""
Expand Down Expand Up @@ -134,6 +141,7 @@ def cli(
invoke_image,
hook_name,
no_memory_limit,
no_watch,
) # pragma: no cover


Expand Down Expand Up @@ -163,6 +171,7 @@ def do_cli( # pylint: disable=R0914
invoke_image,
hook_name,
no_mem_limit,
no_watch,
):
"""
Implementation of the ``cli`` method, just separated out for unit testing purposes
Expand Down Expand Up @@ -209,6 +218,7 @@ def do_cli( # pylint: disable=R0914
add_host=add_host,
invoke_images=processed_invoke_images,
no_mem_limit=no_mem_limit,
no_watch=no_watch,
) as invoke_context:
service = LocalLambdaService(lambda_invoke_context=invoke_context, port=port, host=host)
service.start()
Expand Down
1 change: 1 addition & 0 deletions samcli/commands/local/start_lambda/core/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"add_host",
"invoke_image",
"no_memory_limit",
"no_watch",
]

ARTIFACT_LOCATION_OPTIONS: List[str] = [
Expand Down
25 changes: 17 additions & 8 deletions samcli/lib/providers/sam_function_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,7 @@ def __init__(
global_parameter_overrides: Optional[Dict] = None,
use_raw_codeuri: bool = False,
ignore_code_extraction_warnings: bool = False,
no_watch: Optional[bool] = False,
) -> None:
"""
Initialize the class with SAM template data. The SAM template passed to this provider is assumed
Expand Down Expand Up @@ -831,9 +832,14 @@ def __init__(
self.parent_templates_paths.append(stack.location)

self.is_changed = False
self._observer = FileObserver(self._set_templates_changed)
self._observer.start()
self._watch_stack_templates(stacks)

# Only initialize file watcher if no_watch is False
if not no_watch:
self._observer: Optional[FileObserver] = FileObserver(self._set_templates_changed)
self._observer.start()
self._watch_stack_templates(stacks)
else:
self._observer = None

@property
def stacks(self) -> List[Stack]:
Expand Down Expand Up @@ -896,15 +902,17 @@ def _set_templates_changed(self, paths: List[str]) -> None:
", ".join(paths),
)
self.is_changed = True
for stack in self._stacks:
self._observer.unwatch(stack.location)
if self._observer:
for stack in self._stacks:
self._observer.unwatch(stack.location)

def _watch_stack_templates(self, stacks: List[Stack]) -> None:
"""
initialize the list of stack template watchers
"""
for stack in stacks:
self._observer.watch(stack.location)
if self._observer:
for stack in stacks:
self._observer.watch(stack.location)

def _refresh_loaded_functions(self) -> None:
"""
Expand Down Expand Up @@ -934,4 +942,5 @@ def stop_observer(self) -> None:
"""
Stop Observing.
"""
self._observer.stop()
if self._observer:
self._observer.stop()
21 changes: 21 additions & 0 deletions samcli/lib/utils/file_observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,22 @@ def _on_change(self, resources: List[str], package_type: str) -> None:
package_type: str
determine if the changed resource is a source code path or an image name
"""
LOG.debug(
"Acquiring lock for _on_change to process %s %s resource changes: %s",
len(resources),
package_type,
resources,
)
with self._watch_lock:
changed_functions: List[FunctionConfig] = []
for resource in resources:
if self._observed_functions[package_type].get(resource, None):
changed_functions += self._observed_functions[package_type][resource]
LOG.debug(
"Acquired lock and processing %s changed functions from %s resources",
len(changed_functions),
len(resources),
)
self._input_on_change(changed_functions)

def watch(self, function_config: FunctionConfig) -> None:
Expand All @@ -204,9 +215,18 @@ def watch(self, function_config: FunctionConfig) -> None:
ObserverException:
if not able to observe the input function source path/image
"""
LOG.debug(
"Acquiring lock for watch to observe %s function: %s", function_config.packagetype, function_config.name
)
with self._watch_lock:
if self.get_resources.get(function_config.packagetype, None):
resources = self.get_resources[function_config.packagetype](function_config)
LOG.debug(
"Acquired lock for watch, observing %s resources for function %s: %s",
len(resources),
function_config.name,
resources,
)
for resource in resources:
functions = self._observed_functions[function_config.packagetype].get(resource, [])
functions += [function_config]
Expand Down Expand Up @@ -424,6 +444,7 @@ def __init__(self) -> None:
self._observed_watches: Dict[str, ObservedWatch] = {}
self._watch_dog_observed_paths: Dict[str, List[str]] = {}
self._observer: BaseObserver = Observer()

self._code_modification_handler: PatternMatchingEventHandler = PatternMatchingEventHandler(
patterns=["*"], ignore_patterns=[], ignore_directories=False
)
Expand Down
35 changes: 26 additions & 9 deletions samcli/local/lambdafn/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,9 @@ class WarmLambdaRuntime(LambdaRuntime):
warm containers life cycle.
"""

def __init__(self, container_manager, image_builder, observer=None, mount_symlinks=False, no_mem_limit=False):
def __init__(
self, container_manager, image_builder, observer=None, mount_symlinks=False, no_mem_limit=False, no_watch=False
):
"""
Initialize the Local Lambda runtime

Expand All @@ -405,13 +407,23 @@ def __init__(self, container_manager, image_builder, observer=None, mount_symlin
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.
observer
Optional observer for file watching
mount_symlinks bool
Optional. True if symlinks should be mounted in the container
no_mem_limit bool
Optional. True if memory limit should be disabled
Comment thread
roger-zhangg marked this conversation as resolved.
no_watch bool
Optional. True if file watching should be disabled
"""
self._function_configs = {}
self._containers = {}
self._no_watch = no_watch

self._observer = observer if observer else LambdaFunctionObserver(self._on_code_change)
if no_watch:
self._observer = None
else:
self._observer = observer if observer else LambdaFunctionObserver(self._on_code_change)

super().__init__(container_manager, image_builder, mount_symlinks=mount_symlinks, no_mem_limit=no_mem_limit)

Expand Down Expand Up @@ -453,7 +465,8 @@ def create(
if container:
self._container_manager.stop(container)
self._containers.pop(exist_function_config.full_path, None)
self._observer.unwatch(exist_function_config)
if self._observer is not None:
self._observer.unwatch(exist_function_config)
elif container and container.is_created():
LOG.info("Reuse the created warm container for Lambda function '%s'", function_config.full_path)
return container
Expand All @@ -468,8 +481,10 @@ def create(
)
debug_context = None

self._observer.watch(function_config)
self._observer.start()
# Only watch and start observer if file watching is enabled
if self._observer is not None:
self._observer.watch(function_config)
self._observer.start()

container = super().create(
function_config, debug_context, container_host, container_host_interface, extra_hosts
Expand Down Expand Up @@ -545,7 +560,8 @@ def clean_running_containers_and_related_resources(self):
LOG.debug("Terminate running warm container for Lambda Function '%s'", function_name)
self._container_manager.stop(container)
self._clean_decompressed_paths()
self._observer.stop()
if self._observer is not None:
self._observer.stop()

def _on_code_change(self, functions):
"""
Expand All @@ -566,7 +582,8 @@ def _on_code_change(self, functions):
function_full_path,
resource,
)
self._observer.unwatch(function_config)
if self._observer is not None:
self._observer.unwatch(function_config)
self._function_configs.pop(function_full_path, None)
container = self._containers.get(function_full_path, None)
if container:
Expand Down
Loading
Loading