[clang] [clang][MBD] set up module build daemon infrastructure (PR #67562)
Connor Sughrue via cfe-commits
cfe-commits at lists.llvm.org
Wed Sep 27 07:32:40 PDT 2023
================
@@ -0,0 +1,273 @@
+//===------- 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/DependencyScanning/DependencyScanningService.h"
+#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h"
+#include "clang/Tooling/DependencyScanning/ModuleDepCollector.h"
+#include "clang/Tooling/ModuleBuildDaemon/SocketMsgSupport.h"
+#include "clang/Tooling/ModuleBuildDaemon/SocketSupport.h"
+#include "clang/Tooling/ModuleBuildDaemon/Utils.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/Program.h"
+#include "llvm/Support/ThreadPool.h"
+#include "llvm/Support/Threading.h"
+#include "llvm/Support/YAMLParser.h"
+#include "llvm/Support/YAMLTraits.h"
+
+// TODO: Make portable
+#if LLVM_ON_UNIX
+
+#include <errno.h>
+#include <fstream>
+#include <mutex>
+#include <optional>
+#include <signal.h>
+#include <sstream>
+#include <stdbool.h>
+#include <string>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <type_traits>
+#include <unistd.h>
+#include <unordered_map>
+
+using namespace llvm;
+using namespace clang;
+using namespace tooling::dependencies;
+using namespace cc1modbuildd;
+
+// Create unbuffered STDOUT stream so that any logging done by module build
+// daemon can be viewed without having to terminate the process
+static raw_fd_ostream &unbuff_outs() {
+ static raw_fd_ostream S(STDOUT_FILENO, false, true);
+ return S;
+}
+
+namespace {
+
+class ModuleBuildDaemonServer {
+public:
+ SmallString<128> BasePath;
+ SmallString<128> SocketPath;
+ SmallString<128> PidPath;
+
+ ModuleBuildDaemonServer(SmallString<128> Path, ArrayRef<const char *> Argv)
+ : BasePath(Path), SocketPath(Path) {
+ llvm::sys::path::append(SocketPath, SOCKET_FILE_NAME);
+ }
+
+ ~ModuleBuildDaemonServer() { shutdownDaemon(SIGTERM); }
+
+ int forkDaemon();
+ int launchDaemon();
+ int listenForClients();
+
+ static void handleClient(int Client);
+
+ void shutdownDaemon(int signal) {
+ unlink(SocketPath.c_str());
+ shutdown(ListenSocketFD, SHUT_RD);
+ close(ListenSocketFD);
+ exit(EXIT_SUCCESS);
+ }
+
+private:
+ // Initializes and returns DiagnosticsEngine
+ pid_t Pid = -1;
+ int ListenSocketFD = -1;
+};
+
+// Required to handle SIGTERM by calling Shutdown
+ModuleBuildDaemonServer *DaemonPtr = nullptr;
+void handleSignal(int Signal) {
+ if (DaemonPtr != nullptr) {
+ DaemonPtr->shutdownDaemon(Signal);
+ }
+}
+} // namespace
+
+static bool verbose = false;
+static void verbose_print(const llvm::Twine &message) {
+ if (verbose) {
+ unbuff_outs() << message << '\n';
+ }
+}
+
+// Forks and detaches process, creating module build daemon
+int ModuleBuildDaemonServer::forkDaemon() {
+
+ pid_t pid = fork();
+
+ if (pid < 0) {
+ exit(EXIT_FAILURE);
+ }
+ if (pid > 0) {
+ exit(EXIT_SUCCESS);
+ }
+
+ Pid = getpid();
+
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+
+ SmallString<128> STDOUT = BasePath;
+ llvm::sys::path::append(STDOUT, STDOUT_FILE_NAME);
+ freopen(STDOUT.c_str(), "a", stdout);
+
+ SmallString<128> STDERR = BasePath;
+ llvm::sys::path::append(STDERR, STDERR_FILE_NAME);
+ freopen(STDERR.c_str(), "a", stderr);
+
+ if (signal(SIGTERM, handleSignal) == SIG_ERR) {
+ errs() << "failed to handle SIGTERM" << '\n';
+ exit(EXIT_FAILURE);
+ }
+ if (signal(SIGHUP, SIG_IGN) == SIG_ERR) {
+ errs() << "failed to ignore SIGHUP" << '\n';
+ exit(EXIT_FAILURE);
+ }
+ if (setsid() == -1) {
+ errs() << "setsid failed" << '\n';
+ exit(EXIT_FAILURE);
+ }
+
+ return EXIT_SUCCESS;
+}
+
+// Creates unix socket for IPC with module build daemon
+int ModuleBuildDaemonServer::launchDaemon() {
+
+ // new socket
+ if ((ListenSocketFD = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ std::perror("Socket create error: ");
+ exit(EXIT_FAILURE);
+ }
+
+ struct sockaddr_un addr;
+ memset(&addr, 0, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, SocketPath.c_str(), sizeof(addr.sun_path) - 1);
+
+ // bind to local address
+ if (bind(ListenSocketFD, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+
+ // If the socket address is already in use, exit because another module
+ // build daemon has successfully launched. When translation units are
+ // compiled in parallel, until the socket file is created, all clang
+ // invocations will spawn a module build daemon.
+ if (errno == EADDRINUSE) {
+ close(ListenSocketFD);
+ exit(EXIT_SUCCESS);
+ }
+ std::perror("Socket bind error: ");
+ exit(EXIT_FAILURE);
+ }
+ verbose_print("mbd created and binded to socket address at: " + SocketPath);
+
+ // set socket to accept incoming connection request
+ unsigned MaxBacklog = llvm::hardware_concurrency().compute_thread_count();
+ if (listen(ListenSocketFD, MaxBacklog) == -1) {
+ std::perror("Socket listen error: ");
+ exit(EXIT_FAILURE);
+ }
+
+ return 0;
+}
+
+// Function submitted to thread pool with each client connection. Not
+// responsible for closing client connections
+void ModuleBuildDaemonServer::handleClient(int Client) {
+
+ // Read handshake from client
+ Expected<HandshakeMsg> MaybeHandshakeMsg =
+ readSocketMsgFromSocket<HandshakeMsg>(Client);
+
+ if (!MaybeHandshakeMsg) {
+ writeError(MaybeHandshakeMsg.takeError(),
+ "Failed to read handshake message from socket: ");
+ return;
+ }
+
+ // Handle HANDSHAKE
+ HandshakeMsg Msg(ActionType::HANDSHAKE, StatusType::SUCCESS);
+ llvm::Error WriteErr = writeSocketMsgToSocket(Msg, Client);
+
+ if (WriteErr) {
+ writeError(std::move(WriteErr),
+ "Failed to notify client that handshake was received");
+ return;
+ }
+ return;
+}
+
+int ModuleBuildDaemonServer::listenForClients() {
+
+ llvm::ThreadPool Pool;
+ int Client;
+
+ while (true) {
+
+ if ((Client = accept(ListenSocketFD, NULL, NULL)) == -1) {
+ std::perror("Socket accept error: ");
+ continue;
+ }
+
+ Pool.async(handleClient, Client);
+ }
+ return 0;
+}
+
+// Module build daemon is spawned with the following command line:
+//
+// clang -cc1modbuildd <path> -v
+//
+// <path> defines the location of all files created by the module build daemon
+// and should follow the format /path/to/dir. For example, `clang
+// -cc1modbuildd /tmp/` creates a socket file at `/tmp/mbd.sock`
+//
+// When a module build daemon is spawned by a cc1 invocations, <path> follows
+// the format /tmp/clang-<BLAKE3HashOfClangFullVersion> and looks something like
+// /tmp/clang-3NXKISKJ0WJTN
+//
+// -v is optional and provides berbose debug information
+//
+int cc1modbuildd_main(ArrayRef<const char *> Argv) {
+
+ if (Argv.size() < 1) {
+ outs() << "spawning a module build daemon requies a command line format of "
+ "`clang -cc1modbuildd <path>`. <path> defines where the module "
+ "build daemon will create mbd.out, mbd.err, mbd.sock"
+ << '\n';
+ return 1;
+ }
+
+ // Where to store log files and socket address
+ // TODO: Add check to confirm BasePath is directory
+ SmallString<128> BasePath(Argv[0]);
+ llvm::sys::fs::create_directories(BasePath);
+ ModuleBuildDaemonServer Daemon(BasePath, Argv);
+
+ // Used to handle signals
+ DaemonPtr = &Daemon;
+
+ if (find(Argv, StringRef("-v")) != Argv.end())
+ verbose = true;
+
+ verbose = true;
----------------
cpsughrue wrote:
Forgot to remove
https://github.com/llvm/llvm-project/pull/67562
More information about the cfe-commits
mailing list