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
Fixed #27685 -- Added watchman support to the autoreloader. #8819
Conversation
6d5ce78
to
9333a2c
Compare
Thanks! I commented here https://code.djangoproject.com/ticket/27685#comment:6. I'm planning to review this in the next few days (he says, optimistically). |
@aaugustin, you said ping me by mid august: ping |
I'm taking a look this week-end. |
My original idea was to watch the current working directory rather than all imported Python modules. This changes the behavior for the better — I think. It's easier to explain. It also avoids debates about I'd like to see how watchman will integrate with the APIs you defined. You said "it just needs to implement a The Regarding |
Thanks for taking a look. I will:
I was also onboarding some new junior developers last week, and one thing I noticed that threw them a little bit was the fact that the autoreloader closes the socket while reloading. So often when refreshing after a change the browser would throw an error because nothing was listening on that port. If you agree I'd like to fix that as well, as far as I can tell the fix would be to open the socket in the outer |
Yes, why not. Check the behavior before / after in the following cases: run manage.py runserver:
BTW I just remembered the reason for watching the whole current directory rather just imported Python modules: that will catch file additions like admin.py. |
Hmm, let's perhaps recursively watch all INSTALLED_APPS for *.py additions?
I would like to avoid globbing the cwd, it might have huge directories
(like a node_modules) which would ruin performance.
|
We could have a configurable set of excludes with reasonable defaults for the CWD... Anyway, you write the code, you get to decide :-) Let's focus on watchman integration first! |
I started on getting watchman working, and it was going well. However I got sidetracked with the exception handing situation, which needs some thought and redesigning. I've come up with a solution - however it adds a bit of complexity so I wanted to get some feedback. The tl;dr is that the initial My solution is this: the child This seems cleaner than the current implementation but obviously is more complex. Also some people have strong reactions when I'm going to implement this fully with the current stat reloader and post a message to the developers list to gather feedback I think. Integrating watchman also needs this ticket to be fixed to be much use I think, as the |
8f96562
to
05dd33a
Compare
I've pushed what I've got so far. It works, but I'm not sure I like it. The original code, while being hacky, was quite elegant. This approach is more complex for sure. In any case I'll start working on adding watchman integration next. |
I've added initial support for watchman! It was actually easier than I thought - I took a bit of code from flask to handle finding the appropriate roots to listen on which sped things up a lot. It's not done yet - it will trigger a reload for any change under the roots it listens on, but it's good as a proof of concept. Until watchman push a new |
568b2d5
to
dbedf5f
Compare
I'm interested in continuing to work on this. What's the state of this PR? |
Hey,
I've started a new job and so progress has stalled on this PR. I've got
some good feedback on this PR on the mailing list, so there are things to
be done. I've set time aside from the second week of November to work on
his, but if you'd like to collaborate I'd love the help.
The first step I think would be to refactor the current implementation to
make it a bit more Pythonic.
|
I've had a look at your implementation and I found it complicated (loads of subprocesses and threads etc). But may the problem is simply very complicated. I wonder if an approach with a completely separated autoreloader (like hupper) wouldn't be simpler. I also wonder if we couldn't simply re-use large parts of werkzeugs autoreloader. Anyways, I rebased to current master which you can find here: https://github.com/jonashaag/django/tree/autoreloader |
So yeah, unfortunately this did get overly complicated. I wanted to run with the ideas I had to completion and see how they would work out. The main issue is that on some systems registering a file watcher can be quite expensive. I wanted to have the parent django process manage this, and the child one that actually runs the application notify the parent of which files to watch. This brings in a lot of complexity, I guess it could be avoided by just registering/un-registering the fs notifications in the child process, just as flask does. I think there is much both projects could share. It seems really minor, but one thing I would really like to do is to open the socket in the parent (reloader) process and pass it into the child one. This means refreshing the page as the child is reloading will not result in a |
dbedf5f
to
3ea9163
Compare
a59571e
to
f92aad9
Compare
I shelved the complex multi-process branch and reverted to the simpler threading based approach we currently use. I've added support for Watchman and the current polling approach, and cleaned up the autoreloader.py file a lot. You can try it out with I'll add tests and documentation next, any advice with testing approaches for this would be greatly appreciated. |
b8e33fe
to
74135a8
Compare
364872a
to
6623bd2
Compare
6623bd2
to
aaf56d8
Compare
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.
Should I install watchman from source on the CI machines? docs/internals/contributing/writing-code/unit-tests.html
"Runnig all the tests" also needs an update.
Perhaps the release notes should mention the removal of pyinotify support?
|
||
|
||
def watch_for_translation_changes(sender, **kwargs): | ||
from django.conf import settings |
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.
Some docstrings for these methods could be useful.
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 added a docstring, I was not sure how detailed to be.
I also removed the watching of built-in Django .mo
files, there are a lot of them and I do not think there is any value in continually polling them.
aaf56d8
to
08ce861
Compare
Thank you @timgraham, I've addressed your comments. Regarding the CI: I think you have to install it from source. I think we should install it on a single runner first (the sqlite one?) and get that working before you spend time rolling it out everywhere? Also I think that the daemon will be automatically started by the pywatchman library as long as the binary is available on the system path (it is on MacOS), which might make admin easier. There are a few configuration options that might be worth setting, specifically for a CI environment. The GC times could be turned down from the default of 12 hours: https://facebook.github.io/watchman/docs/config.html#gc_age_seconds The inotify limits may also have to be tuned: https://facebook.github.io/watchman/docs/install.html#linux-inotify-limits |
@orf Do you mention in the docs / release notes that inotify is no longer supported? |
I installed watchman on my machine and the tests are working except:
Does it work for you? I did nothing to "ensure watchman is running" so I'll amend the documentation as I make my edits. |
Ah no, it does not work for me. It seems that the mock is incorrect (as is
the one below it), it's not mocking the instance method. Changing the mock
to be `client().capabilityCheck.side_effect` seems to do the trick.
|
90651a1
to
3e014e8
Compare
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 pushed my edits.
cb33552
to
a34d6ef
Compare
a34d6ef
to
43c1431
Compare
@orf Thanks for the great work here |
Found a bug with infinite reloading.
and it goes to infinite reloading loop (I haven't changed files) |
Please reports bugs with a Trac ticket. I guess we may need more details to understand the problem and reproduce the issue. |
Hey @ArtemBernatskyy, are you able to create a ticket on https://code.djangoproject.com/query? There is a |
@orf @timgraham , damn, I can't reproduce it ( If somehow I'll be able to reproduce it I'll create a ticket. |
Perhaps it's nothing, but 2 different paths to the same file, maybe? |
@kezabelle no it's me ) I've edited the log a little bit. Fixed now. |
Ticket
The ticket is about adding support for watchman to the autoreloader, but a lot of Aymeric's points rang true - especially about a complete rewrite rather than a bit-by-bit refactor. There are a few other related tickets to do with the autoreloader that would require some re-engineering before they could be got right.
This MR does a big cleanup of the autoreloader code, which ends up to be half the size, be (i hope) a lot less hairy and have some interesting features.
Firstly, it implements two signals (
autoreload_started
andfile_changed
) that allow other parts of Django and perhaps even third party apps to customize what files are watched and offer custom handing when a file is changed. This is used to implement thei18n
translations reset code, which currently lives in the autoreload code (and IMO really should not). Users of the signal are given the auto-reload instance which has awatch
method, which accepts aglob
argument, e.g:autoreloader.watch('some_directory/', '**/*.html')
Secondly the autoreload code is split into two classes, a
BaseReloader
and a concreteStatReloader
. In the future this can be used to add support for watchman or any other algorithm for detecting changes - it just needs to implement ayield_changes
function that yields the paths of files that have changed.It also changes the behaviour slightly: the current implementation replaces
.pyc
files with.py
, but I'm not sure this is still valid. It assumes that.py
files live next to.pyc
files, which in Python 3 and__pycache__
directories may not be true. So I removed the code that handled that, as well as Jython-specific stuff.It doesn't currently include support for catching SyntaxErrors, which I kept out in case anyone had a good idea of how to do a clean implementation of it. The current code (to quote Aymeric) is 'horrific'. It also doesn't include the
ensure_echo_on
code, which was a ticket that was added a long time ago. It has no tests and perhaps it's not required any more?