Merge #21007: bitcoind: Add -daemonwait option to wait for initialization

e017a913d0 bitcoind: Add -daemonwait option to wait for initialization (Wladimir J. van der Laan)
c3e6fdee6d shutdown: Use RAII TokenPipe in shutdown (Wladimir J. van der Laan)
612f746a8f 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 https://github.com/bitcoin/bitcoin/pull/21007#issuecomment-769007341.

  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 e017a913d0 checked change since previous review is move-only

Tree-SHA512: 53369b8ca2247e4cf3af8cb2cfd5b3399e8e0e3296423d64be987004758162a7ddc1287b01a92d7692328edcb2da4cf05d279b1b4ef61a665b71440ab6a6dbe2
This commit is contained in:
Wladimir J. van der Laan
2021-03-11 15:23:24 +01:00
9 changed files with 382 additions and 57 deletions

108
src/util/tokenpipe.cpp Normal file
View File

@@ -0,0 +1,108 @@
// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <util/tokenpipe.h>
#include <config/bitcoin-config.h>
#ifndef WIN32
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
TokenPipeEnd TokenPipe::TakeReadEnd()
{
TokenPipeEnd res(m_fds[0]);
m_fds[0] = -1;
return res;
}
TokenPipeEnd TokenPipe::TakeWriteEnd()
{
TokenPipeEnd res(m_fds[1]);
m_fds[1] = -1;
return res;
}
TokenPipeEnd::TokenPipeEnd(int fd) : m_fd(fd)
{
}
TokenPipeEnd::~TokenPipeEnd()
{
Close();
}
int TokenPipeEnd::TokenWrite(uint8_t token)
{
while (true) {
ssize_t result = write(m_fd, &token, 1);
if (result < 0) {
// Failure. It's possible that the write was interrupted by a signal,
// in that case retry.
if (errno != EINTR) {
return TS_ERR;
}
} else if (result == 0) {
return TS_EOS;
} else { // ==1
return 0;
}
}
}
int TokenPipeEnd::TokenRead()
{
uint8_t token;
while (true) {
ssize_t result = read(m_fd, &token, 1);
if (result < 0) {
// Failure. Check if the read was interrupted by a signal,
// in that case retry.
if (errno != EINTR) {
return TS_ERR;
}
} else if (result == 0) {
return TS_EOS;
} else { // ==1
return token;
}
}
return token;
}
void TokenPipeEnd::Close()
{
if (m_fd != -1) close(m_fd);
m_fd = -1;
}
std::optional<TokenPipe> TokenPipe::Make()
{
int fds[2] = {-1, -1};
#if HAVE_O_CLOEXEC && HAVE_DECL_PIPE2
if (pipe2(fds, O_CLOEXEC) != 0) {
return std::nullopt;
}
#else
if (pipe(fds) != 0) {
return std::nullopt;
}
#endif
return TokenPipe(fds);
}
TokenPipe::~TokenPipe()
{
Close();
}
void TokenPipe::Close()
{
if (m_fds[0] != -1) close(m_fds[0]);
if (m_fds[1] != -1) close(m_fds[1]);
m_fds[0] = m_fds[1] = -1;
}
#endif // WIN32

127
src/util/tokenpipe.h Normal file
View File

@@ -0,0 +1,127 @@
// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_UTIL_TOKENPIPE_H
#define BITCOIN_UTIL_TOKENPIPE_H
#ifndef WIN32
#include <cstdint>
#include <optional>
/** One end of a token pipe. */
class TokenPipeEnd
{
private:
int m_fd = -1;
public:
TokenPipeEnd(int fd = -1);
~TokenPipeEnd();
/** Return value constants for TokenWrite and TokenRead. */
enum Status {
TS_ERR = -1, //!< I/O error
TS_EOS = -2, //!< Unexpected end of stream
};
/** Write token to endpoint.
*
* @returns 0 If successful.
* <0 if error:
* TS_ERR If an error happened.
* TS_EOS If end of stream happened.
*/
int TokenWrite(uint8_t token);
/** Read token from endpoint.
*
* @returns >=0 Token value, if successful.
* <0 if error:
* TS_ERR If an error happened.
* TS_EOS If end of stream happened.
*/
int TokenRead();
/** Explicit close function.
*/
void Close();
/** Return whether endpoint is open.
*/
bool IsOpen() { return m_fd != -1; }
// Move-only class.
TokenPipeEnd(TokenPipeEnd&& other)
{
m_fd = other.m_fd;
other.m_fd = -1;
}
TokenPipeEnd& operator=(TokenPipeEnd&& other)
{
Close();
m_fd = other.m_fd;
other.m_fd = -1;
return *this;
}
TokenPipeEnd(const TokenPipeEnd&) = delete;
TokenPipeEnd& operator=(const TokenPipeEnd&) = delete;
};
/** An interprocess or interthread pipe for sending tokens (one-byte values)
* over.
*/
class TokenPipe
{
private:
int m_fds[2] = {-1, -1};
TokenPipe(int fds[2]) : m_fds{fds[0], fds[1]} {}
public:
~TokenPipe();
/** Create a new pipe.
* @returns The created TokenPipe, or an empty std::nullopt in case of error.
*/
static std::optional<TokenPipe> Make();
/** Take the read end of this pipe. This can only be called once,
* as the object will be moved out.
*/
TokenPipeEnd TakeReadEnd();
/** Take the write end of this pipe. This should only be called once,
* as the object will be moved out.
*/
TokenPipeEnd TakeWriteEnd();
/** Close and end of the pipe that hasn't been moved out.
*/
void Close();
// Move-only class.
TokenPipe(TokenPipe&& other)
{
for (int i = 0; i < 2; ++i) {
m_fds[i] = other.m_fds[i];
other.m_fds[i] = -1;
}
}
TokenPipe& operator=(TokenPipe&& other)
{
Close();
for (int i = 0; i < 2; ++i) {
m_fds[i] = other.m_fds[i];
other.m_fds[i] = -1;
}
return *this;
}
TokenPipe(const TokenPipe&) = delete;
TokenPipe& operator=(const TokenPipe&) = delete;
};
#endif // WIN32
#endif // BITCOIN_UTIL_TOKENPIPE_H