Skip to content

[Toy] Fix RemoteDB concurrent logic#1189

Open
ccgsnet wants to merge 1 commit into
masterfrom
remotedb-improvements
Open

[Toy] Fix RemoteDB concurrent logic#1189
ccgsnet wants to merge 1 commit 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

  • Fixes RemoteDB concurrency races by adding recursive locking to InMemoryDB, protecting peer bookkeeping and cache swaps, and using short-lived cache snapshots.
  • Reworks cache lifecycle and persistence, preserving staged writes and improving dependency-safe local persistence; adds RemoteAtomDB::release_caches() and readonly-peer support.
  • Code-quality risks include complex cache-generation/lifetime coordination, altered upsert/deletion semantics, and incomplete validation of concurrent run_toy.sh behavior.
  • Hot-path reads now clone atoms and create cache snapshots, increasing allocation/copy overhead; cache warming and persistence also add dependency-fetch work.
  • Adds substantial coverage in src/tests/ for cache invalidation, readonly peers, federation behavior, InMemoryDB lifecycle/upserts, and disabled composite checks. inmemorydb_test and remote_atomdb_test pass, but concurrent toy-run coverage remains incomplete.

Walkthrough

InMemoryDB gains synchronized operations, deep-copy enumeration, reset/drop APIs, and upsert behavior. Remote peers now use replaceable caches with staged persistence and dependency-aware flushing. Federation reads, writes, deletions, and cache release semantics are updated, with expanded tests and runtime configuration.

Changes

Storage and persistence

Layer / File(s) Summary
Thread-safe in-memory storage
src/atomdb/inmemorydb/*, src/tests/cpp/inmemorydb_test.cc
InMemoryDB operations are mutex-protected, atoms are cloned consistently, tries can be reset, and new get_all_atoms()/drop_all() and upsert tests are added.
Composite link persistence
src/atomdb/redis_mongodb/RedisMongoDB.cc, src/tests/cpp/redis_mongodb_test.cc
Missing-target checks now run only when composite types are enabled.
Peer cache and local persistence
src/atomdb/remotedb/RemoteAtomDBPeer.*, src/tests/cpp/remote_atomdb_test.cc
Peer caches become replaceable shared generations with synchronized access, staged persistence, dependency-aware link persistence, and cache-release controls.
Federation and runtime integration
src/atomdb/remotedb/RemoteAtomDB.*, src/tests/main/evaluation_evolution.cc, config/das.json, src/tests/assets/*, src/tests/cpp/BUILD
Federation aggregation and peer probing are revised, cache release is broadcast, runtime phases flush caches, and peer configurations and test sizing are updated.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant RemoteAtomDB
  participant RemoteAtomDBPeer
  participant InMemoryDB
  participant AtomDB
  Client->>RemoteAtomDB: get_atom(handle)
  RemoteAtomDB->>RemoteAtomDBPeer: probe writable and readonly peers
  RemoteAtomDBPeer->>InMemoryDB: read cache
  RemoteAtomDBPeer->>AtomDB: fetch on cache miss
  AtomDB-->>RemoteAtomDBPeer: return atom
  RemoteAtomDBPeer->>InMemoryDB: warm cache
  Client->>RemoteAtomDB: release_caches(schema)
  RemoteAtomDB->>RemoteAtomDBPeer: release(schema)
  RemoteAtomDBPeer->>AtomDB: persist staged atoms
Loading

Possibly related PRs

  • singnet/das#1155: Related to RemoteAtomDB federation probing, caching, and nested metadata aggregation.
  • singnet/das#1180: Related to composite-type gating in RedisMongoDB link insertion.
  • singnet/das#1181: Related to composite_type_enabled handling in RedisMongoDB.

Suggested reviewers: marcocapozzoli

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: fixing concurrent logic in RemoteDB/RemoteAtomDB.
Description check ✅ Passed The description accurately summarizes the concurrency fixes, locking changes, and test coverage in the PR.
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 logic changes are covered by updated C++ tests in src/tests/cpp for InMemoryDB, RedisMongoDB, and RemoteAtomDB/Peer behavior.
✨ 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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
config/das.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

src/tests/assets/remotedb_config_single.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'


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

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.

1 participant