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