BUG/MINOR: signals/poller: ensure wakeup from signals
Add self-wake in signal_handler() to fix a race condition with a signal
coming in between checking signal_queue_len and entering polling sleep.
The changes in commit 43c891dda ("BUG/MINOR: signals/poller: set the
poller timeout to 0 when there are signals") were insufficient.
Move the signal_queue_len check from the poll implementations to
run_poll_loop() to keep that logic in one place.
The poll loops are terminated either by the parameter wake being set or
wake up due to a write to their poller_wr_pipe by wake_thread() in
signal_handler().
This fixes issue #1841.
Must be backported in every stable version.
diff --git a/src/ev_epoll.c b/src/ev_epoll.c
index 746a0d1..880aa58 100644
--- a/src/ev_epoll.c
+++ b/src/ev_epoll.c
@@ -222,11 +222,10 @@
thread_idle_now();
thread_harmless_now();
- /* Now let's wait for polled events.
- * Check if the signal queue is not empty in case we received a signal
- * before entering the loop, so we don't wait MAX_DELAY_MS for nothing */
- wait_time = (wake | signal_queue_len) ? 0 : compute_poll_timeout(exp);
+ /* Now let's wait for polled events. */
+ wait_time = wake ? 0 : compute_poll_timeout(exp);
clock_entering_poll();
+
do {
int timeout = (global.tune.options & GTUNE_BUSY_POLLING) ? 0 : wait_time;
@@ -239,7 +238,7 @@
}
if (timeout || !wait_time)
break;
- if (signal_queue_len || wake)
+ if (wake)
break;
if (tick_isset(exp) && tick_is_expired(exp, now_ms))
break;
diff --git a/src/ev_evports.c b/src/ev_evports.c
index 7d3e8a8..0a7df15 100644
--- a/src/ev_evports.c
+++ b/src/ev_evports.c
@@ -178,10 +178,8 @@
thread_idle_now();
thread_harmless_now();
- /* Now let's wait for polled events.
- * Check if the signal queue is not empty in case we received a signal
- * before entering the loop, so we don't wait MAX_DELAY_MS for nothing */
- wait_time = (wake | signal_queue_len) ? 0 : compute_poll_timeout(exp);
+ /* Now let's wait for polled events. */
+ wait_time = wake ? 0 : compute_poll_timeout(exp);
clock_entering_poll();
do {
@@ -219,7 +217,7 @@
break;
if (timeout || !wait_time)
break;
- if (signal_queue_len || wake)
+ if (wake)
break;
if (tick_isset(exp) && tick_is_expired(exp, now_ms))
break;
diff --git a/src/ev_kqueue.c b/src/ev_kqueue.c
index 1d6e91d..4796e68 100644
--- a/src/ev_kqueue.c
+++ b/src/ev_kqueue.c
@@ -166,10 +166,8 @@
}
fd_nbupdt = 0;
- /* Now let's wait for polled events.
- * Check if the signal queue is not empty in case we received a signal
- * before entering the loop, so we don't wait MAX_DELAY_MS for nothing */
- wait_time = (wake | signal_queue_len) ? 0 : compute_poll_timeout(exp);
+ /* Now let's wait for polled events. */
+ wait_time = wake ? 0 : compute_poll_timeout(exp);
fd = global.tune.maxpollevents;
clock_entering_poll();
@@ -193,7 +191,7 @@
}
if (timeout || !wait_time)
break;
- if (signal_queue_len || wake)
+ if (wake)
break;
if (tick_isset(exp) && tick_is_expired(exp, now_ms))
break;
diff --git a/src/ev_poll.c b/src/ev_poll.c
index 6fefb3a..e98630c 100644
--- a/src/ev_poll.c
+++ b/src/ev_poll.c
@@ -204,10 +204,8 @@
}
}
- /* Now let's wait for polled events.
- * Check if the signal queue is not empty in case we received a signal
- * before entering the loop, so we don't wait MAX_DELAY_MS for nothing */
- wait_time = (wake | signal_queue_len) ? 0 : compute_poll_timeout(exp);
+ /* Now let's wait for polled events. */
+ wait_time = wake ? 0 : compute_poll_timeout(exp);
clock_entering_poll();
status = poll(poll_events, nbfd, wait_time);
clock_update_date(wait_time, status);
diff --git a/src/haproxy.c b/src/haproxy.c
index 2eb84bd..8d6f587 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -2810,7 +2810,7 @@
if (killed > 1)
break;
- /* expire immediately if events are pending */
+ /* expire immediately if events or signals are pending */
wake = 1;
if (thread_has_tasks())
activity[tid].wake_tasks++;
@@ -2821,6 +2821,10 @@
if (thread_has_tasks()) {
activity[tid].wake_tasks++;
_HA_ATOMIC_AND(&th_ctx->flags, ~TH_FL_SLEEPING);
+ } else if (signal_queue_len) {
+ /* this check is required after setting TH_FL_SLEEPING to avoid
+ * a race with wakeup on signals using wake_threads() */
+ _HA_ATOMIC_AND(&th_ctx->flags, ~TH_FL_SLEEPING);
} else
wake = 0;
}
diff --git a/src/signal.c b/src/signal.c
index 5ec7142..3d7a9a8 100644
--- a/src/signal.c
+++ b/src/signal.c
@@ -56,6 +56,9 @@
signal_state[sig].count++;
if (sig)
signal(sig, signal_handler); /* re-arm signal */
+
+ /* If the thread is TH_FL_SLEEPING we need to wake it */
+ wake_thread(tid);
}
/* Call handlers of all pending signals and clear counts and queue length. The