Skip to content

Don't erase elements if multiple subscriptions use them - #425

Open
kilativ-dotcom wants to merge 18 commits into
ostis-ai:mainfrom
kilativ-dotcom:fix/erase_events
Open

Don't erase elements if multiple subscriptions use them#425
kilativ-dotcom wants to merge 18 commits into
ostis-ai:mainfrom
kilativ-dotcom:fix/erase_events

Conversation

@kilativ-dotcom

@kilativ-dotcom kilativ-dotcom commented Oct 15, 2024

Copy link
Copy Markdown
Member

Summary by Gitar

  • Memory/Core:
    • Optimized sc_storage_element_erase by preventing premature element deletion if active subscriptions are present.
    • Implemented _sc_storage_element_erase_with_incoming_outgoing_connectors_and_hanging_nodes to recursively manage element erasure.
  • Event Handling:
    • Added emitted_erase_events tracking to sc_event_emission_manager to prevent race conditions during concurrent deletions.
    • Refactored sc_event_emission_manager to safely wait for pending events during shutdown.
  • Testing:
    • Added multiple stress tests in test_sc_event.cpp to verify stability of concurrent subscriptions during erasure.
    • Improved test reliability in test_sc_specified_agents.cpp and test_sc_event.cpp using ScMemoryContextEventsPendingGuard.

This will update automatically on new commits.

@codecov

codecov Bot commented Oct 16, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 97.63948% with 11 lines in your changes missing coverage. Please review.

Project coverage is 95.76%. Comparing base (1424e78) to head (b375f96).
Report is 179 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #425      +/-   ##
==========================================
+ Coverage   95.70%   95.76%   +0.05%     
==========================================
  Files         237      237              
  Lines       26965    27315     +350     
  Branches     1863     1880      +17     
==========================================
+ Hits        25808    26157     +349     
- Misses       1157     1158       +1     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread sc-memory/sc-core/src/sc-store/sc-event/sc_event_queue.c Outdated
Comment on lines +253 to +261
sc_uint32 * count = (sc_uint32 *)sc_hash_table_get(
manager->emitted_erase_events, GUINT_TO_POINTER(SC_ADDR_LOCAL_TO_INT(connector_addr)));
if (count == null_ptr)
{
count = sc_mem_new(sc_uint32, 1);
*count = 0;
}
++(*count);
sc_hash_table_insert(manager->emitted_erase_events, GUINT_TO_POINTER(SC_ADDR_LOCAL_TO_INT(connector_addr)), count);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
sc_uint32 * count = (sc_uint32 *)sc_hash_table_get(
manager->emitted_erase_events, GUINT_TO_POINTER(SC_ADDR_LOCAL_TO_INT(connector_addr)));
if (count == null_ptr)
{
count = sc_mem_new(sc_uint32, 1);
*count = 0;
}
++(*count);
sc_hash_table_insert(manager->emitted_erase_events, GUINT_TO_POINTER(SC_ADDR_LOCAL_TO_INT(connector_addr)), count);
sc_uint32 count = (sc_uint32)(sc_uint64)sc_hash_table_get(
manager->emitted_erase_events, GUINT_TO_POINTER(SC_ADDR_LOCAL_TO_INT(connector_addr)));
++count;
sc_hash_table_insert(manager->emitted_erase_events, GUINT_TO_POINTER(SC_ADDR_LOCAL_TO_INT(connector_addr)), count);

Comment thread sc-memory/sc-core/src/sc-store/sc_storage.c
Comment thread sc-memory/sc-core/src/sc-store/sc_storage.c Outdated
Comment thread sc-memory/sc-memory/tests/sc-memory/units/events/test_sc_event.cpp Outdated
Comment thread sc-memory/sc-core/src/sc-store/sc-event/sc_event_queue.c Outdated
@NikitaZotov NikitaZotov changed the title Don't erase elements if multiple subsciptions use them Don't erase elements if multiple subscriptions use them Nov 4, 2024
Comment thread sc-memory/sc-core/src/sc-store/sc_storage.c Outdated
Comment thread sc-memory/sc-core/src/sc-store/sc-event/sc_event_queue.c Outdated
Comment thread sc-memory/sc-core/src/sc-store/sc-event/sc_event_queue.c Outdated
Comment thread sc-memory/sc-memory/tests/sc-memory/units/agents/test_sc_specified_agents.cpp Outdated
Comment thread sc-memory/sc-memory/tests/sc-memory/units/agents/test_sc_specified_agents.cpp Outdated
Comment thread sc-memory/sc-memory/tests/sc-memory/units/agents/test_sc_specified_agents.cpp Outdated
Comment thread sc-memory/sc-memory/tests/sc-memory/units/events/test_sc_event.cpp Outdated
Comment thread sc-memory/sc-memory/tests/sc-memory/units/events/test_sc_event.cpp Outdated
Comment thread sc-memory/sc-core/src/sc-store/sc_storage.c Outdated
Comment thread sc-memory/sc-core/src/sc-store/sc_storage.c

sc_result _sc_storage_element_erase_with_incoming_outgoing_connectors(
sc_memory_context const * ctx,
sc_addr connector_chain_begin_addr,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What is chain here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

chain is like sequence of connectors next connector of which cannot exist without previous(previous element is source/target of next connector)

Comment thread sc-memory/sc-core/src/sc-store/sc_storage.c Outdated
Comment thread sc-memory/sc-core/src/sc-store/sc_storage.c Outdated
Comment thread sc-memory/sc-memory/tests/sc-memory/units/events/test_sc_event.cpp
Comment thread sc-memory/sc-memory/tests/sc-memory/units/events/test_sc_event.cpp
Comment thread sc-memory/sc-memory/tests/sc-memory/units/events/test_sc_event.cpp Outdated
Comment thread sc-memory/sc-memory/tests/sc-memory/units/events/test_sc_event.cpp Outdated
Comment thread sc-memory/sc-memory/tests/sc-memory/units/events/test_sc_event.cpp Outdated
@NikitaZotov NikitaZotov added this to the 0.10.0 milestone Nov 25, 2024
@NikitaZotov NikitaZotov added bug Something isn't working sc-memory Changes in sc-memory module labels Nov 25, 2024

@NikitaZotov NikitaZotov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Tests are failed

@NikitaZotov NikitaZotov modified the milestones: 0.10.0, 0.10.1 Jan 28, 2025
@NikitaZotov NikitaZotov added 0.10.1 and removed 0.10.0 labels Jan 28, 2025
@NikitaZotov NikitaZotov added 0.10.2 and removed 0.10.1 labels Mar 15, 2025
@NikitaZotov NikitaZotov added 0.10.4 and removed 0.10.2 labels May 1, 2025
@NikitaZotov NikitaZotov modified the milestones: 0.10.1, 0.10.4 May 1, 2025
Comment on lines 210 to +212
sc_event_emission_manager * emission_manager = sc_storage_get_event_emission_manager();
if (subscription_manager == null_ptr)
return SC_RESULT_NO;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Bug: Wrong variable in null check leaves emission_manager unchecked

In sc_event_subscription_destroy, after fetching emission_manager, the guard at line 211 mistakenly re-checks subscription_manager == null_ptr instead of emission_manager == null_ptr (copy-paste error). If emission_manager is null while subscription_manager and storage are non-null, the code reaches line 236 and dereferences emission_manager->pool_monitor, causing a null-pointer crash. Change the check to test emission_manager.

Check emission_manager instead of re-checking subscription_manager.:

sc_event_emission_manager * emission_manager = sc_storage_get_event_emission_manager();
if (emission_manager == null_ptr)
  return SC_RESULT_NO;
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

Comment on lines +693 to +698
sc_monitor_acquire_read(&emission_manager->pool_monitor);
sc_uint32 count = (sc_uint32)(sc_uint64)sc_hash_table_get(
emission_manager->emitted_erase_events, GUINT_TO_POINTER(SC_ADDR_LOCAL_TO_INT(addr)));
if (count == 0)
sc_hash_table_insert(incident_elements_under_erasure, key, element);
sc_monitor_release_read(&emission_manager->pool_monitor);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Bug: emitted_erase_events accessed under two different monitors (data race)

The emitted_erase_events hash table is written by the worker and by _sc_event_emission_manager_add while holding emitted_erase_events_monitor, but sc_storage.c reads it while holding only pool_monitor (read). Because readers and the worker never share a common lock, concurrent erase + event processing produces unsynchronized concurrent access to a GHashTable, which is undefined behavior and can corrupt the table or crash. The storage reads should acquire emitted_erase_events_monitor (not pool_monitor) to match the writers.

Guard reads of emitted_erase_events with emitted_erase_events_monitor at both sc_storage.c sites.:

sc_monitor_acquire_read(&emission_manager->emitted_erase_events_monitor);
sc_uint32 count = (sc_uint32)(sc_uint64)sc_hash_table_get(
    emission_manager->emitted_erase_events, GUINT_TO_POINTER(SC_ADDR_LOCAL_TO_INT(addr)));
...
sc_monitor_release_read(&emission_manager->emitted_erase_events_monitor);
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

Comment on lines 77 to 79

if (queue->running == SC_FALSE)
if (manager->running == SC_FALSE)
goto end;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Bug: running flag read/written without synchronization

sc_event_emission_manager_stop now reads and writes manager->running with no lock, while the worker reads manager->running under destroy_monitor. This is an unsynchronized concurrent access to running; previously it was protected by destroy_monitor. In practice this is a benign torn-read of a bool, but it is a data race per the memory model and could delay a worker observing the shutdown. Re-acquire destroy_monitor around the update or make the flag atomic.

Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Jul 19, 2026

Copy link
Copy Markdown
Code Review ⚠️ Changes requested 0 resolved / 3 findings

Prevents premature element erasure for active subscriptions and adds stress tests for concurrent event handling, but contains critical synchronization and pointer validation errors.

⚠️ Bug: Wrong variable in null check leaves emission_manager unchecked

📄 sc-memory/sc-core/src/sc-store/sc_event_subscription.c:210-212 📄 sc-memory/sc-core/src/sc-store/sc_event_subscription.c:236

In sc_event_subscription_destroy, after fetching emission_manager, the guard at line 211 mistakenly re-checks subscription_manager == null_ptr instead of emission_manager == null_ptr (copy-paste error). If emission_manager is null while subscription_manager and storage are non-null, the code reaches line 236 and dereferences emission_manager->pool_monitor, causing a null-pointer crash. Change the check to test emission_manager.

Check emission_manager instead of re-checking subscription_manager.
sc_event_emission_manager * emission_manager = sc_storage_get_event_emission_manager();
if (emission_manager == null_ptr)
  return SC_RESULT_NO;
⚠️ Bug: emitted_erase_events accessed under two different monitors (data race)

📄 sc-memory/sc-core/src/sc-store/sc_storage.c:693-698 📄 sc-memory/sc-core/src/sc-store/sc_storage.c:830-835 📄 sc-memory/sc-core/src/sc-store/sc-event/sc_event_queue.c:107-121 📄 sc-memory/sc-core/src/sc-store/sc-event/sc_event_queue.c:265-274

The emitted_erase_events hash table is written by the worker and by _sc_event_emission_manager_add while holding emitted_erase_events_monitor, but sc_storage.c reads it while holding only pool_monitor (read). Because readers and the worker never share a common lock, concurrent erase + event processing produces unsynchronized concurrent access to a GHashTable, which is undefined behavior and can corrupt the table or crash. The storage reads should acquire emitted_erase_events_monitor (not pool_monitor) to match the writers.

Guard reads of emitted_erase_events with emitted_erase_events_monitor at both sc_storage.c sites.
sc_monitor_acquire_read(&emission_manager->emitted_erase_events_monitor);
sc_uint32 count = (sc_uint32)(sc_uint64)sc_hash_table_get(
    emission_manager->emitted_erase_events, GUINT_TO_POINTER(SC_ADDR_LOCAL_TO_INT(addr)));
...
sc_monitor_release_read(&emission_manager->emitted_erase_events_monitor);
💡 Bug: running flag read/written without synchronization

📄 sc-memory/sc-core/src/sc-store/sc-event/sc_event_queue.c:189-196 📄 sc-memory/sc-core/src/sc-store/sc-event/sc_event_queue.c:77-79

sc_event_emission_manager_stop now reads and writes manager->running with no lock, while the worker reads manager->running under destroy_monitor. This is an unsynchronized concurrent access to running; previously it was protected by destroy_monitor. In practice this is a benign torn-read of a bool, but it is a data race per the memory model and could delay a worker observing the shutdown. Re-acquire destroy_monitor around the update or make the flag atomic.

🤖 Prompt for agents
Code Review: Prevents premature element erasure for active subscriptions and adds stress tests for concurrent event handling, but contains critical synchronization and pointer validation errors.

1. ⚠️ Bug: Wrong variable in null check leaves emission_manager unchecked
   Files: sc-memory/sc-core/src/sc-store/sc_event_subscription.c:210-212, sc-memory/sc-core/src/sc-store/sc_event_subscription.c:236

   In `sc_event_subscription_destroy`, after fetching `emission_manager`, the guard at line 211 mistakenly re-checks `subscription_manager == null_ptr` instead of `emission_manager == null_ptr` (copy-paste error). If `emission_manager` is null while `subscription_manager` and `storage` are non-null, the code reaches line 236 and dereferences `emission_manager->pool_monitor`, causing a null-pointer crash. Change the check to test `emission_manager`.

   Fix (Check emission_manager instead of re-checking subscription_manager.):
   sc_event_emission_manager * emission_manager = sc_storage_get_event_emission_manager();
   if (emission_manager == null_ptr)
     return SC_RESULT_NO;

2. ⚠️ Bug: emitted_erase_events accessed under two different monitors (data race)
   Files: sc-memory/sc-core/src/sc-store/sc_storage.c:693-698, sc-memory/sc-core/src/sc-store/sc_storage.c:830-835, sc-memory/sc-core/src/sc-store/sc-event/sc_event_queue.c:107-121, sc-memory/sc-core/src/sc-store/sc-event/sc_event_queue.c:265-274

   The `emitted_erase_events` hash table is written by the worker and by `_sc_event_emission_manager_add` while holding `emitted_erase_events_monitor`, but `sc_storage.c` reads it while holding only `pool_monitor` (read). Because readers and the worker never share a common lock, concurrent erase + event processing produces unsynchronized concurrent access to a GHashTable, which is undefined behavior and can corrupt the table or crash. The storage reads should acquire `emitted_erase_events_monitor` (not `pool_monitor`) to match the writers.

   Fix (Guard reads of emitted_erase_events with emitted_erase_events_monitor at both sc_storage.c sites.):
   sc_monitor_acquire_read(&emission_manager->emitted_erase_events_monitor);
   sc_uint32 count = (sc_uint32)(sc_uint64)sc_hash_table_get(
       emission_manager->emitted_erase_events, GUINT_TO_POINTER(SC_ADDR_LOCAL_TO_INT(addr)));
   ...
   sc_monitor_release_read(&emission_manager->emitted_erase_events_monitor);

3. 💡 Bug: running flag read/written without synchronization
   Files: sc-memory/sc-core/src/sc-store/sc-event/sc_event_queue.c:189-196, sc-memory/sc-core/src/sc-store/sc-event/sc_event_queue.c:77-79

   `sc_event_emission_manager_stop` now reads and writes `manager->running` with no lock, while the worker reads `manager->running` under `destroy_monitor`. This is an unsynchronized concurrent access to `running`; previously it was protected by `destroy_monitor`. In practice this is a benign torn-read of a bool, but it is a data race per the memory model and could delay a worker observing the shutdown. Re-acquire `destroy_monitor` around the update or make the flag atomic.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

0.10.4 bug Something isn't working sc-memory Changes in sc-memory module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants