Skip to content

Commit 14f61d5

Browse files
mctpd: timerfd based timers
Replace all sd-event timers with timerfd-like interfaces for testability. On release builds, those uses real timerfds. On test builds, those are backed by MockClocks. autojump_clock is ultilized to skip through all the Trio time related wait, reducing test time. Because we still have real timeouts (D-Bus call to mctpd subprocess, mctpd SO_RCVTIMEO wait, ...), autojump_threshold (how much to wait before skipping Trio timeouts) is set to a reasonable value to take that into account. Tested: Only tested with tests. Real timerfd backend is entirely untested. Signed-off-by: Khang D Nguyen <[email protected]>
1 parent 9c68dab commit 14f61d5

File tree

8 files changed

+232
-22
lines changed

8 files changed

+232
-22
lines changed

src/mctp-ops.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#define _GNU_SOURCE
99

10+
#include <sys/time.h>
11+
#include <sys/timerfd.h>
1012
#include <unistd.h>
1113
#include <linux/netlink.h>
1214
#include <err.h>
@@ -57,6 +59,58 @@ static void mctp_bug_warn(const char *fmt, va_list args)
5759
vwarnx(fmt, args);
5860
}
5961

62+
static inline struct itimerspec itimespec_from_usec(uint64_t u)
63+
{
64+
return (struct itimerspec){
65+
.it_interval = { 0 },
66+
.it_value.tv_sec = u / 1000000,
67+
.it_value.tv_nsec = (u % 1000000) * 1000,
68+
};
69+
}
70+
static inline uint64_t usec_from_itimespec(struct itimerspec spec)
71+
{
72+
return (spec.it_value.tv_sec * 1000000) +
73+
(spec.it_value.tv_nsec / 1000);
74+
}
75+
76+
static int mctp_op_timerfd_create()
77+
{
78+
return timerfd_create(CLOCK_MONOTONIC, 0);
79+
}
80+
81+
static int mctp_op_timerfd_settime(int fd, uint64_t new_value,
82+
uint64_t *old_value)
83+
{
84+
struct itimerspec new = itimespec_from_usec(new_value);
85+
struct itimerspec old;
86+
int rc;
87+
88+
rc = timerfd_settime(fd, 0, &new, &old);
89+
if (rc < 0)
90+
return rc;
91+
92+
*old_value = usec_from_itimespec(old);
93+
94+
return 0;
95+
}
96+
97+
static int mctp_op_timerfd_read(int fd, uint64_t *n)
98+
{
99+
ssize_t rc;
100+
101+
rc = read(fd, n, sizeof(*n));
102+
if (rc < 0) {
103+
return rc;
104+
}
105+
106+
if (rc != sizeof(*n)) {
107+
warnx("timerfd read EPROTO");
108+
return -1;
109+
}
110+
111+
return 0;
112+
}
113+
60114
const struct mctp_ops mctp_ops = {
61115
.mctp = {
62116
.socket = mctp_op_mctp_socket,
@@ -74,6 +128,11 @@ const struct mctp_ops mctp_ops = {
74128
.recvfrom = mctp_op_recvfrom,
75129
.close = mctp_op_close,
76130
},
131+
.timerfd = {
132+
.create = mctp_op_timerfd_create,
133+
.settime = mctp_op_timerfd_settime,
134+
.read = mctp_op_timerfd_read,
135+
},
77136
.bug_warn = mctp_bug_warn,
78137
};
79138

src/mctp-ops.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
*/
88
#pragma once
99

10+
#include <stdint.h>
1011
#include <sys/socket.h>
1112
#include <stdarg.h>
13+
#include <sys/timerfd.h>
1214

1315
#define _GNU_SOURCE
1416

@@ -24,9 +26,16 @@ struct socket_ops {
2426
int (*close)(int sd);
2527
};
2628

29+
struct timerfd_ops {
30+
int (*create)();
31+
int (*settime)(int fd, uint64_t new_value, uint64_t *old_value);
32+
int (*read)(int fd, uint64_t *n);
33+
};
34+
2735
struct mctp_ops {
2836
struct socket_ops mctp;
2937
struct socket_ops nl;
38+
struct timerfd_ops timerfd;
3039
void (*bug_warn)(const char *fmt, va_list args);
3140
};
3241

src/mctpd.c

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3079,12 +3079,13 @@ static int method_endpoint_remove(sd_bus_message *call, void *data,
30793079
(MCTP_I2C_TSYM_MT1_MAX_US + 2 * MCTP_I2C_TSYM_MT3_MAX_US)
30803080
#define MCTP_I2C_TSYM_MT2_MAX_MS MCTP_I2C_TSYM_MT4_MIN_US
30813081

3082-
static int peer_endpoint_recover(sd_event_source *s, uint64_t usec,
3082+
static int peer_endpoint_recover(sd_event_source *s, int timerfd, uint revents,
30833083
void *userdata)
30843084
{
30853085
struct peer *peer = userdata;
30863086
struct ctx *ctx = peer->ctx;
30873087
const char *peer_path;
3088+
uint64_t n_expireds;
30883089
int rc;
30893090

30903091
/*
@@ -3099,6 +3100,12 @@ static int peer_endpoint_recover(sd_event_source *s, uint64_t usec,
30993100

31003101
peer->recovery.npolls--;
31013102

3103+
// flush the timerfd
3104+
rc = mctp_ops.timerfd.read(timerfd, &n_expireds);
3105+
if (rc < 0) {
3106+
goto reschedule;
3107+
}
3108+
31023109
/*
31033110
* Test if we still have connectivity to the endpoint. If we do, we will get a
31043111
* response reporting the current EID. This is the test recommended by 8.17.6
@@ -3194,8 +3201,7 @@ static int peer_endpoint_recover(sd_event_source *s, uint64_t usec,
31943201

31953202
reschedule:
31963203
if (peer->recovery.npolls > 0) {
3197-
rc = sd_event_source_set_time_relative(peer->recovery.source,
3198-
peer->recovery.delay);
3204+
rc = mctp_ops.timerfd.settime(timerfd, peer->recovery.delay, NULL);
31993205
if (rc >= 0) {
32003206
rc = sd_event_source_set_enabled(peer->recovery.source,
32013207
SD_EVENT_ONESHOT);
@@ -3217,11 +3223,13 @@ static int method_endpoint_recover(sd_bus_message *call, void *data,
32173223
struct peer *peer;
32183224
bool previously;
32193225
struct ctx *ctx;
3226+
int timerfd;
32203227
int rc;
32213228

32223229
peer = data;
32233230
ctx = peer->ctx;
32243231
previously = peer->degraded;
3232+
timerfd = -1;
32253233

32263234
if (!previously) {
32273235
assert(!peer->recovery.delay);
@@ -3230,13 +3238,38 @@ static int method_endpoint_recover(sd_bus_message *call, void *data,
32303238
peer->recovery.npolls = MCTP_I2C_TSYM_MN1_MIN + 1;
32313239
peer->recovery.delay =
32323240
(MCTP_I2C_TSYM_TRECLAIM_MIN_US / 2) - ctx->mctp_timeout;
3233-
rc = sd_event_add_time_relative(
3234-
ctx->event, &peer->recovery.source, CLOCK_MONOTONIC, 0,
3235-
ctx->mctp_timeout, peer_endpoint_recover, peer);
3241+
3242+
timerfd = mctp_ops.timerfd.create();
3243+
if (timerfd < 0) {
3244+
rc = -errno;
3245+
goto out;
3246+
}
3247+
3248+
rc = mctp_ops.timerfd.settime(timerfd, ctx->mctp_timeout, NULL);
3249+
if (rc < 0) {
3250+
goto out;
3251+
}
3252+
3253+
rc = sd_event_add_io(ctx->event, &peer->recovery.source,
3254+
timerfd, EPOLLIN, peer_endpoint_recover,
3255+
peer);
3256+
if (rc < 0) {
3257+
goto out;
3258+
}
3259+
3260+
rc = sd_event_source_set_enabled(peer->recovery.source,
3261+
SD_EVENT_ONESHOT);
32363262
if (rc < 0) {
32373263
goto out;
32383264
}
32393265

3266+
rc = sd_event_source_set_io_fd_own(peer->recovery.source, true);
3267+
if (rc < 0) {
3268+
goto out;
3269+
}
3270+
// prevent double close, now that sd_event_source own the fd
3271+
timerfd = -1;
3272+
32403273
peer->degraded = true;
32413274

32423275
rc = sd_bus_emit_properties_changed(
@@ -3253,12 +3286,8 @@ static int method_endpoint_recover(sd_bus_message *call, void *data,
32533286

32543287
out:
32553288
if (rc < 0 && !previously) {
3256-
if (peer->degraded) {
3257-
/* Cleanup the timer if it was setup successfully. */
3258-
sd_event_source_set_enabled(peer->recovery.source,
3259-
SD_EVENT_OFF);
3260-
sd_event_source_unref(peer->recovery.source);
3261-
}
3289+
sd_event_source_disable_unref(peer->recovery.source);
3290+
close(timerfd);
32623291
peer->degraded = previously;
32633292
peer->recovery.delay = 0;
32643293
peer->recovery.source = NULL;

tests/conftest.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

22
import sys
3+
import math
34

45
import pytest
56
import asyncdbus
@@ -25,7 +26,14 @@ def config():
2526
return None
2627

2728
@pytest.fixture
28-
async def mctpd(nursery, dbus, sysnet, config):
29+
async def mctpd(nursery, dbus, sysnet, config, autojump_clock):
30+
# some tests ultilize autojump_clock, which might interfere with
31+
# the mctpd initialization NameOwnerChanged D-Bus call timeout
32+
#
33+
# initialize autojump_clock parameter to normal value here,
34+
# then tests can override their own reasonable values
35+
autojump_clock.rate = 1
36+
autojump_clock.autojump_threshold = math.inf
2937
m = mctpenv.MctpdWrapper(dbus, sysnet, config = config)
3038
await m.start_mctpd(nursery)
3139
yield m

tests/mctp-ops-test.c

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#define _GNU_SOURCE
99

10+
#include <assert.h>
1011
#include <err.h>
1112
#include <errno.h>
1213
#include <stdlib.h>
@@ -38,10 +39,12 @@ static int mctp_op_socket(int type)
3839
struct iovec iov;
3940
int rc, var, sd;
4041

41-
if (type == AF_MCTP)
42+
if (type == CONTROL_OP_SOCKET_MCTP)
4243
req.type = CONTROL_OP_SOCKET_MCTP;
43-
else if (type == AF_NETLINK)
44+
else if (type == CONTROL_OP_SOCKET_NL)
4445
req.type = CONTROL_OP_SOCKET_NL;
46+
else if (type == CONTROL_OP_TIMER)
47+
req.type = CONTROL_OP_TIMER;
4548
else
4649
errx(EXIT_FAILURE, "invalid socket type?");
4750

@@ -72,12 +75,12 @@ static int mctp_op_socket(int type)
7275

7376
static int mctp_op_mctp_socket(void)
7477
{
75-
return mctp_op_socket(AF_MCTP);
78+
return mctp_op_socket(CONTROL_OP_SOCKET_MCTP);
7679
}
7780

7881
static int mctp_op_netlink_socket(void)
7982
{
80-
return mctp_op_socket(AF_NETLINK);
83+
return mctp_op_socket(CONTROL_OP_SOCKET_NL);
8184
}
8285

8386
static int mctp_op_bind(int sd, struct sockaddr *addr, socklen_t addrlen)
@@ -227,6 +230,55 @@ static void mctp_bug_warn(const char *fmt, va_list args)
227230
abort();
228231
}
229232

233+
static int mctp_op_timerfd_create()
234+
{
235+
return mctp_op_socket(CONTROL_OP_TIMER);
236+
}
237+
238+
static int mctp_op_timerfd_settime(int fd, uint64_t new_value,
239+
uint64_t *old_value)
240+
{
241+
uint64_t tmp;
242+
ssize_t rc;
243+
244+
rc = write(fd, &new_value, sizeof(new_value));
245+
if (rc < 0)
246+
return rc;
247+
248+
// Maybe handle partial read?
249+
if ((size_t)rc < sizeof(new_value))
250+
errx(EXIT_FAILURE, "ops protocol error");
251+
252+
rc = read(fd, &tmp, sizeof(tmp));
253+
if (rc < 0)
254+
return rc;
255+
256+
if ((size_t)rc < sizeof(tmp))
257+
errx(EXIT_FAILURE, "ops protocol error");
258+
259+
if (old_value)
260+
*old_value = tmp;
261+
262+
return 0;
263+
}
264+
265+
static int mctp_op_timerfd_read(int fd, uint64_t *n)
266+
{
267+
ssize_t rc;
268+
uint64_t v;
269+
270+
rc = read(fd, &v, sizeof(v));
271+
if (rc < 0)
272+
return rc;
273+
274+
if ((size_t)rc < sizeof(v) || v != 0)
275+
errx(EXIT_FAILURE, "ops protocol error");
276+
277+
*n = 1;
278+
279+
return 0;
280+
}
281+
230282
const struct mctp_ops mctp_ops = {
231283
.mctp = {
232284
.socket = mctp_op_mctp_socket,
@@ -244,6 +296,11 @@ const struct mctp_ops mctp_ops = {
244296
.recvfrom = mctp_op_recvfrom,
245297
.close = mctp_op_close,
246298
},
299+
.timerfd = {
300+
.create = mctp_op_timerfd_create,
301+
.settime = mctp_op_timerfd_settime,
302+
.read = mctp_op_timerfd_read,
303+
},
247304
.bug_warn = mctp_bug_warn,
248305
};
249306

0 commit comments

Comments
 (0)