Repository navigation
Return EOS_KERN_NO_MEMORY when queue waiter table is full - #85
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The new test helper changes the kernel’s “current task” without restoring it, leaking global task context across the test execution path.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR fixes a deadlock risk in the kernel message queue implementation by refusing to block when the queue’s waiter table is already full, returning EOS_KERN_NO_MEMORY instead (matching the behavior already used by mutexes/semaphores). It also adds a regression test and documents the fix in the changelog.
Changes:
- Update
eos_queue_send()/eos_queue_receive()to returnEOS_KERN_NO_MEMORYwhensend_waiter_count/recv_waiter_countreachesQ_MAX_WAITERS, instead of blocking without enqueueing. - Add a host-test regression case that drives the waiter table to capacity and verifies
EOS_KERN_NO_MEMORYis reported. - Document the fix in
CHANGELOG.md.
File summaries
| File | Description |
|---|---|
kernel/src/ipc.c |
Adds waiter-table capacity checks for queue send/receive to prevent “blocked with no wakeup path” scenarios. |
tests/test_kernel.c |
Adds a new regression test and a yield hook to simulate filling the queue waiter table in the host test harness. |
CHANGELOG.md |
Notes the behavioral fix for queue waiter overflow returning EOS_KERN_NO_MEMORY. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| eos_task_handle_t w = q7_waiters[q7_next++]; | ||
| eos_task_set_current_internal(w); | ||
| int item = 0; | ||
| int rc = eos_queue_send(q7_q, &item, EOS_WAIT_FOREVER); | ||
| if (rc == EOS_KERN_NO_MEMORY) q7_overflow_reported = 1; | ||
| else q7_enqueued++; |
srpatcha
left a comment
There was a problem hiding this comment.
Verified — this is a permanent hang, not a dropped notification
Confirmed the defect on origin/master. The enqueue is conditional and the block is not:
if (g_q[h].send_waiter_count < Q_MAX_WAITERS) {
g_q[h].send_waiters[g_q[h].send_waiter_count++] = caller;
}
eos_task_block_with_timeout(caller, timeout_ms); // ← unconditional
eos_port_exit_critical(crit);
eos_port_yield();With a full waiter table the task blocks without being in the list nothing will ever scan. eos_queue_send never wakes it, because it wakes waiters it can see. With EOS_WAIT_FOREVER that is a task lost for the lifetime of the system; with a timeout it is a task stalled for the full duration for no reason the caller can observe.
Returning EOS_KERN_NO_MEMORY is the right answer, and it is the same answer #67 gives for mutexes:
eos_mutex_lockreturnsEOS_KERN_NO_MEMORYif the mutex's wait queue is already full. The call fails rather than blocking, because a task that blocks without being enqueued would never be granted the mutex.
So this makes queues consistent with the other synchronisation primitives rather than inventing a convention. Worth saying explicitly in the CHANGELOG line, which you did.
The test is doing real work
q7_hook driving eos_task_set_current_internal to march distinct tasks into the waiter table until it overflows is the only way to reach this path — it needs Q_MAX_WAITERS genuinely distinct blocked callers, which a single-threaded test cannot produce by calling send in a loop. That is the difference between a test that reaches the branch and one that only names it.
One thing worth knowing, since you use it: eos_task_set_current_internal is defined twice on master right now, with different behaviour, and the tree does not compile as a result. #82 resolves that in favour of the version that clears g_current and marks the task RUNNING — which is the one this test needs. Nothing to change here; it just means this cannot be verified against master alone.
Verification
Merged onto origin/master + #82: configure OK, 0 build errors, ctest 28/28. Not verified against master alone, because master does not configure.
Message queues previously blocked callers even when the send/recv waiter tables were full, which could strand tasks with no wakeup path. Match the mutex and semaphore behavior by failing immediately with EOS_KERN_NO_MEMORY. Signed-off-by: Swayam Nayak <154440440+swayam-2003@users.noreply.github.com>
11f8d2a to
dcda013
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The newly added test currently masks eos_task_create() failures by storing its int return value into a uint8_t handle array, and the receive-side overflow path is not covered by tests.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 2
- Review effort level: Lite
| char name[8]; | ||
| snprintf(name, sizeof(name), "qs%d", i); | ||
| q7_waiters[q7_total++] = eos_task_create(name, test_entry, NULL, (uint8_t)(10 + i), 512); | ||
| assert(q7_waiters[q7_total - 1] >= 0); |
| test_queue(); | ||
| test_queue_full(); | ||
| printf("=== ALL KERNEL TESTS PASSED (10/10) ===\n"); | ||
| test_queue_send_waiter_overflow(); |
|
Rebased onto latest upstream/master (a0df2d5) and force-pushed. Ready for CI re-run. |
Summary
When a message queue's send/recv waiter table was full (8 waiters),
eos_queue_send()/eos_queue_receive()still blocked the caller without enqueueing it. That caller could sleep forever with no wakeup path. Mutexes and semaphores already returnEOS_KERN_NO_MEMORYin this case.Approach
Return
EOS_KERN_NO_MEMORYbefore blocking whensend_waiter_countorrecv_waiter_countreachesQ_MAX_WAITERS, matching the mutex/semaphore pattern.Testing
Added
test_queue_send_waiter_overflow()intests/test_kernel.c.DCO
Signed-off-by: Swayam Nayak 154440440+swayam-2003@users.noreply.github.com