Sending 2 Independent UDP packets on same socket from different tasks. Is this thread safe?

Hi,
I have 2 tasks using a single socket to send UDP packets to an external client using the Free RTOS TCP/IP Stack.
Is this operation thread safe? Will the 2 UDP packets reach destination safely?
Thanks

Yes to both the questions. Are you facing any issue?

EDIT - Ignore my response and refer to the @htibosch response below.

It is only allowed to use a socket for reading in one task, and for writing in a different task.

It is not safe to share a socket when both tasks are writing to it or when both tasks are reading from it.

Two reasons:

  1. It has to do with the use of event-groups.
    Both tasks will wait for an eSOCKET_SEND event, and both will set xClearOnExit.

  2. The data will be copied to a circular buffer, the socket’s TX stream buffer. Circular buffers assume that there is only one writer and one reader. If not, the results are unpredictable.

Can’t you make one single task the owner of the UDP socket? And if you want to send a UDP packet, send it to the owning task who will forward it?

2 Likes

I wrote:

It is only allowed to use a socket for reading in one task, and for writing in a different task.

You might get around it by using mutexes, but I would not recommend that. The best and the safest way is to make one task “the owner” of a (series of) socket(s).

Some people want to use two tasks per socket because they use blocking APIs, one for reading and one for writing to a socket. Sometimes you want to read and write, which ever comes first.

There are some alternatives:

Handling multiple sockets within a single task.

There are 4 possibilities:

  1. The most “standard way” is to use FreeRTOS_select(). It takes a socket-set, and each socket has a set of event types. The select APIs can be included by defining ipconfigSUPPORT_SELECT_FUNCTION=1 in your FreeRTOSIPConfig.h.
    BaseType_t rc = FreeRTOS_select( xSocketSet, pdMS_TO_TICKS( 1000U ) );
    if( rc > 0 )
    {
        /* Handle the triggered events. */
    }
  1. A very easy way of handling multiple sockets from a single task is to use a semaphore, and bind that to all of your sockets, using the socket option FREERTOS_SO_SET_SEMAPHORE. Make a main-loop and let it sleep/block on the semaphore. This feature is enabled when ipconfigSOCKET_HAS_USER_SEMAPHORE=1.
    for( ; ; )
    {
        xSemaphoreTake( xServerSemaphore, pdMS_TO_TICKS( 1000U ) );
        /* Check your sockets. */
    }
  1. Another easy approach is to write a callback function and bind to your sockets. The callback (“application hook”) will be called from the IP-task at every important event: received a packet, sent a packet, connected, disconnected, error. It does not specify what exactly has happened. Your handler will just wakeup the owning task, and it will be handled soon. The option is enabled by defining ipconfigSOCKET_HAS_USER_WAKE_CALLBACK=1.
    void socketWakeupCallback( Socket_t xSocket )
    {
        /* Wake up the handler of this socket. */
    }
  1. And finally: ipconfigUSE_CALLBACKS=1. That allows you to set a specific application hooks for five different types of events like reception, transmission, connect/disconnect.
    baseType_t onTCPReceive( Socket_t xSocket, void * pData, size_t xLength )
    {
        /* Inspect the incoming data, wakeup task. */
        return 0;
    }

Note that every application hook is called from the IP-task. You may not call any FreeRTOS+TCP API. That should be delegated to the handling task. You can set flags and wakeup that particular task. And also set a LED if you want to show that a device is connected.

Very useful: using Socket ID’s :

BaseType_t xSocketSetSocketID( const Socket_t xSocket, void * pvSocketID );
void * pvSocketGetSocketID( const ConstSocket_t xSocket );

It allows you to connect a socket with “an object” that exists in your application. So when your handler is called with a socket parameter, your can find the object or task that is handling that socket by calling pvSocketGetSocketID(). For example:

    void onConnected( Socket_t xSocket, BaseType_t ulConnected )
    {
        BaseType_t rc = 0;

        class CHandler_t * handler = ( CHandler_t *) pvSocketGetSocketID( xSocket );
        if( handler != NULL )
        {
            rc = handler->onConnected( xSocket, ulConnected );
        }
        return rc;
    }

Isn’t that beautiful?

4 Likes