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

Usage of @Initializer(after = InitMilestone.COMPLETED) in plugins breaks the initialization logic

    • Icon: Bug Bug
    • Resolution: Fixed
    • Icon: Critical Critical
    • core
    • jenkins-2.7.3-rc

      The problem is in the Initializer code:

      /**
           * Indicates that the specified milestone is necessary before executing this initializer.
           *
           * <p>
           * This has the identical purpose as {@link #requires()}, but it's separated to allow better type-safety
           * when using {@link InitMilestone} as a requirement (since enum member definitions need to be constant.)
           */
          InitMilestone after() default STARTED;
      
          /**
           * Indicates that this initializer is a necessary step before achieving the specified milestone.
           *
           * <p>
           * This has the identical purpose as {@link #attains()}. See {@link #after()} for why there are two things
           * to achieve the same goal.
           */
          InitMilestone before() default COMPLETED;
      

      If an initializer overrides <code>after</code> as COMPLETED, hence we end up in the situation when (<code>before == after == COMPLETED</code>). In such case Jenkins' reactor can never succeed in the canRun() check, and finally we do not invoke "Initialization completed" milestone since the code requires the dependency to be executed. So we get into chicken-and-egg

      Sample script for the issue analysis:

      import hudson.init.InitMilestone; 
      import hudson.util.HttpResponses;
      import javax.servlet.http.HttpServletResponse;
      
      final Jenkins jenkins = Jenkins.getInstance();
      
      if (jenkins.getInitLevel().compareTo(InitMilestone.COMPLETED) < 0) {
        println "Error: Jenkins initialization has not reached the COMPLETED state. Current state is ${jenkins.getInitLevel()}"
        return HttpResponses.status(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
      }
      

      Examples:

      Proposed changes:

      • Add injected test, which verifies the startup InitMilestone of Jenkins
      • Make Jenkins robust against wrong definitions
      • Finally add support of Initializers after COMPLETED by introducing a transient milestone

          [JENKINS-37759] Usage of @Initializer(after = InitMilestone.COMPLETED) in plugins breaks the initialization logic

          Oleg Nenashev created issue -
          Oleg Nenashev made changes -
          Issue Type Original: Task [ 3 ] New: Bug [ 1 ]
          Oleg Nenashev made changes -
          Labels New: lts-candidate
          Ryan Campbell made changes -
          Remote Link New: This issue links to "PR (Web Link)" [ 14776 ]
          Oleg Nenashev made changes -
          Link New: This issue is related to JENKINS-37780 [ JENKINS-37780 ]
          Oleg Nenashev made changes -
          Link New: This issue is related to JENKINS-37772 [ JENKINS-37772 ]
          Oleg Nenashev made changes -
          Link New: This issue is related to JENKINS-37805 [ JENKINS-37805 ]
          Oleg Nenashev made changes -
          Priority Original: Blocker [ 1 ] New: Major [ 3 ]
          Oleg Nenashev made changes -
          Summary Original: After startup of the instance with many jobs1, the setup does not end up in the COMPLETED milestone New: Usage of @Initializer(after = InitMilestone.COMPLETED) in plugins breaks the initialization logic
          Oleg Nenashev made changes -
          Description Original: It happens due to https://github.com/jenkinsci/jenkins/pull/2177/files, which makes COMPLETED dependent on the following logic:

          {code}
                      // All plugins are loaded. Now we can figure out who depends on who.
                       requires(PLUGINS_PREPARED).attains(COMPLETED).add("Resolving Dependant Plugins Graph", new Executable() {
                           @Override
                           public void run(Reactor reactor) throws Exception {
                               resolveDependantPlugins();
                           }
                       });
          {code}

          In this case COMPLETED state logic does not depend on the Job loading state, hence COMPLETED milestone can be achieved before JOB_LOADED in the reactor. It may easily happen when Job loading takes MUCH time. In such case COMPLETED state gets overridden by JOB_LOADED.

          Sample script for the issue analysis:
          {code}
          import hudson.init.InitMilestone;
          import hudson.util.HttpResponses;
          import javax.servlet.http.HttpServletResponse;

          final Jenkins jenkins = Jenkins.getInstance();

          if (jenkins.getInitLevel().compareTo(InitMilestone.COMPLETED) < 0) {
            println "Error: Jenkins initialization has not reached the COMPLETED state. Current state is ${jenkins.getInitLevel()}"
            return HttpResponses.status(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
          }
          {code}
          New:

          The problem is in the Initializer code:

          {code}
          /**
               * Indicates that the specified milestone is necessary before executing this initializer.
               *
               * <p>
               * This has the identical purpose as {@link #requires()}, but it's separated to allow better type-safety
               * when using {@link InitMilestone} as a requirement (since enum member definitions need to be constant.)
               */
              InitMilestone after() default STARTED;

              /**
               * Indicates that this initializer is a necessary step before achieving the specified milestone.
               *
               * <p>
               * This has the identical purpose as {@link #attains()}. See {@link #after()} for why there are two things
               * to achieve the same goal.
               */
              InitMilestone before() default COMPLETED;
          {code}

          If an initializer overrides <code>after</code> as COMPLETED, hence we end up in the situation when (<code>before == after == COMPLETED</code>). In such case Jenkins' reactor can never succeed in the canRun() check, and finally we do not invoke "Initialization completed" milestone since the code requires the dependency to be executed. So we get into chicken-and-egg

          Sample script for the issue analysis:
          {code}
          import hudson.init.InitMilestone;
          import hudson.util.HttpResponses;
          import javax.servlet.http.HttpServletResponse;

          final Jenkins jenkins = Jenkins.getInstance();

          if (jenkins.getInitLevel().compareTo(InitMilestone.COMPLETED) < 0) {
            println "Error: Jenkins initialization has not reached the COMPLETED state. Current state is ${jenkins.getInitLevel()}"
            return HttpResponses.status(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
          }
          {code}

          Examples:
          * https://github.com/jenkinsci/extreme-notification-plugin/blob/0249acced3d5483841e06849a4481c47a7e28fe5/src/main/java/org/jenkinsci/plugins/extremenotification/ServerStateListener.java#L51 (JENKINS-37780)
          * https://github.com/jenkinsci/support-core-plugin/blob/3cc53c1af1d2332bf5c6396987fe1516880b81a8/src/main/java/com/cloudbees/jenkins/support/SupportPlugin.java#L409 (JENKINS-37772)

          Proposed changes:
          * Add injected test, which verifies the startup InitMilestone of Jenkins
          * Make Jenkins robust against wrong definitions
          * Finally add support of Initializers after COMPLETED by introducing a transient milestone

            oleg_nenashev Oleg Nenashev
            oleg_nenashev Oleg Nenashev
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: