Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-26034

Option to interrupt remaining branches when one branch fails

    • Icon: Improvement Improvement
    • Resolution: Fixed
    • Icon: Major Major
    • pipeline
    • None
    • workflow plugin 1.1

      As recorded in https://groups.google.com/d/msg/jenkinsci-dev/kgEc7vZQgi0/IC1rH_6KwNYJ:

      Our deploy process currently builds deploy artifacts and runs tests at the same time. I see how we could implement that using workflow via the 'parallel' step. However, we also want the behavior that if and when build-artifacts fails, it immediately cancels the running tests and fails the deploy job; and likewise if the tests fail, it immediately cancels the building of artifacts and fails the deploy job. If both succeed, then the deploy job continues.

      This is a feature request to implement this sibling-cancel: with a flag (or perhaps as the default, though that's kinda scary), if one of the jobs in parallel() fails, then parallel cancels the other jobs.

      It sounds like parallel returns a map from job-label to job-status. I think under this behavior, the job-status would be "failed" for the job that failed, and "cancelled" for the sibling jobs that were cancelled.

          [JENKINS-26034] Option to interrupt remaining branches when one branch fails

          Craig Silverstein created issue -

          Jesse Glick added a comment -

          It sounds like parallel returns a map from job-label to job-status.

          Currently to branch return value (not a status per se), but see my comment in JENKINS-26033.

          the job-status would be "failed" for the job that failed, and "cancelled" for the sibling jobs that were cancelled.

          There is only one “job”, the workflow as a whole, a build of which is marked as a failure if any of the parallel branches fail—unless you have a try/catch block outside parallel which does something else.

          Jesse Glick added a comment - It sounds like parallel returns a map from job-label to job-status. Currently to branch return value (not a status per se), but see my comment in JENKINS-26033 . the job-status would be "failed" for the job that failed, and "cancelled" for the sibling jobs that were cancelled. There is only one “job”, the workflow as a whole, a build of which is marked as a failure if any of the parallel branches fail—unless you have a try / catch block outside parallel which does something else.
          Jesse Glick made changes -
          Assignee Original: Jesse Glick [ jglick ] New: Kohsuke Kawaguchi [ kohsuke ]

          Sorry, I knew I'd get the terminology wrong!

          It sounds from JENKINS-26033 that you're leaning towards not having a (meaningful) return value from 'branch'. So if I want to record the fact a particular branch failed, I'd have to swallow the exception and rethrow (to cause the sibling-cancel) – at least, I assume the rethrow would cause the sibling-cancel.

          Just thinking out loud about the following use-case: I want to run two tasks in parallel. If either one fails, I want to cancel the other, and then continue executing my job. How would I do that in a world where 'cancel_siblings' was an option to 'parallel'. I guess it would be something like this:

          def one_failed, two_failed;
          try {
             parallel one: {
                try {
                    ...
                } catch (x) {
                    one_failed = true
                    // Need to rethrow to cause parallel to cancel job two
                    rethrow             // I assume this is a thing?
                }
             }, two: {
                try {
                    ...
                } catch (x) {
                    two_failed = true
                    rethrow
                }
             }
          } catch (x) {
          }
          
          ... continue on, looking at one_failed and two_failed if needed ...
          

          It's a little messy but I believe it works. I doubt this is a super-common case, so as long as it's possible I think this design works for our needs, at least.

          Craig Silverstein added a comment - Sorry, I knew I'd get the terminology wrong! It sounds from JENKINS-26033 that you're leaning towards not having a (meaningful) return value from 'branch'. So if I want to record the fact a particular branch failed, I'd have to swallow the exception and rethrow (to cause the sibling-cancel) – at least, I assume the rethrow would cause the sibling-cancel. Just thinking out loud about the following use-case: I want to run two tasks in parallel. If either one fails, I want to cancel the other, and then continue executing my job. How would I do that in a world where 'cancel_siblings' was an option to 'parallel'. I guess it would be something like this: def one_failed, two_failed; try { parallel one: { try { ... } catch (x) { one_failed = true // Need to rethrow to cause parallel to cancel job two rethrow // I assume this is a thing? } }, two: { try { ... } catch (x) { two_failed = true rethrow } } } catch (x) { } ... continue on, looking at one_failed and two_failed if needed ... It's a little messy but I believe it works. I doubt this is a super-common case, so as long as it's possible I think this design works for our needs, at least.

          Jesse Glick added a comment -

          if I want to record the fact a particular branch failed

          You mean, beyond the build log and any flow graph visualizations that show this information, you want to base some decision for the remainder of the flow on which part failed, rather than having the whole build abort then and there. You could do something like

          def oneErr, twoErr
          try {
            parallel one: {
              try {
                …
              } catch (x) {
                oneErr = x
                throw x // or anything for that matter
              }
            }, two: {
              try {
                …
              } catch (x) {
                twoErr = x
                throw x
              }
            }
          } catch (x) {
            if (oneErr) {
              …
            } else if (twoErr) {
              …
            } else {
              // something else went wrong, die
              throw x
            }
          }
          

          Perhaps not the most concise idiom, but the purpose is probably apparent to anyone reading it, and it is not hard to see how you could customize some aspects of the behavior.

          Jesse Glick added a comment - if I want to record the fact a particular branch failed You mean, beyond the build log and any flow graph visualizations that show this information, you want to base some decision for the remainder of the flow on which part failed, rather than having the whole build abort then and there. You could do something like def oneErr, twoErr try { parallel one: { try { … } catch (x) { oneErr = x throw x // or anything for that matter } }, two: { try { … } catch (x) { twoErr = x throw x } } } catch (x) { if (oneErr) { … } else if (twoErr) { … } else { // something else went wrong, die throw x } } Perhaps not the most concise idiom, but the purpose is probably apparent to anyone reading it, and it is not hard to see how you could customize some aspects of the behavior.
          Jesse Glick made changes -
          Link New: This issue is related to JENKINS-26052 [ JENKINS-26052 ]
          Jesse Glick made changes -
          Link New: This issue is related to JENKINS-26033 [ JENKINS-26033 ]

          Right, looks like we came up with the exact same approach! Works for me.

          Craig Silverstein added a comment - Right, looks like we came up with the exact same approach! Works for me.
          Jesse Glick made changes -
          Priority Original: Minor [ 4 ] New: Major [ 3 ]
          Summary Original: Add support for sibling-cancel in parallel(). New: Option to interrupt remaining branches when one branch fails
          James Nord made changes -
          Assignee Original: Kohsuke Kawaguchi [ kohsuke ] New: James Nord [ teilo ]

            Unassigned Unassigned
            csilvers Craig Silverstein
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated:
              Resolved: