graphcycles.cc (20966B)
1 // Copyright 2017 The Abseil Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // GraphCycles provides incremental cycle detection on a dynamic 16 // graph using the following algorithm: 17 // 18 // A dynamic topological sort algorithm for directed acyclic graphs 19 // David J. Pearce, Paul H. J. Kelly 20 // Journal of Experimental Algorithmics (JEA) JEA Homepage archive 21 // Volume 11, 2006, Article No. 1.7 22 // 23 // Brief summary of the algorithm: 24 // 25 // (1) Maintain a rank for each node that is consistent 26 // with the topological sort of the graph. I.e., path from x to y 27 // implies rank[x] < rank[y]. 28 // (2) When a new edge (x->y) is inserted, do nothing if rank[x] < rank[y]. 29 // (3) Otherwise: adjust ranks in the neighborhood of x and y. 30 31 #include "absl/base/attributes.h" 32 // This file is a no-op if the required LowLevelAlloc support is missing. 33 #include "absl/base/internal/low_level_alloc.h" 34 #ifndef ABSL_LOW_LEVEL_ALLOC_MISSING 35 36 #include "absl/synchronization/internal/graphcycles.h" 37 38 #include <algorithm> 39 #include <array> 40 #include <cinttypes> 41 #include <limits> 42 #include "absl/base/internal/hide_ptr.h" 43 #include "absl/base/internal/raw_logging.h" 44 #include "absl/base/internal/spinlock.h" 45 46 // Do not use STL. This module does not use standard memory allocation. 47 48 namespace absl { 49 ABSL_NAMESPACE_BEGIN 50 namespace synchronization_internal { 51 52 namespace { 53 54 // Avoid LowLevelAlloc's default arena since it calls malloc hooks in 55 // which people are doing things like acquiring Mutexes. 56 ABSL_CONST_INIT static absl::base_internal::SpinLock arena_mu( 57 absl::kConstInit, base_internal::SCHEDULE_KERNEL_ONLY); 58 ABSL_CONST_INIT static base_internal::LowLevelAlloc::Arena* arena; 59 60 static void InitArenaIfNecessary() { 61 arena_mu.Lock(); 62 if (arena == nullptr) { 63 arena = base_internal::LowLevelAlloc::NewArena(0); 64 } 65 arena_mu.Unlock(); 66 } 67 68 // Number of inlined elements in Vec. Hash table implementation 69 // relies on this being a power of two. 70 static const uint32_t kInline = 8; 71 72 // A simple LowLevelAlloc based resizable vector with inlined storage 73 // for a few elements. T must be a plain type since constructor 74 // and destructor are not run on elements of type T managed by Vec. 75 template <typename T> 76 class Vec { 77 public: 78 Vec() { Init(); } 79 ~Vec() { Discard(); } 80 81 void clear() { 82 Discard(); 83 Init(); 84 } 85 86 bool empty() const { return size_ == 0; } 87 uint32_t size() const { return size_; } 88 T* begin() { return ptr_; } 89 T* end() { return ptr_ + size_; } 90 const T& operator[](uint32_t i) const { return ptr_[i]; } 91 T& operator[](uint32_t i) { return ptr_[i]; } 92 const T& back() const { return ptr_[size_-1]; } 93 void pop_back() { size_--; } 94 95 void push_back(const T& v) { 96 if (size_ == capacity_) Grow(size_ + 1); 97 ptr_[size_] = v; 98 size_++; 99 } 100 101 void resize(uint32_t n) { 102 if (n > capacity_) Grow(n); 103 size_ = n; 104 } 105 106 void fill(const T& val) { 107 for (uint32_t i = 0; i < size(); i++) { 108 ptr_[i] = val; 109 } 110 } 111 112 // Guarantees src is empty at end. 113 // Provided for the hash table resizing code below. 114 void MoveFrom(Vec<T>* src) { 115 if (src->ptr_ == src->space_) { 116 // Need to actually copy 117 resize(src->size_); 118 std::copy_n(src->ptr_, src->size_, ptr_); 119 src->size_ = 0; 120 } else { 121 Discard(); 122 ptr_ = src->ptr_; 123 size_ = src->size_; 124 capacity_ = src->capacity_; 125 src->Init(); 126 } 127 } 128 129 private: 130 T* ptr_; 131 T space_[kInline]; 132 uint32_t size_; 133 uint32_t capacity_; 134 135 void Init() { 136 ptr_ = space_; 137 size_ = 0; 138 capacity_ = kInline; 139 } 140 141 void Discard() { 142 if (ptr_ != space_) base_internal::LowLevelAlloc::Free(ptr_); 143 } 144 145 void Grow(uint32_t n) { 146 while (capacity_ < n) { 147 capacity_ *= 2; 148 } 149 size_t request = static_cast<size_t>(capacity_) * sizeof(T); 150 T* copy = static_cast<T*>( 151 base_internal::LowLevelAlloc::AllocWithArena(request, arena)); 152 std::copy_n(ptr_, size_, copy); 153 Discard(); 154 ptr_ = copy; 155 } 156 157 Vec(const Vec&) = delete; 158 Vec& operator=(const Vec&) = delete; 159 }; 160 161 // A hash set of non-negative int32_t that uses Vec for its underlying storage. 162 class NodeSet { 163 public: 164 NodeSet() { Init(); } 165 166 void clear() { Init(); } 167 bool contains(int32_t v) const { return table_[FindIndex(v)] == v; } 168 169 bool insert(int32_t v) { 170 uint32_t i = FindIndex(v); 171 if (table_[i] == v) { 172 return false; 173 } 174 if (table_[i] == kEmpty) { 175 // Only inserting over an empty cell increases the number of occupied 176 // slots. 177 occupied_++; 178 } 179 table_[i] = v; 180 // Double when 75% full. 181 if (occupied_ >= table_.size() - table_.size()/4) Grow(); 182 return true; 183 } 184 185 void erase(int32_t v) { 186 uint32_t i = FindIndex(v); 187 if (table_[i] == v) { 188 table_[i] = kDel; 189 } 190 } 191 192 // Iteration: is done via HASH_FOR_EACH 193 // Example: 194 // HASH_FOR_EACH(elem, node->out) { ... } 195 #define HASH_FOR_EACH(elem, eset) \ 196 for (int32_t elem, _cursor = 0; (eset).Next(&_cursor, &elem); ) 197 bool Next(int32_t* cursor, int32_t* elem) { 198 while (static_cast<uint32_t>(*cursor) < table_.size()) { 199 int32_t v = table_[static_cast<uint32_t>(*cursor)]; 200 (*cursor)++; 201 if (v >= 0) { 202 *elem = v; 203 return true; 204 } 205 } 206 return false; 207 } 208 209 private: 210 enum : int32_t { kEmpty = -1, kDel = -2 }; 211 Vec<int32_t> table_; 212 uint32_t occupied_; // Count of non-empty slots (includes deleted slots) 213 214 static uint32_t Hash(int32_t a) { return static_cast<uint32_t>(a) * 41; } 215 216 // Return index for storing v. May return an empty index or deleted index 217 uint32_t FindIndex(int32_t v) const { 218 // Search starting at hash index. 219 const uint32_t mask = table_.size() - 1; 220 uint32_t i = Hash(v) & mask; 221 uint32_t deleted_index = 0; // index of first deleted element we see 222 bool seen_deleted_element = false; 223 while (true) { 224 int32_t e = table_[i]; 225 if (v == e) { 226 return i; 227 } else if (e == kEmpty) { 228 // Return any previously encountered deleted slot. 229 return seen_deleted_element ? deleted_index : i; 230 } else if (e == kDel && !seen_deleted_element) { 231 // Keep searching since v might be present later. 232 deleted_index = i; 233 seen_deleted_element = true; 234 } 235 i = (i + 1) & mask; // Linear probing; quadratic is slightly slower. 236 } 237 } 238 239 void Init() { 240 table_.clear(); 241 table_.resize(kInline); 242 table_.fill(kEmpty); 243 occupied_ = 0; 244 } 245 246 void Grow() { 247 Vec<int32_t> copy; 248 copy.MoveFrom(&table_); 249 occupied_ = 0; 250 table_.resize(copy.size() * 2); 251 table_.fill(kEmpty); 252 253 for (const auto& e : copy) { 254 if (e >= 0) insert(e); 255 } 256 } 257 258 NodeSet(const NodeSet&) = delete; 259 NodeSet& operator=(const NodeSet&) = delete; 260 }; 261 262 // We encode a node index and a node version in GraphId. The version 263 // number is incremented when the GraphId is freed which automatically 264 // invalidates all copies of the GraphId. 265 266 inline GraphId MakeId(int32_t index, uint32_t version) { 267 GraphId g; 268 g.handle = 269 (static_cast<uint64_t>(version) << 32) | static_cast<uint32_t>(index); 270 return g; 271 } 272 273 inline int32_t NodeIndex(GraphId id) { 274 return static_cast<int32_t>(id.handle); 275 } 276 277 inline uint32_t NodeVersion(GraphId id) { 278 return static_cast<uint32_t>(id.handle >> 32); 279 } 280 281 struct Node { 282 int32_t rank; // rank number assigned by Pearce-Kelly algorithm 283 uint32_t version; // Current version number 284 int32_t next_hash; // Next entry in hash table 285 bool visited; // Temporary marker used by depth-first-search 286 uintptr_t masked_ptr; // User-supplied pointer 287 NodeSet in; // List of immediate predecessor nodes in graph 288 NodeSet out; // List of immediate successor nodes in graph 289 int priority; // Priority of recorded stack trace. 290 int nstack; // Depth of recorded stack trace. 291 void* stack[40]; // stack[0,nstack-1] holds stack trace for node. 292 }; 293 294 // Hash table for pointer to node index lookups. 295 class PointerMap { 296 public: 297 explicit PointerMap(const Vec<Node*>* nodes) : nodes_(nodes) { 298 table_.fill(-1); 299 } 300 301 int32_t Find(void* ptr) { 302 auto masked = base_internal::HidePtr(ptr); 303 for (int32_t i = table_[Hash(ptr)]; i != -1;) { 304 Node* n = (*nodes_)[static_cast<uint32_t>(i)]; 305 if (n->masked_ptr == masked) return i; 306 i = n->next_hash; 307 } 308 return -1; 309 } 310 311 void Add(void* ptr, int32_t i) { 312 int32_t* head = &table_[Hash(ptr)]; 313 (*nodes_)[static_cast<uint32_t>(i)]->next_hash = *head; 314 *head = i; 315 } 316 317 int32_t Remove(void* ptr) { 318 // Advance through linked list while keeping track of the 319 // predecessor slot that points to the current entry. 320 auto masked = base_internal::HidePtr(ptr); 321 for (int32_t* slot = &table_[Hash(ptr)]; *slot != -1; ) { 322 int32_t index = *slot; 323 Node* n = (*nodes_)[static_cast<uint32_t>(index)]; 324 if (n->masked_ptr == masked) { 325 *slot = n->next_hash; // Remove n from linked list 326 n->next_hash = -1; 327 return index; 328 } 329 slot = &n->next_hash; 330 } 331 return -1; 332 } 333 334 private: 335 // Number of buckets in hash table for pointer lookups. 336 static constexpr uint32_t kHashTableSize = 262139; // should be prime 337 338 const Vec<Node*>* nodes_; 339 std::array<int32_t, kHashTableSize> table_; 340 341 static uint32_t Hash(void* ptr) { 342 return reinterpret_cast<uintptr_t>(ptr) % kHashTableSize; 343 } 344 }; 345 346 } // namespace 347 348 struct GraphCycles::Rep { 349 Vec<Node*> nodes_; 350 Vec<int32_t> free_nodes_; // Indices for unused entries in nodes_ 351 PointerMap ptrmap_; 352 353 // Temporary state. 354 Vec<int32_t> deltaf_; // Results of forward DFS 355 Vec<int32_t> deltab_; // Results of backward DFS 356 Vec<int32_t> list_; // All nodes to reprocess 357 Vec<int32_t> merged_; // Rank values to assign to list_ entries 358 Vec<int32_t> stack_; // Emulates recursion stack for depth-first searches 359 360 Rep() : ptrmap_(&nodes_) {} 361 }; 362 363 static Node* FindNode(GraphCycles::Rep* rep, GraphId id) { 364 Node* n = rep->nodes_[static_cast<uint32_t>(NodeIndex(id))]; 365 return (n->version == NodeVersion(id)) ? n : nullptr; 366 } 367 368 void GraphCycles::TestOnlyAddNodes(uint32_t n) { 369 uint32_t old_size = rep_->nodes_.size(); 370 rep_->nodes_.resize(n); 371 for (auto i = old_size; i < n; ++i) { 372 rep_->nodes_[i] = nullptr; 373 } 374 } 375 376 GraphCycles::GraphCycles() { 377 InitArenaIfNecessary(); 378 rep_ = new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Rep), arena)) 379 Rep; 380 } 381 382 GraphCycles::~GraphCycles() { 383 for (auto* node : rep_->nodes_) { 384 if (node == nullptr) { continue; } 385 node->Node::~Node(); 386 base_internal::LowLevelAlloc::Free(node); 387 } 388 rep_->Rep::~Rep(); 389 base_internal::LowLevelAlloc::Free(rep_); 390 } 391 392 bool GraphCycles::CheckInvariants() const { 393 Rep* r = rep_; 394 NodeSet ranks; // Set of ranks seen so far. 395 for (uint32_t x = 0; x < r->nodes_.size(); x++) { 396 Node* nx = r->nodes_[x]; 397 void* ptr = base_internal::UnhidePtr<void>(nx->masked_ptr); 398 if (ptr != nullptr && static_cast<uint32_t>(r->ptrmap_.Find(ptr)) != x) { 399 ABSL_RAW_LOG(FATAL, "Did not find live node in hash table %" PRIu32 " %p", 400 x, ptr); 401 } 402 if (nx->visited) { 403 ABSL_RAW_LOG(FATAL, "Did not clear visited marker on node %" PRIu32, x); 404 } 405 if (!ranks.insert(nx->rank)) { 406 ABSL_RAW_LOG(FATAL, "Duplicate occurrence of rank %" PRId32, nx->rank); 407 } 408 HASH_FOR_EACH(y, nx->out) { 409 Node* ny = r->nodes_[static_cast<uint32_t>(y)]; 410 if (nx->rank >= ny->rank) { 411 ABSL_RAW_LOG(FATAL, 412 "Edge %" PRIu32 " ->%" PRId32 413 " has bad rank assignment %" PRId32 "->%" PRId32, 414 x, y, nx->rank, ny->rank); 415 } 416 } 417 } 418 return true; 419 } 420 421 GraphId GraphCycles::GetId(void* ptr) { 422 int32_t i = rep_->ptrmap_.Find(ptr); 423 if (i != -1) { 424 return MakeId(i, rep_->nodes_[static_cast<uint32_t>(i)]->version); 425 } else if (rep_->free_nodes_.empty()) { 426 Node* n = 427 new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Node), arena)) 428 Node; 429 n->version = 1; // Avoid 0 since it is used by InvalidGraphId() 430 n->visited = false; 431 n->rank = static_cast<int32_t>(rep_->nodes_.size()); 432 n->masked_ptr = base_internal::HidePtr(ptr); 433 n->nstack = 0; 434 n->priority = 0; 435 rep_->nodes_.push_back(n); 436 rep_->ptrmap_.Add(ptr, n->rank); 437 return MakeId(n->rank, n->version); 438 } else { 439 // Preserve preceding rank since the set of ranks in use must be 440 // a permutation of [0,rep_->nodes_.size()-1]. 441 int32_t r = rep_->free_nodes_.back(); 442 rep_->free_nodes_.pop_back(); 443 Node* n = rep_->nodes_[static_cast<uint32_t>(r)]; 444 n->masked_ptr = base_internal::HidePtr(ptr); 445 n->nstack = 0; 446 n->priority = 0; 447 rep_->ptrmap_.Add(ptr, r); 448 return MakeId(r, n->version); 449 } 450 } 451 452 void GraphCycles::RemoveNode(void* ptr) { 453 int32_t i = rep_->ptrmap_.Remove(ptr); 454 if (i == -1) { 455 return; 456 } 457 Node* x = rep_->nodes_[static_cast<uint32_t>(i)]; 458 HASH_FOR_EACH(y, x->out) { 459 rep_->nodes_[static_cast<uint32_t>(y)]->in.erase(i); 460 } 461 HASH_FOR_EACH(y, x->in) { 462 rep_->nodes_[static_cast<uint32_t>(y)]->out.erase(i); 463 } 464 x->in.clear(); 465 x->out.clear(); 466 x->masked_ptr = base_internal::HidePtr<void>(nullptr); 467 if (x->version == std::numeric_limits<uint32_t>::max()) { 468 // Cannot use x any more 469 } else { 470 x->version++; // Invalidates all copies of node. 471 rep_->free_nodes_.push_back(i); 472 } 473 } 474 475 void* GraphCycles::Ptr(GraphId id) { 476 Node* n = FindNode(rep_, id); 477 return n == nullptr ? nullptr 478 : base_internal::UnhidePtr<void>(n->masked_ptr); 479 } 480 481 bool GraphCycles::HasNode(GraphId node) { 482 return FindNode(rep_, node) != nullptr; 483 } 484 485 bool GraphCycles::HasEdge(GraphId x, GraphId y) const { 486 Node* xn = FindNode(rep_, x); 487 return xn && FindNode(rep_, y) && xn->out.contains(NodeIndex(y)); 488 } 489 490 void GraphCycles::RemoveEdge(GraphId x, GraphId y) { 491 Node* xn = FindNode(rep_, x); 492 Node* yn = FindNode(rep_, y); 493 if (xn && yn) { 494 xn->out.erase(NodeIndex(y)); 495 yn->in.erase(NodeIndex(x)); 496 // No need to update the rank assignment since a previous valid 497 // rank assignment remains valid after an edge deletion. 498 } 499 } 500 501 static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int32_t upper_bound); 502 static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int32_t lower_bound); 503 static void Reorder(GraphCycles::Rep* r); 504 static void Sort(const Vec<Node*>&, Vec<int32_t>* delta); 505 static void MoveToList( 506 GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst); 507 508 bool GraphCycles::InsertEdge(GraphId idx, GraphId idy) { 509 Rep* r = rep_; 510 const int32_t x = NodeIndex(idx); 511 const int32_t y = NodeIndex(idy); 512 Node* nx = FindNode(r, idx); 513 Node* ny = FindNode(r, idy); 514 if (nx == nullptr || ny == nullptr) return true; // Expired ids 515 516 if (nx == ny) return false; // Self edge 517 if (!nx->out.insert(y)) { 518 // Edge already exists. 519 return true; 520 } 521 522 ny->in.insert(x); 523 524 if (nx->rank <= ny->rank) { 525 // New edge is consistent with existing rank assignment. 526 return true; 527 } 528 529 // Current rank assignments are incompatible with the new edge. Recompute. 530 // We only need to consider nodes that fall in the range [ny->rank,nx->rank]. 531 if (!ForwardDFS(r, y, nx->rank)) { 532 // Found a cycle. Undo the insertion and tell caller. 533 nx->out.erase(y); 534 ny->in.erase(x); 535 // Since we do not call Reorder() on this path, clear any visited 536 // markers left by ForwardDFS. 537 for (const auto& d : r->deltaf_) { 538 r->nodes_[static_cast<uint32_t>(d)]->visited = false; 539 } 540 return false; 541 } 542 BackwardDFS(r, x, ny->rank); 543 Reorder(r); 544 return true; 545 } 546 547 static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int32_t upper_bound) { 548 // Avoid recursion since stack space might be limited. 549 // We instead keep a stack of nodes to visit. 550 r->deltaf_.clear(); 551 r->stack_.clear(); 552 r->stack_.push_back(n); 553 while (!r->stack_.empty()) { 554 n = r->stack_.back(); 555 r->stack_.pop_back(); 556 Node* nn = r->nodes_[static_cast<uint32_t>(n)]; 557 if (nn->visited) continue; 558 559 nn->visited = true; 560 r->deltaf_.push_back(n); 561 562 HASH_FOR_EACH(w, nn->out) { 563 Node* nw = r->nodes_[static_cast<uint32_t>(w)]; 564 if (nw->rank == upper_bound) { 565 return false; // Cycle 566 } 567 if (!nw->visited && nw->rank < upper_bound) { 568 r->stack_.push_back(w); 569 } 570 } 571 } 572 return true; 573 } 574 575 static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int32_t lower_bound) { 576 r->deltab_.clear(); 577 r->stack_.clear(); 578 r->stack_.push_back(n); 579 while (!r->stack_.empty()) { 580 n = r->stack_.back(); 581 r->stack_.pop_back(); 582 Node* nn = r->nodes_[static_cast<uint32_t>(n)]; 583 if (nn->visited) continue; 584 585 nn->visited = true; 586 r->deltab_.push_back(n); 587 588 HASH_FOR_EACH(w, nn->in) { 589 Node* nw = r->nodes_[static_cast<uint32_t>(w)]; 590 if (!nw->visited && lower_bound < nw->rank) { 591 r->stack_.push_back(w); 592 } 593 } 594 } 595 } 596 597 static void Reorder(GraphCycles::Rep* r) { 598 Sort(r->nodes_, &r->deltab_); 599 Sort(r->nodes_, &r->deltaf_); 600 601 // Adds contents of delta lists to list_ (backwards deltas first). 602 r->list_.clear(); 603 MoveToList(r, &r->deltab_, &r->list_); 604 MoveToList(r, &r->deltaf_, &r->list_); 605 606 // Produce sorted list of all ranks that will be reassigned. 607 r->merged_.resize(r->deltab_.size() + r->deltaf_.size()); 608 std::merge(r->deltab_.begin(), r->deltab_.end(), 609 r->deltaf_.begin(), r->deltaf_.end(), 610 r->merged_.begin()); 611 612 // Assign the ranks in order to the collected list. 613 for (uint32_t i = 0; i < r->list_.size(); i++) { 614 r->nodes_[static_cast<uint32_t>(r->list_[i])]->rank = r->merged_[i]; 615 } 616 } 617 618 static void Sort(const Vec<Node*>& nodes, Vec<int32_t>* delta) { 619 struct ByRank { 620 const Vec<Node*>* nodes; 621 bool operator()(int32_t a, int32_t b) const { 622 return (*nodes)[static_cast<uint32_t>(a)]->rank < 623 (*nodes)[static_cast<uint32_t>(b)]->rank; 624 } 625 }; 626 ByRank cmp; 627 cmp.nodes = &nodes; 628 std::sort(delta->begin(), delta->end(), cmp); 629 } 630 631 static void MoveToList( 632 GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst) { 633 for (auto& v : *src) { 634 int32_t w = v; 635 // Replace v entry with its rank 636 v = r->nodes_[static_cast<uint32_t>(w)]->rank; 637 // Prepare for future DFS calls 638 r->nodes_[static_cast<uint32_t>(w)]->visited = false; 639 dst->push_back(w); 640 } 641 } 642 643 int GraphCycles::FindPath(GraphId idx, GraphId idy, int max_path_len, 644 GraphId path[]) const { 645 Rep* r = rep_; 646 if (FindNode(r, idx) == nullptr || FindNode(r, idy) == nullptr) return 0; 647 const int32_t x = NodeIndex(idx); 648 const int32_t y = NodeIndex(idy); 649 650 // Forward depth first search starting at x until we hit y. 651 // As we descend into a node, we push it onto the path. 652 // As we leave a node, we remove it from the path. 653 int path_len = 0; 654 655 NodeSet seen; 656 r->stack_.clear(); 657 r->stack_.push_back(x); 658 while (!r->stack_.empty()) { 659 int32_t n = r->stack_.back(); 660 r->stack_.pop_back(); 661 if (n < 0) { 662 // Marker to indicate that we are leaving a node 663 path_len--; 664 continue; 665 } 666 667 if (path_len < max_path_len) { 668 path[path_len] = 669 MakeId(n, rep_->nodes_[static_cast<uint32_t>(n)]->version); 670 } 671 path_len++; 672 r->stack_.push_back(-1); // Will remove tentative path entry 673 674 if (n == y) { 675 return path_len; 676 } 677 678 HASH_FOR_EACH(w, r->nodes_[static_cast<uint32_t>(n)]->out) { 679 if (seen.insert(w)) { 680 r->stack_.push_back(w); 681 } 682 } 683 } 684 685 return 0; 686 } 687 688 bool GraphCycles::IsReachable(GraphId x, GraphId y) const { 689 return FindPath(x, y, 0, nullptr) > 0; 690 } 691 692 void GraphCycles::UpdateStackTrace(GraphId id, int priority, 693 int (*get_stack_trace)(void** stack, int)) { 694 Node* n = FindNode(rep_, id); 695 if (n == nullptr || n->priority >= priority) { 696 return; 697 } 698 n->nstack = (*get_stack_trace)(n->stack, ABSL_ARRAYSIZE(n->stack)); 699 n->priority = priority; 700 } 701 702 int GraphCycles::GetStackTrace(GraphId id, void*** ptr) { 703 Node* n = FindNode(rep_, id); 704 if (n == nullptr) { 705 *ptr = nullptr; 706 return 0; 707 } else { 708 *ptr = n->stack; 709 return n->nstack; 710 } 711 } 712 713 } // namespace synchronization_internal 714 ABSL_NAMESPACE_END 715 } // namespace absl 716 717 #endif // ABSL_LOW_LEVEL_ALLOC_MISSING