-
-
Notifications
You must be signed in to change notification settings - Fork 11
Super sync part 1 #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Super sync part 1 #114
Changes from all commits
1990708
ced9a76
a967c12
d7998a5
2fdf976
4e689d7
386a340
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,18 @@ | ||
| # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json | ||
| language: "en-US" | ||
|
|
||
| review: | ||
| enable_review: true | ||
| enable_fix_suggestions: true | ||
| enable_tests_generation: false | ||
|
|
||
| context: | ||
| timeout_ms: 900000 # 15 minutes max | ||
| early_access: true | ||
| reviews: | ||
| profile: "chill" | ||
| request_changes_workflow: true | ||
| high_level_summary: true | ||
| poem: true | ||
| review_status: true | ||
| collapse_walkthrough: false | ||
| auto_review: | ||
| enabled: true | ||
| drafts: true | ||
| tools: | ||
| github-checks: | ||
| timeout_ms: 900000 # 15 minutes max | ||
| chat: | ||
| auto_reply: true |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,11 +44,11 @@ class ModuleState { | |
| public: | ||
| JsonObject data = JsonObject(); // isNull() | ||
|
|
||
| static UpdatedItem updatedItem; | ||
| // for post/getUpdate | ||
| static UpdatedItem mutexedUpdatedItem; | ||
| static String mutexedOriginId; | ||
| static SemaphoreHandle_t updateMutex; | ||
| bool updatePending = false; // should not be static as each module needs to keep track of it's own status | ||
|
|
||
| static Char<20> updateOriginId; // static, written by ModuleState::update, no mutex needed as written by one process at a time (http mostly, sveltekit sometimes recursively) | ||
| bool mutexedUpdatePending = false; // should not be static as each module needs to keep track of it's own status | ||
|
Comment on lines
+47
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's examine the Module.h file around lines 47-51 to see the actual code
head -60 src/MoonBase/Module.h | tail -20Repository: MoonModules/MoonLight Length of output: 745 🏁 Script executed: # Get more context from Module.h - check the class definition and static members
sed -n '40,160p' src/MoonBase/Module.hRepository: MoonModules/MoonLight Length of output: 4742 🏁 Script executed: # Check Module.cpp for static member definitions
fd -e cpp -x grep -l "ModuleState::" {} \; | head -5Repository: MoonModules/MoonLight Length of output: 90 🏁 Script executed: # Search for ModuleState instantiations to understand usage patterns
rg "ModuleState" --type cpp --type h -C3 | head -100Repository: MoonModules/MoonLight Length of output: 5174 🏁 Script executed: # Search for Module/ModuleState instantiation to understand usage patterns
rg "new Module|Module(" --type cpp --type h -B2 -A2 | head -60Repository: MoonModules/MoonLight Length of output: 151 🏁 Script executed: # Check for any guard or synchronization that might prevent multiple concurrent instances
rg "ModuleState" --type cpp -A5 -B5 | grep -E "(static|singleton|instance)" | head -20Repository: MoonModules/MoonLight Length of output: 668 🏁 Script executed: # Look at the template usage - how many StatefulService<ModuleState> instances are created
rg "StatefulService<ModuleState>" --type cpp --type h -C3Repository: MoonModules/MoonLight Length of output: 352 🏁 Script executed: # Search for Module instantiation in main and other files
rg "Module\(" --type cpp -B2 -A2 | head -80Repository: MoonModules/MoonLight Length of output: 5646 🏁 Script executed: # Count the number of module types/instances being created
fd -e cpp | xargs grep -l "Module(" | head -10Repository: MoonModules/MoonLight Length of output: 101 🏁 Script executed: # Look at main.cpp to see how many modules are instantiated
grep -n "Module" src/main.cpp | head -30Repository: MoonModules/MoonLight Length of output: 2360 🏁 Script executed: # Check the Module constructor to understand instantiation
sed -n '1,50p' src/MoonBase/Module.cppRepository: MoonModules/MoonLight Length of output: 2036 Race condition confirmed: mixed static/non-static queue variables with multiple Module instances. With 9+ different Module instances in the system (ModuleLightsControl, ModuleDevices, ModuleEffects, ModuleDrivers, ModuleLiveScripts, ModuleChannels, etc.), the current design creates a race condition:
When Module A calls Either make all three static (single shared queue) or all three non-static (per-module queues). 🔧 Option 1: Make mutexedUpdatePending static for a single shared queue- bool mutexedUpdatePending = false; // should not be static as each module needs to keep track of it's own status
+ static bool mutexedUpdatePending;And in Module.cpp: bool ModuleState::mutexedUpdatePending = false;🔧 Option 2: Make all three non-static for per-module queues- static UpdatedItem mutexedUpdatedItem;
- static String mutexedOriginId;
+ UpdatedItem mutexedUpdatedItem;
+ String mutexedOriginId;Remove the corresponding static definitions from Module.cpp. 🤖 Prompt for AI Agents |
||
|
|
||
| ModuleState() { | ||
| EXT_LOGD(MB_TAG, "ModuleState constructor"); | ||
|
|
@@ -97,31 +97,32 @@ class ModuleState { | |
| void setupData(); | ||
|
|
||
| // called from ModuleState::update and ModuleState::setupData() | ||
| bool compareRecursive(const JsonString& parent, const JsonVariant& oldData, const JsonVariant& newData, UpdatedItem& updatedItem, uint8_t depth = UINT8_MAX, uint8_t index = UINT8_MAX); | ||
| bool compareRecursive(const JsonString& parent, const JsonVariant& oldData, const JsonVariant& newData, UpdatedItem& updatedItem, const String& originId, uint8_t depth = UINT8_MAX, uint8_t index = UINT8_MAX); | ||
|
|
||
| // called from ModuleState::update | ||
| bool checkReOrderSwap(const JsonString& parent, const JsonVariant& oldData, const JsonVariant& newData, UpdatedItem& updatedItem, uint8_t depth = UINT8_MAX, uint8_t index = UINT8_MAX); | ||
| bool checkReOrderSwap(const JsonString& parent, const JsonVariant& oldData, const JsonVariant& newData, UpdatedItem& updatedItem, const String& originId, uint8_t depth = UINT8_MAX, uint8_t index = UINT8_MAX); | ||
|
|
||
| std::function<void(const UpdatedItem&)> processUpdatedItem = nullptr; | ||
| std::function<void(const UpdatedItem&, const String&)> processUpdatedItem = nullptr; | ||
|
|
||
| static void read(ModuleState& state, JsonObject& stateJson); | ||
| static StateUpdateResult update(JsonObject& newData, ModuleState& state, const String& originId); //, const String& originId | ||
|
|
||
| ReadHook readHook = nullptr; // called when the UI requests the state, can be used to update the state before sending it to the UI | ||
|
|
||
| void postUpdate(const UpdatedItem& updatedItem) { | ||
| void postUpdate(const UpdatedItem& updatedItem, const String& originId) { | ||
| const char* taskName = pcTaskGetName(xTaskGetCurrentTaskHandle()); | ||
|
|
||
| if (contains(taskName, "SvelteKit") || contains(taskName, "loopTask")) { // at boot, the loopTask starts, after that the loopTask is destroyed | ||
| if (processUpdatedItem) processUpdatedItem(updatedItem); | ||
| if (processUpdatedItem) processUpdatedItem(updatedItem, originId); | ||
| } else { | ||
| // Wait until previous update is processed | ||
| while (true) { | ||
| if (xSemaphoreTake(updateMutex, pdMS_TO_TICKS(10)) == pdTRUE) { | ||
| if (!updatePending) { | ||
| if (!mutexedUpdatePending) { | ||
| // EXT_LOGD(ML_TAG, "%s[%d]%s[%d].%s = %s -> %s", updatedItem.parent[0].c_str(), updatedItem.index[0], updatedItem.parent[1].c_str(), updatedItem.index[1], updatedItem.name.c_str(), updatedItem.oldValue.c_str(), updatedItem.value.as<String>().c_str()); | ||
| this->updatedItem = updatedItem; | ||
| updatePending = true; | ||
| mutexedUpdatedItem = updatedItem; | ||
| mutexedOriginId = originId; | ||
| mutexedUpdatePending = true; | ||
| xSemaphoreGive(updateMutex); | ||
| break; | ||
| } | ||
|
|
@@ -135,14 +136,15 @@ class ModuleState { | |
| void getUpdate() { | ||
| // Try to acquire mutex without blocking | ||
| if (xSemaphoreTake(updateMutex, pdMS_TO_TICKS(10)) == pdTRUE) { | ||
| if (updatePending) { | ||
| if (mutexedUpdatePending) { | ||
| // Copy update data | ||
| UpdatedItem localCopy = updatedItem; | ||
| updatePending = false; | ||
| UpdatedItem localCopy = mutexedUpdatedItem; | ||
| const String localOriginId = mutexedOriginId; | ||
| mutexedUpdatePending = false; | ||
| xSemaphoreGive(updateMutex); | ||
|
|
||
| // Process OUTSIDE the mutex (no lock held during callback) | ||
| if (processUpdatedItem) processUpdatedItem(localCopy); | ||
| if (processUpdatedItem) processUpdatedItem(localCopy, localOriginId); | ||
| return; | ||
| } | ||
| xSemaphoreGive(updateMutex); | ||
|
|
@@ -179,17 +181,17 @@ class Module : public StatefulService<ModuleState> { | |
| } | ||
| } | ||
|
|
||
| void processUpdatedItem(const UpdatedItem& updatedItem) { | ||
| void processUpdatedItem(const UpdatedItem& updatedItem, const String& originId) { | ||
| if (updatedItem.name == "swap") { | ||
| onReOrderSwap(updatedItem.index[0], updatedItem.index[1]); | ||
| saveNeeded = true; | ||
| } else { | ||
| if (updatedItem.oldValue != "" && updatedItem.name != "channel") { // todo: fix the problem at channel, not here... | ||
| if (!_state.updateOriginId.contains("server")) { // only triggered by updates from front-end | ||
| if (!originId.endsWith("server")) { // only triggered by updates from front-end | ||
| saveNeeded = true; | ||
| } | ||
| } | ||
| onUpdate(updatedItem); | ||
| onUpdate(updatedItem, originId); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -205,7 +207,7 @@ class Module : public StatefulService<ModuleState> { | |
|
|
||
| // called in compareRecursive->execOnUpdate | ||
| // called from compareRecursive | ||
| virtual void onUpdate(const UpdatedItem& updatedItem) {}; | ||
| virtual void onUpdate(const UpdatedItem& updatedItem, const String& originId) {}; | ||
| virtual void onReOrderSwap(uint8_t stateIndex, uint8_t newIndex) {}; | ||
|
|
||
| protected: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Block or abort on mutex timeout — continuing without the lock is unsafe.
If both waits fail, the function proceeds without owning
_accessMutex, so callers mutate_stateconcurrently andendTransaction()gives an unowned mutex. This matches the corruption issues observed in this repo. Please keep the warning (no auto‑restart), but do ensure the lock is acquired or fail the transaction.✅ Safer fix (block after warning)
Based on learnings, the warning should remain but execution must not proceed without the mutex.
🤖 Prompt for AI Agents