//
|
// 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
|