code: private void initialize()
      {
          SqlStageExecution rootStage = stages.get(rootStageId);
          rootStage.addStateChangeListener(state -> {
              if (state == FINISHED) {
                  queryStateMachine.transitionToFinishing();
              }
              else if (state == CANCELED) {
                  // output stage was canceled
                  queryStateMachine.transitionToCanceled();
              }
          });
  
          for (SqlStageExecution stage : stages.values()) {
              stage.addStateChangeListener(state -> {
                  if (queryStateMachine.isDone()) {
                      return;
                  }
                  if (state == FAILED) {
                      queryStateMachine.transitionToFailed(stage.getStageInfo().getFailureCause().toException());
                  }
                  else if (state == ABORTED) {
                      // this should never happen, since abort can only be triggered in query clean up after the query is finished
                      queryStateMachine.transitionToFailed(new PrestoException(GENERIC_INTERNAL_ERROR, "Query stage was aborted"));
                  }
                  else if (state == FINISHED) {
                      List<SqlStageExecution> nextStagesToSchedule = getStagesReadyForExecution();
                      if (!nextStagesToSchedule.isEmpty()) {
                          startScheduling(nextStagesToSchedule);
                      }
                  }
                  else if (queryStateMachine.getQueryState() == QueryState.STARTING) {
                      // if the stage has at least one task, we are running
                      if (stage.hasTasks()) {
                          queryStateMachine.transitionToRunning();
                      }
                  }
              });
          }
  
          // when query is done or any time a stage completes, attempt to transition query to "final query info ready"
          queryStateMachine.addStateChangeListener(newState -> {
              if (newState.isDone()) {
                  queryStateMachine.updateQueryInfo(Optional.ofNullable(getStageInfo()));
              }
          });
          for (SqlStageExecution stage : stages.values()) {
              stage.addFinalStageInfoListener(status -> queryStateMachine.updateQueryInfo(Optional.ofNullable(getStageInfo())));
          }
      }
docs: this is a separate method to ensure that the `this` reference is not leaked during construction