public void breakCycleGraph() throws AmbariException{
    List<String> edges = new ArrayList<>();
    for (String role : graph.keySet()){
      RoleGraphNode fromNode = graph.get(role);
      String fnRole = fromNode.getRole().name();
      String fnCommand = fromNode.getCommand().name();

      Iterator<RoleGraphNode> it = fromNode.getEdges().iterator();
      while(it.hasNext()){
        RoleGraphNode toNode = it.next();
        String tnRole = toNode.getRole().name();
        String tnCommand = toNode.getCommand().name();
        //Check if the reversed edge exists in the list already
        //If the edit exists, print an error message and break the edge
        String format = "%s:%s --> %s:%s";
        String edge = String.format(format, fnRole, fnCommand, tnRole, tnCommand);
        String reversedEdge = String.format(format, tnRole, tnCommand, fnRole, fnCommand);
        if (edges.contains(reversedEdge)){
          String msg = String.format(
              "Circular dependencies detected between %s and %s for %s. "
              + "%s already exists in the role command order.", fnRole, tnRole, edge, reversedEdge);
          LOG.error(msg);
          throw new AmbariException(msg);
        } else {
          edges.add(edge);
        }
      }
    }
  }
Cycle graphs indicate circular dependencies such as the following example
that can cause Ambari enter an infinite loop while building stages.
  (DATANODE, START, 2) --> (NAMENODE, START, 2) --> (SECONDARY_NAMENODE, START, 3)
  (HDFS_CLIENT, INSTALL, 0) --> (DATANODE, START, 2) --> (NAMENODE, START, 2) --> (SECONDARY_NAMENODE, START, 3)
  (NAMENODE, START, 2) --> (DATANODE, START, 2) --> (SECONDARY_NAMENODE, START, 3)
  (SECONDARY_NAMENODE, START, 3)
It is important to safe guard against cycle graphs,
when Ambari supports mpacks, custom services and service level role command order.
ambari-server/src/main/java/org/apache/ambari/server/stageplanner/RoleGraph.java