bitcoind
is started using the same datadir it will fail to start up, but during shutdown remove the PID file from the first bitcoind
instance.
bitcoind
is started using the same datadir it will fail to start up, but during shutdown remove the PID file from the first bitcoind
instance.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
For detailed information about the code coverage, see the test coverage report.
See the guideline for information on the review process.
Type | Reviewers |
---|---|
ACK | andrewtoth, romanz, achow101 |
Stale ACK | stickies-v |
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.
Reviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
You can test similarly to #28784:
0will@ubuntu in ~ :
1₿ bitcoind -regtest -daemon=1
2Bitcoin Core starting
3
4will@ubuntu in ~ :
5₿ file ~/.bitcoin/regtest/bitcoind.pid
6/home/will/.bitcoin/regtest/bitcoind.pid: ASCII text
7
8will@ubuntu in ~ :
9₿ bitcoind -regtest -daemon=1
10Error: Cannot obtain a lock on data directory /home/will/.bitcoin/regtest. Bitcoin Core is probably already running.
11
12will@ubuntu in ~ :
13₿ file ~/.bitcoin/regtest/bitcoind.pid
14/home/will/.bitcoin/regtest/bitcoind.pid: cannot open `/home/will/.bitcoin/regtest/bitcoind.pid' (No such file or directory)
181+static void RemovePidFile(NodeContext& node)
182+{
183+ if (!g_generated_pid) return;
184+ try {
185+ if (!fs::remove(GetPidFile(*node.args))) {
186+ LogPrintf("%s: Unable to remove PID file: File does not exist\n", __func__);
0 LogPrintf("Unable to remove PID file: File does not exist\n");
nit: No need to take this over, if you change the function name, the warning is already unique, and user can enable to print the function name, if they really want to.
180
181+static void RemovePidFile(NodeContext& node)
182+{
183+ if (!g_generated_pid) return;
184+ try {
185+ if (!fs::remove(GetPidFile(*node.args))) {
node
, when you only need args?
184+ try {
185+ if (!fs::remove(GetPidFile(*node.args))) {
186+ LogPrintf("%s: Unable to remove PID file: File does not exist\n", __func__);
187+ }
188+ } catch (const fs::filesystem_error& e) {
189+ LogPrintf("%s: Unable to remove PID file: %s\n", __func__, fsbridge::get_filesystem_error_message(e));
185+ if (!fs::remove(GetPidFile(args))) {
186+ LogPrintf("Unable to remove PID file: File does not exist\n");
187+ }
188+ } catch (const fs::filesystem_error& e) {
189+ LogPrintf("Unable to remove PID file: %s\n", fsbridge::get_filesystem_error_message(e));
190+ }
nit: can be simplified
0 if (std::error_code error; !fs::remove(GetPidFile(args), error)) {
1 std::string msg{error ? error.message() : "File does not exist"};
2 LogPrintf("Unable to remove PID file: %s\n", msg);
3 }
Is passing error codes our preferred error handling technique, or just slightly shorter?
I’d naturally opt for the former, and I think errors from it will be slightly more detailed, including an error msg instead of only error code (although it doesn’t make much difference in this case), but can very easily be persuaded to switch to the latter if it’s preferred…
I just like the brevity of it. I’m using error.message()
and not error.value()
so the user will see a user friendly error message. Could include the pid path in the log line if preferred, probably helpful to the user to know which file to change permissions on indeed.
For example, with this patch and chmod a-w
on the datadir this will print:
Unable to remove PID file: Permission denied
For a missing file, this will print
Unable to remove PID file: File does not exist
29@@ -30,6 +30,10 @@ def run_test(self):
30 expected_msg = f"Error: Cannot obtain a lock on data directory {datadir}. {self.config['environment']['PACKAGE_NAME']} is probably already running."
31 self.nodes[1].assert_start_raises_init_error(extra_args=[f'-datadir={self.nodes[0].datadir_path}', '-noserver'], expected_msg=expected_msg)
32
33+ self.log.info("Check that cookie and PID files are not deleted when attempting to start a second bitcoind using the same datadir")
176 } else {
177 return InitError(strprintf(_("Unable to create the PID file '%s': %s"), fs::PathToString(GetPidFile(args)), SysErrorString(errno)));
178 }
179 }
180
181+static void RemovePidFile(const ArgsManager& args)
~#28784 stores the path in static std::optional<std::string> g_generated_cookie
~. I don’t have a particularly strong conviction in either direction, but I’d use the same approach for both. Storing the location of the file to be deleted seems a bit more robust, so I think I’d lean towards this approach:
0diff --git a/src/init.cpp b/src/init.cpp
1index afa0ab1ee6..f2d74db13f 100644
2--- a/src/init.cpp
3+++ b/src/init.cpp
4@@ -96,6 +96,7 @@
5 #include <cstdio>
6 #include <fstream>
7 #include <functional>
8+#include <optional>
9 #include <set>
10 #include <string>
11 #include <thread>
12@@ -155,7 +156,7 @@ static const char* DEFAULT_ASMAP_FILENAME="ip_asn.map";
13 * The PID file facilities.
14 */
15 static const char* BITCOIN_PID_FILENAME = "bitcoind.pid";
16-static bool g_generated_pid{false};
17+static std::optional<fs::path> g_generated_pid;
18
19 static fs::path GetPidFile(const ArgsManager& args)
20 {
21@@ -164,25 +165,26 @@ static fs::path GetPidFile(const ArgsManager& args)
22
23 [[nodiscard]] static bool CreatePidFile(const ArgsManager& args)
24 {
25- std::ofstream file{GetPidFile(args)};
26+ const fs::path pid_path{GetPidFile(args)};
27+ std::ofstream file{pid_path};
28 if (file) {
29 #ifdef WIN32
30 tfm::format(file, "%d\n", GetCurrentProcessId());
31 #else
32 tfm::format(file, "%d\n", getpid());
33 #endif
34- g_generated_pid = true;
35+ g_generated_pid = pid_path;
36 return true;
37 } else {
38- return InitError(strprintf(_("Unable to create the PID file '%s': %s"), fs::PathToString(GetPidFile(args)), SysErrorString(errno)));
39+ return InitError(strprintf(_("Unable to create the PID file '%s': %s"), fs::PathToString(pid_path), SysErrorString(errno)));
40 }
41 }
42
43-static void RemovePidFile(const ArgsManager& args)
44+static void RemovePidFile()
45 {
46 if (!g_generated_pid) return;
47 try {
48- if (!fs::remove(GetPidFile(args))) {
49+ if (!fs::remove(g_generated_pid.value())) {
50 LogPrintf("Unable to remove PID file: File does not exist\n");
51 }
52 } catch (const fs::filesystem_error& e) {
53@@ -364,7 +366,7 @@ void Shutdown(NodeContext& node)
54 node.scheduler.reset();
55 node.kernel.reset();
56
57- RemovePidFile(*node.args);
58+ RemovePidFile();
59
60 LogPrintf("%s: done\n", __func__);
61 }
g_generated_cookie
stores the cookie, not the path. My bad.
{Get|Create|Remove}PidFile()
functions as necessary.
181+static void RemovePidFile(const ArgsManager& args)
182+{
183+ if (!g_generated_pid) return;
184+ if (std::error_code error; !fs::remove(GetPidFile(args), error)) {
185+ std::string msg{error ? error.message() : "File does not exist"};
186+ auto pid_path = args.GetPathArg("-pid", BITCOIN_PID_FILENAME);
GetPidFile()
?
176 } else {
177 return InitError(strprintf(_("Unable to create the PID file '%s': %s"), fs::PathToString(GetPidFile(args)), SysErrorString(errno)));
178 }
179 }
180
181+static void RemovePidFile(const ArgsManager& args)
nit: quick docstring would be nice
0/**
1 * Remove PID file, but only if it was also generated by this process to avoid problems when
2 * multiple bitcoind processes are started on the same datadir.
3 */
0diff --git a/src/init.cpp b/src/init.cpp
1index fced24a6ff..7a87467252 100644
2--- a/src/init.cpp
3+++ b/src/init.cpp
4@@ -177,7 +177,10 @@ static fs::path GetPidFile(const ArgsManager& args)
5 return InitError(strprintf(_("Unable to create the PID file '%s': %s"), fs::PathToString(GetPidFile(args)), SysErrorString(errno)));
6 }
7 }
8-
9+/**
10+ * Remove PID file, but only if it was also generated by this process to avoid problems when
11+ * multiple bitcoind processes are started on the same datadir.
12+ */
13 static void RemovePidFile(const ArgsManager& args)
14 {
15 if (!g_generated_pid) return;
179 }
180
181+static void RemovePidFile(const ArgsManager& args)
182+{
183+ if (!g_generated_pid) return;
184+ auto pid_path = GetPidFile(args);
nit
0 const auto pid_path{GetPidFile(args)};
182+{
183+ if (!g_generated_pid) return;
184+ auto pid_path = GetPidFile(args);
185+ if (std::error_code error; !fs::remove(pid_path, error)) {
186+ std::string msg{error ? error.message() : "File does not exist"};
187+ LogPrintf("Unable to remove PID file %s: %s\n", fs::quoted(fs::PathToString(pid_path)), msg);
nit: I’m not sure how important the quoting is, could just use parentheses to make it a bit more concise?
0 LogPrintf("Unable to remove PID file (%s): %s\n", fs::PathToString(pid_path), msg);
154@@ -155,6 +155,11 @@ static const char* DEFAULT_ASMAP_FILENAME="ip_asn.map";
155 * The PID file facilities.
156 */
157 static const char* BITCOIN_PID_FILENAME = "bitcoind.pid";
158+/**
159+ * Whether this process has created a PID file.
160+ * Used when determining whether we should remove the PID file on shutdown.
161+ */
nit: I think this more clearly explains the rationale without being too explicit about implementation
0//! True if this process created the pid file, so we don't delete it too early if multiple processes were started
Previously, starting a second bitcoind using the same datadir would
correctly fail to init and shutdown. However during shutdown the PID
file belonging to the first instance would be erroneously removed by
the second process shutting down.
Fix this to only delete the PID file if we created it.