Skip to content

Commit 418ed7b

Browse files
authored
Add cycle detection mechanism to Group::dump() (#5693)
This PR adds a cycle-detection mechanism to prevent `Group::dump()` from hanging when there is a group cycle, avoiding infinite looping. Closes CORE-429 --- TYPE: IMPROVEMENT DESC: Add cycle detection to `Group::dump()`.
1 parent 4aad2c8 commit 418ed7b

File tree

3 files changed

+113
-4
lines changed

3 files changed

+113
-4
lines changed

test/src/unit-cppapi-group.cc

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,3 +1165,47 @@ TEST_CASE(
11651165
CHECK(group.is_relative(name));
11661166
}
11671167
}
1168+
1169+
TEST_CASE_METHOD(
1170+
GroupCPPFx,
1171+
"C++ API: Group with cycle, dump and is_relative",
1172+
"[cppapi][group][cycle][non-rest]") {
1173+
// Create groups that form a cycle
1174+
std::string group_a_uri = vfs_test_setup_.array_uri("group_a");
1175+
std::string group_b_uri = vfs_test_setup_.array_uri("group_b");
1176+
tiledb::Group::create(ctx_, group_a_uri);
1177+
tiledb::Group::create(ctx_, group_b_uri);
1178+
1179+
// Open group A and add group B as a member
1180+
{
1181+
tiledb::Group group_a(ctx_, group_a_uri, TILEDB_WRITE);
1182+
group_a.add_member(group_b_uri, false, "group_b");
1183+
group_a.close();
1184+
}
1185+
1186+
// Open group B and add group A as a member, creating a cycle
1187+
{
1188+
tiledb::Group group_b(ctx_, group_b_uri, TILEDB_WRITE);
1189+
group_b.add_member(group_a_uri, false, "group_a");
1190+
group_b.close();
1191+
}
1192+
1193+
// Open group A for reading and test cycle handling
1194+
tiledb::Group group_a(ctx_, group_a_uri, TILEDB_READ);
1195+
1196+
// Test is_relative - this should not hang even with cycles
1197+
CHECK_NOTHROW(group_a.is_relative("group_b"));
1198+
1199+
// Test dump with recursive=false - this should not hang
1200+
std::string dump_non_recursive;
1201+
CHECK_NOTHROW(dump_non_recursive = group_a.dump(false));
1202+
CHECK(dump_non_recursive.find("group_b") != std::string::npos);
1203+
1204+
// Test dump with recursive=true - this should not hang even with cycles
1205+
std::string dump_recursive;
1206+
CHECK_NOTHROW(dump_recursive = group_a.dump(true));
1207+
CHECK(dump_recursive.find("group_b") != std::string::npos);
1208+
CHECK(dump_recursive.find("group_a") != std::string::npos);
1209+
1210+
group_a.close();
1211+
}

tiledb/sm/group/group.cc

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -765,11 +765,30 @@ std::string Group::dump(
765765
const uint64_t num_indents,
766766
bool recursive,
767767
bool print_self) const {
768+
// Create a set to track visited groups and prevent cycles
769+
std::unordered_set<std::reference_wrapper<URI>, URIRefHash, URIRefEqual>
770+
visited;
771+
visited.insert(std::ref(const_cast<URI&>(group_uri_)));
772+
773+
// Create a string stream to hold the dump output
774+
std::stringstream ss;
775+
776+
dump(indent_size, num_indents, recursive, print_self, visited, ss);
777+
return ss.str();
778+
}
779+
780+
void Group::dump(
781+
const uint64_t indent_size,
782+
const uint64_t num_indents,
783+
bool recursive,
784+
bool print_self,
785+
std::unordered_set<std::reference_wrapper<URI>, URIRefHash, URIRefEqual>&
786+
visited,
787+
std::stringstream& ss) const {
768788
// Build the indentation literal and the leading indentation literal.
769789
const std::string indent(indent_size, '-');
770790
const std::string l_indent(indent_size * num_indents, '-');
771791

772-
std::stringstream ss;
773792
if (print_self) {
774793
ss << l_indent << group_uri_.last_path_part() << " "
775794
<< object_type_str(ObjectType::GROUP) << std::endl;
@@ -784,11 +803,23 @@ std::string Group::dump(
784803
member_uri = group_uri_.join_path(it->uri().to_string());
785804
}
786805

806+
// Check if we've already visited this group to avoid cycles
807+
if (visited.find(std::ref(member_uri)) != visited.end()) {
808+
ss << std::endl;
809+
continue;
810+
}
811+
787812
Group group_rec(resources_, member_uri);
788813
try {
789814
group_rec.open(QueryType::READ);
790815
ss << std::endl;
791-
ss << group_rec.dump(indent_size, num_indents + 2, recursive, false);
816+
// Mark this group as visited before recursing
817+
visited.insert(std::ref(member_uri));
818+
group_rec.dump(
819+
indent_size, num_indents + 2, recursive, false, visited, ss);
820+
// Remove from visited set after recursion to allow the same group
821+
// to appear in different branches (but not in the same path)
822+
visited.erase(std::ref(member_uri));
792823
group_rec.close();
793824
} catch (GroupNotFoundException&) {
794825
// If the group no longer exists in storage it will be listed but we
@@ -799,8 +830,6 @@ std::string Group::dump(
799830
ss << std::endl;
800831
}
801832
}
802-
803-
return ss.str();
804833
}
805834

806835
/* ********************************* */

tiledb/sm/group/group.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
#define TILEDB_GROUP_H
3535

3636
#include <atomic>
37+
#include <functional>
38+
#include <sstream>
39+
#include <unordered_set>
3740

3841
#include "tiledb/sm/config/config.h"
3942
#include "tiledb/sm/crypto/encryption_key.h"
@@ -48,6 +51,7 @@ using namespace tiledb::common;
4851
namespace tiledb::sm {
4952

5053
class ContextResources;
54+
class URI;
5155

5256
class GroupDetailsException : public StatusException {
5357
public:
@@ -523,6 +527,38 @@ class Group {
523527

524528
/** Opens an group for writes. */
525529
void group_open_for_writes();
530+
531+
// Hash and equality for std::reference_wrapper<URI>
532+
struct URIRefHash {
533+
size_t operator()(std::reference_wrapper<URI> ref) const {
534+
return std::hash<URI>{}(ref.get());
535+
}
536+
};
537+
struct URIRefEqual {
538+
bool operator()(
539+
std::reference_wrapper<URI> a, std::reference_wrapper<URI> b) const {
540+
return a.get() == b.get();
541+
}
542+
};
543+
544+
/**
545+
* Helper for dump with cycle detection
546+
*
547+
* @param indent_size
548+
* @param num_indents
549+
* @param recursive
550+
* @param print_self
551+
* @param visited Set of visited group URI references to detect cycles
552+
* @param ss Stringstream to append output to
553+
*/
554+
void dump(
555+
const uint64_t indent_size,
556+
const uint64_t num_indents,
557+
bool recursive,
558+
bool print_self,
559+
std::unordered_set<std::reference_wrapper<URI>, URIRefHash, URIRefEqual>&
560+
visited,
561+
std::stringstream& ss) const;
526562
};
527563
} // namespace tiledb::sm
528564

0 commit comments

Comments
 (0)