Skip to content

Commit 6ca0eb1

Browse files
laanwjPastaPastaPasta
authored andcommitted
Merge bitcoin#21007: bitcoind: Add -daemonwait option to wait for initialization
e017a91 bitcoind: Add -daemonwait option to wait for initialization (Wladimir J. van der Laan) c3e6fde shutdown: Use RAII TokenPipe in shutdown (Wladimir J. van der Laan) 612f746 util: Add RAII TokenPipe (Wladimir J. van der Laan) Pull request description: This adds a `-daemonwait` flag that does the same as `-daemon` except that it, from a user perspective, backgrounds the process only after initialization is complete. This is similar to the behaviour of some other software such as c-lightning. This can be useful when the process launching bitcoind wants to guarantee that either the RPC server is running, or that initialization failed, before continuing. The exit code indicates the initialization result. The use of the libc function `daemon()` is replaced by a custom implementation which is inspired by the [glibc implementation](https://github.com/lattera/glibc/blob/master/misc/daemon.c#L44), but which also creates a pipe from the child to the parent process for communication. An additional advantage of having our own `daemon()` implementation is that no MACOS-specific pragmas are needed anymore to silence a deprecation warning. TODO: - [x] Factor out `token_read` and `token_write` to an utility, and use them in `shutdown.cpp` as well—this is exactly the same kind of communication mechanism. - [x] RAII-ify pipe endpoints. - [x] Improve granularity of the `configure.ac` checks. This currently still checks for the function `daemon()` which makes no sense as it's not used. It should check for individual functions such as `fork()` and `setsid()` etc—the former being required, the second optional. - [-] ~~Signal propagation during initialization: if say, pressing Ctrl-C during `-daemonwait` it would be good to pass this SIGINT on to the child process instead of detaching the parent process and letting the child run free.~~ This is not necessary, see bitcoin#21007 (comment). Future: - Consider if it makes sense to use this in the RPC tests (there would be no more need for "is RPC ready" polling loops). I think this is out of scope for this PR. ACKs for top commit: jonatack: Tested ACK e017a91 checked change since previous review is move-only Tree-SHA512: 53369b8ca2247e4cf3af8cb2cfd5b3399e8e0e3296423d64be987004758162a7ddc1287b01a92d7692328edcb2da4cf05d279b1b4ef61a665b71440ab6a6dbe2
1 parent 666e6e2 commit 6ca0eb1

File tree

8 files changed

+375
-55
lines changed

8 files changed

+375
-55
lines changed

configure.ac

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,8 +1007,9 @@ AC_CHECK_DECLS([getifaddrs, freeifaddrs],[CHECK_SOCKET],,
10071007
)
10081008
AC_CHECK_DECLS([strnlen])
10091009

1010-
dnl Check for daemon(3), unrelated to --with-daemon (although used by it)
1011-
AC_CHECK_DECLS([daemon])
1010+
dnl These are used for daemonization in dashd
1011+
AC_CHECK_DECLS([fork])
1012+
AC_CHECK_DECLS([setsid])
10121013

10131014
AC_CHECK_DECLS([pipe2])
10141015

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ BITCOIN_CORE_H = \
355355
util/time.h \
356356
util/thread.h \
357357
util/threadnames.h \
358+
util/tokenpipe.h \
358359
util/trace.h \
359360
util/translation.h \
360361
util/ui_change_type.h \
@@ -792,6 +793,7 @@ libbitcoin_util_a_SOURCES = \
792793
util/string.cpp \
793794
util/thread.cpp \
794795
util/threadnames.cpp \
796+
util/tokenpipe.cpp \
795797
$(BITCOIN_CORE_H)
796798

797799
if USE_LIBEVENT

src/bitcoind.cpp

Lines changed: 111 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <util/system.h>
2222
#include <util/strencodings.h>
2323
#include <util/threadnames.h>
24+
#include <util/tokenpipe.h>
2425
#include <util/translation.h>
2526
#include <stacktraces.h>
2627
#include <util/url.h>
@@ -35,6 +36,79 @@ UrlDecodeFn* const URL_DECODE = urlDecode;
3536
//
3637
// Start
3738
//
39+
#if HAVE_DECL_FORK
40+
41+
/** Custom implementation of daemon(). This implements the same order of operations as glibc.
42+
* Opens a pipe to the child process to be able to wait for an event to occur.
43+
*
44+
* @returns 0 if successful, and in child process.
45+
* >0 if successful, and in parent process.
46+
* -1 in case of error (in parent process).
47+
*
48+
* In case of success, endpoint will be one end of a pipe from the child to parent process,
49+
* which can be used with TokenWrite (in the child) or TokenRead (in the parent).
50+
*/
51+
int fork_daemon(bool nochdir, bool noclose, TokenPipeEnd& endpoint)
52+
{
53+
// communication pipe with child process
54+
std::optional<TokenPipe> umbilical = TokenPipe::Make();
55+
if (!umbilical) {
56+
return -1; // pipe or pipe2 failed.
57+
}
58+
59+
int pid = fork();
60+
if (pid < 0) {
61+
return -1; // fork failed.
62+
}
63+
if (pid != 0) {
64+
// Parent process gets read end, closes write end.
65+
endpoint = umbilical->TakeReadEnd();
66+
umbilical->TakeWriteEnd().Close();
67+
68+
int status = endpoint.TokenRead();
69+
if (status != 0) { // Something went wrong while setting up child process.
70+
endpoint.Close();
71+
return -1;
72+
}
73+
74+
return pid;
75+
}
76+
// Child process gets write end, closes read end.
77+
endpoint = umbilical->TakeWriteEnd();
78+
umbilical->TakeReadEnd().Close();
79+
80+
#if HAVE_DECL_SETSID
81+
if (setsid() < 0) {
82+
exit(1); // setsid failed.
83+
}
84+
#endif
85+
86+
if (!nochdir) {
87+
if (chdir("/") != 0) {
88+
exit(1); // chdir failed.
89+
}
90+
}
91+
if (!noclose) {
92+
// Open /dev/null, and clone it into STDIN, STDOUT and STDERR to detach
93+
// from terminal.
94+
int fd = open("/dev/null", O_RDWR);
95+
if (fd >= 0) {
96+
bool err = dup2(fd, STDIN_FILENO) < 0 || dup2(fd, STDOUT_FILENO) < 0 || dup2(fd, STDERR_FILENO) < 0;
97+
// Don't close if fd<=2 to try to handle the case where the program was invoked without any file descriptors open.
98+
if (fd > 2) close(fd);
99+
if (err) {
100+
exit(1); // dup2 failed.
101+
}
102+
} else {
103+
exit(1); // open /dev/null failed.
104+
}
105+
}
106+
endpoint.TokenWrite(0); // Success
107+
return 0;
108+
}
109+
110+
#endif
111+
38112
static bool AppInit(int argc, char* argv[])
39113
{
40114
NodeContext node;
@@ -72,6 +146,14 @@ static bool AppInit(int argc, char* argv[])
72146
}
73147

74148
CoreContext context{node};
149+
#if HAVE_DECL_FORK
150+
// Communication with parent after daemonizing. This is used for signalling in the following ways:
151+
// - a boolean token is sent when the initialization process (all the Init* functions) have finished to indicate
152+
// that the parent process can quit, and whether it was successful/unsuccessful.
153+
// - an unexpected shutdown of the child process creates an unexpected end of stream at the parent
154+
// end, which is interpreted as failure to start.
155+
TokenPipeEnd daemon_ep;
156+
#endif
75157
try
76158
{
77159
if (!CheckDataDirOption()) {
@@ -117,24 +199,34 @@ static bool AppInit(int argc, char* argv[])
117199
// InitError will have been called with detailed error, which ends up on console
118200
return false;
119201
}
120-
if (args.GetBoolArg("-daemon", false)) {
121-
#if HAVE_DECL_DAEMON
122-
#if defined(MAC_OSX)
123-
#pragma GCC diagnostic push
124-
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
125-
#endif
202+
if (args.GetBoolArg("-daemon", DEFAULT_DAEMON) || args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) {
203+
#if HAVE_DECL_FORK
126204
tfm::format(std::cout, PACKAGE_NAME " starting\n");
127205

128206
// Daemonize
129-
if (daemon(1, 0)) { // don't chdir (1), do close FDs (0)
130-
return InitError(Untranslated(strprintf("daemon() failed: %s\n", strerror(errno))));
207+
switch (fork_daemon(1, 0, daemon_ep)) { // don't chdir (1), do close FDs (0)
208+
case 0: // Child: continue.
209+
// If -daemonwait is not enabled, immediately send a success token the parent.
210+
if (!args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) {
211+
daemon_ep.TokenWrite(1);
212+
daemon_ep.Close();
213+
}
214+
break;
215+
case -1: // Error happened.
216+
return InitError(Untranslated(strprintf("fork_daemon() failed: %s\n", strerror(errno))));
217+
default: { // Parent: wait and exit.
218+
int token = daemon_ep.TokenRead();
219+
if (token) { // Success
220+
exit(EXIT_SUCCESS);
221+
} else { // fRet = false or token read error (premature exit).
222+
tfm::format(std::cerr, "Error during initializaton - check debug.log for details\n");
223+
exit(EXIT_FAILURE);
224+
}
225+
}
131226
}
132-
#if defined(MAC_OSX)
133-
#pragma GCC diagnostic pop
134-
#endif
135227
#else
136228
return InitError(Untranslated("-daemon is not supported on this operating system\n"));
137-
#endif // HAVE_DECL_DAEMON
229+
#endif // HAVE_DECL_FORK
138230
}
139231
// Lock data directory after daemonization
140232
if (!AppInitLockDataDirectory())
@@ -147,6 +239,13 @@ static bool AppInit(int argc, char* argv[])
147239
PrintExceptionContinue(std::current_exception(), "AppInit()");
148240
}
149241

242+
#if HAVE_DECL_FORK
243+
if (daemon_ep.IsOpen()) {
244+
// Signal initialization status to parent, then close pipe.
245+
daemon_ep.TokenWrite(fRet);
246+
daemon_ep.Close();
247+
}
248+
#endif
150249
if (fRet) {
151250
WaitForShutdown();
152251
}

src/init.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -779,8 +779,9 @@ void SetupServerArgs(NodeContext& node)
779779
argsman.AddArg("-statsport=<port>", strprintf("Specify statsd port (default: %u)", DEFAULT_STATSD_PORT), ArgsManager::ALLOW_ANY, OptionsCategory::STATSD);
780780
argsman.AddArg("-statsns=<ns>", strprintf("Specify additional namespace prefix (default: %s)", DEFAULT_STATSD_NAMESPACE), ArgsManager::ALLOW_ANY, OptionsCategory::STATSD);
781781
argsman.AddArg("-statsperiod=<seconds>", strprintf("Specify the number of seconds between periodic measurements (default: %d)", DEFAULT_STATSD_PERIOD), ArgsManager::ALLOW_ANY, OptionsCategory::STATSD);
782-
#if HAVE_DECL_DAEMON
783-
argsman.AddArg("-daemon", "Run in the background as a daemon and accept commands", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
782+
#if HAVE_DECL_FORK
783+
argsman.AddArg("-daemon", strprintf("Run in the background as a daemon and accept commands (default: %d)", DEFAULT_DAEMON), ArgsManager::ALLOW_BOOL, OptionsCategory::OPTIONS);
784+
argsman.AddArg("-daemonwait", strprintf("Wait for initialization to be finished before exiting. This implies -daemon (default: %d)", DEFAULT_DAEMONWAIT), ArgsManager::ALLOW_BOOL, OptionsCategory::OPTIONS);
784785
#else
785786
hidden_args.emplace_back("-daemon");
786787
#endif

src/init.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
#include <memory>
1212
#include <string>
1313

14+
//! Default value for -daemon option
15+
static constexpr bool DEFAULT_DAEMON = false;
16+
//! Default value for -daemonwait option
17+
static constexpr bool DEFAULT_DAEMONWAIT = false;
18+
1419
class ArgsManager;
1520
struct NodeContext;
1621
namespace interfaces {

src/shutdown.cpp

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#include <shutdown.h>
77

88
#include <logging.h>
9+
#include <util/tokenpipe.h>
10+
911
#include <node/ui_interface.h>
1012
#include <warnings.h>
1113

@@ -15,10 +17,6 @@
1517
#include <atomic>
1618
#ifdef WIN32
1719
#include <condition_variable>
18-
#else
19-
#include <errno.h>
20-
#include <fcntl.h>
21-
#include <unistd.h>
2220
#endif
2321

2422
bool AbortNode(const std::string& strMessage, bilingual_str user_message)
@@ -42,25 +40,18 @@ std::mutex g_shutdown_mutex;
4240
std::condition_variable g_shutdown_cv;
4341
#else
4442
/** On UNIX-like operating systems use the self-pipe trick.
45-
* Index 0 will be the read end of the pipe, index 1 the write end.
4643
*/
47-
static int g_shutdown_pipe[2] = {-1, -1};
44+
static TokenPipeEnd g_shutdown_r;
45+
static TokenPipeEnd g_shutdown_w;
4846
#endif
4947

5048
bool InitShutdownState()
5149
{
5250
#ifndef WIN32
53-
#if HAVE_O_CLOEXEC && HAVE_DECL_PIPE2
54-
// If we can, make sure that the file descriptors are closed on exec()
55-
// to prevent interference.
56-
if (pipe2(g_shutdown_pipe, O_CLOEXEC) != 0) {
57-
return false;
58-
}
59-
#else
60-
if (pipe(g_shutdown_pipe) != 0) {
61-
return false;
62-
}
63-
#endif
51+
std::optional<TokenPipe> pipe = TokenPipe::Make();
52+
if (!pipe) return false;
53+
g_shutdown_r = pipe->TakeReadEnd();
54+
g_shutdown_w = pipe->TakeWriteEnd();
6455
#endif
6556
return true;
6657
}
@@ -77,17 +68,10 @@ void StartShutdown()
7768
// case of a reentrant signal.
7869
if (!fRequestShutdown.exchange(true)) {
7970
// Write an arbitrary byte to the write end of the shutdown pipe.
80-
const char token = 'x';
81-
while (true) {
82-
int result = write(g_shutdown_pipe[1], &token, 1);
83-
if (result < 0) {
84-
// Failure. It's possible that the write was interrupted by another signal.
85-
// Other errors are unexpected here.
86-
assert(errno == EINTR);
87-
} else {
88-
assert(result == 1);
89-
break;
90-
}
71+
int res = g_shutdown_w.TokenWrite('x');
72+
if (res != 0) {
73+
LogPrintf("Sending shutdown token failed\n");
74+
assert(0);
9175
}
9276
}
9377
#endif
@@ -124,17 +108,10 @@ void WaitForShutdown()
124108
std::unique_lock<std::mutex> lk(g_shutdown_mutex);
125109
g_shutdown_cv.wait(lk, [] { return fRequestShutdown.load(); });
126110
#else
127-
char token;
128-
while (true) {
129-
int result = read(g_shutdown_pipe[0], &token, 1);
130-
if (result < 0) {
131-
// Failure. Check if the read was interrupted by a signal.
132-
// Other errors are unexpected here.
133-
assert(errno == EINTR);
134-
} else {
135-
assert(result == 1);
136-
break;
137-
}
111+
int res = g_shutdown_r.TokenRead();
112+
if (res != 'x') {
113+
LogPrintf("Reading shutdown token failed\n");
114+
assert(0);
138115
}
139116
#endif
140117
}

0 commit comments

Comments
 (0)