Skip to content

Commit 24f20f0

Browse files
committed
Add test for hang on fork
1 parent 11ed7a2 commit 24f20f0

File tree

4 files changed

+113
-17
lines changed

4 files changed

+113
-17
lines changed

src/common/kevent.c

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -331,9 +331,11 @@ 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+
kqueue_unlock(kq);
337339
}
338340
#endif
339341

@@ -367,7 +369,7 @@ kevent(int kqfd,
367369
if (!changelist) changelist = null_kev;
368370

369371
#ifndef _WIN32
370-
prev_cancel_state = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
372+
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &prev_cancel_state);
371373
#endif
372374
/*
373375
* Grab the global mutex. This prevents
@@ -439,8 +441,10 @@ kevent(int kqfd,
439441
*/
440442
#ifndef _WIN32
441443
(void)pthread_setcancelstate(prev_cancel_state, NULL);
442-
if (prev_cancel_state == PTHREAD_CANCEL_ENABLE)
444+
if (prev_cancel_state == PTHREAD_CANCEL_ENABLE) {
445+
dbg_printf("Checking for deferred cancellations");
443446
pthread_testcancel();
447+
}
444448
#endif
445449
rv = kqops.kevent_wait(kq, nevents, timeout);
446450
#ifndef _WIN32
@@ -482,16 +486,23 @@ kevent(int kqfd,
482486

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

490500
#ifndef _WIN32
491-
pthread_setcancelstate(prev_cancel_state, NULL);
492-
if (prev_cancel_state == PTHREAD_CANCEL_ENABLE)
493-
pthread_testcancel();
501+
pthread_cleanup_pop(0);
494502
#endif
495503

504+
kqueue_unlock(kq);
505+
dbg_printf("--- END kevent %u ret %d ---", myid, rv);
506+
496507
return (rv);
497508
}

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)