Description
A stream batching buffer (xStreamBatchingBufferCreate() / xStreamBatchingBufferCreateStatic(), added in V11.1.0) can be configured, using only documented and configASSERT-accepted arguments, with a trigger level that can never be satisfied, even when the buffer is completely full. A reader task blocked in xStreamBufferReceive() on such a buffer then hangs forever (or until its own timeout, if one is used instead of portMAX_DELAY).
Root cause
prvBytesInBufferMeetTriggerLevel() uses a strict > comparison for batching buffers (it must, to implement "block until the trigger level is exceeded"), whereas plain stream/message buffers use >=:
https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/9db704c/stream_buffer.c#L1599-L1624
static BaseType_t prvBytesInBufferMeetTriggerLevel( const StreamBuffer_t * const pxStreamBuffer,
size_t xBytesInBuffer )
{
...
if( ( pxStreamBuffer->ucFlags & sbFLAGS_IS_BATCHING_BUFFER ) != ( uint8_t ) 0 )
{
if( xBytesInBuffer > pxStreamBuffer->xTriggerLevelBytes ) /* strictly greater */
...
}
else if( xBytesInBuffer >= pxStreamBuffer->xTriggerLevelBytes ) /* greater-or-equal */
...
The maximum number of bytes any stream buffer can ever hold is one less than its internal length (xLength - 1, see prvBytesInBuffer() and the ring-buffer full/empty-ambiguity comment at the top of stream_buffer.c).
The trigger-level bounds checks, however, do not know about the batching buffer's stricter > requirement and let the trigger level be set equal to that maximum, which can then never be exceeded:
None of these three checks special-case sbFLAGS_IS_BATCHING_BUFFER.
Reproduction
StreamBufferHandle_t xStream = xStreamBatchingBufferCreate( 25, 25 ); /* trigger == size, accepted */
/* task A: */ xStreamBufferReceive( xStream, buf, sizeof(buf), portMAX_DELAY );
/* task B: */ xStreamBufferSend( xStream, data, 25, portMAX_DELAY ); /* fills buffer 100% */
/* task A never wakes up, even though the buffer is completely full. */
I verified this concretely (not just by inspection) by compiling the real, unmodified tasks.c / list.c / stream_buffer.c / heap_4.c against the FreeRTOS POSIX/GCC simulator port and running real tasks under a real preemptive scheduler:
[A] xStreamBatchingBufferCreate(size=25, trigger=25) <-- accepted by configASSERT
[A] Reader TIMED OUT after buffer was filled to 100% capacity (25/25 bytes).
[A] ==> BUG CONFIRMED: trigger level == capacity is unreachable for a batching buffer.
[B] xStreamBatchingBufferCreate(size=25, trigger=24) <-- correct upper bound (size-1)
[B] Reader woke normally and received 25 bytes after 100 ms.
[B] ==> CONTROL PASSES: with the correct (size-1) trigger the same fill wakes the reader.
[C] xStreamBufferCreate(size=25, trigger=25) <-- same trigger==size value as [A], but PLAIN buffer
[C] Reader woke normally and received 25 bytes after 100 ms.
[C] ==> CONTROL PASSES: plain (non-batching) buffers use ">=" so trigger==size is fine.
The same defect also exists, one byte tighter, for a statically allocated batching buffer (xStreamBatchingBufferCreateStatic(25, 24) - trigger one less than the raw storage size - also hangs forever), because static buffers don't get the dynamic path's +1 capacity compensation.
Impact
Any application that creates a stream batching buffer and (reasonably) sets the trigger level to "wake me when the buffer is completely full" gets a silent, permanent hang instead - and the configuration is accepted without any assertion warning, at every validation point that is supposed to catch invalid trigger levels.
Suggested fix
I have a fix + the full verification harness ready and will open a PR shortly: reject (via configASSERT at creation, or return pdFALSE from xStreamBufferSetTriggerLevel()) a batching-buffer trigger level that is not strictly less than the buffer's maximum achievable occupancy, while leaving plain stream/message buffers' existing bound unchanged.
Disclosure
I used Claude (Anthropic) to help audit the source and build/run the POSIX-port reproduction described above; I reviewed the analysis and reproduction results myself before filing this issue.
Description
A stream batching buffer (
xStreamBatchingBufferCreate()/xStreamBatchingBufferCreateStatic(), added in V11.1.0) can be configured, using only documented andconfigASSERT-accepted arguments, with a trigger level that can never be satisfied, even when the buffer is completely full. A reader task blocked inxStreamBufferReceive()on such a buffer then hangs forever (or until its own timeout, if one is used instead ofportMAX_DELAY).Root cause
prvBytesInBufferMeetTriggerLevel()uses a strict>comparison for batching buffers (it must, to implement "block until the trigger level is exceeded"), whereas plain stream/message buffers use>=:https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/9db704c/stream_buffer.c#L1599-L1624
The maximum number of bytes any stream buffer can ever hold is one less than its internal length (
xLength - 1, seeprvBytesInBuffer()and the ring-buffer full/empty-ambiguity comment at the top ofstream_buffer.c).The trigger-level bounds checks, however, do not know about the batching buffer's stricter
>requirement and let the trigger level be set equal to that maximum, which can then never be exceeded:xBufferSizeBytes - 1, one less than the dynamic path, because it does not get the internalxBufferSizeBytes++compensation a few lines below the dynamic version's check)xStreamBufferSetTriggerLevel()- https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/9db704c/stream_buffer.c#L747None of these three checks special-case
sbFLAGS_IS_BATCHING_BUFFER.Reproduction
I verified this concretely (not just by inspection) by compiling the real, unmodified
tasks.c/list.c/stream_buffer.c/heap_4.cagainst the FreeRTOS POSIX/GCC simulator port and running real tasks under a real preemptive scheduler:The same defect also exists, one byte tighter, for a statically allocated batching buffer (
xStreamBatchingBufferCreateStatic(25, 24)- trigger one less than the raw storage size - also hangs forever), because static buffers don't get the dynamic path's+1capacity compensation.Impact
Any application that creates a stream batching buffer and (reasonably) sets the trigger level to "wake me when the buffer is completely full" gets a silent, permanent hang instead - and the configuration is accepted without any assertion warning, at every validation point that is supposed to catch invalid trigger levels.
Suggested fix
I have a fix + the full verification harness ready and will open a PR shortly: reject (via
configASSERTat creation, or returnpdFALSEfromxStreamBufferSetTriggerLevel()) a batching-buffer trigger level that is not strictly less than the buffer's maximum achievable occupancy, while leaving plain stream/message buffers' existing bound unchanged.Disclosure
I used Claude (Anthropic) to help audit the source and build/run the POSIX-port reproduction described above; I reviewed the analysis and reproduction results myself before filing this issue.