123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708 |
- // Copyright 2017 The Abseil Authors.
- //
- // 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
- //
- // https://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.
- // GraphCycles provides incremental cycle detection on a dynamic
- // graph using the following algorithm:
- //
- // A dynamic topological sort algorithm for directed acyclic graphs
- // David J. Pearce, Paul H. J. Kelly
- // Journal of Experimental Algorithmics (JEA) JEA Homepage archive
- // Volume 11, 2006, Article No. 1.7
- //
- // Brief summary of the algorithm:
- //
- // (1) Maintain a rank for each node that is consistent
- // with the topological sort of the graph. I.e., path from x to y
- // implies rank[x] < rank[y].
- // (2) When a new edge (x->y) is inserted, do nothing if rank[x] < rank[y].
- // (3) Otherwise: adjust ranks in the neighborhood of x and y.
- #include "absl/base/attributes.h"
- // This file is a no-op if the required LowLevelAlloc support is missing.
- #include "absl/base/internal/low_level_alloc.h"
- #ifndef ABSL_LOW_LEVEL_ALLOC_MISSING
- #include "absl/synchronization/internal/graphcycles.h"
- #include <algorithm>
- #include <array>
- #include <cinttypes>
- #include <limits>
- #include "absl/base/internal/hide_ptr.h"
- #include "absl/base/internal/raw_logging.h"
- #include "absl/base/internal/spinlock.h"
- // Do not use STL. This module does not use standard memory allocation.
- namespace absl {
- ABSL_NAMESPACE_BEGIN
- namespace synchronization_internal {
- namespace {
- // Avoid LowLevelAlloc's default arena since it calls malloc hooks in
- // which people are doing things like acquiring Mutexes.
- ABSL_CONST_INIT static absl::base_internal::SpinLock arena_mu(
- absl::kConstInit, base_internal::SCHEDULE_KERNEL_ONLY);
- ABSL_CONST_INIT static base_internal::LowLevelAlloc::Arena* arena;
- static void InitArenaIfNecessary() {
- arena_mu.Lock();
- if (arena == nullptr) {
- arena = base_internal::LowLevelAlloc::NewArena(0);
- }
- arena_mu.Unlock();
- }
- // Number of inlined elements in Vec. Hash table implementation
- // relies on this being a power of two.
- static const uint32_t kInline = 8;
- // A simple LowLevelAlloc based resizable vector with inlined storage
- // for a few elements. T must be a plain type since constructor
- // and destructor are not run on elements of type T managed by Vec.
- template <typename T>
- class Vec {
- public:
- Vec() { Init(); }
- ~Vec() { Discard(); }
- void clear() {
- Discard();
- Init();
- }
- bool empty() const { return size_ == 0; }
- uint32_t size() const { return size_; }
- T* begin() { return ptr_; }
- T* end() { return ptr_ + size_; }
- const T& operator[](uint32_t i) const { return ptr_[i]; }
- T& operator[](uint32_t i) { return ptr_[i]; }
- const T& back() const { return ptr_[size_-1]; }
- void pop_back() { size_--; }
- void push_back(const T& v) {
- if (size_ == capacity_) Grow(size_ + 1);
- ptr_[size_] = v;
- size_++;
- }
- void resize(uint32_t n) {
- if (n > capacity_) Grow(n);
- size_ = n;
- }
- void fill(const T& val) {
- for (uint32_t i = 0; i < size(); i++) {
- ptr_[i] = val;
- }
- }
- // Guarantees src is empty at end.
- // Provided for the hash table resizing code below.
- void MoveFrom(Vec<T>* src) {
- if (src->ptr_ == src->space_) {
- // Need to actually copy
- resize(src->size_);
- std::copy_n(src->ptr_, src->size_, ptr_);
- src->size_ = 0;
- } else {
- Discard();
- ptr_ = src->ptr_;
- size_ = src->size_;
- capacity_ = src->capacity_;
- src->Init();
- }
- }
- private:
- T* ptr_;
- T space_[kInline];
- uint32_t size_;
- uint32_t capacity_;
- void Init() {
- ptr_ = space_;
- size_ = 0;
- capacity_ = kInline;
- }
- void Discard() {
- if (ptr_ != space_) base_internal::LowLevelAlloc::Free(ptr_);
- }
- void Grow(uint32_t n) {
- while (capacity_ < n) {
- capacity_ *= 2;
- }
- size_t request = static_cast<size_t>(capacity_) * sizeof(T);
- T* copy = static_cast<T*>(
- base_internal::LowLevelAlloc::AllocWithArena(request, arena));
- std::copy_n(ptr_, size_, copy);
- Discard();
- ptr_ = copy;
- }
- Vec(const Vec&) = delete;
- Vec& operator=(const Vec&) = delete;
- };
- // A hash set of non-negative int32_t that uses Vec for its underlying storage.
- class NodeSet {
- public:
- NodeSet() { Init(); }
- void clear() { Init(); }
- bool contains(int32_t v) const { return table_[FindIndex(v)] == v; }
- bool insert(int32_t v) {
- uint32_t i = FindIndex(v);
- if (table_[i] == v) {
- return false;
- }
- if (table_[i] == kEmpty) {
- // Only inserting over an empty cell increases the number of occupied
- // slots.
- occupied_++;
- }
- table_[i] = v;
- // Double when 75% full.
- if (occupied_ >= table_.size() - table_.size()/4) Grow();
- return true;
- }
- void erase(int32_t v) {
- uint32_t i = FindIndex(v);
- if (table_[i] == v) {
- table_[i] = kDel;
- }
- }
- // Iteration: is done via HASH_FOR_EACH
- // Example:
- // HASH_FOR_EACH(elem, node->out) { ... }
- #define HASH_FOR_EACH(elem, eset) \
- for (int32_t elem, _cursor = 0; (eset).Next(&_cursor, &elem); )
- bool Next(int32_t* cursor, int32_t* elem) {
- while (static_cast<uint32_t>(*cursor) < table_.size()) {
- int32_t v = table_[static_cast<uint32_t>(*cursor)];
- (*cursor)++;
- if (v >= 0) {
- *elem = v;
- return true;
- }
- }
- return false;
- }
- private:
- enum : int32_t { kEmpty = -1, kDel = -2 };
- Vec<int32_t> table_;
- uint32_t occupied_; // Count of non-empty slots (includes deleted slots)
- static uint32_t Hash(int32_t a) { return static_cast<uint32_t>(a * 41); }
- // Return index for storing v. May return an empty index or deleted index
- uint32_t FindIndex(int32_t v) const {
- // Search starting at hash index.
- const uint32_t mask = table_.size() - 1;
- uint32_t i = Hash(v) & mask;
- uint32_t deleted_index = 0; // index of first deleted element we see
- bool seen_deleted_element = false;
- while (true) {
- int32_t e = table_[i];
- if (v == e) {
- return i;
- } else if (e == kEmpty) {
- // Return any previously encountered deleted slot.
- return seen_deleted_element ? deleted_index : i;
- } else if (e == kDel && !seen_deleted_element) {
- // Keep searching since v might be present later.
- deleted_index = i;
- seen_deleted_element = true;
- }
- i = (i + 1) & mask; // Linear probing; quadratic is slightly slower.
- }
- }
- void Init() {
- table_.clear();
- table_.resize(kInline);
- table_.fill(kEmpty);
- occupied_ = 0;
- }
- void Grow() {
- Vec<int32_t> copy;
- copy.MoveFrom(&table_);
- occupied_ = 0;
- table_.resize(copy.size() * 2);
- table_.fill(kEmpty);
- for (const auto& e : copy) {
- if (e >= 0) insert(e);
- }
- }
- NodeSet(const NodeSet&) = delete;
- NodeSet& operator=(const NodeSet&) = delete;
- };
- // We encode a node index and a node version in GraphId. The version
- // number is incremented when the GraphId is freed which automatically
- // invalidates all copies of the GraphId.
- inline GraphId MakeId(int32_t index, uint32_t version) {
- GraphId g;
- g.handle =
- (static_cast<uint64_t>(version) << 32) | static_cast<uint32_t>(index);
- return g;
- }
- inline int32_t NodeIndex(GraphId id) {
- return static_cast<int32_t>(id.handle);
- }
- inline uint32_t NodeVersion(GraphId id) {
- return static_cast<uint32_t>(id.handle >> 32);
- }
- struct Node {
- int32_t rank; // rank number assigned by Pearce-Kelly algorithm
- uint32_t version; // Current version number
- int32_t next_hash; // Next entry in hash table
- bool visited; // Temporary marker used by depth-first-search
- uintptr_t masked_ptr; // User-supplied pointer
- NodeSet in; // List of immediate predecessor nodes in graph
- NodeSet out; // List of immediate successor nodes in graph
- int priority; // Priority of recorded stack trace.
- int nstack; // Depth of recorded stack trace.
- void* stack[40]; // stack[0,nstack-1] holds stack trace for node.
- };
- // Hash table for pointer to node index lookups.
- class PointerMap {
- public:
- explicit PointerMap(const Vec<Node*>* nodes) : nodes_(nodes) {
- table_.fill(-1);
- }
- int32_t Find(void* ptr) {
- auto masked = base_internal::HidePtr(ptr);
- for (int32_t i = table_[Hash(ptr)]; i != -1;) {
- Node* n = (*nodes_)[static_cast<uint32_t>(i)];
- if (n->masked_ptr == masked) return i;
- i = n->next_hash;
- }
- return -1;
- }
- void Add(void* ptr, int32_t i) {
- int32_t* head = &table_[Hash(ptr)];
- (*nodes_)[static_cast<uint32_t>(i)]->next_hash = *head;
- *head = i;
- }
- int32_t Remove(void* ptr) {
- // Advance through linked list while keeping track of the
- // predecessor slot that points to the current entry.
- auto masked = base_internal::HidePtr(ptr);
- for (int32_t* slot = &table_[Hash(ptr)]; *slot != -1; ) {
- int32_t index = *slot;
- Node* n = (*nodes_)[static_cast<uint32_t>(index)];
- if (n->masked_ptr == masked) {
- *slot = n->next_hash; // Remove n from linked list
- n->next_hash = -1;
- return index;
- }
- slot = &n->next_hash;
- }
- return -1;
- }
- private:
- // Number of buckets in hash table for pointer lookups.
- static constexpr uint32_t kHashTableSize = 8171; // should be prime
- const Vec<Node*>* nodes_;
- std::array<int32_t, kHashTableSize> table_;
- static uint32_t Hash(void* ptr) {
- return reinterpret_cast<uintptr_t>(ptr) % kHashTableSize;
- }
- };
- } // namespace
- struct GraphCycles::Rep {
- Vec<Node*> nodes_;
- Vec<int32_t> free_nodes_; // Indices for unused entries in nodes_
- PointerMap ptrmap_;
- // Temporary state.
- Vec<int32_t> deltaf_; // Results of forward DFS
- Vec<int32_t> deltab_; // Results of backward DFS
- Vec<int32_t> list_; // All nodes to reprocess
- Vec<int32_t> merged_; // Rank values to assign to list_ entries
- Vec<int32_t> stack_; // Emulates recursion stack for depth-first searches
- Rep() : ptrmap_(&nodes_) {}
- };
- static Node* FindNode(GraphCycles::Rep* rep, GraphId id) {
- Node* n = rep->nodes_[static_cast<uint32_t>(NodeIndex(id))];
- return (n->version == NodeVersion(id)) ? n : nullptr;
- }
- GraphCycles::GraphCycles() {
- InitArenaIfNecessary();
- rep_ = new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Rep), arena))
- Rep;
- }
- GraphCycles::~GraphCycles() {
- for (auto* node : rep_->nodes_) {
- node->Node::~Node();
- base_internal::LowLevelAlloc::Free(node);
- }
- rep_->Rep::~Rep();
- base_internal::LowLevelAlloc::Free(rep_);
- }
- bool GraphCycles::CheckInvariants() const {
- Rep* r = rep_;
- NodeSet ranks; // Set of ranks seen so far.
- for (uint32_t x = 0; x < r->nodes_.size(); x++) {
- Node* nx = r->nodes_[x];
- void* ptr = base_internal::UnhidePtr<void>(nx->masked_ptr);
- if (ptr != nullptr && static_cast<uint32_t>(r->ptrmap_.Find(ptr)) != x) {
- ABSL_RAW_LOG(FATAL, "Did not find live node in hash table %" PRIu32 " %p",
- x, ptr);
- }
- if (nx->visited) {
- ABSL_RAW_LOG(FATAL, "Did not clear visited marker on node %" PRIu32, x);
- }
- if (!ranks.insert(nx->rank)) {
- ABSL_RAW_LOG(FATAL, "Duplicate occurrence of rank %" PRId32, nx->rank);
- }
- HASH_FOR_EACH(y, nx->out) {
- Node* ny = r->nodes_[static_cast<uint32_t>(y)];
- if (nx->rank >= ny->rank) {
- ABSL_RAW_LOG(FATAL,
- "Edge %" PRIu32 " ->%" PRId32
- " has bad rank assignment %" PRId32 "->%" PRId32,
- x, y, nx->rank, ny->rank);
- }
- }
- }
- return true;
- }
- GraphId GraphCycles::GetId(void* ptr) {
- int32_t i = rep_->ptrmap_.Find(ptr);
- if (i != -1) {
- return MakeId(i, rep_->nodes_[static_cast<uint32_t>(i)]->version);
- } else if (rep_->free_nodes_.empty()) {
- Node* n =
- new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Node), arena))
- Node;
- n->version = 1; // Avoid 0 since it is used by InvalidGraphId()
- n->visited = false;
- n->rank = static_cast<int32_t>(rep_->nodes_.size());
- n->masked_ptr = base_internal::HidePtr(ptr);
- n->nstack = 0;
- n->priority = 0;
- rep_->nodes_.push_back(n);
- rep_->ptrmap_.Add(ptr, n->rank);
- return MakeId(n->rank, n->version);
- } else {
- // Preserve preceding rank since the set of ranks in use must be
- // a permutation of [0,rep_->nodes_.size()-1].
- int32_t r = rep_->free_nodes_.back();
- rep_->free_nodes_.pop_back();
- Node* n = rep_->nodes_[static_cast<uint32_t>(r)];
- n->masked_ptr = base_internal::HidePtr(ptr);
- n->nstack = 0;
- n->priority = 0;
- rep_->ptrmap_.Add(ptr, r);
- return MakeId(r, n->version);
- }
- }
- void GraphCycles::RemoveNode(void* ptr) {
- int32_t i = rep_->ptrmap_.Remove(ptr);
- if (i == -1) {
- return;
- }
- Node* x = rep_->nodes_[static_cast<uint32_t>(i)];
- HASH_FOR_EACH(y, x->out) {
- rep_->nodes_[static_cast<uint32_t>(y)]->in.erase(i);
- }
- HASH_FOR_EACH(y, x->in) {
- rep_->nodes_[static_cast<uint32_t>(y)]->out.erase(i);
- }
- x->in.clear();
- x->out.clear();
- x->masked_ptr = base_internal::HidePtr<void>(nullptr);
- if (x->version == std::numeric_limits<uint32_t>::max()) {
- // Cannot use x any more
- } else {
- x->version++; // Invalidates all copies of node.
- rep_->free_nodes_.push_back(i);
- }
- }
- void* GraphCycles::Ptr(GraphId id) {
- Node* n = FindNode(rep_, id);
- return n == nullptr ? nullptr
- : base_internal::UnhidePtr<void>(n->masked_ptr);
- }
- bool GraphCycles::HasNode(GraphId node) {
- return FindNode(rep_, node) != nullptr;
- }
- bool GraphCycles::HasEdge(GraphId x, GraphId y) const {
- Node* xn = FindNode(rep_, x);
- return xn && FindNode(rep_, y) && xn->out.contains(NodeIndex(y));
- }
- void GraphCycles::RemoveEdge(GraphId x, GraphId y) {
- Node* xn = FindNode(rep_, x);
- Node* yn = FindNode(rep_, y);
- if (xn && yn) {
- xn->out.erase(NodeIndex(y));
- yn->in.erase(NodeIndex(x));
- // No need to update the rank assignment since a previous valid
- // rank assignment remains valid after an edge deletion.
- }
- }
- static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int32_t upper_bound);
- static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int32_t lower_bound);
- static void Reorder(GraphCycles::Rep* r);
- static void Sort(const Vec<Node*>&, Vec<int32_t>* delta);
- static void MoveToList(
- GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst);
- bool GraphCycles::InsertEdge(GraphId idx, GraphId idy) {
- Rep* r = rep_;
- const int32_t x = NodeIndex(idx);
- const int32_t y = NodeIndex(idy);
- Node* nx = FindNode(r, idx);
- Node* ny = FindNode(r, idy);
- if (nx == nullptr || ny == nullptr) return true; // Expired ids
- if (nx == ny) return false; // Self edge
- if (!nx->out.insert(y)) {
- // Edge already exists.
- return true;
- }
- ny->in.insert(x);
- if (nx->rank <= ny->rank) {
- // New edge is consistent with existing rank assignment.
- return true;
- }
- // Current rank assignments are incompatible with the new edge. Recompute.
- // We only need to consider nodes that fall in the range [ny->rank,nx->rank].
- if (!ForwardDFS(r, y, nx->rank)) {
- // Found a cycle. Undo the insertion and tell caller.
- nx->out.erase(y);
- ny->in.erase(x);
- // Since we do not call Reorder() on this path, clear any visited
- // markers left by ForwardDFS.
- for (const auto& d : r->deltaf_) {
- r->nodes_[static_cast<uint32_t>(d)]->visited = false;
- }
- return false;
- }
- BackwardDFS(r, x, ny->rank);
- Reorder(r);
- return true;
- }
- static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int32_t upper_bound) {
- // Avoid recursion since stack space might be limited.
- // We instead keep a stack of nodes to visit.
- r->deltaf_.clear();
- r->stack_.clear();
- r->stack_.push_back(n);
- while (!r->stack_.empty()) {
- n = r->stack_.back();
- r->stack_.pop_back();
- Node* nn = r->nodes_[static_cast<uint32_t>(n)];
- if (nn->visited) continue;
- nn->visited = true;
- r->deltaf_.push_back(n);
- HASH_FOR_EACH(w, nn->out) {
- Node* nw = r->nodes_[static_cast<uint32_t>(w)];
- if (nw->rank == upper_bound) {
- return false; // Cycle
- }
- if (!nw->visited && nw->rank < upper_bound) {
- r->stack_.push_back(w);
- }
- }
- }
- return true;
- }
- static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int32_t lower_bound) {
- r->deltab_.clear();
- r->stack_.clear();
- r->stack_.push_back(n);
- while (!r->stack_.empty()) {
- n = r->stack_.back();
- r->stack_.pop_back();
- Node* nn = r->nodes_[static_cast<uint32_t>(n)];
- if (nn->visited) continue;
- nn->visited = true;
- r->deltab_.push_back(n);
- HASH_FOR_EACH(w, nn->in) {
- Node* nw = r->nodes_[static_cast<uint32_t>(w)];
- if (!nw->visited && lower_bound < nw->rank) {
- r->stack_.push_back(w);
- }
- }
- }
- }
- static void Reorder(GraphCycles::Rep* r) {
- Sort(r->nodes_, &r->deltab_);
- Sort(r->nodes_, &r->deltaf_);
- // Adds contents of delta lists to list_ (backwards deltas first).
- r->list_.clear();
- MoveToList(r, &r->deltab_, &r->list_);
- MoveToList(r, &r->deltaf_, &r->list_);
- // Produce sorted list of all ranks that will be reassigned.
- r->merged_.resize(r->deltab_.size() + r->deltaf_.size());
- std::merge(r->deltab_.begin(), r->deltab_.end(),
- r->deltaf_.begin(), r->deltaf_.end(),
- r->merged_.begin());
- // Assign the ranks in order to the collected list.
- for (uint32_t i = 0; i < r->list_.size(); i++) {
- r->nodes_[static_cast<uint32_t>(r->list_[i])]->rank = r->merged_[i];
- }
- }
- static void Sort(const Vec<Node*>& nodes, Vec<int32_t>* delta) {
- struct ByRank {
- const Vec<Node*>* nodes;
- bool operator()(int32_t a, int32_t b) const {
- return (*nodes)[static_cast<uint32_t>(a)]->rank <
- (*nodes)[static_cast<uint32_t>(b)]->rank;
- }
- };
- ByRank cmp;
- cmp.nodes = &nodes;
- std::sort(delta->begin(), delta->end(), cmp);
- }
- static void MoveToList(
- GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst) {
- for (auto& v : *src) {
- int32_t w = v;
- // Replace v entry with its rank
- v = r->nodes_[static_cast<uint32_t>(w)]->rank;
- // Prepare for future DFS calls
- r->nodes_[static_cast<uint32_t>(w)]->visited = false;
- dst->push_back(w);
- }
- }
- int GraphCycles::FindPath(GraphId idx, GraphId idy, int max_path_len,
- GraphId path[]) const {
- Rep* r = rep_;
- if (FindNode(r, idx) == nullptr || FindNode(r, idy) == nullptr) return 0;
- const int32_t x = NodeIndex(idx);
- const int32_t y = NodeIndex(idy);
- // Forward depth first search starting at x until we hit y.
- // As we descend into a node, we push it onto the path.
- // As we leave a node, we remove it from the path.
- int path_len = 0;
- NodeSet seen;
- r->stack_.clear();
- r->stack_.push_back(x);
- while (!r->stack_.empty()) {
- int32_t n = r->stack_.back();
- r->stack_.pop_back();
- if (n < 0) {
- // Marker to indicate that we are leaving a node
- path_len--;
- continue;
- }
- if (path_len < max_path_len) {
- path[path_len] =
- MakeId(n, rep_->nodes_[static_cast<uint32_t>(n)]->version);
- }
- path_len++;
- r->stack_.push_back(-1); // Will remove tentative path entry
- if (n == y) {
- return path_len;
- }
- HASH_FOR_EACH(w, r->nodes_[static_cast<uint32_t>(n)]->out) {
- if (seen.insert(w)) {
- r->stack_.push_back(w);
- }
- }
- }
- return 0;
- }
- bool GraphCycles::IsReachable(GraphId x, GraphId y) const {
- return FindPath(x, y, 0, nullptr) > 0;
- }
- void GraphCycles::UpdateStackTrace(GraphId id, int priority,
- int (*get_stack_trace)(void** stack, int)) {
- Node* n = FindNode(rep_, id);
- if (n == nullptr || n->priority >= priority) {
- return;
- }
- n->nstack = (*get_stack_trace)(n->stack, ABSL_ARRAYSIZE(n->stack));
- n->priority = priority;
- }
- int GraphCycles::GetStackTrace(GraphId id, void*** ptr) {
- Node* n = FindNode(rep_, id);
- if (n == nullptr) {
- *ptr = nullptr;
- return 0;
- } else {
- *ptr = n->stack;
- return n->nstack;
- }
- }
- } // namespace synchronization_internal
- ABSL_NAMESPACE_END
- } // namespace absl
- #endif // ABSL_LOW_LEVEL_ALLOC_MISSING
|