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