-
Notifications
You must be signed in to change notification settings - Fork 163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DOC] Please document how the synchronisation works #1058
Comments
Thanks for raising this subject. I read it with great interest and I will come back to it here. |
Thank you!
I did not find an issue, but I was trying to convince myself that there wasn't one and failing. I am not quite sure what I'd expect to go wrong in the racy case. It's either a memory leak, or some packets not being processed because iterators skip over them. In the latter case, higher-level parts of the stack will correct and simply treat it as packet loss (though I'm not quite sure what would free it in that case). If the contention is sufficiently rare then it's possible that it does show up in production occasionally and just results in one buffer being lost, then it may not be noticed. The probability of hitting the problematic cases looks very low (the producer and consumer threads are each doing tens of thousands of cycles work and need to both hit the wrong point - a window of a handful of instructions - at the same time). |
Thank you for raising the request. We are looking into it and will add the required documentation soon. Thank you |
Summary:In two cases, the scheduler is suspended:
Critical sections are created as well, but suspending the scheduler would be enough. The scheduler is not suspended in these cases:
Details:In this analysis, I only looked at the List_t EDIT Within FreeRTOS+TCP, sockets can not be shared among tasks, except when one task is reading, and the other task is writing to it. During // FreeRTOS_Sockets.c : List initialisation
Socket_t FreeRTOS_socket()
{
vListInitialise( &( pxSocket->u.xUDP.xWaitingPacketsList ) );
} // List deletion, along with the received packets.
void * vSocketClose( FreeRTOS_Socket_t * pxSocket )
{
// Socket is closing, remove/delete received packets
while( listCURRENT_LIST_LENGTH( &( pxSocket->u.xUDP.xWaitingPacketsList ) ) > 0U )
{
pxNetworkBuffer = ( ( NetworkBufferDescriptor_t * ) listGET_OWNER_OF_HEAD_ENTRY( &( pxSocket->u.xUDP.xWaitingPacketsList ) ) );
( void ) uxListRemove( &( pxNetworkBuffer->xBufferListItem ) );
vReleaseNetworkBufferAndDescriptor( pxNetworkBuffer );
}
} Access from First read how many packets are stored already #if ( ipconfigUDP_MAX_RX_PACKETS > 0U )
{
if( xReturn == pdPASS )
{
// Reading length field
// Looks atomic "UBaseType_t uxNumberOfItems"
if( listCURRENT_LIST_LENGTH( &( pxSocket->u.xUDP.xWaitingPacketsList ) ) >= pxSocket->u.xUDP.uxMaxPackets )
{
// No space.
xReturn = pdFAIL; /* we did not consume or release the buffer */
}
}
} Both the addition as well as removal of packets is protected in a critical section. The
Called from recvfrom(), receive UDP packet and remove it from list. static NetworkBufferDescriptor_t * prvRecvFromWaitForPacket( FreeRTOS_Socket_t const * pxSocket,
BaseType_t xFlags,
EventBits_t * pxEventBits )
{
if( lPacketCount > 0 )
{
// A block protected by vTaskSuspendAll()/xTaskResumeAll() would be good enough here.
taskENTER_CRITICAL();
{
/* The owner of the list item is the network buffer. */
// Get the oldest item, which is "xListEnd->pxNext->pvOwner"
pxNetworkBuffer = ( ( NetworkBufferDescriptor_t * ) listGET_OWNER_OF_HEAD_ENTRY( &( pxSocket->u.xUDP.xWaitingPacketsList ) ) );
if( ( ( UBaseType_t ) xFlags & ( UBaseType_t ) FREERTOS_MSG_PEEK ) == 0U )
{
/* Remove the network buffer from the list of buffers waiting to
* be processed by the socket. */
( void ) uxListRemove( &( pxNetworkBuffer->xBufferListItem ) );
}
}
taskEXIT_CRITICAL();
}
} Reading length field void vSocketSelect( const SocketSelect_t * pxSocketSet )
{
/* Select events for UDP are simpler. */
if( ( ( pxSocket->xSelectBits & ( EventBits_t ) eSELECT_READ ) != 0U ) &&
( listCURRENT_LIST_LENGTH( &( pxSocket->u.xUDP.xWaitingPacketsList ) ) > 0U ) )
{
xSocketBits |= ( EventBits_t ) eSELECT_READ;
}
} My recommendation is the following: vTaskSuspendAll();
{
- taskENTER_CRITICAL();
- {
/* Remove (or add) an item from xUDP.xWaitingPacketsList. */
- }
- taskEXIT_CRITICAL();
}
( void ) xTaskResumeAll(); |
Thanks for the detailed write-up, that's great. I presume that the close case also relies on packets destined for that socket being dropped rather than being added to the waiting list, as a result of updating the socket state machine? |
I'm afraid I do not understand exactly what you're asking for. UDP messages aren't stored anywhere, except in the short moment between the IP-task function Network interface calls The user process calls the function |
@davidchisnall closing this issue since the PR addressing the critical section is merged. Please re-open if you have further questions. Thank you. |
The synchronisation in the waiting packet lists is incredibly subtle. I believe it is probably correct and the requirements are:
Even after reading the code five times, I am still not 100% sure because
listGET_OWNER_OF_NEXT_ENTRY
non-atomically mutates thepxIndex
field of the list, butvListInsertEnd
uses this value, and so even ifvListInsertEnd
is atomic, it can happen in the middle of the update ofpxIndex
. If this were incorrect, I think it would lead to memory leaks and have been detected, so I presume it is correct, but it's incredibly hard to determine this from the code / comments. Some comments around thevTaskSuspendAll
/taskENTER_CRITICAL
calls (for example, here) explaining what this needs to be atomic with respect to and what the invariants are would help.It would be great to have some comments in the code (or a higher-level design doc) explaining why this is safe.
The text was updated successfully, but these errors were encountered: