-
Notifications
You must be signed in to change notification settings - Fork 242
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-37984] Matrix throws "Method code too large! error" on #355
Conversation
...efinition/src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/model/Matrix.groovy
Outdated
Show resolved
Hide resolved
...in/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/parser/RuntimeASTTransformer.groovy
Outdated
Show resolved
Hide resolved
...definition/src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/model/Stage.groovy
Outdated
Show resolved
Hide resolved
...in/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/parser/RuntimeASTTransformer.groovy
Outdated
Show resolved
Hide resolved
...in/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/parser/RuntimeASTTransformer.groovy
Outdated
Show resolved
Hide resolved
...in/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/parser/RuntimeASTTransformer.groovy
Outdated
Show resolved
Hide resolved
916d980
to
da8cb47
Compare
da8cb47
to
8d5869c
Compare
59981e5
to
b696894
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.
I think the failing tests are because the new classes/methods you are generating are not being CPS-transformed, causing the sleep
steps to not work correctly in the tests, but I'm not totally sure.
...on/src/main/resources/org/jenkinsci/plugins/pipeline/modeldefinition/ModelInterpreter.groovy
Show resolved
Hide resolved
@@ -837,7 +899,7 @@ class RuntimeASTTransformer { | |||
// TODO: Do I need to create a new ModelASTStage each time? I don't think so. | |||
String name = "Matrix - " + cellLabels.join(", ") | |||
|
|||
return ctorX(ClassHelper.make(Stage.class), | |||
return ctorXFunction(ClassHelper.make(Stage.class), |
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 wonder how this affects variable resolution for stuff inside of the matrix, for example if someone defines a Groovy method at the top of the Jenkinsfile (not recommended, but it happens), now that the matrix stages are generated inside of a static method in a separate class, will those methods still be resolvable?
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.
Good question. Tried it manually and it worked.
@abayer
Is there a test for this scenario? (The only down side to extensive testing it not know where all the tests are.)
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 are tests for this and they caught the problem but I'm not sure that they cover all the cases.
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.
FWIW, I think it would be a good idea to add a system property/public static non-final variable to disable the new behavior and force the old code path in case it causes issues for existing Declarative users in scenarios that aren't covered by tests. Something like this in RuntimeASTTransformer
:
@SuppressFBWarnings(warningId="the real code for the non-final static vars are bad warning", justification="For access from script console")
public static boolean FORCE_SINGLE_EXPRESSION_TRANSFORMATION = Boolean.getBoolean(RuntimeASTTransformer.class.getName() + ".FORCE_SINGLE_EXPRESSION_TRANSFORMATION");
And then inside of ctorXFunction
and friends you look at FORCE_SINGLE_EXPRESSION_TRANSFORMATION
(or whatever you want to call it) and fall back to ctorX
if it is 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.
@dwnusbaum
Solid idea. Done.
The resolve closures in the way pipeline requires we had to move to instanciated classes.
61b0657
to
f8623da
Compare
...n/src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/RuntimeContainerBase.groovy
Outdated
Show resolved
Hide resolved
4c09c2d
to
03a0b12
Compare
f93597b
to
39af42c
Compare
39af42c
to
88bfcd5
Compare
CpsScript is written in java. For some reason writing RuntimeContainerBase in groovy resulted in build error that I couldn't track down to fix. Moving to Java made it go away.
e301242
to
cfc65f0
Compare
...model-definition/src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/Utils.groovy
Outdated
Show resolved
Hide resolved
d25e449
to
2ac878b
Compare
Turned of a few tests that overlapped and switched another to ensure matrix still works with script splitting
This was #174, for reference.
Bear in mind that I only dimly understand what this PR is even doing, or how the original code works for that matter. My understanding is that (hand-waiving here) you are taking a Declarative script and transforming it into an actual executable program, and that in this PR some of the code blocks are being moved to separate JVM classes without otherwise changing behavior. So the straightforward way to do something like that in Pipeline script (without making any reference to def somethingLong() {
// dozens of lines here
}
somethingLong() with, say, def somethingLong = evaluate '''
{ ->
// dozens of lines here
}
'''
somethingLong() so that the closure gets compiled into a separate class, something like (Admittedly the above still makes for a long constant entry, but in the actual problem here, the “dozens of lines” would be computed at runtime somehow.)
It is hard to say, but by directly using this type from I note that none of the newly introduced tests seem to use I understand that there are plenty of votes for this issue, but a lot of them presumably apply to users of Scripted, who will not be helped by this. Are there really so many people with massive Declarative scripts that trigger the limit to justify such a high-risk change? |
On the other hand, this plugin is already horribly intertwined with |
The way this was found was during |
d663606
to
0c0bb40
Compare
Most tests will still run with it turned on, but customers will have to enable it manually.
0c0bb40
to
60e2ff0
Compare
@jglick However, on reviewing with @dwnusbaum I ran into another issue. When pausing the generated classes are serialized. When they are deserialized, I don't believe they retain the reference to the current My question: is there an event or other api that I can hook into get a reference to the current instance of |
Because of the exponential combination of axes I suppose? This plugin is generating code (like a
Not that I know of. Normally if a class needs a reference to the root script (and it might not), it is either held as a strong reference and thus serialized as part of the program state (recall that Java serialization, and JBoss too, handles cycles), or passed in as an argument to particular methods. IIRC both are documented idioms when using Bear in mind that I can just barely follow how program resumption works in Scripted when using nothing out of the ordinary (for example, only stock sandbox calls and steps). If you are talking about programmatically generating classes, I cannot make any promises. |
Oops, forgot that cycles are handled, sorry for the misleading info yesterday @bitwiseman. I guess in that case, going by the in-progress code you showed me yesterday, I would make the field that holds the script non-transient again and see if that works (not sure if you are saying you already tried that and it didn't work, or something else). |
Well, it does when this change is not enabled. With this it can reuse items from variables.
Ah, of, course, why didn't I think of that. Yay! |
6eb232f
to
a19bee2
Compare
This eliminates the only security concern for this change.
a19bee2
to
6c6b201
Compare
Built and tested locally:
|
// With script splitting stagesExpression is a method that creates a new stages expression at runtime | ||
// If disabled, we still want to make this a script closure call so we can reuse it instead of generating code for every cell | ||
if (!SCRIPT_SPLITTING_TRANSFORMATION) { | ||
stagesExpression = wrapper.asWrappedScriptContextVariable(stagesExpression, 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.
@jglick Per your comment, this forces the use of a single expression (method or a closure), which is then called for each cell instead of generating code for each cell.
* | ||
* @author Liam Newman | ||
*/ | ||
public class RuntimeContainerBase extends Script implements Serializable { |
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.
@jglick @dwnusbaum
Now extends Script
instead of CpsScript
. Yay!
86e4833
to
00eafd5
Compare
Not using wrapping in this case is just too inefficient
In some change along the way, I made asScriptContextVariable return an external method instead. Now that it is fixed, I can use less closure wrapping than before.
00eafd5
to
0304624
Compare
There are 3 main parts/goal for this PR:
It generally achieves all these goals for both matrix and non-matrix scenarios, with the caveat that when
def
variables are present it falls back to using more closures making it less effective at avoiding "Method code too large" errors.Tests have been added to cover the new scenarios, but not all of them can run in CI and not time out.