Skip to content

Commit ec48d5e

Browse files
committed
src: fix monerod shutdown hanging by making SIGINT handling async-signal-safe
1 parent ea9be68 commit ec48d5e

File tree

4 files changed

+122
-20
lines changed

4 files changed

+122
-20
lines changed

contrib/epee/include/net/abstract_tcp_server2.inl

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1706,26 +1706,40 @@ namespace net_utils
17061706
boost::unique_lock<boost::mutex> lock(local_shared_context->connect_mut);
17071707
auto connect_callback = [](boost::system::error_code ec_, boost::shared_ptr<local_async_context> shared_context)
17081708
{
1709-
shared_context->connect_mut.lock(); shared_context->ec = ec_; shared_context->cond.notify_one(); shared_context->connect_mut.unlock();
1709+
boost::lock_guard<boost::mutex> callback_lock(shared_context->connect_mut);
1710+
shared_context->ec = ec_;
1711+
shared_context->cond.notify_one();
17101712
};
17111713

17121714
sock_.async_connect(remote_endpoint, std::bind<void>(connect_callback, std::placeholders::_1, local_shared_context));
1715+
const auto deadline = boost::get_system_time() + boost::posix_time::milliseconds(conn_timeout);
17131716
while(local_shared_context->ec == boost::asio::error::would_block)
17141717
{
1715-
bool r = local_shared_context->cond.timed_wait(lock, boost::get_system_time() + boost::posix_time::milliseconds(conn_timeout));
17161718
if (m_stop_signal_sent)
17171719
{
1720+
boost::system::error_code ignored_ec;
1721+
sock_.cancel(ignored_ec);
17181722
if (sock_.is_open())
17191723
sock_.close();
17201724
return CONNECT_FAILURE;
17211725
}
1722-
if(local_shared_context->ec == boost::asio::error::would_block && !r)
1726+
const auto now = boost::get_system_time();
1727+
if (now >= deadline)
17231728
{
1724-
//timeout
1729+
// timeout
17251730
sock_.close();
17261731
_dbg3("Failed to connect to " << adr << ":" << port << ", because of timeout (" << conn_timeout << ")");
17271732
return CONNECT_FAILURE;
17281733
}
1734+
const auto remaining = deadline - now;
1735+
const auto wait_step = remaining < boost::posix_time::milliseconds(100)
1736+
? remaining
1737+
: boost::posix_time::milliseconds(100);
1738+
bool r = local_shared_context->cond.timed_wait(lock, now + wait_step);
1739+
if(local_shared_context->ec == boost::asio::error::would_block && !r)
1740+
{
1741+
continue;
1742+
}
17291743
}
17301744
ec = local_shared_context->ec;
17311745

src/common/util.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include <unistd.h>
3232
#include <cstdio>
3333
#include <wchar.h>
34+
#include <cerrno>
3435

3536
#ifdef __GLIBC__
3637
#include <gnu/libc-version.h>
@@ -79,6 +80,7 @@ using namespace epee;
7980
#else
8081
#include <sys/file.h>
8182
#include <sys/stat.h>
83+
#include <fcntl.h>
8284
#endif
8385
#include <boost/filesystem.hpp>
8486
#include <boost/algorithm/string.hpp>
@@ -133,6 +135,73 @@ namespace tools
133135
}
134136

135137
std::function<void(int)> signal_handler::m_handler;
138+
#ifndef WIN32
139+
std::atomic<bool> signal_handler::m_dispatch_thread_started{false};
140+
std::thread signal_handler::m_dispatch_thread;
141+
int signal_handler::m_signal_pipe_read = -1;
142+
int signal_handler::m_signal_pipe_write = -1;
143+
144+
bool signal_handler::install_posix()
145+
{
146+
static boost::mutex install_mutex;
147+
boost::lock_guard<boost::mutex> install_lock(install_mutex);
148+
149+
if (!m_dispatch_thread_started.load(std::memory_order_acquire))
150+
{
151+
int pipe_fds[2]{-1, -1};
152+
if (pipe(pipe_fds) != 0)
153+
{
154+
MERROR("Failed to create signal pipe: " << errno);
155+
return false;
156+
}
157+
158+
// Make write end non-blocking so signal handler never blocks.
159+
const int flags = fcntl(pipe_fds[1], F_GETFL, 0);
160+
if (flags >= 0)
161+
fcntl(pipe_fds[1], F_SETFL, flags | O_NONBLOCK);
162+
fcntl(pipe_fds[0], F_SETFD, FD_CLOEXEC);
163+
fcntl(pipe_fds[1], F_SETFD, FD_CLOEXEC);
164+
165+
m_signal_pipe_read = pipe_fds[0];
166+
m_signal_pipe_write = pipe_fds[1];
167+
m_dispatch_thread = std::thread(&signal_handler::signal_dispatch_thread);
168+
m_dispatch_thread.detach();
169+
m_dispatch_thread_started.store(true, std::memory_order_release);
170+
}
171+
172+
static struct sigaction sa;
173+
memset(&sa, 0, sizeof(struct sigaction));
174+
sa.sa_handler = posix_handler;
175+
sa.sa_flags = 0;
176+
sigemptyset(&sa.sa_mask);
177+
sigaction(SIGINT, &sa, NULL);
178+
sigaction(SIGTERM, &sa, NULL);
179+
signal(SIGPIPE, SIG_IGN);
180+
return true;
181+
}
182+
183+
void signal_handler::signal_dispatch_thread()
184+
{
185+
for (;;)
186+
{
187+
uint8_t signal_byte = 0;
188+
const ssize_t bytes = read(m_signal_pipe_read, &signal_byte, sizeof(signal_byte));
189+
if (bytes == sizeof(signal_byte))
190+
{
191+
handle_signal(static_cast<int>(signal_byte));
192+
}
193+
else if (bytes < 0 && errno == EINTR)
194+
{
195+
continue;
196+
}
197+
else
198+
{
199+
// Prevent spin on pipe failures.
200+
epee::misc_utils::sleep_no_w(50);
201+
}
202+
}
203+
}
204+
#endif
136205

137206
private_file::private_file() noexcept : m_handle(), m_filename() {}
138207

src/common/util.h

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
#include <functional>
4040
#include <memory>
4141
#include <string>
42+
#include <thread>
43+
#include <atomic>
44+
#include <cstdint>
45+
#ifndef WIN32
46+
#include <unistd.h>
47+
#endif
4248

4349
#ifdef _WIN32
4450
#include "windows.h"
@@ -170,23 +176,14 @@ namespace tools
170176
static bool install(T t)
171177
{
172178
#if defined(WIN32)
173-
bool r = TRUE == ::SetConsoleCtrlHandler(&win_handler, TRUE);
174-
if (r)
175-
{
176-
m_handler = t;
177-
}
179+
m_handler = t;
180+
const bool r = TRUE == ::SetConsoleCtrlHandler(&win_handler, TRUE);
181+
if (!r)
182+
m_handler = {};
178183
return r;
179184
#else
180-
static struct sigaction sa;
181-
memset(&sa, 0, sizeof(struct sigaction));
182-
sa.sa_handler = posix_handler;
183-
sa.sa_flags = 0;
184-
/* Only blocks SIGINT, SIGTERM and SIGPIPE */
185-
sigaction(SIGINT, &sa, NULL);
186-
signal(SIGTERM, posix_handler);
187-
signal(SIGPIPE, SIG_IGN);
188185
m_handler = t;
189-
return true;
186+
return install_posix();
190187
#endif
191188
}
192189

@@ -210,20 +207,38 @@ namespace tools
210207
/*! \brief handler for NIX */
211208
static void posix_handler(int type)
212209
{
213-
handle_signal(type);
210+
// Async-signal-safe: enqueue the signal number for a normal thread.
211+
const uint8_t signal_byte = static_cast<uint8_t>(type);
212+
int ignored = ::write(m_signal_pipe_write, &signal_byte, sizeof(signal_byte));
213+
(void)ignored;
214214
}
215215
#endif
216216

217217
/*! \brief calles m_handler */
218218
static void handle_signal(int type)
219219
{
220+
#ifdef WIN32
220221
static boost::mutex m_mutex;
221222
boost::unique_lock<boost::mutex> lock(m_mutex);
222-
m_handler(type);
223+
#endif
224+
if (m_handler)
225+
m_handler(type);
223226
}
224227

228+
#ifndef WIN32
229+
static bool install_posix();
230+
static void signal_dispatch_thread();
231+
#endif
232+
225233
/*! \brief where the installed handler is stored */
226234
static std::function<void(int)> m_handler;
235+
236+
#ifndef WIN32
237+
static std::atomic<bool> m_dispatch_thread_started;
238+
static std::thread m_dispatch_thread;
239+
static int m_signal_pipe_read;
240+
static int m_signal_pipe_write;
241+
#endif
227242
};
228243

229244
void set_strict_default_file_permissions(bool strict);

src/p2p/net_node.inl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,10 @@ namespace nodetool
11601160
zone.second.m_net_server.get_config_object().close(connection_id);
11611161
}
11621162
m_payload_handler.stop();
1163+
// Join worker threads quickly on shutdown; if some are still stuck in
1164+
// connect/cancel paths, timed_wait_server_stop() will interrupt them.
1165+
for (auto& zone : m_network_zones)
1166+
zone.second.m_net_server.timed_wait_server_stop(2000);
11631167
return true;
11641168
}
11651169
//-----------------------------------------------------------------------------------

0 commit comments

Comments
 (0)