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
Pub/sub for the left panel #5669
Pub/sub for the left panel #5669
Conversation
Codecov Report
@@ Coverage Diff @@
## master #5669 +/- ##
==========================================
+ Coverage 45.13% 45.15% +0.02%
==========================================
Files 652 652
Lines 49433 49453 +20
Branches 6576 6573 -3
==========================================
+ Hits 22310 22332 +22
- Misses 25862 25864 +2
+ Partials 1261 1257 -4 |
Only browsed the code and see no breaking changes. I have been busy lately, will try to look closer in the next days. I tried this rebased on master and when pressing the refresh button the UI is frozen for like 30s.. The first discussion should probably be if this is the correct refactoring. Assuming so we need to convince @RussKie that this is an acceptable change for master. Hint: He is soft on tests. I have no suggestion really what to test though. There are really no tests on left side panel or submodule provider right now. |
I'll rebase and push asap. 30 seconds? That's weird. I wonder if it's the increased async contention for the UI thread. Or maybe you refreshed while an update was in flight, which would cause cancellation. Though I tested that and it seemed to perform fine. Was it the GE repo? Edit: rebased and pushed. Also, I've been trying to repro the long refresh, but have not been able to. I'm running a Release build without VS debugger attached. Were you testing Debug with VS attached? ?Maybe it's slower with VS's exception hooks. Edit 2: Okay, I saw the slowdown compared to the previous version. Was not 30s for me, but sometimes around 5s. I compared to the previous code and there was a difference: formerly, LoadNodesAsync was awaited with ConfigureAwait(false), while in my version, I had not configured the awaiter. I added this, and now it runs much faster. If I understand correctly, ConfigureAwait(false) means we don't care about being on the same thread once the async function completes. In this case, we do want to get back on the UI thread, but I guess it's faster to |
6514241
to
171bbc3
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.
Quick review in the browser but it looks good to me, modulo comments.
{ | ||
TreeViewNode.NodeFont = new Font(AppSettings.Font, FontStyle.Bold); | ||
TreeViewNode.NodeFont = new Font(AppSettings.Font, IsActive ? FontStyle.Bold : default); |
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.
Consider caching Font
objects behind some logic that creates new instances if AppSettings.Font
changes. Then you can select between two fonts based on IsActive
and do a ReferenceEquals
check before calling the NodeFont
setter (if that setter doesn't itself deal with setting to the current value).
Also, Font
is disposable.
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 makes sense, but I think perhaps should be tackled in a separate change? The reason I modified this code is that this fixes a bug with branch deletion where recycled TreeViewNodes wouldn't clear their bolded state.
uiCommands.UICommands.PostRepositoryChanged += UICommands_PostRepositoryChanged; | ||
}; | ||
|
||
uiCommands.UICommands.PostRepositoryChanged += UICommands_PostRepositoryChanged; |
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.
Do we have to unsubscribe UICommandsChanged
and PostRepositoryChanged
at any point? Is this a leak?
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 is the same pattern FormBrowse follows. The only leak is when the application is closed, but otherwise this makes sure to remove and add whenever the UICommands object has changed.
await loadNodesTask(token).ConfigureAwait(false); | ||
|
||
token.ThrowIfCancellationRequested(); | ||
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(token); |
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.
If you remove ConfigureAwait(false)
here you should continue on the main thread, in which case this line is not necessary.
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.
Yeah, so what you said is what I thought, and my original push didn't have the ConfigureAwait(false). But then @gerhardol said he had a 30s stall when refreshing, and when I tested, I had some 5s stalls here and there. Comparing against the original code, I found that the await LoadNodesAsync would ConfigureAwait(false), despite joining on the UI thread right after. I tried it, and indeed it seems to no longer stall for that long.
I was wondering if perhaps ConfigureAwait(false) is also used as a hint to the task manager to run the task on a non-UI thread; or conversely, not having the ConfigureAwait(false) may make it more likely to run on the UI thread, which might cause thread contention, and thus bad performance? I don't know, I'm fairly new to async-await in C# (as as aside, I think it's really cool).
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.
Yes ConfigureAwait(false)
tells the awaiter to not capture the current SynchronizationContext
(which in this case would be WinFormsSynchronizationContext
) which has the effect of scheduling the continuation on the thread pool rather than the UI thread.
Removing ConfigureAwait
it can highlight other threading issues. Possibly loadNodesTask
is trying to do something on the UI thread. If you pause into the debugger when you have one of these slowdowns and look at the "parallel stacks" view of Visual Studio, you'll likely see what's going on.
{ | ||
TreeViewNode.TreeView.Nodes[0].EnsureVisible(); | ||
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); |
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.
Ditto.
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.
So here, I was trying to handle if an exception gets thrown by loadNodesTask without ConfigureAwait(false), which I think means I might end up in the finally block on a non-UI thread, right? If we do decide to go for ConfigureAwait(false) (pending discussion on that above), then I can remove this SwitchToMainThreadAsync.
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 assumed that if you removed ConfigureAwait(false)
then all code would remain on the UI thread and you could avoid both switches.
ClearNodes(); | ||
|
||
await LoadNodesAsync(token).ConfigureAwait(false); | ||
ThreadHelper.ThrowIfNotOnUIThread(); |
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.
+1 for avoiding await
here to get on the UI thread.
@@ -100,13 +100,47 @@ private abstract class Tree | |||
protected readonly Nodes Nodes; | |||
private readonly IGitUICommandsSource _uiCommandsSource; | |||
|
|||
protected readonly CancellationTokenSequence _reloadCancellation = new CancellationTokenSequence(); |
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.
Nit: protected fields should have capital first letter. The name is a little confusing too. Recommend going long with ReloadCancellationTokenSequence
or ReloadCancellationSequence
.
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.
Will do on both counts.
GitUI/GitUICommands.cs
Outdated
} | ||
finally | ||
{ | ||
form.Dispose(); |
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.
Would using (form)
be simpler here? Neighbours tend to use using
statements.
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 didn't think I could use using (form) here without allocating it. If so, I'll do that for sure.
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.
using (value)
is a valid alternative to using (declaration)
.
The form that accepts a declaration is a special case. Usually a declaration (var a = 1
) doesn't produce a value (it's not an expression). An assignment does though, so you can write var i = (j = 0)
and i
will be zero too.
I was wrong, it was not 30s, it was more. After the change it takes 1:30 to refresh. I have 110 local branches and 10 remotes. |
Thank you @gerhardol, I was able to repro the hang locally by creating a hierarchy of many branches. I've got something now that I can debug. I think I know what's going on, actually. My feeling is that this is because I no longer Begin/EndUpdate on the control, so it's constantly trying to render all the nodes that are being created. |
@gerhardol Okay, so adding Begin/EndUpdate seems to do the trick for the perf and OOM exceptions. Can you try now? I still need to go through and make the changes @RussKie asked for. Also, I noticed a font bold bug that I thought came from my changes, but actually was introduced from this change. Basically it's possible now for branch folders to be bolded. I'll open an issue about it, or just fix it, but separate from this PR. |
Works fine for me after the change. |
Pushed another commit based on @RussKie's review comments. |
Pushed another commit where I revert a font change I made, since it actually didn't fix the main issue. Instead, I have a proper fix on another PR here: #5686 Edit: I'll be happy to squash this all down once it's approved. |
@RussKie I made the changes you requested. I know you're busy with v3, so whenever you have some time, could you review this again? Cheers. |
ff2555f
to
6cec3e2
Compare
@RussKie @gerhardol (Edit: sorry, it was @drewnoakes, not @RussKie, who reviewed this code. Meant to add him, but you're welcome too, @RussKie :) ) Hey guys, I just wanted to clarify that this PR is necessary for my submodules ROT work. I've got a branch on my repo based on top of this one for submodules ROT, and I've been using it at work for the past week with no issue. I know you've both looked at this already; I've made the changes requested, and would just need another review, and hopefully a merge into master. After that, I can update the submodules PR with new commits. Sorry, don't mean to sound pushy. If you're super busy, I get it. Just wanted to make sure this didn't slip through the cracks. Cheers! |
They are working on the release process. I wouldn't be surprised if they
held off and waited for the next release.
…On Fri, Nov 9, 2018, 12:07 AM Antonio Maiorano ***@***.***> wrote:
@RussKie <https://github.com/RussKie> @gerhardol
<https://github.com/gerhardol>
Hey guys,
I just wanted to clarify that this PR is necessary for my submodules ROT
work. I've got a branch on my repo
<https://github.com/amaiorano/gitextensions/tree/amaiorano/left-pane-submodules-pubsub>
based on top of this one for submodules ROT, and I've been using it at work
for the past week with no issue. I know you've both looked at this already;
I've made the changes requested, and would just need another review, and
hopefully a merge into master. After that, I can update the submodules PR
<#5617> with new
commits.
Sorry, don't mean to sound pushy. If you're super busy, I get it. Just
wanted to make sure this didn't slip through the cracks. Cheers!
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#5669 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ADdhsQmpv-cg3FRSezr1Lb163zhcK0fTks5utQ2GgaJpZM4YE0T->
.
|
/facepalm I just realized the recent release was an RC. For some reason, I thought it was the official v3 release. Okay, that makes sense. Thanks for clarifying. |
Sorry, no major work will be merged into master right now except fixes. We're in a freeze period, if you like. |
Yep, my bad. Will wait till post release. Thanks. |
6cec3e2
to
b784480
Compare
b784480
to
aeb633d
Compare
I think you did find a legitimate problem with my change. |
Okay, I just pushed a commit that improves node selection and top-level node expansion. As I wrote in my commit comment:
This feels much better. When you open a repo, the Branches and Remotes node will initially expand, while Tags will be collapsed. However, after that, it will respect whatever state you put them in, thanks to the save/restore expanded nodes thing I added not too long ago. Also, if you open a repo that has no active branch, like submodules which typically are in detached head, Branches will be expanded initially thanks to my change here, but no node will be selected, which is how it worked before. Give this is a whirl. |
e84ad36
to
2699726
Compare
Ahh.... Initially I thought it was a bug, and almost raised an issue. |
If you think it shouldn't work this way, we can go back, but it means that if you decide to collapse Branches and Remotes, and the left panel gets refreshed, they will be re-expanded always. I think it makes more sense to only initially expand them, then respect the user's settings during the session. One thing I noticed, though, is that if you switch repos, it will not see that as a "first time", so if you collapsed Branches, then switch repo, it will remain collapsed. We could change that behaviour, which might make more sense. |
I don't mind that collapsed state is persistent for a given repo, but when a repo is changed I would think we should reset the state. |
I agree. I'll see what I can do about that. |
I would have preferred to never expand automatically at all, or just expand to selection only, but am OK with this. |
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 comments at all, which seem strange. Will likely approve tomorrow when I have let this spin another day.
Well, if it makes you feel better, I've been working on this change for a long time. Also, @drewnoakes did review it a while ago. |
I've pushed a fixup commit which makes it so that changing repos effectively resets the state of the treeview nodes, so Branches/Remotes will start off expanded, and Tags collapsed. I think this last change makes more sense, since I found it a little weird that because of how node recycling works, and with my change to save/restore expanded nodes, upon switching repos, if the target repo happened to have similar node paths, their expanded state would be restored, even though it was from a different repo. |
Please squash the fixup before the merge |
Of course. Using fixup commits during PRs as a way to apply requested changes, but without messing up the final set of commits that will be merged, is something I only recently started doing, and I love it. It allows for reviewers to see the new changes as separate commits, but once merged, we don't see "Apply fixes from PR" commits in the graph. Maybe it's just my OCD :) |
…) registers for callbacks and handles its own update, instead of being told to reload
* Only expand/collapse the top nodes (Branches, Remotes, Tags) the first time we've populated them. Subsequently, the expanded/collapsed state that the user sets will be restored upon refresh. * Expand Branches node the first time. * If there's no active branch (i.e. detached head), as before, don't select any node.
1e8773e
to
9975780
Compare
I love them too. I use them to help me rebase all the time too, Autosquash is a great thing. |
Also really like fixup/squash commits while WIP. I would occasionally forget to squash and merge PRs with them in. There are some in the GE history (you're welcome). That was the motivation for adding warning icons in the revision grid alongside commits with messages starting with |
Aha! For that, I thank you :) |
LOL! I figured. |
This is work based off the conversations from this other PR: #5617
Changes proposed in this pull request:
Rework RepoObjectsTree (ROT) so that instead of being "push" updated from FormBrowse via ROT.ReloadAsync, the Tree nodes (Branches, Remotes, and Tags) now "pull" for changes via UICommands.PostRepositoryChanged (i.e. "pubsub").
This refactor will allow for the Submodules tree work proposed in the other PR to be done more easily. In fact, I have a local commit on top of these changes for this, and it works very well now.
Screenshots before and after (if PR changes UI):
What did I do to test the code and ensure quality:
This change removes the "push" update from FormBrowse, so I identified the places that used to perform this push, and made sure to address them, and test them. Namely:
Apart from that, I tested:
Has been tested on (remove any that don't apply):