// // Copyright (C) 2012 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "update_engine/payload_generator/cycle_breaker.h" #include <inttypes.h> #include <limits> #include <set> #include <string> #include <utility> #include <base/stl_util.h> #include <base/strings/string_util.h> #include <base/strings/stringprintf.h> #include "update_engine/payload_generator/graph_utils.h" #include "update_engine/payload_generator/tarjan.h" using std::make_pair; using std::set; using std::vector; namespace chromeos_update_engine { // This is the outer function from the original paper. void CycleBreaker::BreakCycles(const Graph& graph, set<Edge>* out_cut_edges) { cut_edges_.clear(); // Make a copy, which we will modify by removing edges. Thus, in each // iteration subgraph_ is the current subgraph or the original with // vertices we desire. This variable was "A_K" in the original paper. subgraph_ = graph; // The paper calls for the "adjacency structure (i.e., graph) of // strong (-ly connected) component K with least vertex in subgraph // induced by {s, s + 1, ..., n}". // We arbitrarily order each vertex by its index in the graph. Thus, // each iteration, we are looking at the subgraph {s, s + 1, ..., n} // and looking for the strongly connected component with vertex s. TarjanAlgorithm tarjan; skipped_ops_ = 0; for (Graph::size_type i = 0; i < subgraph_.size(); i++) { InstallOperation::Type op_type = graph[i].aop.op.type(); if (op_type == InstallOperation::REPLACE || op_type == InstallOperation::REPLACE_BZ) { skipped_ops_++; continue; } if (i > 0) { // Erase node (i - 1) from subgraph_. First, erase what it points to subgraph_[i - 1].out_edges.clear(); // Now, erase any pointers to node (i - 1) for (Graph::size_type j = i; j < subgraph_.size(); j++) { subgraph_[j].out_edges.erase(i - 1); } } // Calculate SCC (strongly connected component) with vertex i. vector<Vertex::Index> component_indexes; tarjan.Execute(i, &subgraph_, &component_indexes); // Set subgraph edges for the components in the SCC. for (vector<Vertex::Index>::iterator it = component_indexes.begin(); it != component_indexes.end(); ++it) { subgraph_[*it].subgraph_edges.clear(); for (vector<Vertex::Index>::iterator jt = component_indexes.begin(); jt != component_indexes.end(); ++jt) { // If there's a link from *it -> *jt in the graph, // add a subgraph_ edge if (base::ContainsKey(subgraph_[*it].out_edges, *jt)) subgraph_[*it].subgraph_edges.insert(*jt); } } current_vertex_ = i; blocked_.clear(); blocked_.resize(subgraph_.size()); blocked_graph_.clear(); blocked_graph_.resize(subgraph_.size()); Circuit(current_vertex_, 0); } out_cut_edges->swap(cut_edges_); LOG(INFO) << "Cycle breaker skipped " << skipped_ops_ << " ops."; DCHECK(stack_.empty()); } static const size_t kMaxEdgesToConsider = 2; void CycleBreaker::HandleCircuit() { stack_.push_back(current_vertex_); CHECK_GE(stack_.size(), static_cast<vector<Vertex::Index>::size_type>(2)); Edge min_edge = make_pair(stack_[0], stack_[1]); uint64_t min_edge_weight = std::numeric_limits<uint64_t>::max(); size_t edges_considered = 0; for (vector<Vertex::Index>::const_iterator it = stack_.begin(); it != (stack_.end() - 1); ++it) { Edge edge = make_pair(*it, *(it + 1)); if (cut_edges_.find(edge) != cut_edges_.end()) { stack_.pop_back(); return; } uint64_t edge_weight = graph_utils::EdgeWeight(subgraph_, edge); if (edge_weight < min_edge_weight) { min_edge_weight = edge_weight; min_edge = edge; } edges_considered++; if (edges_considered == kMaxEdgesToConsider) break; } cut_edges_.insert(min_edge); stack_.pop_back(); } void CycleBreaker::Unblock(Vertex::Index u) { blocked_[u] = false; for (Vertex::EdgeMap::iterator it = blocked_graph_[u].out_edges.begin(); it != blocked_graph_[u].out_edges.end();) { Vertex::Index w = it->first; blocked_graph_[u].out_edges.erase(it++); if (blocked_[w]) Unblock(w); } } bool CycleBreaker::StackContainsCutEdge() const { for (vector<Vertex::Index>::const_iterator it = ++stack_.begin(), e = stack_.end(); it != e; ++it) { Edge edge = make_pair(*(it - 1), *it); if (base::ContainsKey(cut_edges_, edge)) { return true; } } return false; } bool CycleBreaker::Circuit(Vertex::Index vertex, Vertex::Index depth) { // "vertex" was "v" in the original paper. bool found = false; // Was "f" in the original paper. stack_.push_back(vertex); blocked_[vertex] = true; { static int counter = 0; counter++; if (counter == 10000) { counter = 0; std::string stack_str; for (Vertex::Index index : stack_) { stack_str += std::to_string(index); stack_str += " -> "; } LOG(INFO) << "stack: " << stack_str; } } for (Vertex::SubgraphEdgeMap::iterator w = subgraph_[vertex].subgraph_edges.begin(); w != subgraph_[vertex].subgraph_edges.end(); ++w) { if (*w == current_vertex_) { // The original paper called for printing stack_ followed by // current_vertex_ here, which is a cycle. Instead, we call // HandleCircuit() to break it. HandleCircuit(); found = true; } else if (!blocked_[*w]) { if (Circuit(*w, depth + 1)) { found = true; if ((depth > kMaxEdgesToConsider) || StackContainsCutEdge()) break; } } } if (found) { Unblock(vertex); } else { for (Vertex::SubgraphEdgeMap::iterator w = subgraph_[vertex].subgraph_edges.begin(); w != subgraph_[vertex].subgraph_edges.end(); ++w) { if (blocked_graph_[*w].out_edges.find(vertex) == blocked_graph_[*w].out_edges.end()) { blocked_graph_[*w].out_edges.insert( make_pair(vertex, EdgeProperties())); } } } CHECK_EQ(vertex, stack_.back()); stack_.pop_back(); return found; } } // namespace chromeos_update_engine