dlib C++ Library - linker_kernel_1.cpp

    // Copyright (C) 2003  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_LINKER_KERNEL_1_CPp_
    #define DLIB_LINKER_KERNEL_1_CPp_
    #include "linker_kernel_1.h"

    namespace dlib
    {

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // member function definitions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        linker::
        linker (
        ) :
            running(false),
            running_signaler(running_mutex),
            A(0),
            B(0),
            service_connection_running_signaler(service_connection_running_mutex)
        {
        }

    // ----------------------------------------------------------------------------------------

        linker::
        linker (
            connection& a,
            connection& b
        ) :
            running(false),
            running_signaler(running_mutex),
            A(0),
            B(0),
            service_connection_running_signaler(service_connection_running_mutex)
        {
            link(a,b);
        }

    // ----------------------------------------------------------------------------------------

        linker::
        ~linker (
        )
        {
            clear();
        }

    // ----------------------------------------------------------------------------------------

        void linker::
        clear (
        )
        {

            // shutdown the connections
            cons_mutex.lock();
            if (A != 0 )
            {
                A->shutdown();
                A = 0;
            }
            if (B != 0)
            {
                B->shutdown();
                B = 0;
            }
            cons_mutex.unlock();

            // wait for the other threads to signal that they have ended
            running_mutex.lock();
            while (running == true)
            {
                running_signaler.wait();
            }
            running_mutex.unlock();

        }

    // ----------------------------------------------------------------------------------------

        bool linker::
        is_running (
        ) const
        {
            running_mutex.lock();
            bool temp = running;
            running_mutex.unlock();
            return temp;
        }

    // ----------------------------------------------------------------------------------------

        void linker::
        link (
            connection& a,
            connection& b
        )
        {
            // make sure requires clause is not broken
            DLIB_CASSERT(
                this->is_running() == false ,
                "\tvoid linker::link"
                << "\n\tis_running() == " << this->is_running()
                << "\n\tthis: " << this
                );

            running_mutex.lock();
            running = true;
            running_mutex.unlock();

            cons_mutex.lock();
            A = &a;
            B = &b;
            cons_mutex.unlock();

            service_connection_running_mutex.lock();
            service_connection_running = true;
            service_connection_running_mutex.unlock();

            service_connection_error_mutex.lock();
            service_connection_error = false;
            service_connection_error_mutex.unlock();

            // if we fail to make the thread
            if (!create_new_thread(service_connection,this))
            {
                a.shutdown();
                b.shutdown();

                service_connection_running_mutex.lock();
                service_connection_running = false;
                service_connection_running_mutex.unlock();

                cons_mutex.lock();
                A = 0;
                B = 0;
                cons_mutex.unlock();

                running_mutex.lock();
                running = false;
                running_mutex.unlock();

                throw dlib::thread_error (
                    ECREATE_THREAD,
                    "failed to make new thread in linker::link()"
                    );
            }

            // forward data from a to b
            char buf[200];
            long status;
            bool error = false; // becomes true if one of the connections returns an error
            while (true)
            {
                status = a.read(buf,sizeof(buf));
                // if there was an error reading from the socket
                if (status == OTHER_ERROR)
                {
                    error = true;
                    break;
                }
                else if (status == SHUTDOWN)
                {
                    b.shutdown();
                }

                if (status <= 0)
                {
                    // if a has closed normally
                    if (status == 0)
                        b.shutdown_outgoing();
                    break;
                }

                status = b.write(buf,status);
                // if there was an error writing to the socket then break
                if (status == OTHER_ERROR)
                {
                    error = true;
                    break;
                }

                if (status <= 0)
                    break;
            }

            // if there was an error then shutdown both connections
            if (error)
            {
                a.shutdown();
                b.shutdown();
            }

            // wait for the other thread to end
            service_connection_running_mutex.lock();
            while(service_connection_running)
            {
                service_connection_running_signaler.wait();
            }
            service_connection_running_mutex.unlock();

            // make sure connections are shutdown
            a.shutdown();
            b.shutdown();

            // both threads have ended so the connections are no longer needed
            cons_mutex.lock();
            A = 0;
            B = 0;
            cons_mutex.unlock();

            // if service_connection terminated due to an error then set error to true
            service_connection_error_mutex.lock();
            if (service_connection_error)
                error = true;
            service_connection_error_mutex.unlock();

            // if we are ending because of an error
            if (error)
            {

                // signal that the link() function is ending
                running_mutex.lock();
                running = false;
                running_signaler.broadcast();
                running_mutex.unlock();

                // throw the exception for this error
                throw dlib::socket_error (
                    ECONNECTION,
                    "a connection returned an error in linker::link()"
                    );

            }

            // signal that the link() function is ending
            running_mutex.lock();
            running = false;
            running_signaler.broadcast();
            running_mutex.unlock();
        }

    // ----------------------------------------------------------------------------------------

        void linker::
        service_connection (
            void* param
        )
        {
            linker& p = *static_cast<linker*>(param);

            p.cons_mutex.lock();
            // if the connections are gone for whatever reason then return
            if (p.A == 0 || p.B == 0)
            {
                // signal that this function is ending
                p.service_connection_running_mutex.lock();
                p.service_connection_running = false;
                p.service_connection_running_signaler.broadcast();
                p.service_connection_running_mutex.unlock();
                return;
            }
            connection& a = *p.A;
            connection& b = *p.B;
            p.cons_mutex.unlock();

            // forward data from b to a
            char buf[200];
            long status;
            bool error = false;
            while (true)
            {
                status = b.read(buf,sizeof(buf));
                // if there was an error reading from the socket
                if (status == OTHER_ERROR)
                {
                    error = true;
                    break;
                }
                else if (status == SHUTDOWN)
                {
                    a.shutdown();
                }

                if (status <= 0)
                {
                    // if b has closed normally
                    if (status == 0)
                        a.shutdown_outgoing();
                    break;
                }

                status = a.write(buf,status);
                // if there was an error writing to the socket then break
                if (status == OTHER_ERROR)
                {
                    error = true;
                    break;
                }

                if (status <= 0)
                    break;
            }

            // if there was an error then shutdown both connections
            if (error)
            {
                a.shutdown();
                b.shutdown();
            }

            // if there was an error then signal that
            if (error)
            {
                p.service_connection_error_mutex.lock();
                p.service_connection_error = true;
                p.service_connection_error_mutex.unlock();
            }

            // signal that this function is ending
            p.service_connection_running_mutex.lock();
            p.service_connection_running = false;
            p.service_connection_running_signaler.broadcast();
            p.service_connection_running_mutex.unlock();

        }

    // ----------------------------------------------------------------------------------------

    }
    #endif // DLIB_LINKER_KERNEL_1_CPp_