================ @@ -0,0 +1,289 @@ +//===------- cc1modbuildd_main.cpp - Clang CC1 Module Build Daemon --------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/ModuleBuildDaemon/SocketSupport.h" +#include "clang/Tooling/ModuleBuildDaemon/Utils.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/ThreadPool.h" + +#include <csignal> +#include <cstdbool> +#include <cstdio> +#include <cstring> +#include <fstream> +#include <optional> +#include <string> +#include <system_error> + +#ifdef _WIN32 +#include <windows.h> +#else +#include <unistd.h> +#endif + +using namespace llvm; +using namespace clang::tooling::cc1modbuildd; + +// Create unbuffered STDOUT stream so that any logging done by the module build +// daemon can be viewed without having to terminate the process +static raw_fd_ostream &unbuff_outs() { + static raw_fd_ostream S(fileno(stdout), false, true); + return S; +} + +static bool LogVerbose = false; +static void logVerbose(const llvm::Twine &message) { + if (LogVerbose) { + unbuff_outs() << message << '\n'; + } +} + +static void modifySignals(decltype(SIG_DFL) handler) { + + if (std::signal(SIGTERM, handler) == SIG_ERR) { + errs() << "failed to handle SIGTERM" << '\n'; + exit(EXIT_FAILURE); + } + + if (std::signal(SIGINT, handler) == SIG_ERR) { + errs() << "failed to handle SIGINT" << '\n'; + exit(EXIT_FAILURE); + } + +#ifdef SIGHUP + if (::signal(SIGHUP, SIG_IGN) == SIG_ERR) { + errs() << "failed to handle SIGHUP" << '\n'; + exit(EXIT_FAILURE); + } +#endif +} + +namespace { + +class ModuleBuildDaemonServer { +public: + SmallString<256> SocketPath; + SmallString<256> Stderr; // path to stderr + SmallString<256> Stdout; // path to stdout + + explicit ModuleBuildDaemonServer(StringRef Path) + : SocketPath(Path), Stderr(Path), Stdout(Path) { + llvm::sys::path::append(SocketPath, SocketFileName); + llvm::sys::path::append(Stdout, StdoutFileName); + llvm::sys::path::append(Stderr, StderrFileName); + } + + void setupDaemonEnv(); + void createDaemonSocket(); + void listenForClients(); + + static void handleConnection(std::shared_ptr<raw_socket_stream> Connection); + + // TODO: modify so when shutdownDaemon is called the daemon stops accepting + // new client connections and waits for all existing client connections to + // terminate before closing the file descriptor and exiting + void shutdownDaemon() { + RunServiceLoop = false; + if (ServerListener.has_value()) + ServerListener.value().shutdown(); + } + +private: + std::atomic<bool> RunServiceLoop = true; + std::optional<llvm::ListeningSocket> ServerListener; +}; + +// Used to handle signals +ModuleBuildDaemonServer *DaemonPtr = nullptr; +void handleSignal(int) { DaemonPtr->shutdownDaemon(); } +} // namespace + +// Sets up file descriptors and signals for module build daemon +void ModuleBuildDaemonServer::setupDaemonEnv() { + +#ifdef _WIN32 + if (std::freopen("NUL", "r", stdin) == NULL) { +#else + if (std::freopen("/dev/null", "r", stdin) == NULL) { +#endif + llvm::errs() << "Failed to close stdin" << '\n'; + exit(EXIT_FAILURE); + } + + if (std::freopen(Stdout.c_str(), "a", stdout) == NULL) { + llvm::errs() << "Failed to redirect stdout to " << Stdout << '\n'; + exit(EXIT_FAILURE); + } + if (std::freopen(Stderr.c_str(), "a", stderr) == NULL) { + llvm::errs() << "Failed to redirect stderr to " << Stderr << '\n'; + exit(EXIT_FAILURE); + } + + modifySignals(handleSignal); +} + +// Creates unix socket for IPC with frontends +void ModuleBuildDaemonServer::createDaemonSocket() { + + while (true) { + Expected<ListeningSocket> MaybeServerListener = + llvm::ListeningSocket::createUnix(SocketPath); + + if (llvm::Error Err = MaybeServerListener.takeError()) { + llvm::handleAllErrors(std::move(Err), [&](const llvm::StringError &SE) { + std::error_code EC = SE.convertToErrorCode(); + + // Exit successfully if the socket address is already in use. When + // TUs are compiled in parallel, until the socket file is created, all + // clang invocations will try to spawn a module build daemon. +#ifdef _WIN32 + if (EC.value() == WSAEADDRINUSE) { +#else + if (EC == std::errc::address_in_use) { +#endif + exit(EXIT_SUCCESS); + } else if (EC == std::errc::file_exists) { + if (std::remove(SocketPath.c_str()) != 0) { + llvm::errs() << "Failed to remove " << SocketPath << ": " + << strerror(errno) << '\n'; + exit(EXIT_FAILURE); + } + // If a previous module build daemon invocation crashes, the socket + // file will need to be removed before the address can be binded to + logVerbose("Removing ineligible file: " + SocketPath); + } else { + llvm::errs() << "MBD failed to create unix socket: " + << SE.getMessage() << ": " << EC.message() << '\n'; + exit(EXIT_FAILURE); + } + }); + } else { + logVerbose("MBD created and binded to socket at: " + SocketPath); + ServerListener.emplace(std::move(*MaybeServerListener)); + break; + } + } +} + +// Function submitted to thread pool with each frontend connection. Not +// responsible for closing frontend socket connections +void ModuleBuildDaemonServer::handleConnection( + std::shared_ptr<llvm::raw_socket_stream> MovableConnection) { + + llvm::raw_socket_stream &Connection = *MovableConnection; + + // Read request from frontend + Expected<HandshakeMsg> MaybeHandshakeMsg = + readMsgStructFromSocket<HandshakeMsg>(Connection); + if (!MaybeHandshakeMsg) { + errs() << "MBD failed to read frontend request: " + << llvm::toString(MaybeHandshakeMsg.takeError()) << '\n'; + return; + } + + // Send response to frontend + HandshakeMsg Msg(ActionType::HANDSHAKE, StatusType::SUCCESS); + if (llvm::Error WriteErr = writeMsgStructToSocket(Connection, Msg)) { + errs() << "MBD failed to respond to frontend request: " + << llvm::toString(std::move(WriteErr)) << '\n'; + return; + } + return; +} + +void ModuleBuildDaemonServer::listenForClients() { + + llvm::DefaultThreadPool Pool; + std::chrono::seconds DaemonTimeout(15); + + while (RunServiceLoop) { + Expected<std::unique_ptr<raw_socket_stream>> MaybeConnection = + ServerListener.value().accept(DaemonTimeout); + + if (llvm::Error Err = MaybeConnection.takeError()) { + + llvm::handleAllErrors(std::move(Err), [&](const llvm::StringError &SE) { + std::error_code EC = SE.convertToErrorCode(); + + if (EC == std::errc::timed_out) { + RunServiceLoop = false; + logVerbose("ListeningServer::accept timed out, shutting down"); + } else if (EC == std::errc::bad_file_descriptor && ---------------- Bigcheese wrote:
This should be on operation canceled, needs a fix in the socket code first. https://github.com/llvm/llvm-project/pull/67562 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits