-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Auto reload for local mode #706
Conversation
@jamesls, @stealthycoin Hi guys! If I have some mistakes in this pr could you please tell me about them |
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.
Thanks for taking the time to make this pull request. It's been something I've been wanting for awhile as well and I'd like to see it get shipped.
I had some comments and questions. Scattered throughout. The main thing besides the below comments would be to get some tests added.
@@ -304,7 +304,7 @@ valid-metaclass-classmethod-first-arg=mcs | |||
|
|||
# List of member names, which should be excluded from the protected access | |||
# warning. | |||
exclude-protected=_asdict,_fields,_replace,_source,_make |
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 would prefer not to reach into the _thread
if at all possible. Are there other options?
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.
No, there are no other options.
chalice/cli/reload.py
Outdated
def reload(self): | ||
# type: () -> None | ||
self.triggered = True | ||
if sys.platform == 'win32': |
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.
We prefer to have all platform specific logic in the compat.py
file in one place so we don't need to think about it here.
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.
Sure, I'll fix this.
chalice/cli/__init__.py
Outdated
@@ -79,11 +80,14 @@ def cli(ctx, project_dir, debug=False): | |||
@click.option('--port', default=8000, type=click.INT) | |||
@click.option('--stage', default=DEFAULT_STAGE_NAME, | |||
help='Name of the Chalice stage for the local server to use.') | |||
@click.option('--autoreload', is_flag=True) |
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.
Seems like it should be turned on by default so people don't need to discover the functionality, it just magically works. We could flip it and let them disable it if they want to for some reason. --no-autoreload
. Thoughts?
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.
It makes sense.
chalice/cli/__init__.py
Outdated
factory = ctx.obj['factory'] # type: CLIFactory | ||
run_local_server(factory, host, port, stage, os.environ) | ||
with Reloader(autoreload): |
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.
Seems a little bit strange to me to pass False
to class called Reloader. Could we refactor this to just not use the Reloader context manager if its disabled?
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.
You are right, I'll fix it.
chalice/cli/reload.py
Outdated
def run(self): | ||
# type: () -> None | ||
while True: | ||
time.sleep(AUTORELOAD_INTERVAL) |
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.
This would be nice as a parameter that can be supplied to the reloader.
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.
Yep.
|
||
def find_changes(self): | ||
# type: () -> bool | ||
for module in list(sys.modules.values()): |
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.
- No need to cast to
list
here. - Can we scope this to just the files that are in their Chalice project?
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.
No need to cast to
list
here.
Why? RuntimeError: dictionary changed size during iteration
Can we scope this to just the files that are in their Chalice project?
We can.. but I think it's better to check all modules.
chalice/cli/reload.py
Outdated
subprocess.Popen(sys.argv, close_fds=True) | ||
six.moves._thread.interrupt_main() | ||
else: | ||
os.execv(sys.executable, [sys.executable] + sys.argv) |
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 haven't had the chance to try out this branch yet but I'm curious about this approach and how it compares to managing a subprocess yourself, or hot reloading the python code. For example:
https://docs.python.org/3.4/library/importlib.html#importlib.reload
https://docs.python.org/3.2/library/imp.html#imp.load_module
etc...
I'm mainly trying to think of a more testable approach that would not need any platform specific logic. Would like to hear your thoughts on why you chose this approach.
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.
Reload python module at runtime looks like a bad idea to me (because it causes inconsistently application state)
chalice/cli/reload.py
Outdated
# type: (bool) -> None | ||
super(Reloader, self).__init__() | ||
self.autoreload = autoreload | ||
self.triggered = False |
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 recognize that this is whether or not the process has ever reloaded. But can you give some clarification on why this is important to keep track of?
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.
We need to have additional behavior when interrupting for the reloaded process in windows (raise SystemExit).
chalice/cli/reload.py
Outdated
self.triggered = True | ||
if sys.platform == 'win32': | ||
subprocess.Popen(sys.argv, close_fds=True) | ||
six.moves._thread.interrupt_main() |
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.
Can you explain why this is needed on windows?
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.
execv
in windows is completely different than in linux. Briefly, it's necessary to close the opened file descriptors.
Codecov Report
@@ Coverage Diff @@
## master #706 +/- ##
==========================================
- Coverage 94.79% 93.86% -0.93%
==========================================
Files 21 22 +1
Lines 3707 3767 +60
Branches 478 486 +8
==========================================
+ Hits 3514 3536 +22
- Misses 130 168 +38
Partials 63 63
Continue to review full report at Codecov.
|
@jamesls, @stealthycoin Could you review my changes, please? |
Taking a look now. I haven't had a chance to look at this PR in general (thanks @stealthycoin for the feedback so far), but I think the feature itself would be good to have in chalice. Was there a discussion about pulling in some existing library that can watch for file changes? Seems like it would be preferable to use something based on inotify/fsevents instead of polling for changes. |
Hi, I know there's been a lot of requests for this feature so I'll go ahead and pull in these changes and make a few adjustments before merging. |
…icts in chalice/cli/__init__.py)
@jamesls Thanks, I will be glad to help you. About inotify/fsevents - sounds good to me, but I guess it would be better to deliver working autoreload feature as soon as possible and then improve it in a separate pr (because as you said there are a lot of requests for this stuff). |
This adds a reloader to chalice local that's enabled by default. A new library, watchdog, is pulled in as a dependency of chalice. This allows us to efficiently monitor files for changes using whatever the most efficient OS specific mechanism (FSEvents, inotify, etc). The general approach is borrowed from other frameworks such as django and flask but the implementation is somewhat different. Two processes are spun up, a worker process and a monitoring process. The monitoring process spawns the worker process and restarts it if it exits with a special RC. The worker process launches the dev server in one thread and a file monitor in a separate thread. When files are detected it shuts down the server and exits with a special RC. There's a few differences from the other approaches: * Avoid the use of sys.exit() directly in the reloader. This made the code hard to test and seems odd that a library function is calling sys.exit(). However there is a call to `sys.exit()` in the `local()` function because this is the recommended way in click for a command to exit with a specific RC (see the comments for more info). * We only monitor the project dir for changes. The other reloaders appear to monitor everything in sys.path, which seems like overkill here. The way I see it, stuff on sys.path would only be modified if you're installing new packages, in which case you'll have to modify requirements.txt as well as something in your app.py (in order to import the newly installed module). I dug around their repos for a bit and couldn't find a rationale as to why that much monitoring is needed. A note on coverage: because the reloader works by launching child processes, this isn't going to be tracked via coverage, so I expect codecov to fail. There is some support for tracking coverage across subprocesses, but it involved messing with sitecustomize.py or .pth files. See http://coverage.readthedocs.io/en/coverage-4.2/subprocess.html for more info on that. Supercedes aws#706.
This adds a reloader to chalice local that's enabled by default. A new library, watchdog, is pulled in as a dependency of chalice. This allows us to efficiently monitor files for changes using whatever the most efficient OS specific mechanism (FSEvents, inotify, etc). The general approach is borrowed from other frameworks such as django and flask but the implementation is somewhat different. Two processes are spun up, a worker process and a monitoring process. The monitoring process spawns the worker process and restarts it if it exits with a special RC. The worker process launches the dev server in one thread and a file monitor in a separate thread. When files are detected it shuts down the server and exits with a special RC. There's a few differences from the other approaches: * Avoid the use of sys.exit() directly in the reloader. This made the code hard to test and seems odd that a library function is calling sys.exit(). However there is a call to `sys.exit()` in the `local()` function because this is the recommended way in click for a command to exit with a specific RC (see the comments for more info). * We only monitor the project dir for changes. The other reloaders appear to monitor everything in sys.path, which seems like overkill here. The way I see it, stuff on sys.path would only be modified if you're installing new packages, in which case you'll have to modify requirements.txt as well as something in your app.py (in order to import the newly installed module). I dug around their repos for a bit and couldn't find a rationale as to why that much monitoring is needed. A note on coverage: because the reloader works by launching child processes, this isn't going to be tracked via coverage, so I expect codecov to fail. There is some support for tracking coverage across subprocesses, but it involved messing with sitecustomize.py or .pth files. See http://coverage.readthedocs.io/en/coverage-4.2/subprocess.html for more info on that. Supercedes aws#706.
This adds a reloader to chalice local that's enabled by default. A new library, watchdog, is pulled in as a dependency of chalice. This allows us to efficiently monitor files for changes using whatever the most efficient OS specific mechanism (FSEvents, inotify, etc). The general approach is borrowed from other frameworks such as django and flask but the implementation is somewhat different. Two processes are spun up, a worker process and a monitoring process. The monitoring process spawns the worker process and restarts it if it exits with a special RC. The worker process launches the dev server in one thread and a file monitor in a separate thread. When files are detected it shuts down the server and exits with a special RC. There's a few differences from the other approaches: * Avoid the use of sys.exit() directly in the reloader. This made the code hard to test and seems odd that a library function is calling sys.exit(). However there is a call to `sys.exit()` in the `local()` function because this is the recommended way in click for a command to exit with a specific RC (see the comments for more info). * We only monitor the project dir for changes. The other reloaders appear to monitor everything in sys.path, which seems like overkill here. The way I see it, stuff on sys.path would only be modified if you're installing new packages, in which case you'll have to modify requirements.txt as well as something in your app.py (in order to import the newly installed module). I dug around their repos for a bit and couldn't find a rationale as to why that much monitoring is needed. A note on coverage: because the reloader works by launching child processes, this isn't going to be tracked via coverage, so I expect codecov to fail. There is some support for tracking coverage across subprocesses, but it involved messing with sitecustomize.py or .pth files. See http://coverage.readthedocs.io/en/coverage-4.2/subprocess.html for more info on that. Supercedes aws#706.
This adds a reloader to chalice local that's enabled by default. A new library, watchdog, is pulled in as a dependency of chalice. This allows us to efficiently monitor files for changes using whatever the most efficient OS specific mechanism (FSEvents, inotify, etc). The general approach is borrowed from other frameworks such as django and flask but the implementation is somewhat different. Two processes are spun up, a worker process and a monitoring process. The monitoring process spawns the worker process and restarts it if it exits with a special RC. The worker process launches the dev server in one thread and a file monitor in a separate thread. When files are detected it shuts down the server and exits with a special RC. There's a few differences from the other approaches: * Avoid the use of sys.exit() directly in the reloader. This made the code hard to test and seems odd that a library function is calling sys.exit(). However there is a call to `sys.exit()` in the `local()` function because this is the recommended way in click for a command to exit with a specific RC (see the comments for more info). * We only monitor the project dir for changes. The other reloaders appear to monitor everything in sys.path, which seems like overkill here. The way I see it, stuff on sys.path would only be modified if you're installing new packages, in which case you'll have to modify requirements.txt as well as something in your app.py (in order to import the newly installed module). I dug around their repos for a bit and couldn't find a rationale as to why that much monitoring is needed. A note on coverage: because the reloader works by launching child processes, this isn't going to be tracked via coverage, so I expect codecov to fail. There is some support for tracking coverage across subprocesses, but it involved messing with sitecustomize.py or .pth files. See http://coverage.readthedocs.io/en/coverage-4.2/subprocess.html for more info on that. Supercedes aws#706.
Merged via #846. This will go out in the next release. Thanks again for the original PR. |
I have implemented auto reload feature by #316 request. It doesn't have any dependencies (according to #658 remarks).
@jamesls, @stealthycoin Could you help me merge this PR as soon as possible, please?