-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Add debounce support to daemon hot reload requests #55376
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
Conversation
|
Sorry, it took me a while to get to this. Overall I love the idea of removing delays around hot reload. In terms of the implementation, doing in the daemon seems like the most reasonable approach, since ultimately this is only required for IDE consumption. In terms of the debounce mechanics - why exactly do we need to debounce instead of say, queue? Is it due to multiple events created from saveAll actions? |
Yep! Unfortunately in VS Code we don't have any "Save All" event (request is at microsoft/vscode#86087) so our only options are to either debounce on the client (which we do now) or sent multiple events to Flutter (which we could do if we move this here). My thinking is that by moving it here:
|
|
@jonahwilliams discussing reload times reminded me of this - having the debounce here would allow removing it from the editors so you have more control (though this does make Flutter responsible for merging multiple saves from a Save All from an editor like VS code that doesn't have an event for this). |
|
Lets do it! |
|
@jonahwilliams Ok cool! Do you want to scan over the approach here and see if it seems reasonable? It needs some more tests, though I'm not sure how to handle them without adding these delays in the test (since what we're testing is timebased, and an integration tests so we can't easily interfere with the passing of time). |
|
Yeah, I'll set aside some time tomorrow to do a deep dive on this. For testing with timeouts, probably a combination of real integration tests as well as fake async. |
|
Hey, from the IntelliJ perspective:
With that change, IntelliJ has two usage patterns:
For both usage patterns, IntelliJ will be modifying source files on disk and issuing an immediate hot reload request, w/ no artificial delay. If flutter tools relies solely on file watching to know about changed files, we'll likely have a race condition between when it gets a reload request and when it knows about changed files. We may want to either 1) have flutter tools poll for file changes before performing a hot reload 2) have flutter tools be in charge of an (100ms?) artificial delay before it performs a hot reload, in order to collect any file watching events. |
From some brief trial and error locally, we may not actually have a race condition, or if we do, we're always wining it. I always see my changes active on reload even without an artificial delay. |
|
We don't use file watching in the tool, instead preferring to manually check timestamps after a request. Timestamp granuality isn't great on windows, but as long as the is a ms or two of delay between saving and the request it should be fine |
|
|
Well, it seemed plausible (the original case was #32573 btw), but apparently FAT is cleverer(?) than I, as it always rounds up: https://devblogs.microsoft.com/oldnewthing/20140903-00/?p=83 So probably not the source of that issue!
That's probably also not the current source, since both editors have had fairly large (> 100ms) built-in delays up until this point. Edit: Though this commenter has it only at the terminal (no artificial delay) and not from the editor, so maybe related? 🤷♂️ |
jonahwilliams
left a comment
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.
Generally like the idea. It would be nice if debounce logic was structured so that we could get unit test coverage of it.
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 think real delays are fine for an integration test
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 use Map.remove here and below
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.
Done! (it required adding unawaited() around it as it returns the item being removed which was a future).
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.
Could this be more easily represented as an enum?
enum OperationType {
reload,
restart,
...
}
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, done!
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.
When would thus be null?
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.
debounce? It won't be null, but could be false. Right now the client has to opt-in to this debounce behaviour, since we don't want to result in two lots of delays for existing clients that don't know about this. If they don't send debounce=true we'll execute the restart/reload immediately.
OK, good to know, and comforting that there aren't any race conditions. It looks like IntelliJ won't need debouncing for hot reload requests, so hopefully the debouncing can be an opt-in parameter for clients (or, the debounce period can be a parameter, and will default to 0ms). |
It's currently opt-in here. You have to pass
Yeah, I'll take a look and see what I can come up with. Sorry for the delay! |
|
@jonahwilliams to make it testable, I pulled the implementation out into its own class: I've written some tests and they pass when running real-time, but I'm stuck on making them work with FakeAsync (I think it's because of this behaviour -> https://stackoverflow.com/questions/62656200/why-does-this-test-using-fakeasync-just-hang-on-the-await-even-though-the-futu). If I can get that working properly, does it seem like a reasonable solution? |
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.
@jonahwilliams this is a workaround for the issue described #60675. I don't know if it's the best way, or if we should pull this out somewhere and make it more reusable for the other tests that might have this issue?
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 a handy feature, you could definitely put it into one of the common testing libraries.
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.
Moved it to test/src/common.dart since none of the others seemed appropriate, but lmk if somewhere else (or a new one) would be better. This might make it easy to fix #60675.
|
Pushed my changes into this PR, including a fix for the unit tests (unsure if there's a better one though). PTAL! |
|
I'm not sure what's up with the analyze bot: |
|
The path_provider_linux bug was fixed a few days ago, there was a bug in update_packages |
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 can remove reloadMethod - I'm going to be building this functionality into the regular hot reload instead
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.
That method is currently published in the docs so removing it may be a breaking change? (I don't know who would've used it though)
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 be surprised if anyone is using it, but it could be mapped to hot reload for backwards compat. Doesn't need to be solved in this PR.
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.
sgtm - I'll do in a separate PR once this one's done!
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 was taking a look at removing reloadMethod, but it looks like there is a lot of code relating to this (for ex. arguments passed to device.connect and VM service calls in vmservice.dart. It's not clear how much of this should go (whether it's just the daemon call, or everything related to it everywhere).
If it's easier for you to do this when you're building that functionality into regular hot reload, I'm happy to leave it for you 😄
|
|
||
| try { | ||
| await Future.wait<void>(<Future<void>>[ | ||
| _flutter.hotReload(debounce: 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.
there might be enough latency on CI that we don't hit the 50ms debounce. You could make it configurable (only for the purpose of testing)
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 code is interacting with a flutter run instance, so I don't think that would be easy.
Is your concern that we might see > 50ms between the two "simultaneous" reloads, or that we might see < 50ms between the first set and the second set? (I think we could address the second issue by chaining the 60ms delay onto the end of the initial reloads.. not sure about the first though).
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.
The former. It could be configurable via some parameter passed to flutter daemon that is hidden?
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've pushed changes that add an argument to override the debounce duration (it's not so much hidden as undocumented). I set the value to 250ms for the test, but let me know if you think it needs to be higher (I'm not sure what a good balance is between avoiding flakes and not making too many slow tests!).
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.
250 ms should be good enough for now, but if there are problems with flakiness we can always crank it higher
|
I'm temporarily committing with logging turned on to try and debug the failing test on Windows... It's failing in the opposite way to I'd expect from flakes... < 2 hot reloads rather than > 2! May be that |
|
I've removed the debug logging and increased the time between the reloads we want to be grouped together, and those that don't. Let me know if there's anything else, or if you're happy for me to land. Thanks! |
jonahwilliams
left a comment
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.
LGTM
This was testing that concurrent reloads were rejected. This is now allowed (as of #55376 (which has non-devicelab integration + unit tests).
This was testing that concurrent reloads were rejected. This is now allowed (as of flutter#55376 (which has non-devicelab integration + unit tests).
This was testing that concurrent reloads were rejected. This is now allowed (as of flutter#55376 (which has non-devicelab integration + unit tests).
Description
@devoncarew @jonahwilliams a while back, we discussed the idea of pushing debouncing reload requests into flutter_tool rather than editors doing it. Right now VS code is adding a 200ms debounce which can easily double the hot reload time (or worse).
By moving it into the tool we might be able to speed things up significantly (for example if always applied the first reload immediately, then queue/debounce subsequent ones.. this might result in 2 hot reloads for some Save All operations, but could also slash the time of the common single-file save case).
This PR doesn't immediately apply the first one, but does add debounce support (and reduce it to 50ms - though we may want to play with that) and support queueing (instead of just rejecting) and merging (if a request is queued, just return the same result).
It was a bit more complicated than I expected, because we can't merge requests of different types (reload, restart, reloadMethod).
This isn't fully tested yet, but I wanted to start a discussion about it before going too far. WDYT?
Tests
I added the following tests:
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]). This will ensure a smooth and quick review process.///).flutter analyze --flutter-repo) does not report any problems on my PR.Breaking Change
Did any tests fail when you ran them? Please read Handling breaking changes.