Conversation
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a centralized FreeRTOS render task and RAII RenderLock in Activity; subclasses remove per-activity render tasks/mutexes, expose public render(Activity::RenderLock&&) overrides, and switch from per-activity update flags to a unified requestUpdate() notification flow (subactivities forward requests). Changes
Sequence Diagram(s)sequenceDiagram
participant App as Application
participant Activity as Activity (base)
participant RTOS as FreeRTOS
participant RenderTask as Render Task
participant Sub as SubActivity / Activity::render()
App->>Activity: onEnter()
Activity->>RTOS: xTaskCreate(renderTaskTrampoline, this)
RTOS->>RenderTask: start
loop runtime
App->>Activity: requestUpdate()
Activity->>RTOS: xTaskNotify(renderTaskHandle, 1, eIncrement)
RTOS->>RenderTask: deliver notification
RenderTask->>Activity: acquire RenderLock (take mutex)
RenderTask->>Sub: call render(Activity::RenderLock&&)
Sub-->>RenderTask: render completes
RenderTask->>Activity: release RenderLock (mutex)
end
App->>Activity: onExit()
Activity->>RTOS: vTaskDelete(renderTaskHandle)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/activities/home/RecentBooksActivity.cpp (1)
70-85:⚠️ Potential issue | 🟠 MajorGuard against empty list before modulo operations.
Lines 75 and 82 perform
(selectorIndex ± 1) % listSizewithout checking iflistSizeis zero. WhenrecentBooksis empty,listSizeequals 0, resulting in undefined behavior. Add a guard at the beginning of the modulo block.🛠️ Proposed fix
int listSize = static_cast<int>(recentBooks.size()); + if (listSize == 0) { + return; + } if (upReleased) { if (skipPage) { selectorIndex = std::max(static_cast<int>((selectorIndex / pageItems - 1) * pageItems), 0); } else { selectorIndex = (selectorIndex + listSize - 1) % listSize;src/activities/settings/KOReaderSettingsActivity.cpp (1)
121-125:⚠️ Potential issue | 🔴 CriticalRemove unowned semaphore give call.
Line 123 calls
xSemaphoreGive(renderingMutex)without owning the mutex. On FreeRTOS, giving an unowned mutex causes assertion failures or lock state corruption. Replace withrequestUpdate(), which correctly signals the render task without requiring mutex ownership. All other code paths in this method already userequestUpdate().🔧 Proposed fix
if (!KOREADER_STORE.hasCredentials()) { // Can't authenticate without credentials - just show message briefly - xSemaphoreGive(renderingMutex); + requestUpdate(); return; }src/activities/reader/EpubReaderChapterSelectionActivity.cpp (1)
50-88:⚠️ Potential issue | 🟠 MajorGuard against zero TOC items before modulo.
If
getTotalItems()returns 0 and the user presses Up/Down, the modulo operations will divide by zero.🔧 Proposed fix
const int pageItems = getPageItems(); const int totalItems = getTotalItems(); @@ } else if (prevReleased) { + if (totalItems == 0) { + return; + } if (skipPage) { selectorIndex = ((selectorIndex / pageItems - 1) * pageItems + totalItems) % totalItems; } else { selectorIndex = (selectorIndex + totalItems - 1) % totalItems; } requestUpdate(); } else if (nextReleased) { + if (totalItems == 0) { + return; + } if (skipPage) { selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % totalItems; } else { selectorIndex = (selectorIndex + 1) % totalItems; } requestUpdate(); }
🤖 Fix all issues with AI agents
In `@src/activities/Activity.cpp`:
- Around line 18-35: The onEnter() call to xTaskCreate currently ignores its
return value and relies on an assert, so if task creation fails renderTaskHandle
stays null and onExit()'s vTaskDelete(renderTaskHandle) can delete the current
task; update onEnter() to capture xTaskCreate's BaseType_t result (from calling
renderTaskTrampoline) and only assign/use renderTaskHandle if xTaskCreate
returns pdPASS, logging or handling the failure, and change onExit() to check
renderTaskHandle != nullptr before calling vTaskDelete (and still use RenderLock
to protect rendering); reference functions/vars: onEnter, onExit, xTaskCreate,
renderTaskTrampoline, renderTaskHandle, vTaskDelete, RenderLock.
In `@src/activities/settings/KOReaderAuthActivity.cpp`:
- Around line 56-59: Lock the same mutex used elsewhere before updating state
and statusMessage to avoid a data race: wrap the assignments to state =
AUTHENTICATING and statusMessage = "Authenticating..." (and the subsequent
requestUpdate() call if other paths do) in a scoped lock (e.g.,
std::lock_guard<std::mutex> guard(yourMutex)) so writes are synchronized with
render reads; update the block that sets state, statusMessage, and calls
requestUpdate() to acquire that mutex consistently with other code paths.
🧹 Nitpick comments (5)
src/activities/reader/EpubReaderPercentSelectionActivity.h (1)
25-30: Remove stale RTOS-related comments.The comments at Line 27 and Line 29 refer to fields that no longer exist and are now misleading.
🧹 Proposed cleanup
- // Render dirty flag for the task loop. - - // FreeRTOS task and mutex for rendering. -src/activities/settings/ClearCacheActivity.cpp (1)
117-124: UseRenderLockinstead of manual mutex operations for consistency.The
renderingMutexstill exists and the manualxSemaphoreTake/xSemaphoreGivecalls will compile and work correctly. However, other parts of the codebase use theRenderLockRAII wrapper (e.g.,Activity.cpp:12,Activity.cpp:30,ActivityWithSubactivity.cpp:14). Consider refactoring to useRenderLock lock(*this);at line 121 instead of manual mutex operations for consistency with the established pattern.src/activities/Activity.h (1)
20-21: Verify[[noreturn]]on the trampoline function.The
[[noreturn]]attribute onrenderTaskTrampolineis technically correct since it callsrenderTaskLoop()which never returns. However, from the implementation inActivity.cpp, the trampoline returnsvoidand calls the loop:void Activity::renderTaskTrampoline(void* param) { auto* self = static_cast<Activity*>(param); return self->renderTaskLoop(); // returns void, then calls noreturn }The attribute works here because control never returns from
renderTaskLoop(), but marking a void-returning function as[[noreturn]]when it delegates to another[[noreturn]]function is unconventional. Consider removing[[noreturn]]from the trampoline or changing the return statement to justself->renderTaskLoop();withoutreturn.src/activities/reader/EpubReaderActivity.cpp (1)
600-603: Potential unbounded recursion on persistent load failures.The recursive
return render()call could cause stack overflow if the page file remains corrupt after cache clearing. Consider adding a retry counter or returning an error state instead.🔧 Suggested safeguard
{ auto p = section->loadPageFromSectionFile(); if (!p) { Serial.printf("[%lu] [ERS] Failed to load page from SD - clearing section cache\n", millis()); section->clearCache(); section.reset(); - return render(); + // Request another render attempt rather than recursing + requestUpdate(); + return; }src/activities/home/MyLibraryActivity.cpp (1)
86-88: Minor: Double semicolon.Line 88 has a stray semicolon after the statement.
🧹 Fix
const bool upReleased = mappedInput.wasReleased(MappedInputManager::Button::Left) || mappedInput.wasReleased(MappedInputManager::Button::Up); - ;
| void Activity::onEnter() { | ||
| xTaskCreate(&renderTaskTrampoline, name.c_str(), | ||
| 8192, // Stack size | ||
| this, // Parameters | ||
| 1, // Priority | ||
| &renderTaskHandle // Task handle | ||
| ); | ||
| assert(renderTaskHandle != nullptr && "Failed to create render task"); | ||
| Serial.printf("[%lu] [ACT] Entering activity: %s\n", millis(), name.c_str()); | ||
| } | ||
|
|
||
| void Activity::onExit() { | ||
| RenderLock lock(*this); // Ensure we don't delete the task while it's rendering | ||
| vTaskDelete(renderTaskHandle); | ||
| renderTaskHandle = nullptr; | ||
|
|
||
| Serial.printf("[%lu] [ACT] Exiting activity: %s\n", millis(), name.c_str()); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find and examine the Activity.cpp file
find . -name "Activity.cpp" -type f | head -5Repository: ngxson/crosspoint-reader
Length of output: 97
🌐 Web query:
FreeRTOS vTaskDelete nullptr current task behavior
💡 Result:
In FreeRTOS, calling:
vTaskDelete(NULL);means “delete the calling (current) task”. Passing NULL is explicitly defined to delete the task that calls vTaskDelete(). [1][2]
What happens next:
- The task is removed from the scheduler’s lists (ready/blocked/suspended/event lists), so it will no longer be scheduled. [1]
- The task’s kernel-allocated resources (TCB + stack) are not necessarily freed immediately. They are freed later by the Idle task, so the Idle task must get CPU time or cleanup will be delayed. [1][2]
- Anything your task allocated itself (e.g., heap blocks via
pvPortMalloc(), queues, semaphores, buffers, file/library resources, etc.) is not automatically freed byvTaskDelete()—you must free/close those yourself before deleting the task. [1][3] - If you stored the task handle in a variable somewhere, FreeRTOS will not set your variable to
NULL; if you need that, another task must do it (the deleting task can’t run code “after” deleting itself). [4]
Practical implication: after vTaskDelete(NULL), your task should be considered gone; don’t write code that expects it to continue running, and ensure the Idle task isn’t starved so the TCB/stack cleanup can occur. [1][2]
Sources:
[1] FreeRTOS vTaskDelete() API documentation (task.h / Doxygen mirror)
[2] FreeRTOS forum archive: “Query about task deletion” (Richard Barry)
[3] FreeRTOS forum: “vTaskDelete memory free”
[4] FreeRTOS forum: “Deleting a task and erasing it’s handler”
🏁 Script executed:
# Also check the header file to understand the context and class definition
find . -name "Activity.h" -o -name "Activity.hpp" | head -5Repository: ngxson/crosspoint-reader
Length of output: 95
🏁 Script executed:
# Examine the Activity.cpp file at lines 18-35
sed -n '1,50p' ./src/activities/Activity.cppRepository: ngxson/crosspoint-reader
Length of output: 1462
🏁 Script executed:
# Check the header file to understand the class structure
cat ./src/activities/Activity.hRepository: ngxson/crosspoint-reader
Length of output: 1603
🏁 Script executed:
# Search for callers of onEnter() to understand if early return is acceptable
rg "\.onEnter\(\)" --max-count 20Repository: ngxson/crosspoint-reader
Length of output: 50
🏁 Script executed:
# Search for places where Activity is instantiated or where onEnter/onExit are called
rg "onEnter|onExit" --type cpp --type h -B 2 -A 2Repository: ngxson/crosspoint-reader
Length of output: 39186
🏁 Script executed:
# Check if there are derived classes that override these methods
rg "class.*:.*Activity" --type hRepository: ngxson/crosspoint-reader
Length of output: 3229
Guard task deletion to prevent deleting the current task if render-task creation fails.
Line 19 ignores xTaskCreate's return value; only an assert guards against null handle, which is compiled out in release builds. If creation fails, renderTaskHandle remains null. In onExit() (line 31), calling vTaskDelete(nullptr) deletes the current calling task per FreeRTOS semantics, which is catastrophic—you'd delete the task executing onExit() (the main/UI loop) instead of the render task.
Check the return value of xTaskCreate and guard the deletion:
Suggested fix
void Activity::onEnter() {
- xTaskCreate(&renderTaskTrampoline, name.c_str(),
- 8192, // Stack size
- this, // Parameters
- 1, // Priority
- &renderTaskHandle // Task handle
- );
- assert(renderTaskHandle != nullptr && "Failed to create render task");
+ const BaseType_t ok = xTaskCreate(&renderTaskTrampoline, name.c_str(),
+ 8192, // Stack size
+ this, // Parameters
+ 1, // Priority
+ &renderTaskHandle // Task handle
+ );
+ if (ok != pdPASS || renderTaskHandle == nullptr) {
+ renderTaskHandle = nullptr;
+ Serial.printf("[%lu] [ACT] ERROR: Failed to create render task for %s\n", millis(), name.c_str());
+ return;
+ }
Serial.printf("[%lu] [ACT] Entering activity: %s\n", millis(), name.c_str());
}
void Activity::onExit() {
- RenderLock lock(*this); // Ensure we don't delete the task while it's rendering
- vTaskDelete(renderTaskHandle);
- renderTaskHandle = nullptr;
+ if (renderTaskHandle) {
+ RenderLock lock(*this); // Ensure we don't delete the task while it's rendering
+ vTaskDelete(renderTaskHandle);
+ renderTaskHandle = nullptr;
+ }
Serial.printf("[%lu] [ACT] Exiting activity: %s\n", millis(), name.c_str());
}🤖 Prompt for AI Agents
In `@src/activities/Activity.cpp` around lines 18 - 35, The onEnter() call to
xTaskCreate currently ignores its return value and relies on an assert, so if
task creation fails renderTaskHandle stays null and onExit()'s
vTaskDelete(renderTaskHandle) can delete the current task; update onEnter() to
capture xTaskCreate's BaseType_t result (from calling renderTaskTrampoline) and
only assign/use renderTaskHandle if xTaskCreate returns pdPASS, logging or
handling the failure, and change onExit() to check renderTaskHandle != nullptr
before calling vTaskDelete (and still use RenderLock to protect rendering);
reference functions/vars: onEnter, onExit, xTaskCreate, renderTaskTrampoline,
renderTaskHandle, vTaskDelete, RenderLock.
|
@coderabbitai scan for migration code that I may forgot to copy or to reimplement (in other words, I may delete something by accident). skip reviewing bugs that are already existed there |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain
🏁 Script executed: Length of output: 7017 🏁 Script executed: Length of output: 1475 🏁 Script executed: Length of output: 6816 🏁 Script executed: Length of output: 1285 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 2234 🏁 Script executed: Length of output: 4735 🏁 Script executed: Length of output: 664 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 805 🏁 Script executed: Length of output: 3386 🏁 Script executed: Length of output: 5468 🏁 Script executed: Length of output: 4974 🏁 Script executed: Length of output: 610 🏁 Script executed: Length of output: 2125 🏁 Script executed: Length of output: 928 🏁 Script executed: Length of output: 2720 🏁 Script executed: Length of output: 2411 🏁 Script executed: Length of output: 23 🏁 Script executed: Length of output: 810 🏁 Script executed: Length of output: 1041 🏁 Script executed: Length of output: 373 🏁 Script executed: Length of output: 5458 🏁 Script executed: Length of output: 2286
🔴 Critical Issues Found1. Imbalanced Mutex Usage (Will Cause Crashes/Deadlocks)OtaUpdateActivity.cpp - Mutex Leak:
KOReaderSettingsActivity.cpp - Line 123:
2. vTaskDelay() in render() MethodWifiSelectionActivity.cpp - Lines 4-7: void WifiSelectionActivity::render() {
if (state == WifiSelectionState::PASSWORD_ENTRY) {
vTaskDelay(10 / portTICK_PERIOD_MS); // ❌ WRONG - render() is now called from centralized task
return;
}
3. Inconsistent Use of RenderLock RAII PatternThe migration introduced Files with manual mutex usage:
Why this matters: Manual mutex operations are error-prone (as evidenced by the imbalanced usage above). The Example of proper pattern: // ❌ Old pattern (error-prone)
xSemaphoreTake(renderingMutex, portMAX_DELAY);
state = SYNCING;
xSemaphoreGive(renderingMutex);
// ✅ New pattern (safe)
{
RenderLock lock(*this);
state = SYNCING;
} // Mutex automatically released4. vTaskDelay with Mutex (Minor Issue)KOReaderSyncActivity.cpp, OtaUpdateActivity.cpp, ClearCacheActivity.cpp use 📋 Recommended Fixes
All other aspects of the migration appear correct - the centralized rendering task infrastructure, |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/activities/util/KeyboardEntryActivity.cpp (1)
217-232:⚠️ Potential issue | 🟡 MinorPotential infinite loop if text cannot fit.
The
while(true)loop decrementslineEndIdxwhen text is too wide. If even a single character exceedspageWidth - 40, the loop will never terminate because an empty substring still advanceslineStartIdxwhile the remaining text never fits.Consider adding a safeguard to break when no progress can be made:
🛡️ Proposed fix to prevent infinite loop
while (true) { std::string lineText = displayText.substr(lineStartIdx, lineEndIdx - lineStartIdx); const int textWidth = renderer.getTextWidth(UI_10_FONT_ID, lineText.c_str()); if (textWidth <= pageWidth - 40) { renderer.drawText(UI_10_FONT_ID, 20, inputEndY, lineText.c_str()); if (lineEndIdx == displayText.length()) { break; } inputEndY += renderer.getLineHeight(UI_10_FONT_ID); lineStartIdx = lineEndIdx; lineEndIdx = displayText.length(); } else { + if (lineEndIdx <= lineStartIdx + 1) { + // Can't fit even one character; force draw and advance to prevent infinite loop + renderer.drawText(UI_10_FONT_ID, 20, inputEndY, lineText.c_str()); + inputEndY += renderer.getLineHeight(UI_10_FONT_ID); + lineStartIdx = lineEndIdx; + lineEndIdx = displayText.length(); + if (lineStartIdx >= displayText.length()) break; + continue; + } lineEndIdx -= 1; } }src/activities/settings/KOReaderSettingsActivity.cpp (1)
120-126:⚠️ Potential issue | 🔴 CriticalCritical: Orphaned
xSemaphoreGivewithout matchingxSemaphoreTake.Line 124 calls
xSemaphoreGive(renderingMutex)but there's no correspondingxSemaphoreTakein this code path. This is leftover code from the old rendering model that was not fully migrated. Since the mutex-based rendering was replaced withrequestUpdate(), this line should be removed entirely.This causes mutex imbalance which can lead to undefined behavior or corruption of the semaphore state.
🐛 Proposed fix
} else if (selectedIndex == 4) { // Authenticate if (!KOREADER_STORE.hasCredentials()) { // Can't authenticate without credentials - just show message briefly - xSemaphoreGive(renderingMutex); return; }src/activities/home/MyLibraryActivity.cpp (2)
75-83:⚠️ Potential issue | 🟡 MinorRequest an update after long‑press “go home”.
The basepath and file list change, but the function returns without notifying the render task, so the UI may not refresh until the next input.✅ Suggested fix
if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= GO_HOME_MS && basepath != "/") { basepath = "/"; loadFiles(); selectorIndex = 0; + requestUpdate(); return; }
62-68:⚠️ Potential issue | 🟠 MajorAdd requestUpdate() call after loading files in onEnter().
The render task blocks on
ulTaskNotifyTake()waiting for a notification. Without callingrequestUpdate()after loading data, the render task never executes and the screen remains blank until an update is triggered elsewhere.✅ Suggested fix
void MyLibraryActivity::onEnter() { Activity::onEnter(); loadFiles(); selectorIndex = 0; + requestUpdate(); }
🤖 Fix all issues with AI agents
In `@src/activities/network/WifiSelectionActivity.cpp`:
- Around line 416-422: The render() method in WifiSelectionActivity is blocking
the centralized render task by calling vTaskDelay when state ==
WifiSelectionState::PASSWORD_ENTRY; remove the vTaskDelay call so render()
simply returns early in that case (keep the state check and return) to avoid
blocking the global render loop and ensure no other blocking calls are
introduced in WifiSelectionActivity::render.
In `@src/activities/network/WifiSelectionActivity.h`:
- Around line 95-99: The render() method in WifiSelectionActivity currently
calls vTaskDelay(10 / portTICK_PERIOD_MS), which blocks centralized rendering
during PASSWORD_ENTRY transitions; remove that vTaskDelay call from
WifiSelectionActivity::render() and replace it with an immediate return so
render() doesn't stall the renderer, and move any timing or wait logic into the
activity state machine (e.g., handle delays in loop() or onEnter()/onExit(), or
trigger redraws via requestUpdate() when the PASSWORD_ENTRY state changes) so
that render() only draws and never blocks.
In `@src/activities/reader/EpubReaderPercentSelectionActivity.h`:
- Around line 23-29: The comment above the percent field in
EpubReaderPercentSelectionActivity is stale—remove or update the "Render dirty
flag for the task loop" text since there is no render-dirty flag; edit the
comment near the int percent = 0; declaration in
EpubReaderPercentSelectionActivity (and any nearby comments related to
render/task loop) to either delete the stale line or replace it with a short,
accurate description (e.g., "Current slider percent (0-100)") so comments match
the current state of render() and the percent member.
| void WifiSelectionActivity::render() { | ||
| // Don't render if we're in PASSWORD_ENTRY state - we're just transitioning | ||
| // from the keyboard subactivity back to the main activity | ||
| if (state == WifiSelectionState::PASSWORD_ENTRY) { | ||
| vTaskDelay(10 / portTICK_PERIOD_MS); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Remove vTaskDelay from render() — blocking is inappropriate in the centralized rendering model.
With the centralized render task calling render(), inserting a delay here blocks the render task unnecessarily. For a state where no rendering should occur, simply return early.
Proposed fix
void WifiSelectionActivity::render() {
// Don't render if we're in PASSWORD_ENTRY state - we're just transitioning
// from the keyboard subactivity back to the main activity
if (state == WifiSelectionState::PASSWORD_ENTRY) {
- vTaskDelay(10 / portTICK_PERIOD_MS);
return;
}📝 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.
| void WifiSelectionActivity::render() { | |
| // Don't render if we're in PASSWORD_ENTRY state - we're just transitioning | |
| // from the keyboard subactivity back to the main activity | |
| if (state == WifiSelectionState::PASSWORD_ENTRY) { | |
| vTaskDelay(10 / portTICK_PERIOD_MS); | |
| return; | |
| } | |
| void WifiSelectionActivity::render() { | |
| // Don't render if we're in PASSWORD_ENTRY state - we're just transitioning | |
| // from the keyboard subactivity back to the main activity | |
| if (state == WifiSelectionState::PASSWORD_ENTRY) { | |
| return; | |
| } |
🤖 Prompt for AI Agents
In `@src/activities/network/WifiSelectionActivity.cpp` around lines 416 - 422, The
render() method in WifiSelectionActivity is blocking the centralized render task
by calling vTaskDelay when state == WifiSelectionState::PASSWORD_ENTRY; remove
the vTaskDelay call so render() simply returns early in that case (keep the
state check and return) to avoid blocking the global render loop and ensure no
other blocking calls are introduced in WifiSelectionActivity::render.
| void onEnter() override; | ||
| void onExit() override; | ||
| void loop() override; | ||
| void render() override; | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if the file exists and examine vTaskDelay usage
if [ -f "src/activities/network/WifiSelectionActivity.cpp" ]; then
echo "=== vTaskDelay usage in WifiSelectionActivity.cpp ==="
rg -n "vTaskDelay" src/activities/network/WifiSelectionActivity.cpp -A 2 -B 2
else
echo "File not found at expected path"
# Try to find it
find . -name "WifiSelectionActivity.cpp" -type f 2>/dev/null | head -5
fiRepository: ngxson/crosspoint-reader
Length of output: 312
🏁 Script executed:
#!/bin/bash
# Also check the header file to verify the method signatures
if [ -f "src/activities/network/WifiSelectionActivity.h" ]; then
echo "=== Header file context (lines 90-105) ==="
sed -n '90,105p' src/activities/network/WifiSelectionActivity.h
else
find . -name "WifiSelectionActivity.h" -type f 2>/dev/null | head -5
fiRepository: ngxson/crosspoint-reader
Length of output: 623
🏁 Script executed:
#!/bin/bash
# Check the render method implementation entirely
if [ -f "src/activities/network/WifiSelectionActivity.cpp" ]; then
echo "=== render() method ==="
rg -n "void WifiSelectionActivity::render" src/activities/network/WifiSelectionActivity.cpp -A 50
fiRepository: ngxson/crosspoint-reader
Length of output: 9500
Remove vTaskDelay from the render path to prevent stalling centralized rendering.
The vTaskDelay(10 / portTICK_PERIOD_MS) call in render() blocks rendering during PASSWORD_ENTRY state transitions. Since render() is now centralized, this delay affects the entire activity rendering pipeline. Replace it with a plain return and handle timing via state transitions or requestUpdate() calls.
Change in WifiSelectionActivity.cpp (lines 419-421)
if (state == WifiSelectionState::PASSWORD_ENTRY) {
- vTaskDelay(10 / portTICK_PERIOD_MS);
return;
}🤖 Prompt for AI Agents
In `@src/activities/network/WifiSelectionActivity.h` around lines 95 - 99, The
render() method in WifiSelectionActivity currently calls vTaskDelay(10 /
portTICK_PERIOD_MS), which blocks centralized rendering during PASSWORD_ENTRY
transitions; remove that vTaskDelay call from WifiSelectionActivity::render()
and replace it with an immediate return so render() doesn't stall the renderer,
and move any timing or wait logic into the activity state machine (e.g., handle
delays in loop() or onEnter()/onExit(), or trigger redraws via requestUpdate()
when the PASSWORD_ENTRY state changes) so that render() only draws and never
blocks.
| void render() override; | ||
|
|
||
| private: | ||
| // Current percent value (0-100) shown on the slider. | ||
| int percent = 0; | ||
| // Render dirty flag for the task loop. | ||
| bool updateRequired = false; | ||
| // FreeRTOS task and mutex for rendering. | ||
| TaskHandle_t displayTaskHandle = nullptr; | ||
| SemaphoreHandle_t renderingMutex = nullptr; | ||
|
|
There was a problem hiding this comment.
Remove the stale render-task comment.
The render-dirty flag is gone, but the comment still references the old task loop, which is misleading.
🧹 Suggested cleanup
- // Render dirty flag for the task loop.
-📝 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.
| void render() override; | |
| private: | |
| // Current percent value (0-100) shown on the slider. | |
| int percent = 0; | |
| // Render dirty flag for the task loop. | |
| bool updateRequired = false; | |
| // FreeRTOS task and mutex for rendering. | |
| TaskHandle_t displayTaskHandle = nullptr; | |
| SemaphoreHandle_t renderingMutex = nullptr; | |
| void render() override; | |
| private: | |
| // Current percent value (0-100) shown on the slider. | |
| int percent = 0; |
🤖 Prompt for AI Agents
In `@src/activities/reader/EpubReaderPercentSelectionActivity.h` around lines 23 -
29, The comment above the percent field in EpubReaderPercentSelectionActivity is
stale—remove or update the "Render dirty flag for the task loop" text since
there is no render-dirty flag; edit the comment near the int percent = 0;
declaration in EpubReaderPercentSelectionActivity (and any nearby comments
related to render/task loop) to either delete the stale line or replace it with
a short, accurate description (e.g., "Current slider percent (0-100)") so
comments match the current state of render() and the percent member.
Co-authored-by: znelson <[email protected]>
There was a problem hiding this comment.
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 (2)
src/activities/settings/ButtonRemapActivity.cpp (1)
35-93:⚠️ Potential issue | 🔴 CriticalAvoid calling onBack() while holding RenderLock (deadlock risk).
If onBack triggers Activity::onExit, it will attempt to take the same render mutex and can deadlock. Release the lock before navigation.Suggested fix
- { - RenderLock lock(*this); + bool shouldExit = false; + { + RenderLock lock(*this); // Wait for a front button press to assign to the current role. const int pressedButton = mappedInput.getPressedFrontButton(); if (pressedButton < 0) { return; } @@ tempMapping[currentStep] = static_cast<uint8_t>(pressedButton); currentStep++; if (currentStep >= kRoleCount) { // All roles assigned; save to settings and exit. applyTempMapping(); SETTINGS.saveToFile(); - onBack(); - return; + shouldExit = true; + } else { + requestUpdate(); } - - requestUpdate(); } + if (shouldExit) { + onBack(); + return; + }src/activities/settings/KOReaderSettingsActivity.cpp (1)
120-131:⚠️ Potential issue | 🔴 CriticalRemove the stray
xSemaphoreGivewithout ownership.Line 124 releases the rendering mutex without any matching acquire in this code path. The
handleSelection()method is not protected by a lock when called fromloop(), so this unconditional give will cause FreeRTOS mutex ownership violation and trigger assertions. Remove it.🐛 Proposed fix
- xSemaphoreGive(renderingMutex); return;
🤖 Fix all issues with AI agents
In `@src/activities/settings/ClearCacheActivity.cpp`:
- Around line 121-125: Remove the inconsistent manual semaphore usage in loop():
instead of taking/releasing renderingMutex around the state assignment, directly
assign state = CLEARING (same approach used in onEnter() and clearCache()), keep
the subsequent requestUpdate() and vTaskDelay unchanged, and rely on RenderLock
in the render task for actual render protection; update any references to
renderingMutex in loop() accordingly so there is no
xSemaphoreTake/xSemaphoreGive pair surrounding the state change.
🧹 Nitpick comments (4)
src/activities/settings/OtaUpdateActivity.cpp (1)
23-50: Replace raw semaphore calls withRenderLockfor state updates throughout this activity.This keeps mutex ownership scoped and reduces the risk of future imbalance. The pattern is established across the codebase and is the idiomatic way to protect critical sections.
Apply to both occurrences: lines 23-50 and 159-178.
♻️ Example refactor for one transition (apply similarly to other state changes)
- xSemaphoreTake(renderingMutex, portMAX_DELAY); - state = CHECKING_FOR_UPDATE; - xSemaphoreGive(renderingMutex); - requestUpdate(); + { + Activity::RenderLock lock(*this); + state = CHECKING_FOR_UPDATE; + } + requestUpdate();src/activities/reader/KOReaderSyncActivity.cpp (1)
51-58: Migrate manual mutex pairs to Activity::RenderLock RAII pattern.
RenderLock is now the standard approach across the codebase for handling rendering synchronization. This file has multiple manualxSemaphoreTake/xSemaphoreGivepairs that should be wrapped in RenderLock to reduce the risk of missed releases and maintain consistency with the render sync model.Example refactor for lines 53–56
- xSemaphoreTake(renderingMutex, portMAX_DELAY); - state = SYNCING; - statusMessage = "Syncing time..."; - xSemaphoreGive(renderingMutex); + { + Activity::RenderLock lock(*this); + state = SYNCING; + statusMessage = "Syncing time..."; + }Apply this pattern to all remaining manual mutex pairs in this file (at least 13 other occurrences).
src/activities/Activity.h (1)
45-53: RenderLock is missing move semantics despite being passed by rvalue reference.The
render(RenderLock&&)signature suggests move semantics, butRenderLockonly has deleted copy operations without explicit move constructor/assignment. While this works because the compiler won't implicitly generate move operations when copy operations are deleted, it may cause confusion and prevent storing the lock in certain scenarios.Consider explicitly defaulting or defining move operations for clarity:
♻️ Suggested improvement
public: explicit RenderLock(Activity& activity); RenderLock(const RenderLock&) = delete; RenderLock& operator=(const RenderLock&) = delete; + RenderLock(RenderLock&&) = default; + RenderLock& operator=(RenderLock&&) = delete; ~RenderLock();src/activities/reader/EpubReaderActivity.cpp (1)
222-231: Consider wrapping manual mutex operations with a scoped RAII helper for consistency.This file contains 6 paired
xSemaphoreTake/xSemaphoreGivecalls (at lines 224-228, 244-248, 256-260, 328-333, 406-423, 461-477) that protect inter-task synchronization in theloop()function. While these operations are correctly paired and necessary to prevent concurrent access to thesectionobject during render, the manual mutex handling is inconsistent with the RAIIRenderLockpattern used elsewhere.Notably, other reader activities (
ReaderActivity,TxtReaderActivity,XtcReaderActivity) do not employ this pattern, makingEpubReaderActivityan outlier. A scoped mutex wrapper (e.g., a simple RAII guard) around these critical sections would improve code consistency and reduce error-proneness without requiring architectural changes.
| xSemaphoreTake(renderingMutex, portMAX_DELAY); | ||
| state = CLEARING; | ||
| xSemaphoreGive(renderingMutex); | ||
| updateRequired = true; | ||
| requestUpdate(); | ||
| vTaskDelay(10 / portTICK_PERIOD_MS); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "renderingMutex" -S src/activitiesRepository: ngxson/crosspoint-reader
Length of output: 6069
🏁 Script executed:
rg -n "RenderLock" -S src/activitiesRepository: ngxson/crosspoint-reader
Length of output: 6559
🏁 Script executed:
# Find the ClearCacheActivity.cpp file and examine the onEnter method
fd "ClearCacheActivity.cpp" src/Repository: ngxson/crosspoint-reader
Length of output: 114
🏁 Script executed:
sed -n '1,150p' src/activities/settings/ClearCacheActivity.cpp | cat -nRepository: ngxson/crosspoint-reader
Length of output: 5828
🏁 Script executed:
# Get more context around the area
sed -n '100,140p' src/activities/settings/ClearCacheActivity.cpp | cat -nRepository: ngxson/crosspoint-reader
Length of output: 1485
🏁 Script executed:
# Check the full file to understand the method structure
wc -l src/activities/settings/ClearCacheActivity.cppRepository: ngxson/crosspoint-reader
Length of output: 118
🏁 Script executed:
# Search for state variable usage in ClearCacheActivity
rg -n "state" src/activities/settings/ClearCacheActivity.cpp
# Check Activity base class for requestUpdate implementation
rg -n "requestUpdate" src/activities/Activity.h src/activities/Activity.cppRepository: ngxson/crosspoint-reader
Length of output: 483
🏁 Script executed:
# Get the requestUpdate implementation
sed -n '37,50p' src/activities/Activity.cpp | cat -nRepository: ngxson/crosspoint-reader
Length of output: 561
🏁 Script executed:
# Check the header definition
sed -n '35,45p' src/activities/Activity.h | cat -nRepository: ngxson/crosspoint-reader
Length of output: 542
🏁 Script executed:
# Check the full Activity.cpp to understand the rendering flow
head -60 src/activities/Activity.cpp | cat -nRepository: ngxson/crosspoint-reader
Length of output: 1908
Remove inconsistent semaphore usage in loop() method.
The code at lines 121-125 uses manual xSemaphoreTake/xSemaphoreGive around the state assignment, while onEnter() (line 14) and clearCache() (lines 78, 113) modify state without any semaphore protection. Since requestUpdate() only sends a task notification and the actual render protection comes from RenderLock acquired when the render task runs, this manual semaphore pattern is unnecessary and creates inconsistency.
♻️ Proposed simplification
- xSemaphoreTake(renderingMutex, portMAX_DELAY);
state = CLEARING;
- xSemaphoreGive(renderingMutex);
requestUpdate();📝 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.
| xSemaphoreTake(renderingMutex, portMAX_DELAY); | |
| state = CLEARING; | |
| xSemaphoreGive(renderingMutex); | |
| updateRequired = true; | |
| requestUpdate(); | |
| vTaskDelay(10 / portTICK_PERIOD_MS); | |
| state = CLEARING; | |
| requestUpdate(); | |
| vTaskDelay(10 / portTICK_PERIOD_MS); |
🤖 Prompt for AI Agents
In `@src/activities/settings/ClearCacheActivity.cpp` around lines 121 - 125,
Remove the inconsistent manual semaphore usage in loop(): instead of
taking/releasing renderingMutex around the state assignment, directly assign
state = CLEARING (same approach used in onEnter() and clearCache()), keep the
subsequent requestUpdate() and vTaskDelay unchanged, and rely on RenderLock in
the render task for actual render protection; update any references to
renderingMutex in loop() accordingly so there is no
xSemaphoreTake/xSemaphoreGive pair surrounding the state change.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/network/OtaUpdater.cpp (1)
240-246:⚠️ Potential issue | 🟡 MinorBlocking the render task with 100 ms delays during OTA progress will freeze UI updates and input handling.
delay(100)at line 245 blocks the render task for each OTA chunk; sinceinstallUpdate()is called fromOtaUpdateActivity::loop()(which runs on the centralized render task), this will pause rendering and input during downloads. Consider reducing the delay value—10 ms would be more responsive—or switch to a non-blocking approach oncerequestUpdateAndWait()is available.src/activities/reader/KOReaderSyncActivity.cpp (1)
53-66: 🛠️ Refactor suggestion | 🟠 MajorSwitch state/statusMessage updates to use
RenderLockRAII for consistency and safety.This file still uses manual
xSemaphoreTake/xSemaphoreGivepairs for state updates (11+ instances), while therender()signature already acceptsRenderLock&&. Migrating to the RAII pattern prevents accidental semaphore imbalance if code paths are refactored in the future, and keeps the locking strategy consistent across the activity.Suggested pattern (apply throughout this file)
- xSemaphoreTake(renderingMutex, portMAX_DELAY); - state = SYNCING; - statusMessage = "Syncing time..."; - xSemaphoreGive(renderingMutex); + { + RenderLock lock(*this); + state = SYNCING; + statusMessage = "Syncing time..."; + }
🤖 Fix all issues with AI agents
In `@src/activities/ActivityWithSubactivity.cpp`:
- Around line 3-9: exitActivity() currently calls subActivity->onExit() and
resets subActivity without holding the RenderLock, risking concurrent render
access; modify ActivityWithSubactivity::exitActivity to acquire the RenderLock
(e.g., create a RenderLock guard) before calling subActivity->onExit() and
before resetting subActivity, or alternatively call Activity::onExit() (which
stops rendering) before destroying subActivity—ensure the render task is stopped
or the RenderLock is held while mutating or resetting the subActivity pointer to
avoid use-after-free.
- Around line 25-33: ActivityWithSubactivity::requestUpdate() currently reads
and calls subActivity without synchronization and can race with
enterNewActivity()/exitActivity() which reset subActivity; fix by using the same
RenderLock (or a dedicated mutex used by enterNewActivity()/exitActivity()) to
protect access: acquire the lock, copy the subActivity shared pointer (or check
non-null), release the lock, and then call requestUpdate() on the copied pointer
(or call under the lock if that is safe with your lock ordering) so the pointer
cannot be reset concurrently; ensure you reference the same lock used by
enterNewActivity()/exitActivity() to avoid races or deadlocks.
🧹 Nitpick comments (4)
src/activities/settings/ButtonRemapActivity.cpp (1)
64-93: Remove unnecessary block braces; fix typo in FIXME.The block
{}at lines 64-93 no longer serves a scoping purpose (no RAII object or early-exit requiring it) and can be removed to reduce indentation. Also, minor typo: "one it's" → "once it's".♻️ Suggested cleanup
- { - // Wait for the UI to refresh before accepting another assignment. - // This avoids rapid double-presses that can advance the step without a visible redraw. - delay(100); // FIXME `@ngxson` : use requestUpdateAndWait() one it's implemented + // Wait for the UI to refresh before accepting another assignment. + // This avoids rapid double-presses that can advance the step without a visible redraw. + delay(100); // FIXME `@ngxson` : use requestUpdateAndWait() once it's implemented - // Wait for a front button press to assign to the current role. - const int pressedButton = mappedInput.getPressedFrontButton(); - if (pressedButton < 0) { - return; - } + // Wait for a front button press to assign to the current role. + const int pressedButton = mappedInput.getPressedFrontButton(); + if (pressedButton < 0) { + return; + } - // Update temporary mapping and advance the remap step. - // Only accept the press if this hardware button isn't already assigned elsewhere. - if (!validateUnassigned(static_cast<uint8_t>(pressedButton))) { - requestUpdate(); - return; - } - tempMapping[currentStep] = static_cast<uint8_t>(pressedButton); - currentStep++; - - if (currentStep >= kRoleCount) { - // All roles assigned; save to settings and exit. - applyTempMapping(); - SETTINGS.saveToFile(); - onBack(); - return; - } + // Update temporary mapping and advance the remap step. + // Only accept the press if this hardware button isn't already assigned elsewhere. + if (!validateUnassigned(static_cast<uint8_t>(pressedButton))) { + requestUpdate(); + return; + } + tempMapping[currentStep] = static_cast<uint8_t>(pressedButton); + currentStep++; - requestUpdate(); + if (currentStep >= kRoleCount) { + // All roles assigned; save to settings and exit. + applyTempMapping(); + SETTINGS.saveToFile(); + onBack(); + return; } + + requestUpdate();src/activities/settings/OtaUpdateActivity.cpp (1)
23-50: Use RenderLock RAII for these state transitions to complete the migration.The
onWifiSelectionComplete()method still uses manual semaphore pairs while the rest of this file has migrated toActivity::RenderLock. For consistency and to protect against semaphore imbalance on early returns, replace the manual pairs withRenderLockhere as well.♻️ Suggested pattern (apply similarly to all state updates in this method)
- xSemaphoreTake(renderingMutex, portMAX_DELAY); - state = CHECKING_FOR_UPDATE; - xSemaphoreGive(renderingMutex); + { + RenderLock lock(*this); + state = CHECKING_FOR_UPDATE; + }src/activities/settings/ClearCacheActivity.cpp (1)
121-126: Remove the 100ms delay afterrequestUpdate()or replace with proper synchronization.The event-driven render task is signaled via
xTaskNotify(), which wakes it immediately; an artificial delay provides no benefit and unnecessarily blocks the calling thread. OncerequestUpdateAndWait()is implemented as planned, use that instead to wait for render completion without wasting CPU cycles.src/activities/reader/KOReaderSyncActivity.cpp (1)
88-93: Remove blocking delays after requestUpdate() or replace with requestUpdateAndWait().The delay(100) calls at lines 92 and 138 block the sync task unnecessarily. Since rendering runs in a dedicated task, waiting is not needed. Either drop the delay entirely or implement
requestUpdateAndWait()to wait for the render to complete. This pattern appears across multiple activities (see WifiSelectionActivity, ButtonRemapActivity, ClearCacheActivity, OtaUpdateActivity).
| void ActivityWithSubactivity::exitActivity() { | ||
| // No need to lock, since onExit() already acquires its own lock | ||
| if (subActivity) { | ||
| Serial.printf("[%lu] [ACT] Exiting subactivity...\n", millis()); | ||
| subActivity->onExit(); | ||
| subActivity.reset(); | ||
| } |
There was a problem hiding this comment.
Lock sub-activity teardown to avoid render/data races.
exitActivity() mutates and destroys subActivity without a RenderLock, and Activity::onExit() acquires the lock only after this call. The render task can still run and dereference subActivity concurrently, risking use-after-free. Guard the teardown with RenderLock (or move the base Activity::onExit() earlier, but ensure the render task is stopped before subActivity destruction).
🔧 Suggested fix
void ActivityWithSubactivity::exitActivity() {
- // No need to lock, since onExit() already acquires its own lock
+ RenderLock lock(*this);
if (subActivity) {
Serial.printf("[%lu] [ACT] Exiting subactivity...\n", millis());
subActivity->onExit();
subActivity.reset();
}
}📝 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.
| void ActivityWithSubactivity::exitActivity() { | |
| // No need to lock, since onExit() already acquires its own lock | |
| if (subActivity) { | |
| Serial.printf("[%lu] [ACT] Exiting subactivity...\n", millis()); | |
| subActivity->onExit(); | |
| subActivity.reset(); | |
| } | |
| void ActivityWithSubactivity::exitActivity() { | |
| RenderLock lock(*this); | |
| if (subActivity) { | |
| Serial.printf("[%lu] [ACT] Exiting subactivity...\n", millis()); | |
| subActivity->onExit(); | |
| subActivity.reset(); | |
| } |
🤖 Prompt for AI Agents
In `@src/activities/ActivityWithSubactivity.cpp` around lines 3 - 9,
exitActivity() currently calls subActivity->onExit() and resets subActivity
without holding the RenderLock, risking concurrent render access; modify
ActivityWithSubactivity::exitActivity to acquire the RenderLock (e.g., create a
RenderLock guard) before calling subActivity->onExit() and before resetting
subActivity, or alternatively call Activity::onExit() (which stops rendering)
before destroying subActivity—ensure the render task is stopped or the
RenderLock is held while mutating or resetting the subActivity pointer to avoid
use-after-free.
| void ActivityWithSubactivity::requestUpdate() { | ||
| if (subActivity) { | ||
| // Note: no need lock here. If we reach this branch, that mean enterNewActivity() must have been called, acquired | ||
| // the lock and make sure there is no pending render requests before passing the control to subActivity | ||
| subActivity->requestUpdate(); | ||
| } else { | ||
| Activity::requestUpdate(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Make requestUpdate() safe during sub-activity swaps.
requestUpdate() reads subActivity and calls into it without synchronization. If enterNewActivity()/exitActivity() runs concurrently, this can race with subActivity.reset() and crash. Protect the read/call with the same RenderLock (or a dedicated mutex for sub-activity ownership).
🔧 Suggested fix
void ActivityWithSubactivity::requestUpdate() {
- if (subActivity) {
+ RenderLock lock(*this);
+ if (subActivity) {
// Note: no need lock here. If we reach this branch, that mean enterNewActivity() must have been called, acquired
// the lock and make sure there is no pending render requests before passing the control to subActivity
subActivity->requestUpdate();
} else {
Activity::requestUpdate();
}
}📝 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.
| void ActivityWithSubactivity::requestUpdate() { | |
| if (subActivity) { | |
| // Note: no need lock here. If we reach this branch, that mean enterNewActivity() must have been called, acquired | |
| // the lock and make sure there is no pending render requests before passing the control to subActivity | |
| subActivity->requestUpdate(); | |
| } else { | |
| Activity::requestUpdate(); | |
| } | |
| } | |
| void ActivityWithSubactivity::requestUpdate() { | |
| RenderLock lock(*this); | |
| if (subActivity) { | |
| // Note: no need lock here. If we reach this branch, that mean enterNewActivity() must have been called, acquired | |
| // the lock and make sure there is no pending render requests before passing the control to subActivity | |
| subActivity->requestUpdate(); | |
| } else { | |
| Activity::requestUpdate(); | |
| } | |
| } |
🤖 Prompt for AI Agents
In `@src/activities/ActivityWithSubactivity.cpp` around lines 25 - 33,
ActivityWithSubactivity::requestUpdate() currently reads and calls subActivity
without synchronization and can race with enterNewActivity()/exitActivity()
which reset subActivity; fix by using the same RenderLock (or a dedicated mutex
used by enterNewActivity()/exitActivity()) to protect access: acquire the lock,
copy the subActivity shared pointer (or check non-null), release the lock, and
then call requestUpdate() on the copied pointer (or call under the lock if that
is safe with your lock ordering) so the pointer cannot be reset concurrently;
ensure you reference the same lock used by enterNewActivity()/exitActivity() to
avoid races or deadlocks.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/activities/home/MyLibraryActivity.cpp (1)
118-126:⚠️ Potential issue | 🟠 MajorAdd
requestUpdate()after long-press BACK navigation.This path mutates
basepath/filesbut returns without signaling the centralized renderer, so the UI can remain stale after a long-press BACK.🐛 Proposed fix
if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= GO_HOME_MS && basepath != "/") { basepath = "/"; loadFiles(); selectorIndex = 0; + requestUpdate(); return; }src/activities/network/WifiSelectionActivity.cpp (1)
247-249:⚠️ Potential issue | 🟠 MajorIncomplete migration: replace manual mutex with RAII
RenderLock.This is the only location in this file still using manual
xSemaphoreTake/xSemaphoreGive. Other SD card operations (lines 20, 329, 357) correctly useRenderLock. Manual mutex handling is error-prone and inconsistent with the refactor's goal.Proposed fix
- xSemaphoreTake(renderingMutex, portMAX_DELAY); - WIFI_STORE.setLastConnectedSsid(selectedSSID); - xSemaphoreGive(renderingMutex); + { + RenderLock lock(*this); + WIFI_STORE.setLastConnectedSsid(selectedSSID); + }
🤖 Fix all issues with AI agents
In `@src/activities/settings/OtaUpdateActivity.cpp`:
- Around line 150-155: The render flag in OtaUpdater (observed via
OtaUpdater::getRender()) is never cleared after OtaUpdater::installUpdate()
finishes, causing OtaUpdateActivity::loop() to repeatedly call requestUpdate();
fix by adding a public mutator (e.g., OtaUpdater::clearRender() or
OtaUpdater::setRender(false)) or by updating OtaUpdater::installUpdate() to
reset the internal render flag to false after the download/install completes,
and then call that new method from OtaUpdateActivity::loop() (or let
installUpdate() clear it) so getRender() returns false after completion.
In `@src/activities/settings/SettingsActivity.cpp`:
- Around line 162-166: The lambda enterSubActivity currently creates a
RenderLock then calls exitActivity() and enterNewActivity(), but
enterNewActivity() already acquires the same RenderLock which risks deadlock;
remove the redundant RenderLock acquisition from the enterSubActivity lambda so
it simply calls exitActivity() and enterNewActivity(), relying on
enterNewActivity() (and exitActivity() if needed) to provide the required mutual
exclusion around rendering.
🧹 Nitpick comments (2)
src/activities/settings/OtaUpdateActivity.cpp (2)
23-50: Complete RenderLock migration for state transitions.Manual xSemaphoreTake/xSemaphoreGive usage around Line 23 (and the other state updates in this method) is a leftover from the old render model. Prefer Activity::RenderLock to keep locking consistent with the centralized renderer and prevent leaks on future early returns. Apply the same pattern to the NO_UPDATE branch as well.
♻️ Suggested pattern for each state update
- xSemaphoreTake(renderingMutex, portMAX_DELAY); - state = CHECKING_FOR_UPDATE; - xSemaphoreGive(renderingMutex); + { + RenderLock lock(*this); + state = CHECKING_FOR_UPDATE; + }- xSemaphoreTake(renderingMutex, portMAX_DELAY); - state = FAILED; - xSemaphoreGive(renderingMutex); + { + RenderLock lock(*this); + state = FAILED; + }- xSemaphoreTake(renderingMutex, portMAX_DELAY); - state = WAITING_CONFIRMATION; - xSemaphoreGive(renderingMutex); + { + RenderLock lock(*this); + state = WAITING_CONFIRMATION; + }
169-170: Avoid fixed delays after requestUpdate in the activity thread.Line 169 blocks the activity loop for 100 ms; with centralized rendering this can stall input and background work. Same pattern at Line 27. Consider a non-blocking state step or requestUpdateAndWait once available.
| void OtaUpdateActivity::loop() { | ||
| // TODO @ngxson : refactor this logic later | ||
| if (updater.getRender()) { | ||
| requestUpdate(); | ||
| } | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "*.h" -o -name "*.hpp" -o -name "*.cpp" -o -name "*.cc" | xargs grep -l "OtaUpdater" | head -20Repository: ngxson/crosspoint-reader
Length of output: 217
🏁 Script executed:
rg -l "class OtaUpdater"Repository: ngxson/crosspoint-reader
Length of output: 92
🏁 Script executed:
rg "getRender\s*\(" -C 5Repository: ngxson/crosspoint-reader
Length of output: 1337
🏁 Script executed:
rg "setRender|clearRender|renderFlag|renderRequest|resetRender" -C 3Repository: ngxson/crosspoint-reader
Length of output: 4672
🏁 Script executed:
cat -n src/network/OtaUpdater.hRepository: ngxson/crosspoint-reader
Length of output: 1151
🏁 Script executed:
cat -n src/network/OtaUpdater.cppRepository: ngxson/crosspoint-reader
Length of output: 11175
🏁 Script executed:
rg "requestUpdate" src/activities/settings/OtaUpdateActivity.cpp -C 10Repository: ngxson/crosspoint-reader
Length of output: 2550
The render flag remains sticky after OTA download completes, causing requestUpdate() to be called repeatedly every loop iteration.
In OtaUpdater::installUpdate(), the render flag is set to true (line 244) during the download loop but is never reset to false after the loop exits (line 246). Since OtaUpdater has no public method to clear this flag (only a const getter), the flag stays true indefinitely, triggering requestUpdate() on every loop() tick in OtaUpdateActivity (lines 152-154).
Add a method to clear the flag or explicitly reset it after installUpdate() completes.
🤖 Prompt for AI Agents
In `@src/activities/settings/OtaUpdateActivity.cpp` around lines 150 - 155, The
render flag in OtaUpdater (observed via OtaUpdater::getRender()) is never
cleared after OtaUpdater::installUpdate() finishes, causing
OtaUpdateActivity::loop() to repeatedly call requestUpdate(); fix by adding a
public mutator (e.g., OtaUpdater::clearRender() or OtaUpdater::setRender(false))
or by updating OtaUpdater::installUpdate() to reset the internal render flag to
false after the download/install completes, and then call that new method from
OtaUpdateActivity::loop() (or let installUpdate() clear it) so getRender()
returns false after completion.
| auto enterSubActivity = [this](Activity* activity) { | ||
| xSemaphoreTake(renderingMutex, portMAX_DELAY); | ||
| RenderLock lock(*this); | ||
| exitActivity(); | ||
| enterNewActivity(activity); | ||
| xSemaphoreGive(renderingMutex); | ||
| }; |
There was a problem hiding this comment.
Avoid double-locking the render mutex (deadlock risk).
enterNewActivity() already acquires RenderLock; taking another lock here can deadlock if the mutex is non-recursive. Remove the redundant lock and rely on enterNewActivity() for mutual exclusion.
🛠️ Proposed fix
- auto enterSubActivity = [this](Activity* activity) {
- RenderLock lock(*this);
- exitActivity();
- enterNewActivity(activity);
- };
+ auto enterSubActivity = [this](Activity* activity) {
+ exitActivity();
+ enterNewActivity(activity);
+ };📝 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.
| auto enterSubActivity = [this](Activity* activity) { | |
| xSemaphoreTake(renderingMutex, portMAX_DELAY); | |
| RenderLock lock(*this); | |
| exitActivity(); | |
| enterNewActivity(activity); | |
| xSemaphoreGive(renderingMutex); | |
| }; | |
| auto enterSubActivity = [this](Activity* activity) { | |
| exitActivity(); | |
| enterNewActivity(activity); | |
| }; |
🤖 Prompt for AI Agents
In `@src/activities/settings/SettingsActivity.cpp` around lines 162 - 166, The
lambda enterSubActivity currently creates a RenderLock then calls exitActivity()
and enterNewActivity(), but enterNewActivity() already acquires the same
RenderLock which risks deadlock; remove the redundant RenderLock acquisition
from the enterSubActivity lambda so it simply calls exitActivity() and
enterNewActivity(), relying on enterNewActivity() (and exitActivity() if needed)
to provide the required mutual exclusion around rendering.
Mirror upstream crosspoint-reader#774
Summary by CodeRabbit