1 //===--- Shutdown.h - Unclean exit scenarios --------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // LSP specifies a protocol for shutting down: a `shutdown` request followed 10 // by an `exit` notification. If this protocol is followed, clangd should 11 // finish outstanding work and exit with code 0. 12 // 13 // The way this works in the happy case: 14 // - when ClangdLSPServer gets `shutdown`, it sets a flag 15 // - when ClangdLSPServer gets `exit`, it returns false to indicate end-of-LSP 16 // - Transport::loop() returns with no error 17 // - ClangdServer::run() checks the shutdown flag and returns with no error. 18 // - we `return 0` from main() 19 // - destructor of ClangdServer and other main()-locals runs. 20 // This blocks until outstanding requests complete (results are ignored) 21 // - global destructors run, such as fallback deletion of temporary files 22 // 23 // There are a number of things that can go wrong. Some are handled here, and 24 // some elsewhere. 25 // - `exit` notification with no `shutdown`: 26 // ClangdServer::run() sees this and returns false, main() returns nonzero. 27 // - stdin/stdout are closed 28 // The Transport detects this while doing IO and returns an error from loop() 29 // ClangdServer::run() logs a message and then returns false, etc 30 // - a request thread gets stuck, so the ClangdServer destructor hangs. 31 // Before returning from main(), we start a watchdog thread to abort() the 32 // process if it takes too long to exit. See abortAfterTimeout(). 33 // - clangd crashes (e.g. segfault or assertion) 34 // A fatal signal is sent (SEGV, ABRT, etc) 35 // The installed signal handler prints a stack trace and exits. 36 // - parent process goes away or tells us to shut down 37 // A "graceful shutdown" signal is sent (TERM, HUP, etc). 38 // The installed signal handler calls requestShutdown() which sets a flag. 39 // The Transport IO is interrupted, and Transport::loop() checks the flag and 40 // returns an error, etc. 41 // 42 //===----------------------------------------------------------------------===// 43 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SUPPORT_SHUTDOWN_H 44 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SUPPORT_SHUTDOWN_H 45 46 #include <cerrno> 47 #include <chrono> 48 49 namespace clang { 50 namespace clangd { 51 52 /// Causes this process to crash if still running after Timeout. 53 void abortAfterTimeout(std::chrono::seconds Timeout); 54 55 /// Sets a flag to indicate that clangd was sent a shutdown signal, and the 56 /// transport loop should exit at the next opportunity. 57 /// If shutdown was already requested, aborts the process. 58 /// This function is threadsafe and signal-safe. 59 void requestShutdown(); 60 /// Checks whether requestShutdown() was called. 61 /// This function is threadsafe and signal-safe. 62 bool shutdownRequested(); 63 64 /// Retry an operation if it gets interrupted by a signal. 65 /// This is like llvm::sys::RetryAfterSignal, except that if shutdown was 66 /// requested (which interrupts IO), we'll fail rather than retry. 67 template <typename Fun, typename Ret = decltype(std::declval<Fun>()())> retryAfterSignalUnlessShutdown(const std::enable_if_t<true,Ret> & Fail,const Fun & F)68 Ret retryAfterSignalUnlessShutdown( 69 const std::enable_if_t<true, Ret> &Fail, // Suppress deduction. 70 const Fun &F) { 71 Ret Res; 72 do { 73 if (shutdownRequested()) 74 return Fail; 75 errno = 0; 76 Res = F(); 77 } while (Res == Fail && errno == EINTR); 78 return Res; 79 } 80 81 } // namespace clangd 82 } // namespace clang 83 84 #endif 85