Can resumed CI reuse successful matrix configurations?
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 42/100
- Loại issue
- Tính năng
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- groovy
- Lĩnh vực
- build-system, ci-cd
Hướng nghiên cứu
Bắt đầu với jenkins/scripts/VersionSelectorScript.groovy và node-test-commit-linux matrix-selection script, sau đó lần theo MultiJobResumeControl và hành vi tiếp tục của matrix trong Jenkins. Xác thực opt-in được đề xuất dựa trên các trường hợp thành công, thất bại, tiếp tục lại nhiều lần, siêu dữ liệu không đúng định dạng và tất cả đều bị bỏ qua đã được ghi chép; hoàn tất có nghĩa là các cấu hình thành công được tái sử dụng mà không thay đổi các job không opt-in.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Refs: https://openjs-foundation.slack.com/archives/C03BJP63CH0/p1787917001257859
I do not know Jenkins well enough to be confident about the implementation details. I asked an LLM to inspect the relevant build metadata, job configuration, and plugin sources. This is how I understand its suggestion.
Resume already seems to work between Multijob children. For example, node-test-commit #91372 reused the same builds for the twelve successful direct children from #91368.
The gap seems to be inside matrix jobs.
This uses node-test-commit-linux as a worked example. The intended scope is eventually all matrix jobs under node-test-commit, after checking their configurations individually.
node-test-commit-linux #72543 had ten configurations. Eight succeeded, alpine-last-latest-x64 was UNSTABLE, and rhel8-x64 was FAILURE. Its resume, #72548, ran all ten configurations again.
AFAICT, Multijob sees node-test-commit-linux as one unsuccessful child. It starts a new matrix parent, whose selector then selects every eligible configuration without considering the individual results from the build being resumed.
The suggested change is to extend VersionSelectorScript.groovy. Multijob already passes the previous child build through MultiJobResumeControl, so the selector could follow that lineage and exclude configurations whose newest exact result was SUCCESS.
The selector is also used by jobs outside the normal node-test-commit hierarchy. The proposed behavior is therefore opt-in and defaults to off. The example enables it for node-test-commit-linux first; the other matrix jobs under node-test-commit could opt in after being checked.
Suggested VersionSelectorScript.groovy diff
diff --git a/jenkins/scripts/VersionSelectorScript.groovy b/jenkins/scripts/VersionSelectorScript.groovy
--- a/jenkins/scripts/VersionSelectorScript.groovy
+++ b/jenkins/scripts/VersionSelectorScript.groovy
@@ -97,26 +97,109 @@
// NOTE: this assumes that the default "Agents"->"Name" in the Configuration
// Matrix is left as "nodes", if it's changed then `it.nodes` below won't work
// and returning a result with a "nodes" property won't work.
result['nodes'] = []
// Before running this script, `def buildType = 'release'` or some other value
// to be able to use the appropriate `buildType` in the exclusions
def _buildType
try {
_buildType = buildType
} catch (groovy.lang.MissingPropertyException e) {
_buildType = 'test'
}
combinations.each{
def builderLabel = it.nodes
// Default to running all builders if nodeMajorVersion is still -1
// (i.e. the version check failed)
if (nodeMajorVersion >= 4) {
if (!canBuild(nodeMajorVersion, builderLabel, _buildType)) {
println "Skipping $builderLabel for Node.js $nodeMajorVersion"
return
}
}
result['nodes'].add(it)
}
+
+// This is opt-in because this script is shared by jobs outside
+// node-test-commit.
+if (!(binding.hasVariable('reuseSuccessfulConfigurations') &&
+ binding.getVariable('reuseSuccessfulConfigurations') == true))
+ return
+
+// A resumed Multijob child carries the MatrixBuild it is resuming.
+// Avoid importing the action class because this script is evaluated by a
+// plugin whose class loader does not declare a Multijob dependency.
+def resumeControlClassName =
+ 'com.tikal.jenkins.plugins.multijob.MultiJobResumeControl'
+def currentBuild = execution.build
+
+try {
+ def resumeControl = currentBuild.actions.find {
+ it.class.name == resumeControlClassName
+ }
+ def resumeSource = resumeControl?.run
+ def previousBuild = resumeSource
+ def latestResults = [:]
+ def seenBuilds = [] as Set
+
+ // Follow the complete resume lineage. getRuns() is deliberately not used:
+ // it may inherit a run from an unrelated matrix build which ran in between.
+ while (previousBuild != null) {
+ if (previousBuild.class.name != 'hudson.matrix.MatrixBuild' ||
+ previousBuild.parent != currentBuild.parent) {
+ throw new IllegalStateException(
+ "Unexpected resumed build ${previousBuild.externalizableId}")
+ }
+
+ if (!seenBuilds.add(previousBuild.externalizableId)) {
+ throw new IllegalStateException(
+ "Cycle in resume lineage at ${previousBuild.externalizableId}")
+ }
+
+ // The first exact run found for a combination is its newest result.
+ previousBuild.exactRuns.each { run ->
+ def combination = run.parent.combination
+ if (!latestResults.containsKey(combination))
+ latestResults[combination] = run.result
+ }
+
+ resumeControl = previousBuild.actions.find {
+ it.class.name == resumeControlClassName
+ }
+ previousBuild = resumeControl?.run
+ }
+
+ def successfulResumeCombinations = [] as Set
+ latestResults.each { combination, previousResult ->
+ if (previousResult?.toString() == 'SUCCESS')
+ successfulResumeCombinations.add(combination)
+ }
+
+ // Apply this after the normal version and MACHINES selection.
+ // If it would select no work, rerun the eligible set so a failure outside
+ // the individual cells cannot become a no-op success.
+ def eligibleCombinations = result['nodes']
+ def combinationsToRun = eligibleCombinations.findAll {
+ !successfulResumeCombinations.contains(it)
+ }
+ int skippedCount =
+ eligibleCombinations.size() - combinationsToRun.size()
+
+ if (skippedCount > 0) {
+ if (combinationsToRun.isEmpty()) {
+ println 'Resume would skip every eligible combination; running all'
+ } else {
+ println "Resume: reusing ${skippedCount} successful combination(s)"
+ // Otherwise Jenkins defaults to the immediately preceding matrix build,
+ // which may be unrelated to this resume.
+ currentBuild.setBaseBuild(resumeSource)
+ result['nodes'] = combinationsToRun
+ }
+ }
+} catch (Exception e) {
+ // Keep the normal selection on malformed or unexpected resume metadata.
+ println "Resume metadata could not be read; running all eligible " +
+ "combinations (${e.class.name}: ${e.message})"
+}
The node-test-commit-linux matrix-selection script would initially opt in with:
buildType = 'test'
+reuseSuccessfulConfigurations = true
evaluate(code)
My understanding of the proposed behavior is:
- the existing version and
MACHINESselection still runs first - jobs which do not opt in retain their current behavior
- only exact
SUCCESSresults are reused UNSTABLE,FAILURE,ABORTED,NOT_BUILT, and missing configurations run again- repeated resumes follow the explicit resume lineage
- malformed or unexpected resume metadata leaves the existing full selection unchanged
- if every configuration would be skipped, the eligible set runs again in case the matrix parent failed outside an individual configuration
The setBaseBuild() call is apparently needed so Jenkins inherits results from the matrix build actually being resumed, rather than whichever unrelated matrix build happened to run immediately before it.
Does this match how the matrix jobs are intended to resume? In particular:
- Is
VersionSelectorScript.groovythe right place to carry the existing Multijob resume behavior into matrix configurations? - Is making the behavior opt-in appropriate given the selector's other consumers?
- If it works as expected for Linux, should the other matrix jobs under
node-test-commitopt in as well?
- Ngôn ngữ chính
- Jinja
- Star
- 541
- Fork
- 185
- Merge trung bình
- 2 ngày 18 giờ
- Pull request đã merge (30 ngày)
- 6
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của nodejs/build
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
-
platform:ppc
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 65/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
-
incident platform:arm
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 35/100
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 45/100
Issue tương tự
-
core dependencies
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 80/100
-
bug github_actions
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
registrystack/registry-stack#1393 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
-
Name consistency Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
eellak/triplestore#65 · 1 bình luận ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100