Skip to content

Commit 6eba1b5

Browse files
committed
Add test for hang on fork
1 parent 11ed7a2 commit 6eba1b5

File tree

4 files changed

+115
-17
lines changed

4 files changed

+115
-17
lines changed

src/common/kevent.c

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -331,9 +331,13 @@ kevent_copyin(struct kqueue *kq, const struct kevent changelist[], int nchanges,
331331

332332
#ifndef _WIN32
333333
static void
334-
kevent_release_kq_mutex(void *kq)
334+
kevent_release_kq_mutex(void *arg)
335335
{
336-
kqueue_unlock((struct kqueue *)kq);
336+
struct kqueue *kq = arg;
337+
dbg_printf("Unlocking kq=%p due to cancellation", kq);
338+
tracing_mutex_lock(&kq_mtx); /* keep tsan happy */
339+
kqueue_unlock(kq);
340+
tracing_mutex_unlock(&kq_mtx);
337341
}
338342
#endif
339343

@@ -367,7 +371,7 @@ kevent(int kqfd,
367371
if (!changelist) changelist = null_kev;
368372

369373
#ifndef _WIN32
370-
prev_cancel_state = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
374+
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &prev_cancel_state);
371375
#endif
372376
/*
373377
* Grab the global mutex. This prevents
@@ -439,8 +443,10 @@ kevent(int kqfd,
439443
*/
440444
#ifndef _WIN32
441445
(void)pthread_setcancelstate(prev_cancel_state, NULL);
442-
if (prev_cancel_state == PTHREAD_CANCEL_ENABLE)
446+
if (prev_cancel_state == PTHREAD_CANCEL_ENABLE) {
447+
dbg_printf("Checking for deferred cancellations");
443448
pthread_testcancel();
449+
}
444450
#endif
445451
rv = kqops.kevent_wait(kq, nevents, timeout);
446452
#ifndef _WIN32
@@ -482,16 +488,23 @@ kevent(int kqfd,
482488

483489
out:
484490
#ifndef _WIN32
485-
pthread_cleanup_pop(0);
491+
/*
492+
* Test for cancellations first, so we don't
493+
* double unlock the kqueue.
494+
*/
495+
pthread_setcancelstate(prev_cancel_state, NULL);
496+
if (prev_cancel_state == PTHREAD_CANCEL_ENABLE) {
497+
dbg_printf("Checking for deferred cancellations");
498+
pthread_testcancel();
499+
}
486500
#endif
487-
kqueue_unlock(kq);
488-
dbg_printf("--- END kevent %u ret %d ---", myid, rv);
489501

490502
#ifndef _WIN32
491-
pthread_setcancelstate(prev_cancel_state, NULL);
492-
if (prev_cancel_state == PTHREAD_CANCEL_ENABLE)
493-
pthread_testcancel();
503+
pthread_cleanup_pop(0);
494504
#endif
495505

506+
kqueue_unlock(kq);
507+
dbg_printf("--- END kevent %u ret %d ---", myid, rv);
508+
496509
return (rv);
497510
}

src/common/kqueue.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ libkqueue_parent_fork(void)
190190
if (!libkqueue_fork_cleanup_active)
191191
return;
192192

193+
dbg_puts("resuming execution in parent");
194+
193195
tracing_mutex_unlock(&kq_mtx);
194196
}
195197

@@ -359,7 +361,7 @@ kqueue(void)
359361
#endif
360362

361363
#ifndef _WIN32
362-
prev_cancel_state = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
364+
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &prev_cancel_state);
363365
#endif
364366
kq = calloc(1, sizeof(*kq));
365367
if (kq == NULL)

src/common/private.h

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,10 @@ struct evfilt_data;
7878
*
7979
*/
8080
#ifndef LIST_FOREACH_SAFE
81-
#define LIST_FOREACH_SAFE(var, head, field, tvar) \
82-
for ((var) = LIST_FIRST((head)); \
83-
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
84-
(var) = (tvar))
81+
#define LIST_FOREACH_SAFE(var, head, field, tvar) \
82+
for ((var) = LIST_FIRST((head)); \
83+
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
84+
(var) = (tvar))
8585
#endif
8686

8787
/** Convenience macros
@@ -666,8 +666,16 @@ extern unsigned int kq_cnt;
666666
* kqueue internal API
667667
*/
668668
#define kqueue_mutex_assert(kq, state) tracing_mutex_assert(&(kq)->kq_mtx, state)
669-
#define kqueue_lock(kq) tracing_mutex_lock(&(kq)->kq_mtx)
670-
#define kqueue_unlock(kq) tracing_mutex_unlock(&(kq)->kq_mtx)
669+
670+
#define kqueue_lock(kq) do { \
671+
dbg_printf("locking kq=%p", kq); \
672+
tracing_mutex_lock(&(kq)->kq_mtx); \
673+
} while(0)
674+
675+
#define kqueue_unlock(kq) do { \
676+
dbg_printf("unlocking kq=%p", kq); \
677+
tracing_mutex_unlock(&(kq)->kq_mtx); \
678+
} while(0)
671679

672680
/*
673681
* knote internal API

test/libkqueue.c

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1515
*/
1616
#include "common.h"
17+
#include <time.h>
1718

1819
#ifdef EVFILT_LIBKQUEUE
1920
static void
@@ -52,10 +53,84 @@ test_libkqueue_version_str(struct test_context *ctx)
5253
}
5354
}
5455

56+
static void *
57+
test_libkqueue_fork_no_hang_thread(void *arg)
58+
{
59+
struct test_context *ctx = arg;
60+
struct kevent receipt;
61+
62+
/*
63+
* We shouldn't ever wait for 10 seconds...
64+
*/
65+
if (kevent(ctx->kqfd, NULL, 0, &receipt, 1, &(struct timespec){ .tv_sec = 10 }) > 0) {
66+
printf("Failed waiting...\n");
67+
die("kevent - waiting");
68+
}
69+
70+
printf("Shouldn't have hit timeout, expected to be cancelled\n");
71+
die("kevent - timeout");
72+
73+
return NULL;
74+
}
75+
76+
static void
77+
test_libkqueue_fork_no_hang(struct test_context *ctx)
78+
{
79+
struct kevent kev, receipt;
80+
pthread_t thread;
81+
time_t start, end;
82+
pid_t child;
83+
84+
start = time(NULL);
85+
86+
/*
87+
* Create a new thread
88+
*/
89+
if (pthread_create(&thread, NULL, test_libkqueue_fork_no_hang_thread, ctx) < 0)
90+
die("kevent");
91+
92+
printf("Created test_libkqueue_fork_no_hang_thread [%u]\n", (unsigned int)thread);
93+
94+
/*
95+
* We don't know when the thread will start
96+
* listening on the kqueue, so we just
97+
* deschedule ourselves for 10ms and hope...
98+
*/
99+
nanosleep(&(struct timespec){ .tv_nsec = 10000000}, NULL);
100+
101+
/*
102+
* Test that we can fork... The child exits
103+
* immediately, we're just check that we _can_
104+
* fork().
105+
*/
106+
#if 0
107+
child = fork();
108+
if (child == 0) {
109+
testing_end_quiet();
110+
exit(EXIT_SUCCESS);
111+
}
112+
113+
printf("Forked child [%u]\n", (unsigned int)child);
114+
#endif
115+
116+
/*
117+
* This also tests proper behaviour of kqueues
118+
* on cancellation.
119+
*/
120+
if (pthread_cancel(thread) < 0)
121+
die("pthread_cancel");
122+
123+
if ((time(NULL) - start) > 5) {
124+
printf("Thread hung instead of being cancelled");
125+
die("kevent");
126+
}
127+
}
128+
55129
void
56130
test_evfilt_libkqueue(struct test_context *ctx)
57131
{
58132
test(libkqueue_version, ctx);
59133
test(libkqueue_version_str, ctx);
134+
test(libkqueue_fork_no_hang, ctx);
60135
}
61136
#endif

0 commit comments

Comments
 (0)