StreamingSocket Listener Bug! [Solved]


#1

Dear reader(s),

In conclusion of a previous post, I'd like to submit a bug report.

On Windows 7 Ultimate 64 bit using the Visual Studio 2013 (v120) toolset, creating a socket listener on another thread results in Failure every time. This may cause undefined behaviour from accepting a client which can never receive data to returning a NULL client.

I've produced a stable code that creates this problem effortlessly consistently.
As suggested in another post, I've produced this code into a newly generated project using the Introjucer.
This is the only code used and is placed in Main.cpp.

I am using:
Latest release build (Juce version 3.2.0) [I've checked git repository on issues and commits, no changes to the streamingsocket past 3 weeks]
Windows 7 Ultimate SP1 64-bit.
Visual Studio 2013 (v120) toolset for compiling.

Can anyone else verify?


#include "../JuceLibraryCode/JuceHeader.h"
class ListenerTest : public juce::Thread
{
    public:
        ListenerTest(void);
        ~ListenerTest(void);
        void run(void);
        void start(void);
    private:
        juce::StreamingSocket* listener;
};
ListenerTest::ListenerTest(void) : juce::Thread("LISTENER"),
listener(NULL)
{
}
ListenerTest::~ListenerTest(void)
{
    delete listener;
    listener = NULL;
}
void ListenerTest::start(void)
{
    
    listener = new juce::StreamingSocket();
    std::cout << "Listener opening success: " << listener->createListener(2300, "") << std::endl;
}
void ListenerTest::run(void)
{
    while (!threadShouldExit())
    {
        if (listener->waitUntilReady(true, 10) == 1)
        {
            juce::StreamingSocket* s = listener->waitForNextConnection();
            if (s != NULL)
            {
                std::cout << "isConnected: " << s->isConnected() << std::endl;
                std::cout << "isLocal: " << s->isLocal() << std::endl;
                std::cout << "Adress: " << s->getHostName() << std::endl;
                std::cout << "Bound port: " << s->getBoundPort() << std::endl;
                std::cout << "Port: " << s->getPort() << std::endl;
                std::cout << s->waitUntilReady(false, 500) << std::endl;
                std::cout << s->waitUntilReady(true, 500) << std::endl;
                s->close();
                delete s;
                s = NULL;
            }
            else
            {
                std::cout << "Invalid connection.." << std::endl;
            }
        }
        else
        {
            Thread::yield();
        }
    }
    listener->close();
    delete listener;
    listener = NULL;
}

//==============================================================================
int main (int argc, char* argv[])
{
    ListenerTest* test = new ListenerTest();
    //This code cannot be run within juce::Thread:run for it will never start listening!
    test->start();//Start listener using local (main) thread.
    //create a socket to satisfy listener WaitForNewConnection
    juce::StreamingSocket* client = new juce::StreamingSocket();
    if (client->connect("192.168.1.25", 2300))
    {
        std::cout << "connected" << std::endl;
    }
    //test->run();//local (main) thread
    test->startThread();//separate thread
    Thread::sleep(4000);
    client->close();
    delete client;
    client = NULL;
    test->stopThread(1000);
    delete test;
    test = NULL;
    return 0;
}

I would like to know since I am heavily relying on TCP sockets in a project I am involved in which has some higher priority.
PS: I did some more testing and read more upon the winsock API. Now I want to clarify that the original issue at hand for reading on an incoming socket was a mistake on my part. WaitUntilReady(true) only returns 1 when data is in the buffer.

However, the listener can still not be started from another thread and I'd like to know why.

Edit:
Ultimately it came down to a thread being locked due to mutex. I found no notable problems after solving this issue.
 


#2

Glad you figured it out in the end Marco (we were going to take a look at some point soon..)

BTW it's painful to still see people still using 'delete' and NULL in 2015.. Please do yourself a favour and move on to modern C++ practices, they're encouraged for very good reasons! (Perhaps your entire problem because you weren't using RAII to lock/unlock your mutexes..?)


#3

In my original code I am mostly using RAII methods to lock and unlock the mutexes, however;
I drew up that piece of code fairly quickly and I am not entirely accustomed to the whole RAII climate yet as I moved up from C (embedded engineering/programming) to c++. Thank you for the tip. As I am a real fan of the code behind Juce, I feel very prone to follow those words. Unfortunately, I am coming from an environment that is pretty old fashioned and still uses c++ 98 style and a hybrid with C syntax.
I still have to learn and remember to keep using RAII structures!

Despite this 'minor' mistake, luckily I am using OwnedArrays, ScopedLocks and String operations from Juce already.

Jules, you may not agree with me on this one, but sometimes it is not possible to use RAII lock structures (like scopedlocks) if you are working with micro-optimalisations. I felt compelled in using a spinlock instead of a scopedlock which was the cause of the problem to begin with.
Me, being a non-believer, even tried to blame Juce code but unfortunately after more testing, it was simply my own fault.

So for that, I truly wish to thank you and the team for writing such a reliable library/framework! I'll think twice to suspect Juce from now on!