Skip to content

[Toy] Fix RemoteDB concurrent logic - #1189

Open
ccgsnet wants to merge 14 commits into
masterfrom
remotedb-improvements
Open

[Toy] Fix RemoteDB concurrent logic#1189
ccgsnet wants to merge 14 commits into
masterfrom
remotedb-improvements

Conversation

@ccgsnet

@ccgsnet ccgsnet commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Makes RemoteAtomDB safe under concurrent pattern queries (e.g. run_toy.sh AndNot / multiple LinkTemplates), which previously segfaulted (exit 139) due to races on the peer cache.

InMemoryDB thread-safety

  • Add an internal recursive_mutex and take it on every public API method.
  • Mutex is recursive because public methods re-enter each other (e.g. delete_linkdelete_atom, re-index visitor → add_pattern).
  • Callers can share one InMemoryDB across threads without external locking.

RemoteAtomDBPeer locking (kept simple)

  • Cache atom data no longer needs peer-level locking; InMemoryDB owns that.
  • peer_mutex_ only guards peer bookkeeping: cache_ pointer swap, fetched_link_templates_, and staged_handles_.
  • Reads use a short-lived cache() snapshot (shared_ptr); writes hold peer_mutex_ so staged handles stay consistent with the cache generation.
  • release_cache never drops staged writes, even on a “clear without persistence” path.

Test plan

  • //tests/cpp:inmemorydb_test
  • //tests/cpp:remote_atomdb_test
  • src/tests/scripts/run_toy.sh with RemoteDB peers (concurrent LinkTemplates)

@ccgsnet ccgsnet self-assigned this Jul 22, 2026
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: eb65055f-a538-4c56-add6-6a386c975250

📥 Commits

Reviewing files that changed from the base of the PR and between 8500319 and 7898cf7.

📒 Files selected for processing (8)
  • config/das.json
  • src/atomdb/inmemorydb/InMemoryDB.cc
  • src/atomdb/inmemorydb/InMemoryDB.h
  • src/atomdb/remotedb/RemoteAtomDB.cc
  • src/atomdb/remotedb/RemoteAtomDBPeer.cc
  • src/atomdb/remotedb/RemoteAtomDBPeer.h
  • src/tests/cpp/remote_atomdb_test.cc
  • src/tests/main/evaluation_evolution.cc
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/tests/main/evaluation_evolution.cc
  • src/atomdb/remotedb/RemoteAtomDBPeer.h
  • src/atomdb/remotedb/RemoteAtomDB.cc
  • src/atomdb/remotedb/RemoteAtomDBPeer.cc
  • src/atomdb/inmemorydb/InMemoryDB.cc

  • Fixes RemoteDB concurrency risks by adding recursive locking to InMemoryDB and scoped locking for RemoteAtomDBPeer cache, template, and staged-handle state.
  • Separates write buffers from read caches. Cache snapshots reduce read contention, but deep atom copies and shared-trie snapshots may increase allocation and memory use on hot paths.
  • Preserves staged writes after failed or non-persistent cache release. Persistence now resolves dependencies and drops unresolved links, which requires careful correctness review.
  • Updates peer configuration, cache flushing, composite-link handling, and public cache-release APIs. These behavior changes have coverage in inmemorydb_test, redis_mongodb_test, and remote_atomdb_test.
  • Tests cover concurrent writes, cache release, failed flushes, read-only peers, and persistence. Concurrent run_toy.sh validation remains incomplete.

Walkthrough

This change adds snapshot-based concurrency to InMemoryDB, separates remote write buffers from read caches, updates federation lookup and aggregation, adds cache release and persistence handling, changes composite-link validation, and expands configuration and test coverage.

Changes

Remote federation behavior

Layer / File(s) Summary
Thread-safe in-memory storage
src/atomdb/inmemorydb/InMemoryDB.*, src/tests/cpp/inmemorydb_test.cc
InMemoryDB uses shared trie snapshots for reads and serialized mutation helpers. It adds get_all_atoms() and drop_all(), deep-copy reads, reset support, and replacement-aware index updates.
Composite link persistence
src/atomdb/redis_mongodb/RedisMongoDB.cc, src/tests/cpp/redis_mongodb_test.cc
Missing-target checks now depend on composite_type_enabled_. Tests cover persistence and deletion of links with missing targets.
Peer cache and local persistence
src/atomdb/remotedb/RemoteAtomDBPeer.*, src/tests/cpp/remote_atomdb_test.cc, src/tests/assets/remotedb_config_single.json
Remote peers use separate write and read-cache generations. Cache release persists dirty atoms, handles failed flushes, resolves link dependencies, and supports readonly peers.
Federation aggregation and cache release
src/atomdb/remotedb/RemoteAtomDB.*, src/main/bus_client.cc
Federation lookup prioritizes writable peers and caches. Existence fan-out and write aggregation are consolidated. Cache release is broadcast to peers.
Runtime configuration and integration validation
config/das.json, src/tests/main/evaluation_evolution.cc, src/tests/cpp/BUILD
Peer endpoints and persistence settings are updated. Evaluation flows flush remote link-template caches and bulk-insert parser-produced atoms.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Evaluation
  participant RemoteAtomDB
  participant RemoteAtomDBPeer
  participant LocalPersistence
  Evaluation->>RemoteAtomDB: release_caches(link_schema, persist, force)
  RemoteAtomDB->>RemoteAtomDBPeer: release(link_schema, persist, force)
  RemoteAtomDBPeer->>LocalPersistence: persist staged nodes and links
  LocalPersistence-->>RemoteAtomDBPeer: persistence result
  RemoteAtomDBPeer-->>RemoteAtomDB: release result
Loading

Possibly related PRs

  • singnet/das#1154: Overlaps in evaluation_evolution.cc link-building and query-evolution changes.
  • singnet/das#1155: Directly relates to RemoteAtomDB and RemoteAtomDBPeer cache, persistence, and federation changes.
  • singnet/das#1203: Builds on related InMemoryDB trie lookup, cloning, and size changes.

Suggested reviewers: marcocapozzoli

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: fixing concurrent logic in RemoteDB.
Description check ✅ Passed The description directly explains the concurrency fixes, affected components, and test coverage.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Tests For Behavior Changes ✅ Passed Production code changes (InMemoryDB locking, RemoteAtomDB/Peer concurrent cache handling, RedisMongoDB composite-type checks, new public APIs) have corresponding comprehensive test updates: +123 li...
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch remotedb-improvements

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/atomdb/remotedb/RemoteAtomDB.cc (1)

415-440: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add a multi-peer batch-delete test. max matches the replicated-peer model here, but remote_atomdb_test.cc doesn’t cover delete_atoms/delete_nodes/delete_links across multiple peers, so the count semantics aren’t pinned down.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/atomdb/remotedb/RemoteAtomDB.cc` around lines 415 - 440, Add a multi-peer
batch-delete test in remote_atomdb_test.cc covering delete_atoms, delete_nodes,
and delete_links through RemoteAtomDB with multiple replicated peers. Assert
that each method returns the maximum deletion count reported by any peer,
preserving the existing max-based semantics.
src/atomdb/inmemorydb/InMemoryDB.cc (1)

190-208: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Critical: get_matching_atoms wraps trie-owned Atom* in a fresh shared_ptr, causing a double-free/use-after-free.

shared_ptr<Node>(dynamic_cast<Node*>(atom)) (and the Link branch) take ownership of the same raw pointer still owned by AtomTrieValue. When either the returned shared_ptr or ~AtomTrieValue() runs first, the other holder is left with a dangling pointer, and the second destruction double-frees. get_atom() and get_all_atoms() were just fixed to return safe deep clones via the new clone_atom() helper — this method was left with the pre-existing unsafe pattern despite the added locking, which only serializes access to (but doesn't fix) the unsafe aliasing. Given this PR's explicit goal is eliminating concurrency-related crashes, this is a live segfault/UB risk on this hot path.

🐛 Proposed fix using the new `clone_atom()` helper
     Atom* atom = atom_trie_value->get_atom();
-    if (Atom::is_node(*atom)) {
-        matching_atoms.push_back(shared_ptr<Node>(dynamic_cast<Node*>(atom)));
-    } else {
-        matching_atoms.push_back(shared_ptr<Link>(dynamic_cast<Link*>(atom)));
-    }
+    auto cloned = clone_atom(atom);
+    if (cloned != nullptr) {
+        matching_atoms.push_back(cloned);
+    }
     return matching_atoms;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/atomdb/inmemorydb/InMemoryDB.cc` around lines 190 - 208, Update
InMemoryDB::get_matching_atoms to return deep clones of the trie-owned atom
using the existing clone_atom() helper instead of constructing shared_ptr<Node>
or shared_ptr<Link> from the raw Atom*. Preserve the node/link result behavior
while ensuring the returned pointers have independent ownership from
AtomTrieValue.
src/tests/cpp/inmemorydb_test.cc (1)

546-737: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

No concurrency test for the newly synchronized InMemoryDB.

All new tests (GetAllAtoms, DropAll*, UpsertReplacesCustomAttributes) are single-threaded. The PR's stated motivation is fixing segfaults from concurrent races on this exact class, and the header now documents it as thread-safe — but nothing here exercises multiple threads calling add_link/get_atom/drop_all/query_for_pattern concurrently to validate that claim. As per path instructions, tests here should prioritize "thread/proxy interactions" for behavior changes like this.

Want me to draft a stress test that spawns several threads doing concurrent add_link/get_atom/delete_atom against a shared InMemoryDB and asserts no crash/inconsistent state under ThreadSanitizer?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tests/cpp/inmemorydb_test.cc` around lines 546 - 737, Extend the
InMemoryDB tests with a multithreaded stress test that shares one database
across threads and concurrently exercises add_link, get_atom, drop_all or
delete_atom, and query_for_pattern. Synchronize thread start and completion, run
enough iterations to expose races, and assert only safe post-join invariants
such as a valid atom count or empty state; ensure the test is suitable for
ThreadSanitizer and does not dereference results invalidated by concurrent
drop_all.

Source: Path instructions

🧹 Nitpick comments (2)
src/atomdb/inmemorydb/InMemoryDB.h (1)

20-27: 🚀 Performance & Scalability | 🔵 Trivial

Single recursive mutex serializes all readers and writers.

Every public method — including pure reads like get_atom/query_for_pattern — now contends on the same recursive_mutex. This is a reasonable, safe trade-off for reentrancy (methods call each other), but under heavy concurrent pattern-query load it removes any read/read parallelism that existed before. Worth keeping an eye on if this becomes a throughput bottleneck; a shared_mutex isn't easily reentrant-safe for the current call patterns, so this isn't a quick swap.

Also applies to: 89-91

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/atomdb/inmemorydb/InMemoryDB.h` around lines 20 - 27, Keep the single
internal recursive mutex and its serialization of all public InMemoryDB methods,
including read operations such as get_atom and query_for_pattern; no code change
is required for this advisory performance observation.
src/atomdb/inmemorydb/InMemoryDB.cc (1)

462-513: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stale comment: "Release locks before calling delete_atom to avoid deadlock" no longer reflects the locking model.

With the new recursive_mutex held via lock_guard at function entry (line 463), no lock is actually released before the recursive call into delete_atom — the comment predates the new locking and is now misleading about how re-entrancy is achieved.

✏️ Suggested comment update
-    // Release locks before calling delete_atom to avoid deadlock
-    // Delete targets that have no other incoming links
+    // Safe to re-enter: api_mutex_ is recursive, so delete_atom can be called
+    // while this lock is still held by the current thread.
+    // Delete targets that have no other incoming links
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/atomdb/inmemorydb/InMemoryDB.cc` around lines 462 - 513, Update the
comment immediately before the targets_to_delete loop in InMemoryDB::delete_link
to accurately describe the current recursive_mutex-based locking behavior;
remove the claim that locks are released before delete_atom, while preserving
the existing recursive deletion logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/atomdb/inmemorydb/InMemoryDB.h`:
- Around line 84-85: Add brief Doxygen blocks immediately above the public
InMemoryDB methods get_all_atoms() and drop_all() in the header, documenting
each API’s purpose according to the project’s existing style.

---

Outside diff comments:
In `@src/atomdb/inmemorydb/InMemoryDB.cc`:
- Around line 190-208: Update InMemoryDB::get_matching_atoms to return deep
clones of the trie-owned atom using the existing clone_atom() helper instead of
constructing shared_ptr<Node> or shared_ptr<Link> from the raw Atom*. Preserve
the node/link result behavior while ensuring the returned pointers have
independent ownership from AtomTrieValue.

In `@src/atomdb/remotedb/RemoteAtomDB.cc`:
- Around line 415-440: Add a multi-peer batch-delete test in
remote_atomdb_test.cc covering delete_atoms, delete_nodes, and delete_links
through RemoteAtomDB with multiple replicated peers. Assert that each method
returns the maximum deletion count reported by any peer, preserving the existing
max-based semantics.

In `@src/tests/cpp/inmemorydb_test.cc`:
- Around line 546-737: Extend the InMemoryDB tests with a multithreaded stress
test that shares one database across threads and concurrently exercises
add_link, get_atom, drop_all or delete_atom, and query_for_pattern. Synchronize
thread start and completion, run enough iterations to expose races, and assert
only safe post-join invariants such as a valid atom count or empty state; ensure
the test is suitable for ThreadSanitizer and does not dereference results
invalidated by concurrent drop_all.

---

Nitpick comments:
In `@src/atomdb/inmemorydb/InMemoryDB.cc`:
- Around line 462-513: Update the comment immediately before the
targets_to_delete loop in InMemoryDB::delete_link to accurately describe the
current recursive_mutex-based locking behavior; remove the claim that locks are
released before delete_atom, while preserving the existing recursive deletion
logic.

In `@src/atomdb/inmemorydb/InMemoryDB.h`:
- Around line 20-27: Keep the single internal recursive mutex and its
serialization of all public InMemoryDB methods, including read operations such
as get_atom and query_for_pattern; no code change is required for this advisory
performance observation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 793c694b-19d0-449d-9f26-da5f0f483739

📥 Commits

Reviewing files that changed from the base of the PR and between b89bc77 and 44ee6da.

📒 Files selected for processing (14)
  • config/das.json
  • src/atomdb/inmemorydb/InMemoryDB.cc
  • src/atomdb/inmemorydb/InMemoryDB.h
  • src/atomdb/redis_mongodb/RedisMongoDB.cc
  • src/atomdb/remotedb/RemoteAtomDB.cc
  • src/atomdb/remotedb/RemoteAtomDB.h
  • src/atomdb/remotedb/RemoteAtomDBPeer.cc
  • src/atomdb/remotedb/RemoteAtomDBPeer.h
  • src/tests/assets/remotedb_config_single.json
  • src/tests/cpp/BUILD
  • src/tests/cpp/inmemorydb_test.cc
  • src/tests/cpp/redis_mongodb_test.cc
  • src/tests/cpp/remote_atomdb_test.cc
  • src/tests/main/evaluation_evolution.cc

Comment on lines +84 to +85
vector<shared_ptr<Atom>> get_all_atoms();
void drop_all();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add brief Doxygen blocks for the new public get_all_atoms() / drop_all() APIs.

As per coding guidelines, src/**/*.h files should "Use brief Doxygen /** ... */ blocks above public API methods in C++ header files." Neither new method has one.

+    /** Returns deep clones of every stored atom. */
     vector<shared_ptr<Atom>> get_all_atoms();
+    /** Removes all stored atoms and resets all indexes. */
     void drop_all();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
vector<shared_ptr<Atom>> get_all_atoms();
void drop_all();
/** Returns deep clones of every stored atom. */
vector<shared_ptr<Atom>> get_all_atoms();
/** Removes all stored atoms and resets all indexes. */
void drop_all();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/atomdb/inmemorydb/InMemoryDB.h` around lines 84 - 85, Add brief Doxygen
blocks immediately above the public InMemoryDB methods get_all_atoms() and
drop_all() in the header, documenting each API’s purpose according to the
project’s existing style.

Sources: Coding guidelines, Path instructions

@andre-senna
andre-senna self-requested a review July 27, 2026 13:44
Comment thread src/tests/main/evaluation_evolution.cc Outdated
}

static void flush_remote_link_template_cache(bool force = false) {
if (auto remote_db = dynamic_pointer_cast<RemoteAtomDB>(db); remote_db != nullptr) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (auto remote_db = dynamic_pointer_cast<RemoteAtomDB>(db); remote_db != nullptr) {
if (auto remote_db = dynamic_pointer_cast<RemoteAtomDB>(db), remote_db != nullptr) {

Comment thread src/atomdb/inmemorydb/InMemoryDB.cc Outdated
Comment on lines +66 to +76
if (atom == nullptr) {
return nullptr;
}

if (atom->arity() == 0) {
auto node = dynamic_cast<const Node*>(atom);
return (node != nullptr) ? make_shared<Node>(*node) : nullptr;
}

auto link = dynamic_cast<const Link*>(atom);
return (link != nullptr) ? make_shared<Link>(*link) : nullptr;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (atom == nullptr) {
return nullptr;
}
if (atom->arity() == 0) {
auto node = dynamic_cast<const Node*>(atom);
return (node != nullptr) ? make_shared<Node>(*node) : nullptr;
}
auto link = dynamic_cast<const Link*>(atom);
return (link != nullptr) ? make_shared<Link>(*link) : nullptr;
auto link = dynamic_cast<const Link*>(atom);
if (link != nullptr) {
return make_shared<Link>(*link);
}
auto node = dynamic_cast<const Node*>(atom);
if (node != nullptr) {
return make_shared<Node>(*node);
}
return nullptr;

This was referenced Jul 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/atomdb/remotedb/RemoteAtomDB.cc (1)

165-179: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

get_matching_atoms keeps the first peer's copy, while get_atom prefers writable peers.

get_atom was changed to probe writable_peers_ first, because the writable peer holds the authoritative copy of a content-addressed handle whose custom attributes changed. StrengthUpdateVisibleAcrossPeers locks that behavior in.

This method still iterates remote_db_ in map order and keeps the first atom seen for each handle. A readonly peer whose uid sorts before the writable peer returns the stale copy, and the seen set then rejects the fresh one. The two read paths disagree for the same handle.

Iterate writable_peers_ first, then readonly_peers_, so the precedence matches get_atom.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/atomdb/remotedb/RemoteAtomDB.cc` around lines 165 - 179, Update
RemoteAtomDB::get_matching_atoms to iterate writable_peers_ before
readonly_peers_, preserving the existing handle-based deduplication so
writable-peer atoms are retained and readonly duplicates are ignored. Align its
peer precedence with get_atom without changing the matching or result behavior.
src/atomdb/inmemorydb/InMemoryDB.cc (1)

375-420: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Confirm that is_new gating cannot skip required index entries.

Index building now runs only when the handle is absent from atoms_trie_. This is correct while every link insertion path passes through add_links_unlocked and every deletion path removes the pattern and incoming-set entries together with the atom. delete_link_unlocked does both, so the invariant holds today.

One case deserves a test: a link that is deleted and then re-added in the same InMemoryDB instance must regain its incoming-set and pattern entries. A second case: re_index_patterns(false) followed by add_links on an existing handle must not silently lose entries. As per coding guidelines, test updates are required when production code changes behavior. Please add these to src/tests/cpp/inmemorydb_test.cc.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/atomdb/inmemorydb/InMemoryDB.cc` around lines 375 - 420, Add regression
tests in inmemorydb_test.cc covering re-adding a deleted link and verifying its
incoming-set and pattern entries are restored, plus calling
re_index_patterns(false) followed by add_links for an existing handle and
verifying required index entries remain present. Use the existing InMemoryDB
test helpers and assert both scenarios preserve indexing behavior.

Source: Coding guidelines

🧹 Nitpick comments (6)
src/tests/cpp/redis_mongodb_test.cc (1)

1171-1177: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse test_atomdb_json_config() instead of rebuilding the config inline.

CompositeTypeEnabledFlag at lines 1252-1254 already builds a disabled-composite database with test_atomdb_json_config(). This test duplicates the endpoints, credentials, and type fields by hand. If the shared fixture endpoints change, this test breaks alone.

♻️ Proposed reuse of the shared config helper
-    auto json = nlohmann::json();
-    json["type"] = "redismongodb";
-    json["composite_type_enabled"] = false;
-    json["redis"] = {{"endpoint", "localhost:40020"}, {"cluster", false}};
-    json["mongodb"] = {{"endpoint", "localhost:40021"}, {"username", "admin"}, {"password", "admin"}};
-    auto local_db = make_shared<RedisMongoDB>("test_", false, commons::JsonConfig(json));
+    auto config_disabled = test_atomdb_json_config();
+    config_disabled["composite_type_enabled"] = false;
+    auto local_db = make_shared<RedisMongoDB>("test_", false, config_disabled);

Note: the hardcoded ports themselves are fine. Based on learnings, hardcoded ports in src/tests/cpp/ are an intentional team convention.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tests/cpp/redis_mongodb_test.cc` around lines 1171 - 1177, Update the
test setup around RedisMongoDB construction to reuse the existing
test_atomdb_json_config() helper instead of assembling the nlohmann::json
configuration inline. Preserve the disabled composite_type_enabled setting and
existing test behavior while relying on the shared fixture for type, endpoints,
and credentials.

Source: Learnings

src/atomdb/inmemorydb/InMemoryDB.cc (1)

573-576: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the leftover reasoning from this comment.

The comment describes an approach that was considered and rejected ("We temporarily expose the unlocked path by having the public methods detect nested use... Instead, inline:"). It describes code that does not exist. Keep only the fact that matters: the visitor must use the unlocked helpers because write_mutex_ is not recursive.

♻️ Proposed comment cleanup
-    // re_index_visitor calls the public add_pattern / match_pattern_index_schema locking
-    // wrappers — which would deadlock on a plain mutex. Use unlocked helpers via a thin
-    // visitor that calls a free function taking InMemoryDB*. We temporarily expose the
-    // unlocked path by having the public methods detect nested use... Instead, inline:
+    // write_mutex_ is not recursive, so the visitor must call the *_unlocked helpers
+    // instead of the public locking wrappers.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/atomdb/inmemorydb/InMemoryDB.cc` around lines 573 - 576, Remove the
discarded-approach text from the comment near re_index_visitor. Keep only that
the visitor must call unlocked helpers because write_mutex_ is non-recursive.
src/atomdb/remotedb/RemoteAtomDB.h (1)

87-88: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a brief Doxygen block to release_caches.

release_caches is new public API and its two boolean parameters carry non-obvious semantics. persist selects flush-versus-drop and force overrides the "schema not cached" no-op path in RemoteAtomDBPeer::release. A caller cannot infer that from the signature. The class doc and the dependency-injection constructor in this header already use /** ... */; follow the same pattern here.

As per coding guidelines: "Use brief Doxygen /** ... */ blocks above public API methods in C++ header files".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/atomdb/remotedb/RemoteAtomDB.h` around lines 87 - 88, Add a brief Doxygen
block immediately above the public RemoteAtomDB::release_caches declaration,
documenting the link_schema argument and clarifying that persist selects
flushing versus dropping caches while force overrides the no-op path when the
schema is not cached. Follow the existing /** ... */ documentation style in the
header.

Source: Coding guidelines

src/atomdb/remotedb/RemoteAtomDB.cc (1)

320-385: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

The write fan-out ignores writable_peers_ and copies the handle vector.

Two points.

First, the comment on line 321 states that writes only land on writable peers, but every one of these six methods iterates remote_db_. Each readonly peer is still called and each one returns an empty result from its internal is_readonly() gate. finalize_peer_lists builds writable_peers_ exactly so hot paths avoid this check, and no write path uses it. Iterate writable_peers_ here.

Second, handles = peer_handles; copies the whole vector for every peer that returns a non-empty result. Move it instead.

♻️ Proposed change, shown for `add_atoms`; apply the same shape to the other five
 vector<string> RemoteAtomDB::add_atoms(const vector<atoms::Atom*>& atoms,
                                        bool is_transactional,
                                        const atoms::Merger* merger) {
     vector<string> handles;
-    for (auto& [uid, peer] : remote_db_) {
+    for (auto& [uid, peer] : writable_peers_) {
         LOG_DEBUG("add_atoms(" << atoms.size() << ") to peer [" << uid << "]");
         auto peer_handles = peer->add_atoms(atoms, is_transactional, merger);
-        if (!peer_handles.empty()) handles = peer_handles;
+        if (!peer_handles.empty()) handles = std::move(peer_handles);
     }
     return handles;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/atomdb/remotedb/RemoteAtomDB.cc` around lines 320 - 385, Update add_atom,
add_node, add_link, add_atoms, add_nodes, and add_links to iterate
writable_peers_ instead of remote_db_, preserving the existing logging and write
calls while avoiding readonly peers. In the vector-returning methods, replace
the non-empty result assignment with a move from peer_handles so the final
handle vector is transferred rather than copied.

Source: Path instructions

src/tests/cpp/remote_atomdb_test.cc (1)

1091-1112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The failed-flush test only covers the node path.

FailedFlushRestagesDirtyAtoms stages one node. RemoteAtomDBPeer::persist_atoms_to_local handles links through a separate path: dependency discovery through atomdb_->get_atom, the composite_type_enabled() branch, and the batch loop that logs "dropping N links with unresolved targets" and abandons those writes. None of that runs in this test.

The link drop branch loses data silently. Add a case that stages a link whose targets exist on no reachable backend and assert the resulting state. Add a case where the flush of links throws and assert that the link is re-staged.

I can write both cases if you want them. Do you want me to open an issue to track this?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tests/cpp/remote_atomdb_test.cc` around lines 1091 - 1112, Extend
FailedFlushRestagesDirtyAtoms with coverage for links: add a case staging a link
whose targets are absent from every reachable backend and assert the expected
resulting state, including the unresolved-target batch path; add another case
using a failing local backend, assert the link remains cached after
release_cache throws, then restore the backend and verify the re-staged link
persists. Exercise RemoteAtomDBPeer::persist_atoms_to_local and preserve the
existing node assertions.

Source: Path instructions

src/atomdb/remotedb/RemoteAtomDBPeer.cc (1)

705-710: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

The quiescence wait is an unbounded spin loop.

while (old_write_buffer.use_count() > 1) this_thread::yield(); burns a full core while any writer still holds the retired buffer. InMemoryDB::add_atoms takes write_mutex_ and can hold the snapshot for the whole batch, so a large bulk insert makes this spin for the duration of that batch. auto_cleanup() calls release_cache() from the cleanup thread, so the spin can run on a background thread under memory pressure, which is the worst moment to consume CPU.

The ownership reasoning is sound. Add a backoff so the wait yields the core instead of spinning hot.

♻️ Proposed backoff
-    while (old_write_buffer.use_count() > 1) {
-        this_thread::yield();
-    }
+    unsigned int spins = 0;
+    while (old_write_buffer.use_count() > 1) {
+        if (++spins < 64) {
+            this_thread::yield();
+        } else {
+            this_thread::sleep_for(chrono::microseconds(100));
+        }
+    }

This needs #include <chrono> in this file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/atomdb/remotedb/RemoteAtomDBPeer.cc` around lines 705 - 710, Update the
quiescence loop around old_write_buffer in the cleanup path to add a timed
backoff between use_count() checks, using the proposed chrono-based sleep and
adding the required <chrono> include. Preserve the existing ownership condition
and flush behavior once the buffer becomes solely owned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@config/das.json`:
- Line 3: Document the breaking atomdb.type change from redismongodb to remotedb
and the required peer2/peer3 database endpoints in the migration notes, or add
provisioning for ports 40030–40032 and 40040–40042. Preserve the existing config
keys and JSON structure, and ensure a fresh deployment can start against the new
federation topology.

In `@src/atomdb/inmemorydb/InMemoryDB.cc`:
- Around line 609-613: Update InMemoryDB::match_pattern_index_schema() to
acquire write_mutex_ before delegating to match_pattern_index_schema_unlocked(),
ensuring pattern_index_schema_map is protected while being iterated. Preserve
the unlocked variant for callers that already hold the mutation lock, and revise
the stale safety comment accordingly.
- Around line 560-571: Prevent use-after-free during trie replacement by
updating src/atomdb/inmemorydb/InMemoryDB.cc:560-571 in drop_all() and
re_index_patterns() to use a shared_mutex model with unique locking for
reset_trie operations, or clear trie contents in place while preserving trie
object lifetimes; update src/atomdb/inmemorydb/InMemoryDB.h:23-30 to document
the implemented reader/writer locking behavior and the effects of drop_all() and
re_index_patterns().

In `@src/atomdb/remotedb/RemoteAtomDB.cc`:
- Around line 115-124: Change the log statement in
RemoteAtomDB::composite_type_enabled() so this normal derivation message is not
emitted at ERROR level; remove it or use the project’s appropriate non-error
logging level while preserving the peer iteration and return behavior.

In `@src/atomdb/remotedb/RemoteAtomDBPeer.cc`:
- Around line 463-497: In src/atomdb/remotedb/RemoteAtomDBPeer.cc:463-497,
update delete_atoms, delete_nodes, and delete_links to return the number of
distinct handles removed from at least one layer rather than summing overlapping
per-layer counts. In src/atomdb/remotedb/RemoteAtomDBPeer.cc:509-531, update
node_count, link_count, and atom_count to use one documented layer as the peer
population source instead of adding read-cache, write-buffer, and
local-persistence counts. Extend AtomsCount to perform a read that warms the
cache and verify the count remains correct.
- Around line 68-80: Update get_atom so local_persistence_ lookups do not
redundantly warm read_cache_ on every hit. Preserve the
NonStagedPrefersLocalOverStaleCache lookup order and local result behavior, but
skip rc->add_atom(atom.get()) when the cache already contains the handle, or
remove this local-to-cache warming entirely.

In `@src/atomdb/remotedb/RemoteAtomDBPeer.h`:
- Around line 140-142: Update the comment above peer_mutex_ to remove the claim
that it is never held across InMemoryDB calls. Document that release_cache() and
release() may hold peer_mutex_ while calling atom_count() on write_buffer_ or
read_cache_, while preserving the invariant that InMemoryDB does not acquire
peer_mutex_ and avoiding claims about local_persistence_ or atomdb_ I/O unless
they remain accurate.

In `@src/tests/cpp/remote_atomdb_test.cc`:
- Around line 1128-1139: The writer worker launched in the test should not call
ASSERT_FALSE directly. Record failures from peer->add_node in an atomic failure
counter accessible to each worker, allow all threads to finish, then assert the
counter is zero after joining the writer and releaser threads.

In `@src/tests/scripts/run_toy.sh`:
- Around line 19-32: Restore the database setup and knowledge-base loading steps
in run_toy.sh, including das-cli db start and make run-db-loader using the $KB
argument, so the script provisions the remotedb peers defined by
config/das.json. If setup must remain external, remove the disabled block and
add an explicit comment documenting the required running, preloaded database
precondition; otherwise ensure $KB is validated or consumed.

---

Outside diff comments:
In `@src/atomdb/inmemorydb/InMemoryDB.cc`:
- Around line 375-420: Add regression tests in inmemorydb_test.cc covering
re-adding a deleted link and verifying its incoming-set and pattern entries are
restored, plus calling re_index_patterns(false) followed by add_links for an
existing handle and verifying required index entries remain present. Use the
existing InMemoryDB test helpers and assert both scenarios preserve indexing
behavior.

In `@src/atomdb/remotedb/RemoteAtomDB.cc`:
- Around line 165-179: Update RemoteAtomDB::get_matching_atoms to iterate
writable_peers_ before readonly_peers_, preserving the existing handle-based
deduplication so writable-peer atoms are retained and readonly duplicates are
ignored. Align its peer precedence with get_atom without changing the matching
or result behavior.

---

Nitpick comments:
In `@src/atomdb/inmemorydb/InMemoryDB.cc`:
- Around line 573-576: Remove the discarded-approach text from the comment near
re_index_visitor. Keep only that the visitor must call unlocked helpers because
write_mutex_ is non-recursive.

In `@src/atomdb/remotedb/RemoteAtomDB.cc`:
- Around line 320-385: Update add_atom, add_node, add_link, add_atoms,
add_nodes, and add_links to iterate writable_peers_ instead of remote_db_,
preserving the existing logging and write calls while avoiding readonly peers.
In the vector-returning methods, replace the non-empty result assignment with a
move from peer_handles so the final handle vector is transferred rather than
copied.

In `@src/atomdb/remotedb/RemoteAtomDB.h`:
- Around line 87-88: Add a brief Doxygen block immediately above the public
RemoteAtomDB::release_caches declaration, documenting the link_schema argument
and clarifying that persist selects flushing versus dropping caches while force
overrides the no-op path when the schema is not cached. Follow the existing /**
... */ documentation style in the header.

In `@src/atomdb/remotedb/RemoteAtomDBPeer.cc`:
- Around line 705-710: Update the quiescence loop around old_write_buffer in the
cleanup path to add a timed backoff between use_count() checks, using the
proposed chrono-based sleep and adding the required <chrono> include. Preserve
the existing ownership condition and flush behavior once the buffer becomes
solely owned.

In `@src/tests/cpp/redis_mongodb_test.cc`:
- Around line 1171-1177: Update the test setup around RedisMongoDB construction
to reuse the existing test_atomdb_json_config() helper instead of assembling the
nlohmann::json configuration inline. Preserve the disabled
composite_type_enabled setting and existing test behavior while relying on the
shared fixture for type, endpoints, and credentials.

In `@src/tests/cpp/remote_atomdb_test.cc`:
- Around line 1091-1112: Extend FailedFlushRestagesDirtyAtoms with coverage for
links: add a case staging a link whose targets are absent from every reachable
backend and assert the expected resulting state, including the unresolved-target
batch path; add another case using a failing local backend, assert the link
remains cached after release_cache throws, then restore the backend and verify
the re-staged link persists. Exercise RemoteAtomDBPeer::persist_atoms_to_local
and preserve the existing node assertions.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 42eac09d-4f50-4b94-ba3b-d99c5b2994d4

📥 Commits

Reviewing files that changed from the base of the PR and between 44ee6da and 8500319.

📒 Files selected for processing (15)
  • config/das.json
  • src/atomdb/inmemorydb/InMemoryDB.cc
  • src/atomdb/inmemorydb/InMemoryDB.h
  • src/atomdb/redis_mongodb/RedisMongoDB.cc
  • src/atomdb/remotedb/RemoteAtomDB.cc
  • src/atomdb/remotedb/RemoteAtomDB.h
  • src/atomdb/remotedb/RemoteAtomDBPeer.cc
  • src/atomdb/remotedb/RemoteAtomDBPeer.h
  • src/main/bus_client.cc
  • src/tests/cpp/BUILD
  • src/tests/cpp/inmemorydb_test.cc
  • src/tests/cpp/redis_mongodb_test.cc
  • src/tests/cpp/remote_atomdb_test.cc
  • src/tests/main/evaluation_evolution.cc
  • src/tests/scripts/run_toy.sh
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/tests/cpp/BUILD
  • src/tests/main/evaluation_evolution.cc
  • src/atomdb/redis_mongodb/RedisMongoDB.cc
  • src/tests/cpp/inmemorydb_test.cc

Comment thread config/das.json Outdated
Comment thread src/atomdb/inmemorydb/InMemoryDB.cc Outdated
Comment thread src/atomdb/inmemorydb/InMemoryDB.cc
Comment thread src/atomdb/remotedb/RemoteAtomDB.cc
Comment thread src/atomdb/remotedb/RemoteAtomDBPeer.cc
Comment thread src/atomdb/remotedb/RemoteAtomDBPeer.cc
Comment thread src/atomdb/remotedb/RemoteAtomDBPeer.h Outdated
Comment thread src/tests/cpp/remote_atomdb_test.cc
Comment thread src/tests/scripts/run_toy.sh Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants