Search code examples
linuxmultithreadingsocketssystem-callsrecv

How to tell another thread that one thread is within recv() call *now*


There is an embedded Linux system with an externally connected (ethernet) device which sends lots of UDP data, once it's started by a command. At startup, I have one thread which is to continuously receive UDP data for the remainder of the program runtime. As I see it, for reliability in principle, not just by accident, it must be ensured that the UDP reception loop must make its first call to recv() before the external data source is started, or the first packet or so might me lost, depending on scheduler whims. (this is all in a very local, purposefully simple network setup - packet loss is normally not an issue and not handled - speed is king) Currently, right before calling that UDP reception code, I start a temporary thread which delays for some time, then enables the data source to send UDP data. This is currently the way to "ensure" that the first UDP packet arrives when the reception thread is "armed", i.e. within a recv() call, the OS waiting for data.

Even if I, say, set a condition variable, right before the first call to recv() to tell the rest of the program "ok, you can enable the data source now, I'm ready" - it could, in theory, happen that there is some scheduling induced delay between that signal flagging and the actual call to recv (or/and for the internals of recv actually being ready).

Is there a more elegant / proper way to solve this, than using some "empirical delay time"?

Pseudo code for illustration:

// ******** main thread ********
thread delayed( [&]{ sleepMs(500); enableUdpDataSource(); } );
thread udpRecv( [&]{ udpRecvUntilTimeout() } );
delayed.join();
udpRecv.join();
return 0;

// ******** UDP thread ********
void udpRecvUntilTimeout()
{
  udpInit(); // set up socket, buffer sizes etc

  while (shouldRun)
  {
    // recv() needs to be "armed" *before* the data source is enabled.
    // If I set a condition variable for another thread right here,
    // there may be a scheduling intervention between it and the actual
    // engaging of recv() - when the other thread happily enables the datasource.
    int received = recv( sockFd, buf, maxlen, 0 );
    timeoutWatchdogReset();
    processReceivedData();
  }
}

Solution

  • In an earlier version, I suggested that the call to bind is optional, but of course it is not. You have to call it in order to tell the kernel which UDP port to open. After bind, the kernel will buffer incoming UDP packets and you can call recv if you're not interested in the client network details (otherwise, call recvfrom).

    Something along these lines:

    char buf[1500];
    struct sockaddr_in addr;
    
    int sd = socket(AF_INET, SOCK_DGRAM, 0);
    
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons((unsigned short) 1234); // UDP port
    
    bind(sd, (struct sockaddr *)&addr, sizeof(addr));
    
    // start data sending thread
    
    sleep(1); // for testing
    
    recv(sd, buf, 100, 0);
    

    But there are no guarantees with UDP; you might still lose packets (eg. if the sender is overloading the receiver)