Skip to content

Commit 50bd5a0

Browse files
kkdwivediborkmann
authored andcommitted
selftests/bpf: Add timer lockup selftest
Add a selftest that tries to trigger a situation where two timer callbacks are attempting to cancel each other's timer. By running them continuously, we hit a condition where both run in parallel and cancel each other. Without the fix in the previous patch, this would cause a lockup as hrtimer_cancel on either side will wait for forward progress from the callback. Ensure that this situation leads to a EDEADLK error. Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> Link: https://lore.kernel.org/bpf/20240711052709.2148616-1-memxor@gmail.com
1 parent 0c23734 commit 50bd5a0

File tree

2 files changed

+178
-0
lines changed

2 files changed

+178
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
#define _GNU_SOURCE
4+
#include <sched.h>
5+
#include <test_progs.h>
6+
#include <pthread.h>
7+
#include <network_helpers.h>
8+
9+
#include "timer_lockup.skel.h"
10+
11+
static long cpu;
12+
static int *timer1_err;
13+
static int *timer2_err;
14+
static bool skip;
15+
16+
volatile int k = 0;
17+
18+
static void *timer_lockup_thread(void *arg)
19+
{
20+
LIBBPF_OPTS(bpf_test_run_opts, opts,
21+
.data_in = &pkt_v4,
22+
.data_size_in = sizeof(pkt_v4),
23+
.repeat = 1000,
24+
);
25+
int i, prog_fd = *(int *)arg;
26+
cpu_set_t cpuset;
27+
28+
CPU_ZERO(&cpuset);
29+
CPU_SET(__sync_fetch_and_add(&cpu, 1), &cpuset);
30+
ASSERT_OK(pthread_setaffinity_np(pthread_self(), sizeof(cpuset),
31+
&cpuset),
32+
"cpu affinity");
33+
34+
for (i = 0; !READ_ONCE(*timer1_err) && !READ_ONCE(*timer2_err); i++) {
35+
bpf_prog_test_run_opts(prog_fd, &opts);
36+
/* Skip the test if we can't reproduce the race in a reasonable
37+
* amount of time.
38+
*/
39+
if (i > 50) {
40+
WRITE_ONCE(skip, true);
41+
break;
42+
}
43+
}
44+
45+
return NULL;
46+
}
47+
48+
void test_timer_lockup(void)
49+
{
50+
int timer1_prog, timer2_prog;
51+
struct timer_lockup *skel;
52+
pthread_t thrds[2];
53+
void *ret;
54+
55+
skel = timer_lockup__open_and_load();
56+
if (!ASSERT_OK_PTR(skel, "timer_lockup__open_and_load"))
57+
return;
58+
59+
timer1_prog = bpf_program__fd(skel->progs.timer1_prog);
60+
timer2_prog = bpf_program__fd(skel->progs.timer2_prog);
61+
62+
timer1_err = &skel->bss->timer1_err;
63+
timer2_err = &skel->bss->timer2_err;
64+
65+
if (!ASSERT_OK(pthread_create(&thrds[0], NULL, timer_lockup_thread,
66+
&timer1_prog),
67+
"pthread_create thread1"))
68+
goto out;
69+
if (!ASSERT_OK(pthread_create(&thrds[1], NULL, timer_lockup_thread,
70+
&timer2_prog),
71+
"pthread_create thread2")) {
72+
pthread_exit(&thrds[0]);
73+
goto out;
74+
}
75+
76+
pthread_join(thrds[1], &ret);
77+
pthread_join(thrds[0], &ret);
78+
79+
if (skip) {
80+
test__skip();
81+
goto out;
82+
}
83+
84+
if (*timer1_err != -EDEADLK && *timer1_err != 0)
85+
ASSERT_FAIL("timer1_err bad value");
86+
if (*timer2_err != -EDEADLK && *timer2_err != 0)
87+
ASSERT_FAIL("timer2_err bad value");
88+
out:
89+
timer_lockup__destroy(skel);
90+
return;
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
#include <linux/bpf.h>
4+
#include <time.h>
5+
#include <errno.h>
6+
#include <bpf/bpf_helpers.h>
7+
#include <bpf/bpf_tracing.h>
8+
#include "bpf_misc.h"
9+
10+
char _license[] SEC("license") = "GPL";
11+
12+
struct elem {
13+
struct bpf_timer t;
14+
};
15+
16+
struct {
17+
__uint(type, BPF_MAP_TYPE_ARRAY);
18+
__uint(max_entries, 1);
19+
__type(key, int);
20+
__type(value, struct elem);
21+
} timer1_map SEC(".maps");
22+
23+
struct {
24+
__uint(type, BPF_MAP_TYPE_ARRAY);
25+
__uint(max_entries, 1);
26+
__type(key, int);
27+
__type(value, struct elem);
28+
} timer2_map SEC(".maps");
29+
30+
int timer1_err;
31+
int timer2_err;
32+
33+
static int timer_cb1(void *map, int *k, struct elem *v)
34+
{
35+
struct bpf_timer *timer;
36+
int key = 0;
37+
38+
timer = bpf_map_lookup_elem(&timer2_map, &key);
39+
if (timer)
40+
timer2_err = bpf_timer_cancel(timer);
41+
42+
return 0;
43+
}
44+
45+
static int timer_cb2(void *map, int *k, struct elem *v)
46+
{
47+
struct bpf_timer *timer;
48+
int key = 0;
49+
50+
timer = bpf_map_lookup_elem(&timer1_map, &key);
51+
if (timer)
52+
timer1_err = bpf_timer_cancel(timer);
53+
54+
return 0;
55+
}
56+
57+
SEC("tc")
58+
int timer1_prog(void *ctx)
59+
{
60+
struct bpf_timer *timer;
61+
int key = 0;
62+
63+
timer = bpf_map_lookup_elem(&timer1_map, &key);
64+
if (timer) {
65+
bpf_timer_init(timer, &timer1_map, CLOCK_BOOTTIME);
66+
bpf_timer_set_callback(timer, timer_cb1);
67+
bpf_timer_start(timer, 1, BPF_F_TIMER_CPU_PIN);
68+
}
69+
70+
return 0;
71+
}
72+
73+
SEC("tc")
74+
int timer2_prog(void *ctx)
75+
{
76+
struct bpf_timer *timer;
77+
int key = 0;
78+
79+
timer = bpf_map_lookup_elem(&timer2_map, &key);
80+
if (timer) {
81+
bpf_timer_init(timer, &timer2_map, CLOCK_BOOTTIME);
82+
bpf_timer_set_callback(timer, timer_cb2);
83+
bpf_timer_start(timer, 1, BPF_F_TIMER_CPU_PIN);
84+
}
85+
86+
return 0;
87+
}

0 commit comments

Comments
 (0)