Skip to content

Commit 09ef7a4

Browse files
committed
Mimic kernel signal coalescing behavior for synthetic SIGCHLDs.
On the real kernel multiple SIGCHLDs (and all non-rt signals, of course) are coalesced and only a single SIGCHLD is delivered. It's important for us to copy this behavior here because RecordTask::has_other_actionable signals does not know about the signals that effectively exist in emulated_[ptrace_]SIGCHLD_pending. If we allow multiple emulated SIGCHLDs and those SIGCHLDs happen to interrupt e.g. a recv(2) the EV_SIGNAL_DELIVERY case in RecordSession::signal_state_changed will set up the syscall restart after the first signal only to immediately take a second one, and when we go to replay the second signal we will find the registers have diverged.
1 parent ec66b21 commit 09ef7a4

File tree

3 files changed

+72
-9
lines changed

3 files changed

+72
-9
lines changed

CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,7 @@ set(BASIC_TESTS
11001100
grandchild_threads_main_running
11011101
grandchild_threads_thread_running
11021102
grandchild_threads_parent_alive
1103+
group_stop_thundering_herd
11031104
x86/hle
11041105
x86/hlt
11051106
inotify

src/RecordTask.cc

+8-9
Original file line numberDiff line numberDiff line change
@@ -997,9 +997,10 @@ void RecordTask::set_siginfo_for_synthetic_SIGCHLD(siginfo_t* si) {
997997
RecordTask* from_task = nullptr;
998998
for (RecordTask* tracee : emulated_ptrace_tracees) {
999999
if (tracee->emulated_ptrace_SIGCHLD_pending) {
1000-
from_task = tracee;
1001-
from_task->emulated_ptrace_SIGCHLD_pending = false;
1002-
break;
1000+
if (!from_task) {
1001+
from_task = tracee;
1002+
}
1003+
tracee->emulated_ptrace_SIGCHLD_pending = false;
10031004
}
10041005
}
10051006

@@ -1008,14 +1009,12 @@ void RecordTask::set_siginfo_for_synthetic_SIGCHLD(siginfo_t* si) {
10081009
for (Task* child : child_tg->task_set()) {
10091010
auto rchild = static_cast<RecordTask*>(child);
10101011
if (rchild->emulated_SIGCHLD_pending) {
1011-
from_task = rchild;
1012-
from_task->emulated_SIGCHLD_pending = false;
1013-
break;
1012+
if (!from_task) {
1013+
from_task = rchild;
1014+
}
1015+
rchild->emulated_SIGCHLD_pending = false;
10141016
}
10151017
}
1016-
if (from_task) {
1017-
break;
1018-
}
10191018
}
10201019

10211020
if (!from_task) {

src/test/group_stop_thundering_herd.c

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
2+
3+
#include "util.h"
4+
#include "ptrace_util.h"
5+
6+
static void* do_thread(void* arg) {
7+
int pipe_fd = *(int*)arg;
8+
uint32_t tid = gettid();
9+
10+
write(pipe_fd, &tid, 4);
11+
/* Sleep long enough that it will be noticed if it's not interrupted. */
12+
sleep(1000);
13+
14+
return NULL;
15+
}
16+
17+
int main(void) {
18+
pid_t child, grandchild;
19+
int sock_fds[2];
20+
21+
test_assert(0 == socketpair(AF_LOCAL, SOCK_STREAM, 0, sock_fds));
22+
23+
if (0 == (child = fork())) {
24+
uint32_t tid;
25+
int pipe_fds[2];
26+
test_assert(0 == pipe(pipe_fds));
27+
28+
if (0 == (grandchild = fork())) {
29+
pthread_t t;
30+
31+
pthread_create(&t, NULL, do_thread, &pipe_fds[1]);
32+
pthread_join(t, NULL);
33+
34+
return 77;
35+
}
36+
37+
test_assert(4 == read(pipe_fds[0], &tid, 4));
38+
39+
close(pipe_fds[0]);
40+
sched_yield();
41+
42+
test_assert(sizeof(grandchild) == send(sock_fds[1], &grandchild, sizeof(grandchild), 0));
43+
/* Nothing is ever sent the other way so this just blocks. */
44+
recv(sock_fds[1], &grandchild, sizeof(grandchild), 0);
45+
46+
return 66;
47+
}
48+
49+
/* Get the grandchild pid. */
50+
recv(sock_fds[0], &grandchild, sizeof(grandchild), 0);
51+
52+
/* Hit the entire process group with a SIGSTOP. */
53+
tgkill(grandchild, grandchild, SIGSTOP);
54+
55+
/* Force the rr scheduler to run. */
56+
sched_yield();
57+
58+
kill(SIGKILL, grandchild);
59+
kill(SIGKILL, child);
60+
61+
atomic_puts("EXIT-SUCCESS");
62+
return 0;
63+
}

0 commit comments

Comments
 (0)