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
Issue 3320 - Plugin registration phase #5776
Conversation
Codecov Report
@@ Coverage Diff @@
## master #5776 +/- ##
==========================================
+ Coverage 43.14% 43.89% +0.75%
==========================================
Files 638 638
Lines 48501 48522 +21
Branches 6487 6490 +3
==========================================
+ Hits 20924 21301 +377
+ Misses 26427 25999 -428
- Partials 1150 1222 +72 |
GitUI/CommandsDialogs/FormBrowse.cs
Outdated
@@ -657,6 +658,8 @@ private void UnregisterPlugins() | |||
{ | |||
plugin.Unregister(UICommands); | |||
} | |||
|
|||
PluginRegistry.ArePluginsRegistered = 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.
At this point the app is closed - FormBrowse
is the main form, when it is closed the app is done.
I think we have to solve first the technical debt we have regarding to plugins. I don't think it is a good idea to scan for plugins each time the commit dialog is created (on its own as a separate process). The scanning is taking too long. |
vs-mef currently isn't caching the component catalog. Once it is, scanning is just a matter of loading a small file with the pre-scanned results. |
@jbialobr thank you for your thoughts. The first part re: re-scanning - whilst it is certainly an performance issue, I personally wouldn't consider it as a release blocker. We can address it in subsequent maintenance releases, especially if we could enable the vs-mef caching. The second part is interesting.
Is it easy to enable? |
You made me to measure it. Running For me it is a substantial decrease in performance (read: a blocker).
Of course you are not. But the current design is bad. I am against adding more mess here. Spreading plugin registration logic through all the app is a code smell. From one hand we have a global variable |
@jbialobr Is there a way I can help? |
Sure. Could you try to replace MEF with https://github.com/Microsoft/vs-mef and report us how the load time has changed? |
Yes. The hard part is knowing when to delete the cache and scan again. |
Ok, I'm going to try and let you know. |
@jbialobr Sorry, I didn't realize I never submitted the change to use vs-mef. I'll send a PR. |
😄 It happened to me recently as well. We were talking about performance and I realized that I had never merged one optimization into the production. I redirect your apology to @maraf - I hope that you have at least learned something new if you have started working on this. |
It's ok, I haven't started yet. |
📝 I submitted #5788 |
Thank you @sharwell. I have merged your PR into this PR and I have noticed ~1 second improvement in the load time. It is still 2 seconds worse than running |
GitUI/Plugin/PluginRegistry.cs
Outdated
/// Initialises all available plugins on the background thread. | ||
/// </summary> | ||
/// <param name="postInitialiseAsync">A function to execute once plugins are loaded.</param> | ||
public static Task InitializeAsync(Func<Task> postInitialiseAsync) |
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.
💡 Passing continuations via a callback argument reminds me of the days before async/await. It would be better to reorganize this code to avoid the need for this form.
GitUI/Plugin/PluginRegistry.cs
Outdated
} | ||
|
||
#pragma warning disable VSTHRD105 // Avoid method overloads that assume TaskScheduler.Current |
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 should be fixed rather than suppressed. You can make the containing method asynchronous, and then simply put the flag initialization in a finally
block.
GitUI/GitUICommands.cs
Outdated
{ | ||
form.ShowDialog(owner); | ||
PluginRegistry.Plugins.ForEach(p => p.Unregister(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.
💡 It seems like PluginRegistry
would be responsible for this action.
GitUI/GitUICommands.cs
Outdated
await PluginRegistry.InitializeAsync(() => | ||
{ | ||
// this will only execute, if start without the main form | ||
PluginRegistry.Plugins.ForEach(p => p.Register(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.
💡 It seems like PluginRegistry
would be responsible for this action.
I have updated code based on the code review. About performance, is there a way to cache whether exists plugins interested in commit dialog? I'm not sure whats wrong with Appveyor, any thoughts? |
Looks like a rebase on master will be needed, because of the conflicting file. Conflicting files |
} | ||
catch | ||
{ | ||
// no-op |
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.
❓ Why?
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 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 a plugin fails to load we don't want to crash the app because of it.
} | ||
PluginRegistry.Initialize(); | ||
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); | ||
RegisterPlugins(); |
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 seems we have a race condition, where the dialog can be closed before plugin registration completes. What are we expecting to happen in this case?
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.
FormBrowse
is the main form, if it closed the process is finished, so it probably doesn't matter at this point...
I'd like to resurrect this PR and move on with it. @sharwell I see a number of your comments are now outdated, it looks like your feedback has been addressed. |
So there is going to be an issue depending on where plugins are stored and what is loaded and how. The issue is going to be assembly binding issues. I have been fighting such an issue in the main problem and getting it squared away. Example couple of our plugins used a different package that provided a different version of a tfs library. It was the same file path. That one was easier because that nuget package was unlisted so I found the official package. With a system like this, it is going to be quite easy for a plugin to require newtosoft json 11 or some other dependency. So we need to use app domains or some other means to make sure plugin dependencies don't combat with our dependencies. Why I am putting this here. The plugin registry might keep track of the needed dependencies or help in this regard. |
👍 However what are the chances that a user would need to have all 3 tfs plugins at the same time? And I fully appreciate that various plugins may use different versions of same assembly (i.e. newtonsoft). @maraf is it possible to install plugins in own directories to avoid version conflicts? |
This is a massive understatement. I learned some hard lessons on this in the past. The safest assumption in the current workflow is that all external extensions will require recompilation for every build of Git Extensions they want to work with (i.e. patch updates to the host will break all extensions until they are updated). Plugins that want to support multiple host versions will need one assembly for each supported version.
This is going to be difficult, since IIUC it won't be a working solution when we execute Git Extensions as a .NET Core 3.0 app. |
So is it similar to the VS extension love-hate relationships - a new version of an extension must be released for a new version of VS? If so, I am not totally opposed to this model. |
Do we have any other alternatives? I'd like to remove plugins from the main codebase. |
The tfs was a way to showcase the issue. Since they were different packages, automatic binding didn't help. The same issue could happen. Don't focus on multiple versions of tfs plugins, which is POSSIBLE to do. Focus on the issue I am pointing out. |
@drewnoakes @sharwell Since you seem to be in at least the right area and could put forth some feedback in the right spot possibly. Nuget needs a file output verification. Package X adds xyz.dll |
@RussKie Plugins can totally be installed in its own directories, its minor change in plugin manager. |
@maraf great. So it would be
|
For VS extensions, things don't break as often:
|
Would the version issue be (largely) addressed if we do something like the following?
|
@RussKie Yes, provided a strict versioning policy is applied to the interfaces assembly (no binary breaking changes) and the host application provides a binding redirect for it. |
I'm merging this as is, however the discussion above is duly noted and we assume a certain tech debt which needs to be addressed going forward. Items to work through include the following:
Until then external plugins may have to be recompiled for every new version of GE. |
Thank you gentlemen, your work and input are highly appreciated. |
@maraf do we need to re-enable the PM bundling? |
@RussKie I don't think so. This PR was not related to PM, AFAIK. I needed it for one of my plugins. |
This is WIP on #3320.
I need plugins to be registered even when GitExtensions are started with arguments to start other dialog than browse.
If this approach is ok, I will extend this PR also for other dialogs.
Yet I'm considering moving original plugin registration from FormBrowse into GitUICommands and let only menu items binding there. Thoughts?