#include "config.hpp" #include "connection.hpp" #include "http_handler.hpp" #include "metric.hpp" #include "perfetto_categories.hpp" #include "process_collector.hpp" #include "reference.hpp" #include "server.hpp" #include #include #include #include #include #include #include #include #include #include PERFETTO_TRACK_EVENT_STATIC_STORAGE(); // Global server instance for signal handler access static Server *g_server = nullptr; void signal_handler(int sig) { if (sig == SIGTERM || sig == SIGINT) { if (g_server) { g_server->shutdown(); } } } int create_unix_socket(const std::string &path) { int sfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sfd == -1) { perror("socket"); std::abort(); } // Remove existing socket file if it exists unlink(path.c_str()); struct sockaddr_un addr; std::memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; if (path.length() >= sizeof(addr.sun_path)) { std::fprintf(stderr, "Unix socket path too long: %s\n", path.c_str()); std::abort(); } std::strncpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path) - 1); if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { perror("bind"); std::abort(); } if (listen(sfd, SOMAXCONN) == -1) { perror("listen"); std::abort(); } return sfd; } int create_tcp_socket(const std::string &address, int port) { struct addrinfo hints; struct addrinfo *result, *rp; int s; std::memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_STREAM; /* stream socket */ hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ hints.ai_protocol = 0; /* Any protocol */ hints.ai_canonname = nullptr; hints.ai_addr = nullptr; hints.ai_next = nullptr; s = getaddrinfo(address.c_str(), std::to_string(port).c_str(), &hints, &result); if (s != 0) { std::fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); std::abort(); } int sfd = -1; for (rp = result; rp != nullptr; rp = rp->ai_next) { sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) continue; int val = 1; if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) == -1) { perror("setsockopt SO_REUSEADDR"); close(sfd); continue; } // Enable TCP_NODELAY for low latency (only for TCP sockets) if (rp->ai_family == AF_INET || rp->ai_family == AF_INET6) { if (setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) == -1) { perror("setsockopt TCP_NODELAY"); close(sfd); continue; } } if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) { if (listen(sfd, SOMAXCONN) == -1) { perror("listen"); close(sfd); freeaddrinfo(result); std::abort(); } break; /* Success */ } close(sfd); sfd = -1; } freeaddrinfo(result); if (sfd == -1) { std::fprintf(stderr, "Could not bind to %s:%d\n", address.c_str(), port); std::abort(); } return sfd; } std::vector create_listen_sockets(const weaseldb::Config &config) { std::vector listen_fds; for (const auto &iface : config.server.interfaces) { int fd; if (iface.type == weaseldb::ListenInterface::Type::TCP) { fd = create_tcp_socket(iface.address, iface.port); std::cout << "Listening on TCP " << iface.address << ":" << iface.port << std::endl; } else { fd = create_unix_socket(iface.path); std::cout << "Listening on Unix socket " << iface.path << std::endl; } listen_fds.push_back(fd); } if (listen_fds.empty()) { std::fprintf(stderr, "No interfaces configured\n"); std::abort(); } return listen_fds; } void print_help(const char *program_name) { std::cout << "WeaselDB - High-performance write-side database component\n\n"; std::cout << "Usage: " << program_name << " [OPTIONS]\n\n"; std::cout << "Options:\n"; std::cout << " --config Path to TOML configuration file (default: " "config.toml)\n"; std::cout << " --help Show this help message and exit\n"; std::cout << " -h Show this help message and exit\n\n"; std::cout << "Examples:\n"; std::cout << " " << program_name << " # Use default config.toml\n"; std::cout << " " << program_name << " --config my.toml # Use custom config file\n"; std::cout << " " << program_name << " --help # Show this help\n\n"; std::cout << "For configuration documentation, see config.md\n"; } int main(int argc, char *argv[]) { #if ENABLE_PERFETTO perfetto::TracingInitArgs args; args.backends |= perfetto::kSystemBackend; perfetto::Tracing::Initialize(args); perfetto::TrackEvent::Register(); #endif // Register the process collector for default metrics. metric::register_collector(make_ref()); std::string config_file = "config.toml"; // Parse command line arguments for (int i = 1; i < argc; i++) { std::string arg = argv[i]; if (arg == "--help" || arg == "-h") { print_help(argv[0]); return 0; } else if (arg == "--config") { if (i + 1 >= argc) { std::cerr << "Error: --config requires a filename argument\n"; print_help(argv[0]); return 1; } config_file = argv[++i]; } else if (arg.starts_with("--config=")) { config_file = arg.substr(9); } else { // For backward compatibility, treat lone argument as config file if (argc == 2) { config_file = arg; } else { std::cerr << "Error: Unknown argument '" << arg << "'\n"; print_help(argv[0]); return 1; } } } auto config = weaseldb::ConfigParser::load_from_file(config_file); if (!config) { std::cerr << "Failed to load config from: " << config_file << std::endl; std::cerr << "Using default configuration..." << std::endl; config = weaseldb::Config{}; } std::cout << "Configuration loaded successfully:" << std::endl; std::cout << "Interfaces: " << config->server.interfaces.size() << std::endl; for (const auto &iface : config->server.interfaces) { if (iface.type == weaseldb::ListenInterface::Type::TCP) { std::cout << " TCP: " << iface.address << ":" << iface.port << std::endl; } else { std::cout << " Unix socket: " << iface.path << std::endl; } } std::cout << "Max request size: " << config->server.max_request_size_bytes << " bytes" << std::endl; std::cout << "I/O threads: " << config->server.io_threads << std::endl; std::cout << "Epoll instances: " << config->server.epoll_instances << std::endl; std::cout << "Event batch size: " << config->server.event_batch_size << std::endl; std::cout << "Max connections: " << config->server.max_connections << std::endl; std::cout << "Read buffer size: " << config->server.read_buffer_size << " bytes" << std::endl; std::cout << "Min request ID length: " << config->commit.min_request_id_length << std::endl; std::cout << "Request ID retention: " << config->commit.request_id_retention_hours.count() << " hours" << std::endl; std::cout << "Subscription buffer size: " << config->subscription.max_buffer_size_bytes << " bytes" << std::endl; std::cout << "Keepalive interval: " << config->subscription.keepalive_interval.count() << " seconds" << std::endl; std::cout << "Health check resolve iterations: " << config->benchmark.ok_resolve_iterations << std::endl; // Create listen sockets std::vector listen_fds = create_listen_sockets(*config); // Create handler and server HttpHandler http_handler(*config); auto server = Server::create(*config, http_handler, listen_fds); g_server = server.get(); // Setup signal handling std::signal(SIGPIPE, SIG_IGN); std::signal(SIGTERM, signal_handler); std::signal(SIGINT, signal_handler); std::cout << "Starting WeaselDB HTTP server..." << std::endl; server->run(); std::cout << "Server shutdown complete." << std::endl; return 0; }