OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-cogserver / opencog / network / NetworkServer.cc

193 lines · cc ref:
/*
 * opencog/server/NetworkServer.cc
 *
 * Copyright (C) 2002-2007 Novamente LLC
 * Copyright (C) 2008 by OpenCog Foundation
 * Written by Andre Senna <senna@vettalabs.com>
 *            Gustavo Gama <gama@vettalabs.com>
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

#include <netinet/tcp.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>

#include <asio/ip/tcp.hpp>
#include <opencog/util/Logger.h>
#include <opencog/network/ServerSocket.h>
#include <opencog/network/ConsoleSocket.h>

#include "NetworkServer.h"

using namespace opencog;

NetworkServer::NetworkServer(unsigned short port, const char* name, SocketManager* mgr) :
    _name(name),
    _port(port),
    _running(false),
    _acceptor(_io_service),
    _socket_manager(mgr)
{
    logger().debug("[NetworkServer] constructor for %s at %d", name, port);

    // Try IPv6 dual-stack mode first (accepts both IPv6 and IPv4)
    bool ipv6_success = false;
    try {
        _acceptor.open(asio::ip::tcp::v6());
        _acceptor.set_option(asio::socket_base::reuse_address(true));
        _acceptor.set_option(asio::ip::v6_only(false));
        _acceptor.bind(asio::ip::tcp::endpoint(asio::ip::tcp::v6(), port));
        _acceptor.listen();
        logger().info("[NetworkServer] dual-stack IPv4/IPv6 mode enabled");
        ipv6_success = true;
    }
    catch (const std::system_error& e) {
        logger().info("[NetworkServer] IPv6 not available (%s), falling back to IPv4-only mode",
                      e.what());
    }

    // Fall back to IPv4-only if IPv6 failed
    if (!ipv6_success) {
        try {
            _acceptor.open(asio::ip::tcp::v4());
            _acceptor.set_option(asio::socket_base::reuse_address(true));
            _acceptor.bind(asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port));
            _acceptor.listen();
            logger().info("[NetworkServer] IPv4-only mode enabled");
        }
        catch (const std::system_error& e) {
            logger().error("[NetworkServer] Failed to bind to port %d: %s", port, e.what());
            throw;
        }
    }

    _start_time = time(nullptr);
    _last_connect = 0;
    _nconnections = 0;
}

NetworkServer::~NetworkServer()
{
    logger().debug("[NetworkServer] enter destructor for %s at %d",
                   _name.c_str(), _port);

    stop_listening();
    join_threads();

    logger().debug("[NetworkServer] all threads joined, exit destructor");
}

void NetworkServer::stop_listening()
{
    if (not _running) return;
    _running = false;
    _socket_manager->network_gone();

    std::error_code ec;
    _acceptor.cancel(ec);
    _io_service.stop();

    // asio hangs, despite the above.  Brute-force it to
    // get it's head out of it's butt, and do the right thing.
    pthread_cancel(_listener_thread->native_handle());

    _listener_thread->join();
    delete _listener_thread;
    _listener_thread = nullptr;
}

void NetworkServer::join_threads()
{
    // Join all connection handler threads to ensure complete shutdown.
    // This guarantees all TCP/IP packets have been processed and all
    // handler threads have finished before serverLoop() returns.
    logger().debug("[NetworkServer] Joining %zu handler threads",
                   _handler_threads.size());

    std::list<std::thread*> threads_to_join;
    {
        std::lock_guard<std::mutex> lock(_handler_threads_mtx);
        threads_to_join.swap(_handler_threads);
    }

    for (std::thread* thr : threads_to_join)
    {
        thr->join();
        delete thr;
    }

    logger().debug("[NetworkServer] All handler threads joined");
}

void NetworkServer::listen(void)
{
    prctl(PR_SET_NAME, "cogserv:listen", 0, 0, 0);
    printf("%s listening on port %d\n", _name.c_str(), _port);
    while (_running)
    {
        // The call to _acceptor.accept() will block this thread until
        // a network connection is made. Thus, we defer the creation
        // of the connection handler thread until after accept()
        // returns.  However, the asio design violates RAII principles,
        // so instead, what we do is to hand off the socket created here,
        // to the ServerSocket class, which will delete it, when the
        // connection socket closes (just before the connection handler
        // thread exits).  That is why there is no delete of the *ss
        // below, and that is why there is the weird self-delete at the
        // end of ServerSocket::handle_connection().
        asio::ip::tcp::socket* sock = new asio::ip::tcp::socket(_io_service);

        _acceptor.accept(*sock);

        // Exit, if cogserver is being shut down.
        if (not _running) break;

        _nconnections++;
        _last_connect = time(nullptr);

        asio::ip::tcp::no_delay ndly(true);
        sock->set_option(ndly);

        int fd = sock->native_handle();
        // We are going to be sending oceans of tiny packets,
        // and we want the fastest-possible responses.
        int flags = 1;
        setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(flags));
        flags = 1;
        setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &flags, sizeof(flags));

        // The total number of concurrently open sockets is managed by
        // the SocketManager, which blocks when there are too many.
        ServerSocket* ss = _getServer(_socket_manager);
        ss->set_connection(sock);

        // Create handler thread and track it for proper cleanup.
        std::thread* handler_thread = new std::thread(&ServerSocket::handle_connection, ss);
        {
            std::lock_guard<std::mutex> lock(_handler_threads_mtx);
            _handler_threads.push_back(handler_thread);
        }
    }
}

void NetworkServer::run(std::function<ServerSocket*(SocketManager*)> handler)
{
    if (_running) return;
    _running = true;
    _getServer = handler;

    try {
        _io_service.run();
    } catch (const std::system_error& e) {
        logger().error("Error in asio io_service::run() => %s", e.what());
    }

    _listener_thread = new std::thread(&NetworkServer::listen, this);
}

// ==================================================================