DatagramSocket::read is always blocking (and a fix)


#1

Hello,

I was writting an UDP listener and I faced the problem that the call to DatagramSocket::read always blocks the calling thread until data is received, no matter the value of the parameter blockUntilSpecifiedAmountHasArrived. If data is not received then the calling thread is blocked forever. If using DatagramSocket::read from a thread's "run" method then that thread will have to be killed the hard way.

Specifically, the recvrecvfrom calls at the method readSocket (juce_Socket.cpp) block the execution until data is received.

I solved the problem by configuring a timeout for the socket (I found the solution here: http://forums.codeguru.com/showthread.php?248123).

I modified the method resetSocketOptions in juce_Socket.cpp as follows:

[EDIT: DISCARDED. See the next post for the correct fix]

static bool resetSocketOptions (const SocketHandle handle, const bool isDatagram, const bool allowBroadcast) noexcept
{
    const int sndBufSize = 65536;
    const int rcvBufSize = 65536;
    const int one = 1;
    const int tmOut = 100;

    return handle > 0
        && setsockopt (handle, SOL_SOCKET, SO_RCVBUF, (const char*) &rcvBufSize, sizeof (rcvBufSize)) == 0
        && setsockopt (handle, SOL_SOCKET, SO_SNDBUF, (const char*)&sndBufSize, sizeof (sndBufSize)) == 0
        && (isDatagram ? ((! allowBroadcast) || setsockopt (handle, SOL_SOCKET, SO_BROADCAST, (const char*) &one, sizeof (one)) == 0)
            && setsockopt (handle, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tmOut, sizeof (tmOut)) == 0
           : (setsockopt (handle, IPPROTO_TCP, TCP_NODELAY, (const char*) &one, sizeof (one)) == 0));
}

So when the socket is a datagram socket, it configures a reading timeout of 100 ms. I'm compiling on Windows now, and the fix works for me. I'll have to compile this project for Linux-ARM next week.

I don't know if this is the more convenient method. Another possible solution is to use fcntl for configuring the socket as non-blocking (described here: http://stackoverflow.com/questions/10312535/how-to-unblock-recv-or-recvfrm-function-in-linux-c).

Is there a better fix/workaround for having non-blocking DatagramSocket::read calls? Can be fixed in the JUCE base code?

Thank you!


#2

I've just noticed that juce_Sockets.cpp already contains a method for setting the block state of a socket: SocketHelpers::setSocketBlockingState which uses fcntl. The bug is that this call is missing in DatagramSocket.

I've just appended

SocketHelpers::setSocketBlockingState (handle, false);

in the constructor of DatagramSocket and now it works perfect.


#3

Which version of JUCE are you using? If you look at the curren API of the read method in the DatagramSocket here, you will see that it has a blockUntilSpecifiedAmountHasArrived parameter. That should do what you need.


#4

Thanks for your reply. I'm using JUCE 3.2.0, and Introjucer says that my modules are up to date.

That's why I think it's a bug: even with that parameter being false, the socket blocks the calling thread when reading until some data is received. I debugged the application and it resulted to block in SocketHelpers::readSocket (juce_Socket.cpp), specifically inside the calls to recv or recvfrom. Thus there's nothing the parameter blockUntilSpecifiedAmountHasArrived can do.

Looks like the socket must be put into non-blocking state for it to work in the way expected by JUCE. It seems that part to be missing in DatagramSocket.

I fixed the problem by adding SocketHelpers::setSocketBlockingState (handle, false); at the end of the constructor of DatagramSocket, here:

https://github.com/julianstorer/JUCE/blob/master/modules/juce_core/network/juce_Socket.cpp#L588

Then it works as expected.

Note that I'm using the socket for reading only. The blocking state might require further considerations when writting. 


#5

Thanks Edy! You are absolutely right. I just fixed this on the latest tip. Please let me know if this works for you.


#6

I've tested your fix and it works like a charm, thank you very much!


#7

Sorry ahead of time, i'm not going to be providing too much details!

Commit 166f54f seems to have done something adverse to socket::read.   Here is my call(Mac OS)

socket->read (buffer, HTTP_MAX_HEADER_SIZE, false); 

The function(bytesRead) used to return -1 when the connection was done, now it returns 0 all the time.

If I revert juce_socket to commit 6b66d63 everything works great.  I get the bytes come in, I process them, and it returns -1 when done.

The problem for me is line 212

  if (bytesRead == 0 && blockUntilSpecifiedAmountHasArrived)

                    bytesRead = -1;

bytesRead is 0 and blockUntilSpecifiedA... is false, so bytesRead doesn't become -1.

 

Suggestions?


#8

Well I think the function is now works correctly, i.e. it doesn't block if no bytes are available and will return that it read zero bytes. Can you take the zero as an indication that the read is done in your case?


#9

I can certainly test.. I just know that before the change as the messages came in I would get some bytes then occassionally 0 while it waiting, and when the connection dropped/finished it would return -1, so I coded to that.