Skip to content

fix: properly implement requestUpdateAndWait()#1218

Merged
daveallie merged 6 commits intocrosspoint-reader:masterfrom
ngxson:xsn/render_wait
Mar 1, 2026
Merged

fix: properly implement requestUpdateAndWait()#1218
daveallie merged 6 commits intocrosspoint-reader:masterfrom
ngxson:xsn/render_wait

Conversation

@ngxson
Copy link
Contributor

@ngxson ngxson commented Feb 27, 2026

Summary

Properly implement requestUpdateAndWait() using freeRTOS direct task notification.

FWIW, I think most of the current use cases of requestUpdateAndWait() are redundant, better to be replaced by requestUpdate(true). But just keeping them in case we can find a proper use case for it in the future.


AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? YES, it's trivial, so I asked an AI to write the code

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 27, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Activity::requestUpdateAndWait now delegates to ActivityManager::requestUpdateAndWait. ActivityManager adds single-waiter coordination: callers can request a render and block until the render task signals completion. WifiSelectionActivity's PASSWORD_ENTRY render no longer invokes the wait-based update.

Changes

Cohort / File(s) Summary
Core synchronization
src/activities/ActivityManager.h, src/activities/ActivityManager.cpp
Add waitingTaskHandle to store a single waiter; implement ActivityManager::requestUpdateAndWait() which validates the caller, records the waiter, notifies the render task, and blocks until the render task clears/notifies the waiter; render loop clears and notifies waiter after rendering.
Activity delegation
src/activities/Activity.cpp
Change Activity::requestUpdateAndWait() to delegate to activityManager.requestUpdateAndWait() (remove previous manual request/delay).
UI state rendering
src/activities/network/WifiSelectionActivity.cpp
Remove the call to requestUpdateAndWait() during PASSWORD_ENTRY rendering; render now returns immediately and relies on the next loop iteration to update state.

Sequence Diagram

sequenceDiagram
    participant Caller as Calling Task
    participant AM as ActivityManager
    participant RT as Render Task

    Caller->>AM: requestUpdateAndWait()
    Note over AM: validate (not render task,\nno existing waiter,\nrender task exists)
    AM->>AM: store waitingTaskHandle
    AM->>RT: notify render task to render
    Caller->>Caller: block (wait)
    RT->>RT: perform render/frame
    RT->>AM: notify render complete
    AM->>Caller: notify waiting task
    AM->>AM: clear waitingTaskHandle
    Caller->>Caller: unblock and resume
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • znelson
  • daveallie
  • osteotek
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: implementing requestUpdateAndWait() properly, which is the core objective reflected across all modified files.
Description check ✅ Passed The description is directly related to the changeset, explaining the implementation approach using FreeRTOS task notifications and acknowledging potential redundancy with existing functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/activities/ActivityManager.cpp`:
- Around line 41-45: The notify sequence can race with a higher-priority task
and cause the assertion in requestUpdateAndWait() because waitingTaskHandle
isn't cleared before a context switch; wrap the clear-and-notify in a critical
section: enter a critical region, set waitingTaskHandle = nullptr, call
xTaskNotify(waitingTaskHandle, 1, eIncrement) (or call notify after clearing but
ensure you notify the saved handle), then exit the critical region so the wakeup
can't preempt while the handle is still set; update the code around
waitingTaskHandle and xTaskNotify in ActivityManager.cpp and ensure
requestUpdateAndWait() (which asserts !waitingTaskHandle) will see the cleared
value.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4fc4ef and ef6dd0c.

📒 Files selected for processing (4)
  • src/activities/Activity.cpp
  • src/activities/ActivityManager.cpp
  • src/activities/ActivityManager.h
  • src/activities/network/WifiSelectionActivity.cpp
💤 Files with no reviewable changes (1)
  • src/activities/network/WifiSelectionActivity.cpp
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: cppcheck
  • GitHub Check: build
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-02-16T20:43:19.339Z
Learnt from: Levrk
Repo: crosspoint-reader/crosspoint-reader PR: 909
File: src/activities/reader/EpubReaderActivity.cpp:461-461
Timestamp: 2026-02-16T20:43:19.339Z
Learning: In src/activities/reader/EpubReaderActivity.cpp, when using ConfirmationActivity with enterNewActivity(), it's not necessary to call exitActivity() beforehand. The ConfirmationActivity operates as a modal dialog and cleanup is handled via the pendingSubactivityExit flag in the callback lambda. This differs from other activities like EpubReaderChapterSelectionActivity or KOReaderSyncActivity which do require exitActivity() before enterNewActivity().

Applied to files:

  • src/activities/Activity.cpp
🧬 Code graph analysis (3)
src/activities/ActivityManager.h (2)
src/activities/Activity.cpp (2)
  • requestUpdateAndWait (11-11)
  • requestUpdateAndWait (11-11)
src/activities/ActivityManager.cpp (2)
  • requestUpdateAndWait (233-244)
  • requestUpdateAndWait (233-233)
src/activities/Activity.cpp (1)
src/activities/ActivityManager.cpp (2)
  • requestUpdateAndWait (233-244)
  • requestUpdateAndWait (233-233)
src/activities/ActivityManager.cpp (1)
src/activities/Activity.cpp (2)
  • requestUpdateAndWait (11-11)
  • requestUpdateAndWait (11-11)
🔇 Additional comments (4)
src/activities/ActivityManager.h (2)

52-54: Good single-waiter contract documentation.

The member comment clearly documents ownership and lifecycle of waitingTaskHandle.


105-107: Clear API usage constraints.

The contract around render-task context and RenderLock usage is explicit and helpful.

src/activities/Activity.cpp (1)

11-11: Nice centralization of wait behavior.

Delegating to ActivityManager::requestUpdateAndWait() keeps synchronization logic in one place.

src/activities/ActivityManager.cpp (1)

233-244: Replace assert-based safety checks with runtime error handling.

Lines 238-240 use plain assert() for critical safety invariants. Since assert() compiles out in release builds (standard C++ behavior), invalid call paths will silently proceed rather than fail safely, risking hangs or state corruption.

Replace both assertions with explicit runtime checks that return early on violation. Remove the assert(!waitingTaskHandle...) and assert(xTaskGetCurrentTaskHandle() != renderTaskHandle...) conditions and handle them as error returns instead.

Note: The proposed diff references xSemaphoreGetMutexHolder() and taskENTER_CRITICAL() which are not available in this codebase. Use simpler runtime error handling: store the current task handle and compare it, then return on mismatch. Check waitingTaskHandle != nullptr under direct access (this is single-threaded at function entry before any yields) and return if already set.

Likely an incorrect or invalid review comment.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/activities/ActivityManager.cpp`:
- Around line 237-248: Add a guard in ActivityManager::requestUpdateAndWait to
assert the caller is not holding the RenderLock/renderingMutex before blocking:
check that the current task does not own renderingMutex (or call a
RenderLock::isHeldByCurrentTask()) and assert/fail if it does, so we detect the
situation that would cause the deadlock between requestUpdateAndWait(), the
renderTaskHandle, and renderingMutex; update the assertion message to reference
RenderLock/renderingMutex and requestUpdateAndWait for clarity.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef6dd0c and 81a6b7f.

📒 Files selected for processing (1)
  • src/activities/ActivityManager.cpp
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: cppcheck
🧰 Additional context used
🧬 Code graph analysis (1)
src/activities/ActivityManager.cpp (1)
src/activities/Activity.cpp (2)
  • requestUpdateAndWait (11-11)
  • requestUpdateAndWait (11-11)
🔇 Additional comments (1)
src/activities/ActivityManager.cpp (1)

41-49: LGTM!

The critical section correctly ensures waitingTaskHandle is cleared before the notification is sent. This properly addresses the preemption race where a higher-priority waiting task could be immediately scheduled and re-enter requestUpdateAndWait() before the handle was cleared.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/activities/ActivityManager.cpp`:
- Around line 242-250: The registration of waitingTaskHandle in
requestUpdateAndWait() is vulnerable to a race: the assert(!waitingTaskHandle)
and the assignment waitingTaskHandle = xTaskGetCurrentTaskHandle() must be
atomic to avoid lost-waiter deadlocks; wrap the check-and-set in a
synchronization primitive (e.g., a short critical section via
taskENTER_CRITICAL/taskEXIT_CRITICAL or a dedicated FreeRTOS mutex/semaphore) so
only one caller can pass the assert and set waitingTaskHandle, then perform
xTaskNotify(renderTaskHandle, 1, eIncrement) and ulTaskNotifyTake as before;
ensure you do not use renderingMutex inside this region (use a separate mutex or
CPU critical section) and clear waitingTaskHandle atomically after
ulTaskNotifyTake completes.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81a6b7f and 1f53a14.

📒 Files selected for processing (1)
  • src/activities/ActivityManager.cpp
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: cppcheck
🧰 Additional context used
🧬 Code graph analysis (1)
src/activities/ActivityManager.cpp (1)
src/activities/Activity.cpp (2)
  • requestUpdateAndWait (11-11)
  • requestUpdateAndWait (11-11)
🔇 Additional comments (1)
src/activities/ActivityManager.cpp (1)

41-49: Good fix: waiter clear-and-notify sequence is now preemption-safe.

The Line 43-46 critical section correctly snapshots and clears waitingTaskHandle before notifying, which removes the stale-handle race.

znelson
znelson previously approved these changes Feb 27, 2026
Comment on lines -465 to -470
// 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) {
requestUpdateAndWait();
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

While requestUpdateAndWait is bad here because it leads to a deadlock, I think the early return was still valuable. When the keyboard subactivity exits and render fires before loop has advanced the state, there's no point drawing the full wifi network list just to immediately overwrite it.

Comment on lines +243 to +244
// Render task cannot call requestUpdateAndWait() or it will cause a deadlock
assert(xTaskGetCurrentTaskHandle() != renderTaskHandle && "Render task cannot call requestUpdateAndWait()");
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the @coderabbitai observation here is correct, there's a really small potential for deadlock if two tasks happened to call requestUpdateAndWait and interrupt back and forth in the precise sequence to overwrite waitingTaskHandle.

Suggested change
// Render task cannot call requestUpdateAndWait() or it will cause a deadlock
assert(xTaskGetCurrentTaskHandle() != renderTaskHandle && "Render task cannot call requestUpdateAndWait()");
// Render task cannot call requestUpdateAndWait() or it will cause a deadlock
static portMUX_TYPE waitingTaskMutex = portMUX_INITIALIZER_UNLOCKED;
taskENTER_CRITICAL(&waitingTaskMutex);
bool alreadyWaiting = (waitingTaskHandle != nullptr);
if (!alreadyWaiting) {
waitingTaskHandle = xTaskGetCurrentTaskHandle();
}
taskEXIT_CRITICAL(&waitingTaskMutex);
assert(!alreadyWaiting && "Already waiting for a render to complete");
if (alreadyWaiting) {
return;
}

Or maybe clearer if you make std::atomic<TaskHandle_t> waitingTaskHandle{nullptr}; and:

  TaskHandle_t expected = nullptr;
  TaskHandle_t self = xTaskGetCurrentTaskHandle();
  if (!waitingTaskHandle.compare_exchange_strong(expected, self)) {
    assert(false && "Already waiting for a render to complete");
    return;
  }

And then the clear side in renderTaskLoop:

  if (TaskHandle_t waiter = waitingTaskHandle.exchange(nullptr)) {
    xTaskNotify(waiter, 1, eIncrement);
  }

Copy link
Contributor

Choose a reason for hiding this comment

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

@znelson Great catch on the race condition! You're absolutely right that the check-and-set sequence needs to be atomic.

Both solutions correctly address the issue. Here's my analysis:

Option 1 (Critical Section):

  • ✅ Always works in FreeRTOS environments
  • ✅ Explicit and clear about the critical region
  • ❌ More verbose with the static mutex declaration
  • ❌ Additional assert + early return adds complexity

Option 2 (std::atomic):

  • ✅ Much cleaner and more idiomatic modern C++
  • ✅ Single atomic operation (compare_exchange_strong) combines check and set
  • ✅ The renderTaskLoop side with exchange(nullptr) is also cleaner
  • ⚠️ Requires C++11 atomic support (should be fine for most embedded toolchains today)

Recommendation: I'd go with Option 2 (std::atomic) unless there's a specific constraint against C++11 atomics in this codebase. It's more elegant, less code, and the atomic semantics are exactly what you need here.

The key advantage is that compare_exchange_strong does the check-and-swap atomically, and the exchange(nullptr) in renderTaskLoop is also a single atomic operation—both are clearer than critical sections for this use case.

Would you like me to generate the complete code changes applying the atomic solution across all the affected locations (ActivityManager.h for the member declaration, this function, and renderTaskLoop)?

Copy link
Contributor Author

@ngxson ngxson Feb 27, 2026

Choose a reason for hiding this comment

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

I decided to go with the taskENTER_CRITICAL instead of std::atomic as it looks cleaner in the particular case, see 882e327

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/activities/ActivityManager.cpp (1)

43-46: ⚠️ Potential issue | 🔴 Critical

Use a real shared spinlock instead of taskENTER_CRITICAL(nullptr).

waitingTaskHandle protection is currently tied to taskENTER_CRITICAL(nullptr) (Line [43], Line [243]). On common FreeRTOS/ESP-IDF ports, this should use a valid shared portMUX_TYPE*; otherwise atomicity is not guaranteed (and may assert).

🔧 Suggested fix
+namespace {
+portMUX_TYPE waitingTaskMux = portMUX_INITIALIZER_UNLOCKED;
+}
+
 // Notify any task blocked in requestUpdateAndWait() that the render is done.
 TaskHandle_t waiter = nullptr;
-taskENTER_CRITICAL(nullptr);
+taskENTER_CRITICAL(&waitingTaskMux);
 waiter = waitingTaskHandle;
 waitingTaskHandle = nullptr;
-taskEXIT_CRITICAL(nullptr);
+taskEXIT_CRITICAL(&waitingTaskMux);
 if (waiter) {
   xTaskNotify(waiter, 1, eIncrement);
 }
@@
-  taskENTER_CRITICAL(nullptr);
+  taskENTER_CRITICAL(&waitingTaskMux);
   auto currTaskHandler = xTaskGetCurrentTaskHandle();
   auto mutexHolder = xSemaphoreGetMutexHolder(renderingMutex);
   bool isRenderTask = (currTaskHandler == renderTaskHandle);
   bool alreadyWaiting = (waitingTaskHandle != nullptr);
   bool holdingRenderLock = (mutexHolder == currTaskHandler);
   if (!alreadyWaiting && !isRenderTask && !holdingRenderLock) {
     waitingTaskHandle = currTaskHandler;
   }
-  taskEXIT_CRITICAL(nullptr);
+  taskEXIT_CRITICAL(&waitingTaskMux);
#!/bin/bash
set -euo pipefail

echo "=== Call sites using nullptr critical sections ==="
rg -n --type cpp 'taskENTER_CRITICAL\s*\(\s*nullptr\s*\)|taskEXIT_CRITICAL\s*\(\s*nullptr\s*\)' src

echo
echo "=== FreeRTOS critical-section API definitions in repo headers ==="
fd -t f 'task.h|FreeRTOS.h|portmacro.h' . | xargs -r rg -n 'taskENTER_CRITICAL|taskEXIT_CRITICAL|portENTER_CRITICAL|portMUX_TYPE'

Expected: confirm whether this toolchain requires a non-null portMUX_TYPE* and identify all affected call sites.

Also applies to: 243-253

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/activities/ActivityManager.cpp` around lines 43 - 46, The code uses
taskENTER_CRITICAL(nullptr)/taskEXIT_CRITICAL(nullptr) around accesses to
waitingTaskHandle (e.g., in the block that sets waiter = waitingTaskHandle;
waitingTaskHandle = nullptr;) which can be invalid on some FreeRTOS/ESP-IDF
ports; replace the nullptr critical sections with a real shared spinlock: add or
reuse a portMUX_TYPE (or equivalent) mutex (e.g., declare a static portMUX_TYPE
activity_mutex or use an existing shared portMUX_TYPE pointer), and replace
taskENTER_CRITICAL(nullptr)/taskEXIT_CRITICAL(nullptr) calls with the
port-specific macros that accept that mutex
(portENTER_CRITICAL(&activity_mutex)/portEXIT_CRITICAL(&activity_mutex) or
taskENTER_CRITICAL(&activity_mutex)/taskEXIT_CRITICAL(&activity_mutex)),
ensuring every other call site that touches waitingTaskHandle (including the
ones around lines ~243-253) uses the same mutex.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/activities/ActivityManager.cpp`:
- Around line 254-265: The assertions in requestUpdateAndWait() (checking
isRenderTask, alreadyWaiting, holdingRenderLock) are not sufficient when asserts
are disabled—add explicit runtime guards that return early if any of these
conditions are true to avoid executing xTaskNotify/render wait and causing a
deadlock; specifically, after the assert lines for isRenderTask, alreadyWaiting,
and holdingRenderLock, add conditional checks that log or handle the error and
return immediately before calling xTaskNotify(renderTaskHandle, 1, eIncrement)
and ulTaskNotifyTake(...).

---

Duplicate comments:
In `@src/activities/ActivityManager.cpp`:
- Around line 43-46: The code uses
taskENTER_CRITICAL(nullptr)/taskEXIT_CRITICAL(nullptr) around accesses to
waitingTaskHandle (e.g., in the block that sets waiter = waitingTaskHandle;
waitingTaskHandle = nullptr;) which can be invalid on some FreeRTOS/ESP-IDF
ports; replace the nullptr critical sections with a real shared spinlock: add or
reuse a portMUX_TYPE (or equivalent) mutex (e.g., declare a static portMUX_TYPE
activity_mutex or use an existing shared portMUX_TYPE pointer), and replace
taskENTER_CRITICAL(nullptr)/taskEXIT_CRITICAL(nullptr) calls with the
port-specific macros that accept that mutex
(portENTER_CRITICAL(&activity_mutex)/portEXIT_CRITICAL(&activity_mutex) or
taskENTER_CRITICAL(&activity_mutex)/taskEXIT_CRITICAL(&activity_mutex)),
ensuring every other call site that touches waitingTaskHandle (including the
ones around lines ~243-253) uses the same mutex.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f53a14 and 882e327.

📒 Files selected for processing (2)
  • src/activities/ActivityManager.cpp
  • src/activities/network/WifiSelectionActivity.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/activities/network/WifiSelectionActivity.cpp

Comment on lines +254 to +265
// Render task cannot call requestUpdateAndWait() or it will cause a deadlock
assert(!isRenderTask && "Render task cannot call requestUpdateAndWait()");

// There should never be the case where 2 tasks are waiting for a render at the same time
assert(!alreadyWaiting && "Already waiting for a render to complete");

// Cannot call while holding RenderLock or it will cause a deadlock
assert(!holdingRenderLock && "Cannot call requestUpdateAndWait() while holding RenderLock");

xTaskNotify(renderTaskHandle, 1, eIncrement);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t rely on assert alone for wait-path safety.

If assertions are disabled, invalid states still execute Line [263]-Line [264], which can deadlock/hang. Add a runtime early return after asserting.

🔧 Suggested fix
   // Render task cannot call requestUpdateAndWait() or it will cause a deadlock
   assert(!isRenderTask && "Render task cannot call requestUpdateAndWait()");
@@
   // Cannot call while holding RenderLock or it will cause a deadlock
   assert(!holdingRenderLock && "Cannot call requestUpdateAndWait() while holding RenderLock");
+
+  if (isRenderTask || alreadyWaiting || holdingRenderLock) {
+    return;
+  }
 
   xTaskNotify(renderTaskHandle, 1, eIncrement);
   ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/activities/ActivityManager.cpp` around lines 254 - 265, The assertions in
requestUpdateAndWait() (checking isRenderTask, alreadyWaiting,
holdingRenderLock) are not sufficient when asserts are disabled—add explicit
runtime guards that return early if any of these conditions are true to avoid
executing xTaskNotify/render wait and causing a deadlock; specifically, after
the assert lines for isRenderTask, alreadyWaiting, and holdingRenderLock, add
conditional checks that log or handle the error and return immediately before
calling xTaskNotify(renderTaskHandle, 1, eIncrement) and ulTaskNotifyTake(...).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

assertions are always enabled, it's fine here. this should be a hard-crash so that it's visible to developers

Copy link
Contributor

Choose a reason for hiding this comment

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

@ngxson, understood! If assertions are always enabled in your build configuration, then the hard-crash behavior is appropriate for catching these programmer errors during development.


✏️ Learnings added
Learnt from: ngxson
Repo: crosspoint-reader/crosspoint-reader PR: 1218
File: src/activities/ActivityManager.cpp:254-265
Timestamp: 2026-02-27T22:49:59.600Z
Learning: In this codebase, assertions are always enabled (never disabled in builds), so assert() statements are used intentionally to hard-crash and make programmer errors visible to developers rather than silently failing.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

znelson
znelson previously approved these changes Feb 27, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/activities/ActivityManager.h (1)

106-108: Document the one-waiter limit in the public method contract.

The internal member comment states single-waiter behavior, but the public API comment doesn’t. Adding that here will reduce misuse by callers.

✏️ Suggested doc tweak
-  // Trigger a render and block until it completes.
-  // Must NOT be called from the render task or while holding a RenderLock.
+  // Trigger a render and block until it completes.
+  // Supports only one waiting task at a time.
+  // Must NOT be called from the render task or while holding a RenderLock.
   void requestUpdateAndWait();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/activities/ActivityManager.h` around lines 106 - 108, Update the public
comment for requestUpdateAndWait() to explicitly state the single-waiter
limitation: document that at most one caller may block waiting for the render to
complete (concurrent callers will fail or deadlock), and reiterate that it must
not be called from the render task or while holding a RenderLock; mention
expected behavior if multiple callers attempt to wait (e.g., only the first
waiter is honored or others will return/raise).
src/activities/ActivityManager.cpp (1)

244-248: Use currTaskHandle naming for consistency.

Minor readability nit: Line 244 uses currTaskHandler, while the type and surrounding code use “Handle”.

✏️ Suggested rename
-  auto currTaskHandler = xTaskGetCurrentTaskHandle();
+  auto currTaskHandle = xTaskGetCurrentTaskHandle();
   auto mutexHolder = xSemaphoreGetMutexHolder(renderingMutex);
-  bool isRenderTask = (currTaskHandler == renderTaskHandle);
+  bool isRenderTask = (currTaskHandle == renderTaskHandle);
   bool alreadyWaiting = (waitingTaskHandle != nullptr);
-  bool holdingRenderLock = (mutexHolder == currTaskHandler);
+  bool holdingRenderLock = (mutexHolder == currTaskHandle);
   if (!alreadyWaiting && !isRenderTask && !holdingRenderLock) {
-    waitingTaskHandle = currTaskHandler;
+    waitingTaskHandle = currTaskHandle;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/activities/ActivityManager.cpp` around lines 244 - 248, Rename the local
variable currTaskHandler to currTaskHandle for consistency with surrounding code
and types: change the declaration that assigns xTaskGetCurrentTaskHandle() and
all subsequent uses (including comparisons with renderTaskHandle,
waitingTaskHandle checks, and the mutexHolder comparison used to set
holdingRenderLock) to currTaskHandle; ensure the variable name is updated
wherever referenced (e.g., in the xSemaphoreGetMutexHolder result comparison and
isRenderTask/alreadyWaiting/holdingRenderLock assignments) so naming is
consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/activities/ActivityManager.cpp`:
- Around line 244-248: Rename the local variable currTaskHandler to
currTaskHandle for consistency with surrounding code and types: change the
declaration that assigns xTaskGetCurrentTaskHandle() and all subsequent uses
(including comparisons with renderTaskHandle, waitingTaskHandle checks, and the
mutexHolder comparison used to set holdingRenderLock) to currTaskHandle; ensure
the variable name is updated wherever referenced (e.g., in the
xSemaphoreGetMutexHolder result comparison and
isRenderTask/alreadyWaiting/holdingRenderLock assignments) so naming is
consistent.

In `@src/activities/ActivityManager.h`:
- Around line 106-108: Update the public comment for requestUpdateAndWait() to
explicitly state the single-waiter limitation: document that at most one caller
may block waiting for the render to complete (concurrent callers will fail or
deadlock), and reiterate that it must not be called from the render task or
while holding a RenderLock; mention expected behavior if multiple callers
attempt to wait (e.g., only the first waiter is honored or others will
return/raise).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 882e327 and 858b215.

📒 Files selected for processing (2)
  • src/activities/ActivityManager.cpp
  • src/activities/ActivityManager.h
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: cppcheck
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-02-27T22:49:59.600Z
Learnt from: ngxson
Repo: crosspoint-reader/crosspoint-reader PR: 1218
File: src/activities/ActivityManager.cpp:254-265
Timestamp: 2026-02-27T22:49:59.600Z
Learning: In this codebase, assertions are always enabled (no NDEBUG). Use assert() to crash on programmer errors and surface logic bugs during development and in production builds. Do not rely on asserts for runtime error handling; they should enforce invariants that must always hold. Keep asserts side-effect free and inexpensive, and avoid relying on them for user-visible failures. Include <cassert> where appropriate and document the invariant being tested.

Applied to files:

  • src/activities/ActivityManager.cpp
🧬 Code graph analysis (2)
src/activities/ActivityManager.cpp (1)
src/activities/Activity.cpp (2)
  • requestUpdateAndWait (11-11)
  • requestUpdateAndWait (11-11)
src/activities/ActivityManager.h (2)
src/activities/ActivityManager.cpp (2)
  • requestUpdateAndWait (237-265)
  • requestUpdateAndWait (237-237)
src/activities/Activity.cpp (2)
  • requestUpdateAndWait (11-11)
  • requestUpdateAndWait (11-11)
🔇 Additional comments (3)
src/activities/ActivityManager.h (1)

53-55: Single-waiter state is clearly documented and safely initialized.

Good addition—this makes waiter coordination explicit and keeps the invariant readable.

src/activities/ActivityManager.cpp (2)

41-49: Clear-before-notify waiter handoff is correctly synchronized.

This closes the preemption window where stale waiter state could otherwise be observed.


254-261: The assert-based invariants are appropriate on this blocking path.

These checks are side-effect free and correctly prevent invalid/deadlocking call contexts before notify/wait.
Based on learnings: In this codebase, assertions are always enabled (no NDEBUG) and are used to hard-crash on programmer errors.

@ngxson ngxson requested a review from a team February 28, 2026 10:23
@daveallie daveallie merged commit a57c62f into crosspoint-reader:master Mar 1, 2026
6 checks passed
laird pushed a commit to laird/crosspoint-claw that referenced this pull request Mar 1, 2026
## Summary

Properly implement `requestUpdateAndWait()` using freeRTOS direct task
notification.

FWIW, I think most of the current use cases of `requestUpdateAndWait()`
are redundant, better to be replaced by `requestUpdate(true)`. But just
keeping them in case we can find a proper use case for it in the future.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? **YES**, it's trivial, so
I asked an AI to write the code

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
laird added a commit to laird/crosspoint-claw that referenced this pull request Mar 1, 2026
Cherry-picked upstream fixes and features applied via rescue-1.0.0 worktree:
- fix: properly implement requestUpdateAndWait() (crosspoint-reader#1218)
- fix: Hide unusable button hints in empty directory (crosspoint-reader#1253)
- fix: add missing keyboard metrics to Lyra3CoversTheme (crosspoint-reader#1101)
- fix: remove bookProgressBarHeight from Lyra3CoversTheme
- feat: replace picojpeg with JPEGDEC for JPEG decoding (crosspoint-reader#1136)
- feat: WIFI pill, feed log fix, JPEGDEC version string
- feat: Add git branch to version string (crosspoint-reader#1225)
- fix: navigate directly to QR code after DZ auto-connect
- perf: Removed unused ConfirmationActivity member (crosspoint-reader#1234)
- refactor: Simplify new setting introduction (crosspoint-reader#1086)
- fix: UI fonts, settings stack overflow, PULSR theme name
- fix: convert SettingsList to push_back (prevent stack overflow)

All commits built and verified on device (confirmed 1.1.0-dev+d1e786a).
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.

3 participants