Hi there,I met a question about the function xQueueGenericSend() and xQueueReceive().I notice that when the time is out,xQueueGenericSend() return “errQUEUE_FULL“ directly.But xQueueReceive() is different, it judged the queue is empty again.
I want to know why xQueueGenericSend() does not judged the queue is full again when the time is out just now?
I’m going to use an analogy for a second to better explain the queue. Let’s imagine the queue as a box which you can place and remove things from.
When you want to put things into the box, you would call xQueueSend. If there is space in the box, it will place the item in the box and return that it was able to put the item into the box (success). However, if the box is full and cannot fit anything else, xQueueSend has two choices. The first option is to return that the queue is full (aka errQUEUE_FULL). The second option is to wait for the box to have space. You can control how long this waits with xTicksToWait. If you set this value, it will wait up to that number of ticks for there to be space in the box. If something is removed from the box before that number of ticks, then xQueueSend places the item into the box and returns success. If it does not, it will time out as the box (aka queue) is still full.
When you take things from the box pretty much the opposite happens. If there is something in the box when you call xQueueReceive then you can take it from the box immediately and the function returns success. If there is nothing in the box however, you have the two options once again. You can either immediately return that the box is empty (aka errQUEUE_EMPTY) or you can wait for a certain time until timeout for something to be in the box.
With this background in mind, your question is rather puzzling. xQueueGenericSend shouldn’t return that the queue is full if xQueueReceive is returning empty assuming no items are placed into the queue between the calls.
Can you include some of your code so we can take a look at what’s going on?
The two screenshots above show parts of the code for xQueueGenericSend and xQueueReceive respectively. My question is: when sending to the queue, why does it directly return “queue is full” after determining a timeout, without checking again whether the current queue is full? However, when receiving from the queue, after determining a timeout, it checks again whether the queue is empty. The handling of enqueue and dequeue is inconsistent. Is there any special consideration for this?
The two screenshots above show parts of the code for xQueueGenericSend and xQueueReceive respectively. My question is: when sending to the queue, why does it directly return “queue is full” after determining a timeout, without checking again whether the current queue is full? However, when receiving from the queue, after determining a timeout, it checks again whether the queue is empty. The handling of enqueue and dequeue is inconsistent. Is there any special consideration for this?
I can understand that when in an extreme situation: in the xQueueReceive function, after a judgment timeout, if an interrupt or a high-priority task re-sends a message to the queue, then re-checking whether it is empty does not require losing this message. However, xQueueGenericSend can also encounter such an extreme situation. When the queue is full and times out, a certain interrupt or high-priority task happens to read a message, causing the queue to have spare space. At this point, in my understanding, before returning that the queue is full, it should be rechecked whether it is full.
yes, I think so.I’m just not sure if there are any special scenario considerations in this, which is why it was designed this way. If not, I think sending messages and receiving messages should be completely symmetrical and reverse operations. Either check the queue condition once after the timeout, or return the timeout result directly without checking either after the timeout.
Sorry, I don’t know.I’m just a little confused about this asymmetry, and i have never raise a PR or something others, so I don’t know what i should to do.
My guess s the asymmetry came about as they likely started the same, but the missing the late (after timeout) becoming ready was bothersome to someone on one path, so it was changed there, but not the other.
The difference can’t be called an error, as it depends on a race condition.
I found a few more instances where when there is timeout, the code still tries to squeeze in one more iteration if there is any more data.
I removed the additional check as @aggarg suggested. I like this better rather than adding the same check in queue-send functions since the timeout has happened and the code should deal with it. With the additional logic to check for data in the queue, the code can potentially get stuck there for a long time. Here is a diagram for the same (of course this is a very extreme case but illustrates my point )
Time ──────────────────────────────────────────────────────────────────────►
High-priority task (sender) — sends rapidly
│ send send send send send send send send send send ...
│ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
├──┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼──────
Med-priority task (receiver)
│ │recv ✓ recv ✓ recv ✓ recv ✓ recv ✓ recv ✓ recv ✓ recv ✓ ...
│ │◄──── ◄──── ◄──── ◄──── ◄──── ◄──── ◄──── ◄────
├──┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼──────
Low-priority task (receiver, timeout = T ticks)
│
│ xQueueReceive(T)
│ blocks...
│ ║
│ ║ T ticks elapse
│ ▼
│ TIMEOUT expired
│ re-check → data present → loop back
│ ↓
│ try read → empty (med took it) → check timeout → expired
│ re-check → data present → loop back ◄── tight loop
│ ↓ no sleep
│ try read → empty (med took it) → check timeout → expired
│ re-check → data present → loop back
│ ↓
│ try read → empty (med took it) → check timeout → expired
│ re-check → data present → loop back
│ │
│ ▼
│ ... spins indefinitely, never returns errQUEUE_EMPTY ...
│
│ STARVED — burns CPU in a tight loop past its timeout
Also tagging @rtel as this reverts the change that was made - maybe there is some history as to why this was done like this.
Apologies I’ve not ready the whole thread as travelling and can’t check the code itself right now - but would caution about removing secondary checks of the queue status, especially as formal methods have been used to prove this part of the code as correct. I also suspect the original soak tests will eventually fail with the additional status checks removed. Consider all scenarios, for example, where:
Task A is waiting for data on a queue.
Task B sends to the queue, unblocking task A, but A doesn’t run yet as there are higher priority tasks able to run.
Task C removes data from the queue, leaving it empty again.
Task A eventually runs, finding the queue empty again, so has to re-enter the blocked state to wait the remains of its block time. If it doesn’t it will return that the queue is empty without waiting long enough and the test fails (because the code logic isn’t correct).
……. other similar corner cases
Then again……perhaps you are talking about a different part of the code, in which case my post here may not be relevant.
@rtel The scenario in question occurs when a timeout has already expired. The current behavior is inconsistent — in some code paths, an error is returned immediately upon timeout expiration, while in others, one additional attempt is made. It would be reasonable to expect a queue empty/full error to be returned once the timeout has expired?
Right - now I’ve checked the code I see you are talking about this line. I suspect this is to ensure a postcondition check in the soak test does not flag false positives in extreme timing edge cases, rather than part of the queue functionality implementation directly. The function will run billions of times in the multithreaded soak test, so it is possible that a postcondition check will eventually find something in the queue even though the function returned errQUEUE_EMPTY—simply due to the order in which test threads happened to execute that time.