CrowCpp / Crow

A Fast and Easy to use microframework for the web.
https://crowcpp.org
Other
3.12k stars 345 forks source link

Child process keeps crow instance running. #866

Open kyrylolvov opened 1 month ago

kyrylolvov commented 1 month ago

Hello! Is there a way to somehow kill crow inside a child process or not let it inherit?

#define CROW_JSON_USE_MAP

#include "controllers/resources_controller.h"

crow::App<middleware::RestApiMiddleware> app;

int main() {
    std::cout << "Starting ArcHPC Baremetal API..." << std::endl;

    api::ResourcesController::init();

    app.port(18080).multithreaded().run();

    return 0;
}
#include "controllers/resources_controller.h"

#include <sys/socket.h>
#include <unistd.h>

namespace api {

void ResourcesController::init() {
    CROW_ROUTE(app, "/resources").methods(crow::HTTPMethod::GET)(listResources);
    CROW_ROUTE(app, "/resources").methods(crow::HTTPMethod::POST)(createResource);
}

void ResourcesController::listResources(const crow::request &req, crow::response &res) {
    crow::json::wvalue response;
    response["data"] = "Resources List";

    res.code = 201;
    res.body = response.dump();

    res.end();
}

void ResourcesController::createResource(const crow::request &req, crow::response &res) {
    crow::json::wvalue response;
    response["data"] = "Create Resource";

    pid_t pid = fork();

    if (pid == -1) {
        std::cerr << "Fork failed" << std::endl;

        exit(1);
    } else if (pid == 0) {
        // Child process
        std::cout << "Child process: PID = " << getpid() << std::endl;

        app.stop();

        // Simulate some work
        while (1) {
        };

        std::cout << "Child process exiting" << std::endl;

        exit(0);
    } else {
        // Parent process
        std::cout << "Parent process: Child PID = " << pid << std::endl;

        res.code = 201;
        res.body = response.dump();

        res.end();
    }
}

}   // namespace api

This is my code. Even though logs for closing app are printed in the console, when I exit the app, the port is still taken.

kyrylolvov commented 1 month ago

Is there a solution which doesn't involve killing the child processes? Rather just killing crow app.

kyrylolvov commented 1 month ago

@gittiver Any idea on how to solve this? Somehow the .stop() doesn't fully close the file descriptors (at least in a child process).

gittiver commented 1 month ago

Not really.

There is a while(1) loop after stop() in createResource. What about the used middleware, maybe remove it temporarily to check if it works then? and do you use the master branch or the release?

kyrylolvov commented 1 month ago

Not really.

There is a while(1) loop after stop() in createResource. What about the used middleware, maybe remove it temporarily to check if it works then? and do you use the master branch or the release?

The used middleware is just setting Content-Type header to json.

#pragma once

#include "crow.h"

namespace middleware {

struct RestApiMiddleware {
    struct context {};

    void before_handle(crow::request &req, crow::response &res, context &ctx) {}

    void after_handle(crow::request &req, crow::response &res, context &ctx) {
        res.set_header("Content-Type", "application/json");
    }
};

}   // namespace middleware

I have just updated Crow to latest release, and it's still not fully stopped in the child process.

I wrote this function, and calling it in the child process actually fixes the issue.

#include "utilities/common.h"

void utilities::CommonUtilities::cleanUpSocket(int port) {
    // Stop the application
    app.stop();

    // Iterate through all possible file descriptors
    for (int fd = 3; fd < sysconf(_SC_OPEN_MAX); ++fd) {
        struct sockaddr_in addr;
        socklen_t len = sizeof(addr);

        // Try to get the socket name for the current file descriptor
        if (getsockname(fd, (struct sockaddr *)&addr, &len) == 0) {
            // Check if the socket is an IPv4 socket and matches the given port
            if (addr.sin_family == AF_INET && ntohs(addr.sin_port) == port) {
                std::cout << "Closed socket on port " << port << " in child process" << std::endl;
                // Close the socket
                close(fd);
                // Exit the loop as we've found and closed the desired socket
                break;
            }
        }
    }
}
gittiver commented 4 weeks ago

Maybe I am wrong but you are closing the server in the request_handler but after that the :: after_handle of the middleware is called by the app, I am not shure, possibly this is the reason why the server does not stop completely.

Could you check without the middlewar, if the server stops correctly?

gittiver commented 4 weeks ago

btw. you can directly return a json object from the handler, then the content-type will be set correctly already.

kyrylolvov commented 4 weeks ago

Maybe I am wrong but you are closing the server in the request_handler but after that the :: after_handle of the middleware is called by the app, I am not shure, possibly this is the reason why the server does not stop completely.

Could you check without the middlewar, if the server stops correctly?

Yeah, I just tried removing middleware and calling app.stop() in the child, without the function to close sockets manually. The logs that it has been closed appear, but when I exit the program, ss -ln src :18080 shows that the port is still taken.

kyrylolvov commented 4 weeks ago

btw. you can directly return a json object from the handler, then the content-type will be set correctly already.

But if I just return the json object directly, how do I change the status code of the response? Couldn't find that in the examples.