feat: add --no-reload flag to disable hot reload for local commands - #56
feat: add --no-reload flag to disable hot reload for local commands#56cortex-assistant[bot] wants to merge 1 commit into
Conversation
Fixes aws#8782. Adds a --no-reload flag to sam local start-api and sam local start-lambda commands that disables file watching and hot reload functionality. This addresses severe performance issues on Windows where Microsoft Defender causes multi-minute delays during recursive directory observation. Changes: - Added --no-reload CLI option to warm_containers_common_options in options.py - Wired flag through start_api/cli.py and start_lambda/cli.py - Propagated flag through InvokeContext to WarmLambdaRuntime and RefreshableSamFunctionProvider - WarmLambdaRuntime skips observer.watch/start when --no-reload is set - RefreshableSamFunctionProvider skips template file watching when --no-reload is set - Added unit tests for both components
There was a problem hiding this comment.
PR Review: feat: add --no-reload flag to disable hot reload for local commands
Good feature that addresses a real pain point (Windows Defender causing multi-minute delays with file watchers). The implementation is clean and well-scoped overall. I have a few issues to address before this should be merged.
✅ What looks good
- CLI option placement — Adding
--no-reloadtowarm_containers_common_optionsis the right grouping since hot reload only applies to warm containers. - Conditional guard in
invoke_context.py— Only passingno_reloadto the function provider whenContainersMode.WARMis correct and avoids leaking the flag to cold mode. - Debug logging — Good use of
LOG.debug()messages so users can diagnose behavior. - Unit tests — Both positive and negative cases are covered for
WarmLambdaRuntimeandRefreshableSamFunctionProvider.
🔴 Issues to address
See inline comments for details.
| self._observer = FileObserver(self._set_templates_changed) | ||
| self._observer.start() | ||
| self._watch_stack_templates(stacks) | ||
| if no_reload: |
There was a problem hiding this comment.
Bug: Observer is still started even when no_reload=True
The FileObserver is unconditionally created and started on the lines above:
self._observer = FileObserver(self._set_templates_changed)
self._observer.start()But the no_reload guard only skips _watch_stack_templates(). This means the observer thread is still spawned and running (consuming resources) even though nothing will ever be watched. This partially defeats the purpose of the flag — on Windows, even an idle FileObserver thread may interact with the file system.
You should also skip self._observer.start() when no_reload=True, or better yet, skip creating the observer entirely:
if no_reload:
LOG.debug("Hot reload is disabled (--no-reload). Skipping template file observer.")
self._observer = None
else:
self._observer = FileObserver(self._set_templates_changed)
self._observer.start()
self._watch_stack_templates(stacks)self._observer.stop() in the stop() method as well (check for None).
| """ | ||
|
|
||
| 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_reload=False): |
There was a problem hiding this comment.
Nit: Line length exceeds 120 characters
This line is quite long. Consider wrapping the parameters:
def __init__(
self, container_manager, image_builder, observer=None,
mount_symlinks=False, no_mem_limit=False, no_reload=False,
):| @@ -256,7 +258,6 @@ def __enter__(self) -> "InvokeContext": | |||
| ContainersMode.WARM: [self._stacks, self._parameter_overrides, self._global_parameter_overrides], | |||
| ContainersMode.COLD: [self._stacks], | |||
| } | |||
There was a problem hiding this comment.
Nit: Unrelated whitespace change
This removes a blank line that was separating two logical blocks. It's a minor formatting change but unrelated to the feature — I'd restore the original blank line to avoid noise in the diff.
| ), | ||
| click.option( | ||
| "--no-reload", | ||
| is_flag=True, |
There was a problem hiding this comment.
Consider: Should --no-reload warn or error when used without --warm-containers?
Since --no-reload is only meaningful with warm containers, using it with cold containers silently does nothing. This could be confusing to users. Consider either:
- Emitting a warning when
--no-reloadis passed without--warm-containers, or - Documenting in the help text that it requires
--warm-containers.
At minimum, updating the help text to mention the dependency:
"Disable automatic reloading of Lambda functions when source code or templates change. "
"Only applies when used with --warm-containers. "
"Useful on Windows where file watchers can cause significant latency with security software."
| runtime.create(func_config) | ||
|
|
||
| self.observer_mock.watch.assert_called_once_with(func_config) | ||
| self.observer_mock.start.assert_called_once() |
There was a problem hiding this comment.
Suggestion: Assert the container is still created successfully when no_reload=True
The tests verify observer behavior but don't assert that the container itself was still created and returned. Adding assertions like:
result = runtime.create(func_config)
self.manager_mock.create.assert_called_once()
self.assertIsNotNone(result)would strengthen the test by verifying that skipping the observer doesn't break container creation.
There was a problem hiding this comment.
PR Review: feat: add --no-reload flag to disable hot reload for local commands
Overall this is a well-structured change that cleanly threads a new --no-reload flag through the CLI → InvokeContext → runtime/provider pipeline. The motivation (Windows Defender latency on file watchers) is valid and the approach is sound. However, I have a few concerns that should be addressed before merging.
✅ What looks good
- Clean plumbing: The flag is properly wired through
options.py→cli.py→invoke_context.py→WarmLambdaRuntime/RefreshableSamFunctionProvider. - Good test coverage: Both positive and negative test cases for
WarmLambdaRuntimeandRefreshableSamFunctionProvider. - Appropriate scoping: The flag is only propagated to
WarmLambdaRuntime(notLambdaRuntime) and toRefreshableSamFunctionProvider(notSamFunctionProvider), which is correct since only warm containers use file watching. - Debug logging: Helpful debug messages when the flag is active.
🔴 Issues to address
-
RefreshableSamFunctionProviderstill starts the observer even whenno_reload=True— TheFileObserveris unconditionally created and started (self._observer.start()), but only the_watch_stack_templatescall is skipped. This means a background observer thread is still running doing nothing. If the goal is to avoid file-watcher overhead, the observer should also be skipped entirely. -
--no-reloadwithout--warm-containersis silently ignored — The flag is defined inwarm_containers_common_optionsand only takes effect in warm container mode, but there's no user-facing warning if someone passes--no-reloadwithout--warm-containers. This could be confusing. Consider either logging a warning or validating the combination. -
Line length violation — The
__init__signature inruntime.pyexceeds typical line length limits after adding the new parameter. -
Unrelated whitespace change — A blank line was removed in
invoke_context.pywhich is unrelated to the feature. -
Missing
__init__docstring update — Theno_reloadparameter isn't documented in the docstrings ofWarmLambdaRuntime.__init__,RefreshableSamFunctionProvider.__init__, orInvokeContext.__init__.
| self._observer = FileObserver(self._set_templates_changed) | ||
| self._observer.start() | ||
| self._watch_stack_templates(stacks) | ||
| if no_reload: |
There was a problem hiding this comment.
🔴 Observer still started when no_reload=True: The FileObserver is created and self._observer.start() is called unconditionally on line 936, even when no_reload=True. This means a background thread is still running even though nothing is being watched.
Consider wrapping both the observer creation and start in the condition:
if no_reload:
LOG.debug("Hot reload is disabled (--no-reload). Skipping template file observer.")
self._observer = None
else:
self._observer = FileObserver(self._set_templates_changed)
self._observer.start()
self._watch_stack_templates(stacks)Note: you'd also need to guard self._observer.stop() in the stop() method with an if self._observer: check.
| """ | ||
|
|
||
| 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_reload=False): |
There was a problem hiding this comment.
🟡 Line too long: This line is ~113 characters after adding no_reload=False. Consider wrapping the parameters:
def __init__(
self, container_manager, image_builder, observer=None,
mount_symlinks=False, no_mem_limit=False, no_reload=False
):| @@ -256,7 +258,6 @@ def __enter__(self) -> "InvokeContext": | |||
| ContainersMode.WARM: [self._stacks, self._parameter_overrides, self._global_parameter_overrides], | |||
| ContainersMode.COLD: [self._stacks], | |||
| } | |||
There was a problem hiding this comment.
🟡 Unrelated whitespace change: This removes a blank line that was separating the dict definitions from the following comment block. Minor, but it's unrelated to the feature and adds noise to the diff. Consider reverting.
| @@ -267,6 +268,9 @@ def __enter__(self) -> "InvokeContext": | |||
| if self._function_logical_ids: | |||
| _function_providers_kwargs["function_logical_ids"] = self._function_logical_ids | |||
|
|
|||
There was a problem hiding this comment.
🟡 Consider warning when --no-reload is used without --warm-containers: Currently no_reload is only passed to the function provider when containers_mode == WARM. If a user passes --no-reload without --warm-containers, it's silently ignored. A LOG.warning() (or a click validation callback) would improve the UX:
if self._no_reload and self._containers_mode != ContainersMode.WARM:
LOG.warning("--no-reload has no effect without --warm-containers")| ), | ||
| click.option( | ||
| "--no-reload", | ||
| is_flag=True, |
There was a problem hiding this comment.
🟢 Nice help text — clearly explains the use case. Consider also mentioning that this flag only takes effect with --warm-containers so users know upfront:
"Disable automatic reloading of Lambda functions when source code or templates change. "
"Only effective with --warm-containers. "
"Useful on Windows where file watchers can cause significant latency with security software."
Fixes aws#8782.
Summary
Adds a
--no-reloadflag tosam local start-apiandsam local start-lambdacommands that disables file watching and hot reload functionality. This addresses severe performance issues on Windows where Microsoft Defender causes multi-minute delays during recursive directory observation.Changes
--no-reloadCLI option towarm_containers_common_optionsinoptions.pystart_api/cli.pyandstart_lambda/cli.pyInvokeContexttoWarmLambdaRuntimeandRefreshableSamFunctionProviderWarmLambdaRuntimeskipsobserver.watch()andobserver.start()when--no-reloadis setRefreshableSamFunctionProviderskips template file watching when--no-reloadis setTesting
WarmLambdaRuntime(skips observer when--no-reload) andRefreshableSamFunctionProvider(skips template watch when--no-reload)--no-reloadis used with--warm-containers, containers still work but code changes are not automatically detected