Skip to content

Commit 7d314cf

Browse files
committed
Add tests for cross-thread user events, and for closing kqueues resulting in waits being cancelled
1 parent a83d56a commit 7d314cf

5 files changed

Lines changed: 132 additions & 0 deletions

File tree

test/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1515
#
16+
cmake_minimum_required(VERSION 3.7.2)
17+
1618
project(libkqueue-test LANGUAGES C)
1719

1820
set(LIBKQUEUE_TEST_SOURCES
@@ -23,6 +25,7 @@ set(LIBKQUEUE_TEST_SOURCES
2325
proc.c
2426
read.c
2527
test.c
28+
threading.c
2629
timer.c
2730
user.c
2831
vnode.c

test/common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ void test_evfilt_proc(struct test_context *);
119119
void test_evfilt_user(struct test_context *);
120120
#endif
121121
void test_evfilt_libkqueue(struct test_context *);
122+
void test_threading(struct test_context *);
122123

123124
#define test(f, ctx ,...) do { \
124125
if ((ctx->test->ut_num >= ctx->test->ut_start) && (ctx->test->ut_num <= ctx->test->ut_end)) {\

test/main.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ main(int argc, char **argv)
186186
.ut_end = INT_MAX },
187187
{ NULL, 0, NULL },
188188
#endif
189+
{ .ut_name = "threading",
190+
.ut_enabled = 1,
191+
.ut_func = test_threading,
192+
.ut_end = INT_MAX },
189193
};
190194
struct unit_test *test;
191195
int c, i, iterations;

test/threading.c

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright (c) 2025 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
3+
*
4+
* Permission to use, copy, modify, and distribute this software for any
5+
* purpose with or without fee is hereby granted, provided that the above
6+
* copyright notice and this permission notice appear in all copies.
7+
*
8+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9+
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10+
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11+
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12+
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13+
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14+
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15+
*/
16+
17+
#include "common.h"
18+
19+
struct trigger_args {
20+
int kqfd;
21+
uintptr_t ident;
22+
};
23+
24+
static void *
25+
close_kqueue(void *arg)
26+
{
27+
/* Sleep until we're fairly sure the other thread is waiting */
28+
sleep(1);
29+
30+
/* Close the waiting kqueue from this thread */
31+
if (close(*((int *)arg)) != 0)
32+
die("close failed");
33+
34+
return NULL;
35+
}
36+
37+
static void
38+
test_kevent_threading_close(struct test_context *ctx)
39+
{
40+
struct kevent kev, ret[1];
41+
pthread_t th;
42+
int kqfd = kqueue();
43+
44+
/* Add the event, then trigger it from another thread */
45+
kevent_add(kqfd, &kev, 1, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, NULL);
46+
47+
if (pthread_create(&th, NULL, close_kqueue, &kqfd) != 0)
48+
die("failed creating thread");
49+
50+
/* Wait for event (should be interrupted by close) */
51+
if (kevent(kqfd, NULL, 0, ret, 1, NULL) == -1) {
52+
if (errno != EBADF)
53+
die("kevent failed with the wrong errno");
54+
} else {
55+
die("kevent did not fail");
56+
}
57+
58+
/* Subsequent calls should also fail with EBADF */
59+
if (kevent(kqfd, NULL, 0, ret, 1, NULL) == -1) {
60+
if (errno != EBADF)
61+
die("kevent failed with the wrong errno (second call)");
62+
} else {
63+
die("kevent did not fail (second call)");
64+
}
65+
66+
if (pthread_join(th, NULL) != 0)
67+
die("pthread_join failed");
68+
}
69+
70+
void
71+
test_threading(struct test_context *ctx)
72+
{
73+
test(kevent_threading_close, ctx);
74+
}

test/user.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,55 @@ test_kevent_user_dispatch(struct test_context *ctx)
176176
}
177177
#endif /* EV_DISPATCH */
178178

179+
struct trigger_args {
180+
int kqfd;
181+
uintptr_t ident;
182+
};
183+
184+
static void *
185+
trigger_user_event_thread(void *arg)
186+
{
187+
struct trigger_args *ta = arg;
188+
struct kevent tmp;
189+
190+
/* Trigger the user event from this thread */
191+
kevent_add(ta->kqfd, &tmp, ta->ident, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL);
192+
return NULL;
193+
}
194+
195+
static void
196+
test_kevent_user_trigger_from_thread(struct test_context *ctx)
197+
{
198+
struct kevent kev, ret[1];
199+
pthread_t th;
200+
struct trigger_args args;
201+
202+
test_no_kevents(ctx->kqfd);
203+
204+
/* Use a distinct ident to avoid confusion with other tests */
205+
args.kqfd = ctx->kqfd;
206+
args.ident = 3;
207+
208+
/* Add the event, then trigger it from another thread */
209+
kevent_add(ctx->kqfd, &kev, args.ident, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, NULL);
210+
211+
if (pthread_create(&th, NULL, trigger_user_event_thread, &args) != 0)
212+
die("failed creating thread");
213+
214+
if (pthread_join(th, NULL) != 0)
215+
die("pthread_join failed");
216+
217+
/* Prepare expected event (mask out control bits) */
218+
kev.fflags &= ~NOTE_FFCTRLMASK;
219+
kev.fflags &= ~NOTE_TRIGGER;
220+
221+
/* Fetch and compare */
222+
kevent_get(ret, NUM_ELEMENTS(ret), ctx->kqfd, 1);
223+
kevent_cmp(&kev, ret);
224+
225+
test_no_kevents(ctx->kqfd);
226+
}
227+
179228
void
180229
test_evfilt_user(struct test_context *ctx)
181230
{
@@ -188,5 +237,6 @@ test_evfilt_user(struct test_context *ctx)
188237
#ifdef EV_DISPATCH
189238
test(kevent_user_dispatch, ctx);
190239
#endif
240+
test(kevent_user_trigger_from_thread, ctx);
191241
/* TODO: try different fflags operations */
192242
}

0 commit comments

Comments
 (0)