Skip to content
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

FormBrowse init optimizations #9729

Merged
merged 1 commit into from Nov 22, 2021

Conversation

gerhardol
Copy link
Member

@gerhardol gerhardol commented Nov 14, 2021

Proposed changes

@RussKie has refactored FormBrowse init and filter handling recently.
After this handling some logic like InternalInitialize() was running several times.
This included a few Git commands too, delaying the startup slightly.
There are some test instabilities for FormBrowse, this is an attempt to at least improve investigation.

I initially got som stability issues with this change when testing in isolation but this works better when restructuring commits.
'Loading Revisions' didn't finish in 100 iterations can still occur

Test methodology

There are tests for this already

Merge strategy

Rebase merge


✒️ I contribute this code under The Developer Certificate of Origin.

Copy link
Member Author

@gerhardol gerhardol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some explanations to changes

@@ -447,11 +448,9 @@ protected override void OnLoad(EventArgs e)
{
_formBrowseMenus.CreateToolbarsMenus(ToolStripMain, ToolStripFilters, ToolStripScripts);

HideVariableMainMenuItems();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Run from SetGitModule(), always run before opening module or dashboard so no explicit call needed.

RefreshSplitViewLayout();
LayoutRevisionInfo();
SetSplitterPositions();
InternalInitialize(false);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method has repo specific setup and runs from RefreshRevisions()
Previously needed here as it must run after RevisionGrid.ForceRefreshRevisions()


// We're launching the main form, and whilst the module has been set for us we still need to formally switch to it
SetGitModule(this, new GitModuleEventArgs(new GitModule(Module.WorkingDir)));

RevisionGrid.ResumeRefreshRevisions();
RefreshRevisions();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, RevisionGrid.ResumeRefreshRevisions() would run RevisionGrid.ForceRefreshRevisions() only, so some other methods in RefreshRevisions() had to run in OnLoad()

@@ -572,22 +570,33 @@ private void RefreshRevisions()
return;
}

_gitStatusMonitor.InvalidateGitWorkingDirectoryStatus();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Run from SetGitModule()

GitUI/CommandsDialogs/FormBrowse.cs Show resolved Hide resolved
{
revisionDiff.RefreshArtificial();
RevisionGrid.ForceRefreshRevisions();
RevisionGrid.IndexWatcher.Reset();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runs in RevisionGrid.ForceRefreshRevisions()


InternalInitialize();

RefreshGitStatusMonitor();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let the separate git-status calls run after the UI is refreshed

@@ -1800,7 +1810,6 @@ private void SetGitModule(object sender, GitModuleEventArgs e)

// If we're applying custom branch or revision filters - reset them
RevisionGrid.DisableFilters();
SetPathFilter(pathFilter: null);
Copy link
Member Author

@gerhardol gerhardol Nov 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This triggers a Grid refresh, so this must not occur (see also comments for the commit).

Maybe the CanRefresh check in
Maybe add RevisionGrid.SuspendRefreshRevisions()/RevisionGrid.ResumeRefreshRevisions() in GitSetModule to permanently avoid this as the refresh is done in UICommands_PostRepositoryChanged()

Edit: Changed this to use RevisionGrid.SuspendRefreshRevisions()/RevisionGrid.ResumeRefreshRevisions() in GitSetModule and Debug.Assert in RequestRefresh.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is likely because of the changes to ResumeRefreshRevisions

@@ -572,22 +570,33 @@ private void RefreshRevisions()
return;
}

_gitStatusMonitor.InvalidateGitWorkingDirectoryStatus();
RequestRefresh();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to RefreshGitStatusMonitor

GitUI/CommandsDialogs/FormBrowse.cs Show resolved Hide resolved
if (_updatingFilters == 0)
{
ForceRefreshRevisions();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coupled with above comment I'm finding this change the most puzzling. This was meant to guard against nested unbalanced suspensions (i.e., without a resume call), and execute a refresh only on the outer scope.
Now it feels like we may be returning into a realm where we may be calling multiple refreshes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above, the refresh is always called explicitly after Suspend/Resume.

}

toolStripButtonPush.DisplayAheadBehindInformation(Module.GetSelectedBranch());
Debug.Assert(RevisionGrid.CanRefresh, "Already loading revisions when running RefreshRevisions(). This could cause the commits in the grid to be loaded several times.");
RevisionGrid.ForceRefreshRevisions();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 Generally speaking we should strive to avoid calling this API.

Copy link
Member

@RussKie RussKie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general the changes look sensible and positive, however I am not quite content with the changes to ResumeRefreshRevisions - I spent a considerable time checking various code paths (primarily in the context of filters) to reduce redundant refreshes.

@RussKie RussKie added the 📭 needs: author feedback More info/confirmation awaited from OP; issues typically get closed after 30 days of inactivity label Nov 18, 2021
@gerhardol
Copy link
Member Author

This PR is required to avoid that the left panel is init twice in #9734, and for #9735 that can save seconds opening a repo.
(After #9735 the tests seem to be more stable too.)

In general the changes look sensible and positive, however I am not quite content with the changes to ResumeRefreshRevisions - I spent a considerable time checking various code paths (primarily in the context of filters) to reduce redundant refreshes.

I also have spent considerable time debugging and trying to understand what the intention was. The handling was not easy before, but the changed handling does not go all the way. I tried to explain the changes in my review, another try here:
The major change is to handle startup in the same way as a repo change, remove the special handling.

The handling after GE has started once is not changed much, just some adjustments.

SetGitModule() -> PostRepositoryChanged() -> RefreshRevisions()
This will then make all repo related changes including RevisionGrid.EnableRefreshRevisions().

The startup had another path:

FormBrowse() DisableGuard; init all (including some repo related like init left panel as in #9734), SetGitModule() etc except RevisionGrid.EnableRefreshRevisions(), EnableGuard that triggers RevisionGrid.EnableRefreshRevisions().
A few repo related then runs (again) in OnLoad() to get the init correctly. Especially InternalInitialize() does repo related setup.

This PR changes the start:

FormBrowse() DisableGuard; init avoiding repo related, EnableGuard then explicit SetGitModule() that will run repo related similar to at a repo switch, including RevisionGrid.EnableRefreshRevisions().
SetGitModule() has the guard internally too. Before #9733 RevisionGrid.EnableRefreshRevisions() was actually run before PostRepositoryChanged() (so in a tiny repo the revisions could have been refreshed twice).
OnLoad() has no repo related.

This change was probably not enough, SetGitModule() should be called from OnLoad() instead.

RevisionGrid.ResumeRefreshRevisions(); should maybe be renamed to RevisionGrid.EnableRefreshRevisions();

The naming was inspired by SuspendLayout and ResumeLayout. There's also a PerformLayout and I've been thinking maybe renaming ForceRefreshRevisions into PerformRefreshRevisions... 🤔

These are now pure guards, the refresh is always done in post repo change.

All refresh is done from SetGitModule() -> PostRepoChanged

Not quite. There are quite a few calls to ForceRefreshRevisions throughout, e.g. applying various filters, and this mechanism guarded against chained refreshes.

All revision reading related to repo changes is now in PostRepositoryChanged().
There are of course refresh in other situations too. If there happen when updating the repo in SetGitModule(), they are now ignored as there will be an explicit call.

@ghost ghost removed the 📭 needs: author feedback More info/confirmation awaited from OP; issues typically get closed after 30 days of inactivity label Nov 19, 2021
@RussKie
Copy link
Member

RussKie commented Nov 21, 2021

I ran it locally with few different filters, and I couldn't observer any reentrancy I was worried about. So 👍 from me.
Proposed few API tweaks to compliment the change.

public bool CanRefresh => !_isRefreshingRevisions && _updatingFilters == 0;

/// <summary>
/// Queries git for the new set of revisions, and refreshed the grid.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename is of course fine, will just change this wording.

Suggested change
/// Queries git for the new set of revisions, and refreshed the grid.
/// Queries git for the new set of revisions and refreshes the grid.

OK to squash and merge?
I would like to get #9735 ready to merge, that is both improving startup a lot and seem to improve test stability.

@@ -582,7 +582,7 @@ private void RefreshRevisions()
}

Debug.Assert(RevisionGrid.CanRefresh, "Already loading revisions when running RefreshRevisions(). This could cause the commits in the grid to be loaded several times.");
RevisionGrid.ForceRefreshRevisions();
RevisionGrid.RefreshRevisions();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must be PerformRefreshRevisions() (that must be public again.

Reorganize the startup so also first startup runs all repo related operations
in PostRepositoryChanged. Before some init operations had to run
several times at the startup.
@gerhardol gerhardol merged commit 46a0256 into gitextensions:master Nov 22, 2021
@ghost ghost added this to the vNext milestone Nov 22, 2021
@gerhardol gerhardol deleted the feature/i9553-browse-init branch November 22, 2021 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants