Home > OS >  Bad interaction between fork() and inheritance
Bad interaction between fork() and inheritance

Time:01-29

I have the following code, not minimal, but it's unclear how to further reduce it without masking the effect.

I have a single class representing both server and client, but I don't think this is the problem.

Problem arises because I try to check in server constructor if one server is already running and, if not, I fork and detach (actually "daemonize") the server.

This seems to work but spawned server is always the base class, even if I am trying to start a child.

Actually base class is "empty" and it's supposed to be subclassed overriding the actual handing of messages... which doesn't work at all.

I suspect at time of "fork()" child class is not set up and I fork the parent, but I would like confirmation and, if possible, a workaround.

Note: printing on the child (server) does not (currently) work I tried to convert to standard iostream from spdlog, but I must have goofed somewhere.

//=============== HEADER ======================
#include <cstdint>
#include <vector>
#include <netinet/in.h>
#include <fstream>
#include <iostream>

#define DAEMON_BUFSIZE (1024)

class daemon {
private:
    int                             sockfd;
    struct sockaddr_in              servaddr;

    void init_logger(const char *fn="log.txt") {
        std::ofstream out("out.txt");
        std::streambuf *coutbuf = std::cout.rdbuf(); //save old buf
        std::cout.rdbuf(out.rdbuf()); //redirect std::cout to out.txt!
    }

    void server();
    void client();

protected:
    enum server_cmd {
        server_reply,
        server_noreply,
        server_teardown
    };

    virtual void init_server() {}
    virtual server_cmd handle_msg(std::vector<char> &msg)  {
        std::cout << "base class called!" << std::endl;
        return server_noreply;
    };

public:
    class exception : public std::exception {};

    explicit daemon(uint16_t port);
    daemon(const char *addr, uint16_t port);
    virtual ~daemon() = default;

    int send_msg(std::vector<char> &msg, bool needs_reply= false);

};

//=============== IMPLEMENTATION ======================

#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <cstring>

static int check_daemon(uint16_t port) {
    int sockfd;

    // Creating socket file descriptor
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        std::cout << "[CRITICAL] server socket creation failed (should never happen): " << strerror(errno) << std::endl;
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in servaddr{AF_INET, htons(port), INADDR_ANY};
    // Bind the socket with the server address
    if (bind(sockfd, (const struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
        // port is busy, so daemon is running!
        std::cout << "daemon found" << std::endl;
        return -1;
    }
    return sockfd;
}

/**
 * Server constructor
 * @param port
 */
daemon::daemon(uint16_t port) {
    sockfd = check_daemon(port);
    if (sockfd <= 0) {
        throw exception();
    }

    auto pid = fork();
    if (pid < 0) {
        std::cout << "[CRITICAL] fork failed: " << strerror(errno) << std::endl;
    } else if (pid == 0) {
        init_logger();
        // child; daemonize
        server();
        exit(EXIT_SUCCESS);
    }
    // parent; wait for child
    sleep(1);   // should be done better
}

void daemon::server() {
    std::cout << "[INFO] starting daemon" << std::endl;
    ::daemon(1, 1);
    // here we are in daemon
    std::cout << "[INFO] server_daemon: initializing" << std::endl;

    init_server();

    struct sockaddr_in cliaddr{};
    char buff[DAEMON_BUFSIZE];
    bool running = true;
    std::cout << "[TRACE] server_daemon: entering loop" << std::endl;
    while (running) {
        socklen_t len = sizeof(cliaddr);
        auto n = recvfrom(sockfd, buff, DAEMON_BUFSIZE, MSG_WAITALL, (struct sockaddr*) &cliaddr, &len);
        if (n == -1) {
            std::cout << "[CRITICAL] recvfrom error (" << errno << "): " << strerror(errno) << std::endl;
            throw exception();
        }
        std::vector<char> msg(buff, buff n);
        std::cout << "[TRACE] server_daemon: message received (" << n << " bytes)" << std::endl;
        auto v = handle_msg(msg);
        switch (v) {
            case server_reply:
                sendto(sockfd, msg.data(), msg.size(), MSG_CONFIRM, (const struct sockaddr *) &cliaddr, len);
                std::cout << "[TRACE] reply sent: " << msg.size() << " bytes" << std::endl;
                break;
            case server_noreply:
                std::cout << "[TRACE] no reply sent" << std::endl;
                break;
            case server_teardown:
                running = false;
                break;
        }
    }
    std::cout << "[INFO] server_daemon: at exit" << std::endl;
}

daemon::daemon(const char *addr, uint16_t port) {
    // Creating socket file descriptor
    if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 ) {
        std::cout << "[CRITICAL] socket creation failed (" << errno <<"): " << strerror(errno) << std::endl;
        throw exception();
    }

    // Filling server information
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);
    servaddr.sin_addr.s_addr = inet_addr(addr);
}

int daemon::send_msg(std::vector<char> &msg, bool needs_reply) {
    auto ret = sendto(sockfd, msg.data(), msg.size(), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
    if (ret < 0) {
        std::cout << "[ERROR] send failed: " << strerror(errno) << std::endl;
        return -1;
    }
    if (needs_reply) {
        char buff[DAEMON_BUFSIZE];
        socklen_t len;
        auto n = recvfrom(sockfd, buff, DAEMON_BUFSIZE, MSG_WAITALL, (struct sockaddr *)&servaddr, &len);
        msg.assign(buff, buff n);
        std::cout << "[TRACE] received answer (" << n << " bytes)" << std::endl;
        return n;
    }
    return 0;
}

void daemon::client() {

}

#define test_me
#ifdef test_me

#include <sstream>
class server : public daemon {
public:
    explicit server(uint16_t port)
    : daemon(port)
    {
        std::cout << "[INFO] server constructor" << std::endl;
    }

protected:
    void init_server() override {
        std::cout << "[INFO] init_server" << std::endl;
    }

    server_cmd handle_msg(std::vector<char> &msg) override {
        std::string s(msg.begin(), msg.end());
        std::cout << "[INFO] received '" << s << "'" << std::endl;
        return (s == "END")? server_teardown: server_noreply;
    };

public:
};

class client : public daemon {
public:
    explicit client(const char *addr, uint16_t port)
            : daemon(addr, port)
    {}

protected:
    server_cmd handle_msg(std::vector<char> &msg) override {
        std::cout << "[CRITICAL] client handle_msg() called" << std::endl;
        return server_teardown;
    };

public:
};

int main() {
    try {
        server server(12345);
    } catch (daemon::exception &e) {
        std::cout << "[INFO] daemon already running" << std::endl;
    }
    client client("127.0.0.1", 12345);
    for (auto i : {1, 2, 3}) {
        std::ostringstream msg;
        msg << "message No." << i << " sent";
        auto str = msg.str();
        std::vector<char> vec(str.begin(), str.end());
        client.send_msg(vec);
    }
    std::vector<char> v({'E', 'N', 'D'});
    client.send_msg(v);
}
#endif

CodePudding user response:

This has nothing to do with fork(). If you replace fork() with a random number generator every time the random number picked the server code path you will get the same results.

class server : public daemon 

In C when you construct a class that inherits from a base class, the base class gets constructed first. Until the base class finishes constructing the derived class does not exist. This is fundamental to C , that's how C works.

In the base class's constructor:

daemon::daemon(uint16_t port) {

// ...

    auto pid = fork();
    if (pid < 0) {
        std::cout << "[CRITICAL] fork failed: " << strerror(errno) << std::endl;
    } else if (pid == 0) {
        init_logger();
        // child; daemonize
        server();

This is the base class, the child class does not get constructed until the base constructor returns. The server() function expects to invoke virtual methods that are overridden in the child class. But the child class does not exist. It is yet to be born, in a manner of speaking.

This is just how C works fundamentally. You'll need to change the design of your classes, accordingly, in order to achieve the desired results.

  • Related