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