cycle_breaker.cc revision 161c4a132743f15fc4757112b673085c2a7a7f29
1// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "update_engine/payload_generator/cycle_breaker.h"
6
7#include <inttypes.h>
8
9#include <set>
10#include <utility>
11
12#include <base/strings/string_util.h>
13#include <base/strings/stringprintf.h>
14
15#include "update_engine/payload_generator/graph_utils.h"
16#include "update_engine/payload_generator/tarjan.h"
17#include "update_engine/utils.h"
18
19using std::make_pair;
20using std::set;
21using std::vector;
22
23namespace chromeos_update_engine {
24
25// This is the outer function from the original paper.
26void CycleBreaker::BreakCycles(const Graph& graph, set<Edge>* out_cut_edges) {
27  cut_edges_.clear();
28
29  // Make a copy, which we will modify by removing edges. Thus, in each
30  // iteration subgraph_ is the current subgraph or the original with
31  // vertices we desire. This variable was "A_K" in the original paper.
32  subgraph_ = graph;
33
34  // The paper calls for the "adjacency structure (i.e., graph) of
35  // strong (-ly connected) component K with least vertex in subgraph
36  // induced by {s, s + 1, ..., n}".
37  // We arbitrarily order each vertex by its index in the graph. Thus,
38  // each iteration, we are looking at the subgraph {s, s + 1, ..., n}
39  // and looking for the strongly connected component with vertex s.
40
41  TarjanAlgorithm tarjan;
42  skipped_ops_ = 0;
43
44  for (Graph::size_type i = 0; i < subgraph_.size(); i++) {
45    DeltaArchiveManifest_InstallOperation_Type op_type = graph[i].op.type();
46    if (op_type == DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
47        op_type == DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
48      skipped_ops_++;
49      continue;
50    }
51
52    if (i > 0) {
53      // Erase node (i - 1) from subgraph_. First, erase what it points to
54      subgraph_[i - 1].out_edges.clear();
55      // Now, erase any pointers to node (i - 1)
56      for (Graph::size_type j = i; j < subgraph_.size(); j++) {
57        subgraph_[j].out_edges.erase(i - 1);
58      }
59    }
60
61    // Calculate SCC (strongly connected component) with vertex i.
62    vector<Vertex::Index> component_indexes;
63    tarjan.Execute(i, &subgraph_, &component_indexes);
64
65    // Set subgraph edges for the components in the SCC.
66    for (vector<Vertex::Index>::iterator it = component_indexes.begin();
67         it != component_indexes.end(); ++it) {
68      subgraph_[*it].subgraph_edges.clear();
69      for (vector<Vertex::Index>::iterator jt = component_indexes.begin();
70           jt != component_indexes.end(); ++jt) {
71        // If there's a link from *it -> *jt in the graph,
72        // add a subgraph_ edge
73        if (utils::MapContainsKey(subgraph_[*it].out_edges, *jt))
74          subgraph_[*it].subgraph_edges.insert(*jt);
75      }
76    }
77
78    current_vertex_ = i;
79    blocked_.clear();
80    blocked_.resize(subgraph_.size());
81    blocked_graph_.clear();
82    blocked_graph_.resize(subgraph_.size());
83    Circuit(current_vertex_, 0);
84  }
85
86  out_cut_edges->swap(cut_edges_);
87  LOG(INFO) << "Cycle breaker skipped " << skipped_ops_ << " ops.";
88  DCHECK(stack_.empty());
89}
90
91static const size_t kMaxEdgesToConsider = 2;
92
93void CycleBreaker::HandleCircuit() {
94  stack_.push_back(current_vertex_);
95  CHECK_GE(stack_.size(),
96           static_cast<std::vector<Vertex::Index>::size_type>(2));
97  Edge min_edge = make_pair(stack_[0], stack_[1]);
98  uint64_t min_edge_weight = kuint64max;
99  size_t edges_considered = 0;
100  for (vector<Vertex::Index>::const_iterator it = stack_.begin();
101       it != (stack_.end() - 1); ++it) {
102    Edge edge = make_pair(*it, *(it + 1));
103    if (cut_edges_.find(edge) != cut_edges_.end()) {
104      stack_.pop_back();
105      return;
106    }
107    uint64_t edge_weight = graph_utils::EdgeWeight(subgraph_, edge);
108    if (edge_weight < min_edge_weight) {
109      min_edge_weight = edge_weight;
110      min_edge = edge;
111    }
112    edges_considered++;
113    if (edges_considered == kMaxEdgesToConsider)
114      break;
115  }
116  cut_edges_.insert(min_edge);
117  stack_.pop_back();
118}
119
120void CycleBreaker::Unblock(Vertex::Index u) {
121  blocked_[u] = false;
122
123  for (Vertex::EdgeMap::iterator it = blocked_graph_[u].out_edges.begin();
124       it != blocked_graph_[u].out_edges.end(); ) {
125    Vertex::Index w = it->first;
126    blocked_graph_[u].out_edges.erase(it++);
127    if (blocked_[w])
128      Unblock(w);
129  }
130}
131
132bool CycleBreaker::StackContainsCutEdge() const {
133  for (std::vector<Vertex::Index>::const_iterator it = ++stack_.begin(),
134           e = stack_.end(); it != e; ++it) {
135    Edge edge = make_pair(*(it - 1), *it);
136    if (utils::SetContainsKey(cut_edges_, edge)) {
137      return true;
138    }
139  }
140  return false;
141}
142
143bool CycleBreaker::Circuit(Vertex::Index vertex, Vertex::Index depth) {
144  // "vertex" was "v" in the original paper.
145  bool found = false;  // Was "f" in the original paper.
146  stack_.push_back(vertex);
147  blocked_[vertex] = true;
148  {
149    static int counter = 0;
150    counter++;
151    if (counter == 10000) {
152      counter = 0;
153      std::string stack_str;
154      for (vector<Vertex::Index>::const_iterator it = stack_.begin();
155           it != stack_.end(); ++it) {
156        stack_str += base::StringPrintf("%lu -> ",
157                                        static_cast<long unsigned int>(*it));
158      }
159      LOG(INFO) << "stack: " << stack_str;
160    }
161  }
162
163  for (Vertex::SubgraphEdgeMap::iterator w =
164           subgraph_[vertex].subgraph_edges.begin();
165       w != subgraph_[vertex].subgraph_edges.end(); ++w) {
166    if (*w == current_vertex_) {
167      // The original paper called for printing stack_ followed by
168      // current_vertex_ here, which is a cycle. Instead, we call
169      // HandleCircuit() to break it.
170      HandleCircuit();
171      found = true;
172    } else if (!blocked_[*w]) {
173      if (Circuit(*w, depth + 1)) {
174        found = true;
175        if ((depth > kMaxEdgesToConsider) || StackContainsCutEdge())
176          break;
177      }
178    }
179  }
180
181  if (found) {
182    Unblock(vertex);
183  } else {
184    for (Vertex::SubgraphEdgeMap::iterator w =
185             subgraph_[vertex].subgraph_edges.begin();
186         w != subgraph_[vertex].subgraph_edges.end(); ++w) {
187      if (blocked_graph_[*w].out_edges.find(vertex) ==
188          blocked_graph_[*w].out_edges.end()) {
189        blocked_graph_[*w].out_edges.insert(make_pair(vertex,
190                                                      EdgeProperties()));
191      }
192    }
193  }
194  CHECK_EQ(vertex, stack_.back());
195  stack_.pop_back();
196  return found;
197}
198
199}  // namespace chromeos_update_engine
200