Skip to content

Commit 3fa2dd3

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 <khangng@os.amperecomputing.com>
1 parent ef0d2dc commit 3fa2dd3

File tree

8 files changed

+248
-25
lines changed

8 files changed

+248
-25
lines changed

src/mctp-ops.c

Lines changed: 64 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,63 @@ 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+
if (old_value)
93+
*old_value = usec_from_itimespec(old);
94+
95+
return 0;
96+
}
97+
98+
static int mctp_op_timerfd_read(int fd, uint64_t *n)
99+
{
100+
uint64_t tmp;
101+
ssize_t rc;
102+
103+
rc = read(fd, &tmp, sizeof(tmp));
104+
if (rc < 0) {
105+
return rc;
106+
}
107+
108+
if (rc != sizeof(tmp)) {
109+
warnx("timerfd read EPROTO");
110+
return -1;
111+
}
112+
113+
if (n)
114+
*n = tmp;
115+
116+
return 0;
117+
}
118+
60119
const struct mctp_ops mctp_ops = {
61120
.mctp = {
62121
.socket = mctp_op_mctp_socket,
@@ -74,6 +133,11 @@ const struct mctp_ops mctp_ops = {
74133
.recvfrom = mctp_op_recvfrom,
75134
.close = mctp_op_close,
76135
},
136+
.timerfd = {
137+
.create = mctp_op_timerfd_create,
138+
.settime = mctp_op_timerfd_settime,
139+
.read = mctp_op_timerfd_read,
140+
},
77141
.bug_warn = mctp_bug_warn,
78142
};
79143

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: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -478,9 +478,11 @@ static int cb_exit_loop_io(sd_event_source *s, int fd, uint32_t revents,
478478
return 0;
479479
}
480480

481-
static int cb_exit_loop_timeout(sd_event_source *s, uint64_t usec,
481+
static int cb_exit_loop_timeout(sd_event_source *s, int timerfd, uint revents,
482482
void *userdata)
483483
{
484+
// flush the timerfd
485+
mctp_ops.timerfd.read(timerfd, NULL);
484486
sd_event_exit(sd_event_source_get_event(s), -ETIMEDOUT);
485487
return 0;
486488
}
@@ -490,15 +492,28 @@ static int cb_exit_loop_timeout(sd_event_source *s, uint64_t usec,
490492
static int wait_fd_timeout(int fd, short events, uint64_t timeout_usec)
491493
{
492494
int rc;
495+
int timerfd = -1;
493496
sd_event *ev = NULL;
494497

495498
// Create a new event loop just for the event+timeout
496499
rc = sd_event_new(&ev);
497500
if (rc < 0)
498501
goto out;
499502

500-
rc = sd_event_add_time_relative(ev, NULL, CLOCK_MONOTONIC, timeout_usec,
501-
0, cb_exit_loop_timeout, NULL);
503+
timerfd = mctp_ops.timerfd.create();
504+
if (timerfd < 0) {
505+
rc = -errno;
506+
goto out;
507+
}
508+
509+
rc = mctp_ops.timerfd.settime(timerfd, timeout_usec, NULL);
510+
if (rc < 0) {
511+
rc = -errno;
512+
goto out;
513+
}
514+
515+
rc = sd_event_add_io(ev, NULL, timerfd, EPOLLIN, cb_exit_loop_timeout,
516+
NULL);
502517
if (rc < 0)
503518
goto out;
504519

@@ -510,8 +525,8 @@ static int wait_fd_timeout(int fd, short events, uint64_t timeout_usec)
510525
rc = sd_event_loop(ev);
511526

512527
out:
513-
if (ev)
514-
sd_event_unref(ev);
528+
sd_event_unref(ev);
529+
close(timerfd);
515530
return rc;
516531
}
517532

@@ -3115,7 +3130,7 @@ static int method_endpoint_remove(sd_bus_message *call, void *data,
31153130
(MCTP_I2C_TSYM_MT1_MAX_US + 2 * MCTP_I2C_TSYM_MT3_MAX_US)
31163131
#define MCTP_I2C_TSYM_MT2_MAX_MS MCTP_I2C_TSYM_MT4_MIN_US
31173132

3118-
static int peer_endpoint_recover(sd_event_source *s, uint64_t usec,
3133+
static int peer_endpoint_recover(sd_event_source *s, int timerfd, uint revents,
31193134
void *userdata)
31203135
{
31213136
struct peer *peer = userdata;
@@ -3135,6 +3150,12 @@ static int peer_endpoint_recover(sd_event_source *s, uint64_t usec,
31353150

31363151
peer->recovery.npolls--;
31373152

3153+
// flush the timerfd
3154+
rc = mctp_ops.timerfd.read(timerfd, NULL);
3155+
if (rc < 0) {
3156+
goto reschedule;
3157+
}
3158+
31383159
/*
31393160
* Test if we still have connectivity to the endpoint. If we do, we will get a
31403161
* response reporting the current EID. This is the test recommended by 8.17.6
@@ -3230,8 +3251,8 @@ static int peer_endpoint_recover(sd_event_source *s, uint64_t usec,
32303251

32313252
reschedule:
32323253
if (peer->recovery.npolls > 0) {
3233-
rc = sd_event_source_set_time_relative(peer->recovery.source,
3234-
peer->recovery.delay);
3254+
rc = mctp_ops.timerfd.settime(timerfd, peer->recovery.delay,
3255+
NULL);
32353256
if (rc >= 0) {
32363257
rc = sd_event_source_set_enabled(peer->recovery.source,
32373258
SD_EVENT_ONESHOT);
@@ -3253,11 +3274,13 @@ static int method_endpoint_recover(sd_bus_message *call, void *data,
32533274
struct peer *peer;
32543275
bool previously;
32553276
struct ctx *ctx;
3277+
int timerfd;
32563278
int rc;
32573279

32583280
peer = data;
32593281
ctx = peer->ctx;
32603282
previously = peer->degraded;
3283+
timerfd = -1;
32613284

32623285
if (!previously) {
32633286
assert(!peer->recovery.delay);
@@ -3266,13 +3289,39 @@ static int method_endpoint_recover(sd_bus_message *call, void *data,
32663289
peer->recovery.npolls = MCTP_I2C_TSYM_MN1_MIN + 1;
32673290
peer->recovery.delay =
32683291
(MCTP_I2C_TSYM_TRECLAIM_MIN_US / 2) - ctx->mctp_timeout;
3269-
rc = sd_event_add_time_relative(
3270-
ctx->event, &peer->recovery.source, CLOCK_MONOTONIC, 0,
3271-
ctx->mctp_timeout, peer_endpoint_recover, peer);
3292+
3293+
timerfd = mctp_ops.timerfd.create();
3294+
if (timerfd < 0) {
3295+
rc = -errno;
3296+
goto out;
3297+
}
3298+
3299+
rc = mctp_ops.timerfd.settime(timerfd, ctx->mctp_timeout, NULL);
3300+
if (rc < 0) {
3301+
rc = -errno;
3302+
goto out;
3303+
}
3304+
3305+
rc = sd_event_add_io(ctx->event, &peer->recovery.source,
3306+
timerfd, EPOLLIN, peer_endpoint_recover,
3307+
peer);
32723308
if (rc < 0) {
32733309
goto out;
32743310
}
32753311

3312+
rc = sd_event_source_set_enabled(peer->recovery.source,
3313+
SD_EVENT_ONESHOT);
3314+
if (rc < 0) {
3315+
goto out;
3316+
}
3317+
3318+
rc = sd_event_source_set_io_fd_own(peer->recovery.source, true);
3319+
if (rc < 0) {
3320+
goto out;
3321+
}
3322+
// prevent double close, now that sd_event_source own the fd
3323+
timerfd = -1;
3324+
32763325
peer->degraded = true;
32773326

32783327
rc = sd_bus_emit_properties_changed(
@@ -3289,12 +3338,8 @@ static int method_endpoint_recover(sd_bus_message *call, void *data,
32893338

32903339
out:
32913340
if (rc < 0 && !previously) {
3292-
if (peer->degraded) {
3293-
/* Cleanup the timer if it was setup successfully. */
3294-
sd_event_source_set_enabled(peer->recovery.source,
3295-
SD_EVENT_OFF);
3296-
sd_event_source_unref(peer->recovery.source);
3297-
}
3341+
sd_event_source_disable_unref(peer->recovery.source);
3342+
close(timerfd);
32983343
peer->degraded = previously;
32993344
peer->recovery.delay = 0;
33003345
peer->recovery.source = NULL;

tests/conftest.py

Lines changed: 1 addition & 0 deletions
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

tests/mctp-ops-test.c

Lines changed: 62 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)
@@ -221,6 +224,56 @@ static void mctp_bug_warn(const char *fmt, va_list args)
221224
abort();
222225
}
223226

227+
static int mctp_op_timerfd_create()
228+
{
229+
return mctp_op_socket(CONTROL_OP_TIMER);
230+
}
231+
232+
static int mctp_op_timerfd_settime(int fd, uint64_t new_value,
233+
uint64_t *old_value)
234+
{
235+
uint64_t tmp;
236+
ssize_t rc;
237+
238+
rc = write(fd, &new_value, sizeof(new_value));
239+
if (rc < 0)
240+
return rc;
241+
242+
// Maybe handle partial read?
243+
if ((size_t)rc < sizeof(new_value))
244+
errx(EXIT_FAILURE, "ops protocol error");
245+
246+
rc = read(fd, &tmp, sizeof(tmp));
247+
if (rc < 0)
248+
return rc;
249+
250+
if ((size_t)rc < sizeof(tmp))
251+
errx(EXIT_FAILURE, "ops protocol error");
252+
253+
if (old_value)
254+
*old_value = tmp;
255+
256+
return 0;
257+
}
258+
259+
static int mctp_op_timerfd_read(int fd, uint64_t *n)
260+
{
261+
ssize_t rc;
262+
uint64_t v;
263+
264+
rc = read(fd, &v, sizeof(v));
265+
if (rc < 0)
266+
return rc;
267+
268+
if ((size_t)rc < sizeof(v) || v != 0)
269+
errx(EXIT_FAILURE, "ops protocol error");
270+
271+
if (n)
272+
*n = 1;
273+
274+
return 0;
275+
}
276+
224277
const struct mctp_ops mctp_ops = {
225278
.mctp = {
226279
.socket = mctp_op_mctp_socket,
@@ -238,6 +291,11 @@ const struct mctp_ops mctp_ops = {
238291
.recvfrom = mctp_op_recvfrom,
239292
.close = mctp_op_close,
240293
},
294+
.timerfd = {
295+
.create = mctp_op_timerfd_create,
296+
.settime = mctp_op_timerfd_settime,
297+
.read = mctp_op_timerfd_read,
298+
},
241299
.bug_warn = mctp_bug_warn,
242300
};
243301

0 commit comments

Comments
 (0)