summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/3rdparty/forkfd/forkfd.c90
-rw-r--r--tests/auto/corelib/io/qprocess/tst_qprocess.cpp33
2 files changed, 106 insertions, 17 deletions
diff --git a/src/3rdparty/forkfd/forkfd.c b/src/3rdparty/forkfd/forkfd.c
index 067e41d486..c4a670e1fc 100644
--- a/src/3rdparty/forkfd/forkfd.c
+++ b/src/3rdparty/forkfd/forkfd.c
@@ -160,6 +160,14 @@ static ProcessInfo *allocateInfo(Header **header)
return info;
}
+#ifdef HAVE_WAITID
+static int isChildReady(pid_t pid, siginfo_t *info)
+{
+ info->si_pid = 0;
+ return waitid(P_PID, pid, info, WEXITED | WNOHANG | WNOWAIT) == 0 && info->si_pid == pid;
+}
+#endif
+
static int tryReaping(pid_t pid, siginfo_t *info)
{
/* reap the child */
@@ -226,9 +234,74 @@ static void sigchld_handler(int signum)
siginfo_t info;
int i;
+#ifdef HAVE_WAITID
+ /* be optimistic: try to see if we can get the child that exited */
+search_next_child:
+ /* waitid returns -1 ECHILD if there are no further children at all;
+ * it returns 0 and sets si_pid to 0 if there are children but they are not ready
+ * to be waited (we're passing WNOHANG). We should not get EINTR because
+ * we're passing WNOHANG and we should definitely not get EINVAL or anything else.
+ * That means we can actually ignore the return code and only inspect si_pid.
+ */
+ info.si_pid = 0;
+ waitid(P_ALL, 0, &info, WNOHANG | WNOWAIT | WEXITED);
+ if (info.si_pid == 0) {
+ /* there are no further un-waited-for children, so we can just exit.
+ * But before, transfer control to the chained SIGCHLD handler.
+ */
+ goto chain_handler;
+ }
+
+ for (i = 0; i < (int)sizeofarray(children.entries); ++i) {
+ /* acquire the child first: swap the PID with -1 to indicate it's busy */
+ int pid = info.si_pid;
+ if (ffd_atomic_compare_exchange(&children.entries[i].pid, &pid, -1,
+ FFD_ATOMIC_ACQUIRE, FFD_ATOMIC_RELAXED)) {
+ /* this is our child, send notification and free up this entry */
+ /* ### FIXME: what if tryReaping returns false? */
+ if (tryReaping(pid, &info))
+ notifyAndFreeInfo(&children.header, &children.entries[i], &info);
+ goto search_next_child;
+ }
+ }
+
+ /* try the arrays */
+ array = ffd_atomic_load(&children.header.nextArray, FFD_ATOMIC_ACQUIRE);
+ while (array != NULL) {
+ for (i = 0; i < (int)sizeofarray(array->entries); ++i) {
+ int pid = info.si_pid;
+ if (ffd_atomic_compare_exchange(&array->entries[i].pid, &pid, -1,
+ FFD_ATOMIC_ACQUIRE, FFD_ATOMIC_RELAXED)) {
+ /* this is our child, send notification and free up this entry */
+ /* ### FIXME: what if tryReaping returns false? */
+ if (tryReaping(pid, &info))
+ notifyAndFreeInfo(&array->header, &array->entries[i], &info);
+ goto search_next_child;
+ }
+ }
+
+ array = ffd_atomic_load(&array->header.nextArray, FFD_ATOMIC_ACQUIRE);
+ }
+
+ /* if we got here, we couldn't find this child in our list. That means this child
+ * belongs to one of the chained SIGCHLD handlers. However, there might be another
+ * child that exited and does belong to us, so we need to check each one individually.
+ */
+#endif
+
for (i = 0; i < (int)sizeofarray(children.entries); ++i) {
int pid = ffd_atomic_load(&children.entries[i].pid, FFD_ATOMIC_ACQUIRE);
- if (pid > 0 && tryReaping(pid, &info)) {
+ if (pid <= 0)
+ continue;
+#ifdef HAVE_WAITID
+ /* The child might have been reaped by the block above in another thread,
+ * so first check if it's ready and, if it is, lock it */
+ if (!isChildReady(pid, &info) ||
+ !ffd_atomic_compare_exchange(&children.entries[i].pid, &pid, -1,
+ FFD_ATOMIC_RELAXED, FFD_ATOMIC_RELAXED))
+ continue;
+#endif
+ if (tryReaping(pid, &info)) {
/* this is our child, send notification and free up this entry */
notifyAndFreeInfo(&children.header, &children.entries[i], &info);
}
@@ -239,7 +312,17 @@ static void sigchld_handler(int signum)
while (array != NULL) {
for (i = 0; i < (int)sizeofarray(array->entries); ++i) {
int pid = ffd_atomic_load(&array->entries[i].pid, FFD_ATOMIC_ACQUIRE);
- if (pid > 0 && tryReaping(pid, &info)) {
+ if (pid <= 0)
+ continue;
+#ifdef HAVE_WAITID
+ /* The child might have been reaped by the block above in another thread,
+ * so first check if it's ready and, if it is, lock it */
+ if (!isChildReady(pid, &info) ||
+ !ffd_atomic_compare_exchange(&array->entries[i].pid, &pid, -1,
+ FFD_ATOMIC_RELAXED, FFD_ATOMIC_RELAXED))
+ continue;
+#endif
+ if (tryReaping(pid, &info)) {
/* this is our child, send notification and free up this entry */
notifyAndFreeInfo(&array->header, &array->entries[i], &info);
}
@@ -249,6 +332,9 @@ static void sigchld_handler(int signum)
}
}
+#ifdef HAVE_WAITID
+chain_handler:
+#endif
if (old_sigaction.sa_handler != SIG_IGN && old_sigaction.sa_handler != SIG_DFL)
old_sigaction.sa_handler(signum);
}
diff --git a/tests/auto/corelib/io/qprocess/tst_qprocess.cpp b/tests/auto/corelib/io/qprocess/tst_qprocess.cpp
index 59042f6498..c8ba69c0fb 100644
--- a/tests/auto/corelib/io/qprocess/tst_qprocess.cpp
+++ b/tests/auto/corelib/io/qprocess/tst_qprocess.cpp
@@ -1223,21 +1223,24 @@ void tst_QProcess::processInAThread()
void tst_QProcess::processesInMultipleThreads()
{
for (int i = 0; i < 10; ++i) {
- TestThread thread1;
- TestThread thread2;
- TestThread thread3;
-
- thread1.start();
- thread2.start();
- thread3.start();
-
- QVERIFY(thread2.wait(10000));
- QVERIFY(thread3.wait(10000));
- QVERIFY(thread1.wait(10000));
-
- QCOMPARE(thread1.code(), 0);
- QCOMPARE(thread2.code(), 0);
- QCOMPARE(thread3.code(), 0);
+ // run from 1 to 10 threads, but run at least some tests
+ // with more threads than the ideal
+ int threadCount = i;
+ if (i > 7)
+ threadCount = qMax(threadCount, QThread::idealThreadCount() + 2);
+
+ QVector<TestThread *> threads(threadCount);
+ for (int j = 0; j < threadCount; ++j)
+ threads[j] = new TestThread;
+ for (int j = 0; j < threadCount; ++j)
+ threads[j]->start();
+ for (int j = 0; j < threadCount; ++j) {
+ QVERIFY(threads[j]->wait(10000));
+ }
+ for (int j = 0; j < threadCount; ++j) {
+ QCOMPARE(threads[j]->code(), 0);
+ }
+ qDeleteAll(threads);
}
}