-
-
Notifications
You must be signed in to change notification settings - Fork 9k
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
[JENKINS-75232] Prevent dynamic plugin installation from registering the same extension twice in some cases #10240
base: master
Are you sure you want to change the base?
Conversation
…the same extension twice in some cases Co-authored-by: Julien Greffe <[email protected]>
@Restricted(NoExternalUse.class) | ||
public boolean refresh(ExtensionComponentSet delta) { |
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 an incompatible API change, but given the Javadoc, there really should not be any external callers, and a quick GitHub search (I also checked the cloudbees
org) looked ok.
If desired, I can instead introduce a new method and preserve the old one for compatibility. We might still want to add @Restricted(NoExternalUse.class)
to the old method if we take that approach though.
// Refresh all extension lists before firing any listeners in case a listener would cause any new extension | ||
// lists to be forcibly loaded, which may lead to duplicate entries for the same extension object in a list. | ||
for (var el : listsToFireOnChangeListeners) { | ||
el.fireOnChangeListeners(); |
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 am also happy to consider alternative fixes if anyone has any ideas. One thing I considered was to instead check for duplicates in
l.addAll(found); |
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.
Duplicate prevention added in 6817532 to fix issues with extensions that load other extensions in their constructors.
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.
Source code looks fine. Recommend trying another approach for test plugin maintenance.
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.
Whilst this fixes the test case, I can't help but feel this doesn't fix the case where an Extension
loads another extension (either in its constructor, or a Thread it kicks off).
The listener causing a load of an extension by proxy of calling some code, would just be one trigger of the wider cause here?
The suggested alternative of checking for duplicates and not using addAll would seem sane, however IIUC we would create 2 objects for the same extension (not the return the same Object). So if the extension is kicking off something async in its constructor things have already got into a bad state (and the plugin is already liable highly suspect to race conditions)?
Iff we are only creating a single extension for the class but registering it twice I think the addAll
fix would be more robust
Ok, I can check this case.
As far as I can tell, right now we really are only creating one object, just registering it twice. I'll recheck though. |
if that is the case it may be worthwhile doing both.
|
@jtnord Indeed this case is still broken. I confirmed that in the problematic cases the extension is only instantiated once but registered twice. I'll add duplicate prevention to |
it's a shame that |
Please take a moment and address the merge conflicts of your pull request. Thanks! |
Yeah, in retrospect it seems that having this class actually be a collection was not really necessary. If we had instead only defined collection-like methods to covered the critical use cases, I would feel more comfortable refactoring the internal representation. |
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 reasonable, did not pay much attention to the test code.
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.
Main code change is opaque to me, but test looks nice.
smells like a regression at least in test code? |
Please take a moment and address the merge conflicts of your pull request. Thanks! |
Ah, yes, |
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 were quite a few tests that were installing variant
dynamically. I tried to fix the failing test and clean things up in 99b8be8.
*/ | ||
@Issue("JENKINS-60449") | ||
@WithPlugin("variant.hpi") | ||
@Test public void installDependedOptionalPluginWithoutRestart() throws Exception { |
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 into ExtensionListRjrTest.installDependedOptionalPluginWithoutRestart
.
Really, most of the tests here should probably be migrated to use RealJenkinsRule
and synthetic plugins, but there are a lot of them.
// | ||
@Issue("JENKINS-50336") | ||
@Test | ||
public void optionalExtensionCanBeFoundAfterDynamicLoadOfVariant() throws Exception { |
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.
Also moved into ExtensionListRjrTest.installDependedOptionalPluginWithoutRestart
.
I did make some changes though to the test to make it more closely match the scenario described in JENKINS-50336 (also so that I could delete variant.hpi
).
@@ -139,7 +139,7 @@ public void enablePluginWithRestart() throws IOException { | |||
@Issue("JENKINS-52950") | |||
public void enableNoPluginsWithRestartIsNoOp() { | |||
assumeNotWindows(); | |||
String name = "variant"; | |||
String name = "icon-shim"; |
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.
Just swapping for another no-op plugin that is already here. Probably all of these tests should be using RealJenkinsRule
and synthetic plugins though.
@Issue({ "JENKINS-50336", "JENKINS-60449" }) | ||
public void installDependedOptionalPluginWithoutRestart() throws Throwable { | ||
var optionalDependerJpi = rjr.createSyntheticPlugin(new SyntheticPlugin(OptionalDepender.class.getPackage()) | ||
.header("Plugin-Dependencies", "variant:0,dependee:0;resolution:=optional")); |
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.
A lot easier to follow the test, not to say edit it!
@@ -190,11 +190,10 @@ public void disableAlreadyDisabledPluginNotRestart() throws Exception { | |||
@Ignore("TODO calling restart seems to break Surefire") |
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.
Clearly could stand to be rewritten.
I think the |
likely FWIW this PR triggered narrowing down a bit jenkinsci/avatar-plugin#18. I've tested and this PR doesn't resolve it though, but seems to be another issue related to dynamic loading. |
See JENKINS-75232. The relevant
ExtensionList
code has not changed since it was introduced in #1673, but in the real-world case I saw the listener that is causing the issues comes from #3496.This PR requires jenkinsci/jenkins-test-harness#920 for the new test.
Testing done
A new automated test has been created to demonstrate the bug and test the fix. @jgreffe and I also tested the fix against the real-world plugin which triggered the bug (a proprietary CloudBees plugin).
Proposed changelog entries
Proposed upgrade guidelines
N/A
Submitter checklist
Desired reviewers
Before the changes are marked as
ready-for-merge
:Maintainer checklist