Skip to content

Commit ced4076

Browse files
update expiration handling
1 parent 048d77a commit ced4076

File tree

4 files changed

+55
-46
lines changed

4 files changed

+55
-46
lines changed

libcanard/canard.c

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -398,10 +398,11 @@ void canard_refcount_dec(canard_t* const self, const canard_bytes_t obj)
398398
// hence it would not affect the ordering.
399399
#define CAN_ID_MSb_BITS (29U - 7U)
400400

401-
// The struct is manually packed to ensure it fits into a 128-byte O1Heap block in common embedded configurations.
401+
// The struct must fit into a 128-byte O1Heap block in common embedded configurations.
402402
struct canard_txfer_t
403403
{
404404
canard_tree_t index_pending[CANARD_IFACE_COUNT];
405+
canard_tree_t index_deadline;
405406
canard_listed_t list_agewise;
406407

407408
// Constant transfer properties supplied by the client.
@@ -418,8 +419,7 @@ struct canard_txfer_t
418419
// Application context.
419420
canard_user_context_t user_context;
420421
};
421-
static_assert((CANARD_IFACE_COUNT > 2) || (sizeof(void*) > 4) || (sizeof(void (*)(void)) > 4) ||
422-
(sizeof(canard_txfer_t) <= 120),
422+
static_assert((CANARD_IFACE_COUNT > 2) || (sizeof(void*) > 4) || (sizeof(canard_txfer_t) <= 120),
423423
"On a 32-bit platform with a half-fit heap, the TX transfer object should fit in a 128-byte block");
424424

425425
static canard_txfer_t* txfer_new(canard_t* const self,
@@ -434,12 +434,13 @@ static canard_txfer_t* txfer_new(canard_t* const self,
434434
FOREACH_IFACE (i) {
435435
tr->index_pending[i] = TREE_NULL;
436436
}
437-
tr->list_agewise = LIST_NULL;
438-
tr->deadline = deadline;
439-
tr->seqno = self->tx.seqno++;
440-
tr->transfer_id = transfer_id & CANARD_TRANSFER_ID_MAX;
441-
tr->can_id_msb = (can_id_template >> (29U - CAN_ID_MSb_BITS)) & ((1U << CAN_ID_MSb_BITS) - 1U);
442-
tr->fd = fd ? 1U : 0U;
437+
tr->index_deadline = TREE_NULL;
438+
tr->list_agewise = LIST_NULL;
439+
tr->deadline = deadline;
440+
tr->seqno = self->tx.seqno++;
441+
tr->transfer_id = transfer_id & CANARD_TRANSFER_ID_MAX;
442+
tr->can_id_msb = (can_id_template >> (29U - CAN_ID_MSb_BITS)) & ((1U << CAN_ID_MSb_BITS) - 1U);
443+
tr->fd = fd ? 1U : 0U;
443444
FOREACH_IFACE (i) {
444445
tr->head[i] = NULL;
445446
tr->cursor[i] = NULL;
@@ -449,17 +450,11 @@ static canard_txfer_t* txfer_new(canard_t* const self,
449450
return tr;
450451
}
451452

452-
static canard_prio_t txfer_prio(const canard_txfer_t* const tr)
453-
{
454-
return (canard_prio_t)((((unsigned)tr->can_id_msb) >> (CAN_ID_MSb_BITS - 3U)) & 7U);
455-
}
456-
457453
static bool txfer_is_pending(const canard_t* const self, const canard_txfer_t* const tr)
458454
{
459455
FOREACH_IFACE (i) {
460456
if (cavl2_is_inserted(self->tx.pending[i], &tr->index_pending[i])) {
461-
CANARD_ASSERT(tr->head[i] != NULL);
462-
CANARD_ASSERT(tr->cursor[i] != NULL);
457+
CANARD_ASSERT((tr->head[i] != NULL) && (tr->cursor[i] != NULL));
463458
return true;
464459
}
465460
}
@@ -491,6 +486,16 @@ static int32_t tx_cavl_compare_pending_order(const void* const user, const canar
491486
return (lhs->seqno < rhs->seqno) ? -1 : +1; // clang-format on
492487
}
493488

489+
// Soonest to expire (smallest deadline) on the left, then smaller seqno on the left.
490+
static int32_t tx_cavl_compare_deadline(const void* const user, const canard_tree_t* const node)
491+
{
492+
const canard_txfer_t* const lhs = (const canard_txfer_t*)user;
493+
const canard_txfer_t* const rhs = CAVL2_TO_OWNER(node, canard_txfer_t, index_deadline); // clang-format off
494+
if (lhs->deadline < rhs->deadline) { return -1; }
495+
if (lhs->deadline > rhs->deadline) { return +1; }
496+
return (lhs->seqno < rhs->seqno) ? -1 : +1; // clang-format on
497+
}
498+
494499
static void tx_make_pending(canard_t* const self, canard_txfer_t* const tr)
495500
{
496501
FOREACH_IFACE (i) { // Enqueue for transmission unless it's there already (stalled interface?)
@@ -507,15 +512,12 @@ static void tx_make_pending(canard_t* const self, canard_txfer_t* const tr)
507512
// Retire one transfer and release its resources.
508513
static void txfer_retire(canard_t* const self, canard_txfer_t* const tr)
509514
{
510-
if (self->tx.iter == tr) {
511-
self->tx.iter = LIST_NEXT(tr, canard_txfer_t, list_agewise); // May be NULL, is OK.
512-
}
513515
FOREACH_IFACE (i) {
514516
(void)cavl2_remove_if(&self->tx.pending[i], &tr->index_pending[i]);
515517
}
518+
CANARD_ASSERT(cavl2_is_inserted(self->tx.deadline, &tr->index_deadline));
519+
cavl2_remove(&self->tx.deadline, &tr->index_deadline);
516520
delist(&self->tx.agewise, &tr->list_agewise);
517-
518-
// Free the memory. The payload memory may already be empty depending on where we were invoked from.
519521
txfer_free_payload(self, tr);
520522
mem_free(self->mem.tx_transfer, sizeof(canard_txfer_t), tr);
521523
}
@@ -714,6 +716,18 @@ static size_t tx_predict_frame_count(const size_t transfer_size, const size_t mt
714716
return ((transfer_size + CRC_SIZE_BYTES + bytes_per_frame) - 1U) / bytes_per_frame; // rounding up
715717
}
716718

719+
static void tx_expire(canard_t* const self, const canard_us_t now)
720+
{
721+
canard_txfer_t* tr = CAVL2_TO_OWNER(cavl2_min(self->tx.deadline), canard_txfer_t, index_deadline);
722+
while ((tr != NULL) && (now > tr->deadline)) {
723+
canard_txfer_t* const tr_next =
724+
CAVL2_TO_OWNER(cavl2_next_greater(&tr->index_deadline), canard_txfer_t, index_deadline);
725+
txfer_retire(self, tr);
726+
self->err.tx_expiration++;
727+
tr = tr_next;
728+
}
729+
}
730+
717731
// Enqueues a transfer for transmission.
718732
static bool tx_push(canard_t* const self,
719733
canard_txfer_t* const tr,
@@ -726,6 +740,11 @@ static bool tx_push(canard_t* const self,
726740
CANARD_ASSERT((!tr->fd) || !v0); // The caller must ensure this.
727741
CANARD_ASSERT(iface_bitmap != 0);
728742

743+
const canard_us_t now = self->vtable->now(self);
744+
745+
// Expire old transfers first to free up queue space.
746+
tx_expire(self, now);
747+
729748
// Ensure the queue has enough space. v0 transfers always use Classic CAN regardless of tr->fd.
730749
const size_t mtu = tr->fd ? CANARD_MTU_CAN_FD : CANARD_MTU_CAN_CLASSIC;
731750
const size_t size = bytes_chain_size(payload);
@@ -770,6 +789,10 @@ static bool tx_push(canard_t* const self,
770789
}
771790

772791
// Register the transfer and schedule for transmission.
792+
const canard_tree_t* const deadline_tree = cavl2_find_or_insert(
793+
&self->tx.deadline, tr, tx_cavl_compare_deadline, &tr->index_deadline, cavl2_trivial_factory);
794+
CANARD_ASSERT(deadline_tree == &tr->index_deadline);
795+
(void)deadline_tree;
773796
enlist_tail(&self->tx.agewise, &tr->list_agewise);
774797
tx_make_pending(self, tr);
775798
return true;
@@ -781,21 +804,6 @@ static canard_txfer_t* tx_pending_node_to_transfer(const canard_tree_t* const no
781804
node, offsetof(canard_txfer_t, index_pending) + (((size_t)iface_index) * sizeof(canard_tree_t)));
782805
}
783806

784-
static void tx_expire_iterative(canard_t* const self, const canard_us_t now)
785-
{
786-
if (self->tx.iter == NULL) {
787-
self->tx.iter = LIST_HEAD(self->tx.agewise, canard_txfer_t, list_agewise);
788-
}
789-
if (self->tx.iter != NULL) {
790-
canard_txfer_t* const tr = self->tx.iter;
791-
self->tx.iter = LIST_NEXT(tr, canard_txfer_t, list_agewise);
792-
if (now > tr->deadline) {
793-
txfer_retire(self, tr);
794-
self->err.tx_expiration++;
795-
}
796-
}
797-
}
798-
799807
static void tx_eject_pending(canard_t* const self, const byte_t iface_index)
800808
{
801809
while (true) {
@@ -838,8 +846,9 @@ static void tx_eject_pending(canard_t* const self, const byte_t iface_index)
838846
void canard_poll(canard_t* const self, const uint_least8_t tx_ready_iface_bitmap)
839847
{
840848
if (self != NULL) {
841-
tx_expire_iterative(self, self->vtable->now(self)); // deadline maintenance first to keep queue pressure bounded
842-
FOREACH_IFACE (i) { // submit queued frames through all currently writable interfaces
849+
const canard_us_t now = self->vtable->now(self);
850+
tx_expire(self, now); // deadline maintenance first to keep queue pressure bounded
851+
FOREACH_IFACE (i) { // submit queued frames through all currently writable interfaces
843852
if ((tx_ready_iface_bitmap & (1U << i)) != 0U) {
844853
tx_eject_pending(self, (byte_t)i);
845854
}

libcanard/canard.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ struct canard_subscription_t
235235
typedef struct canard_vtable_t
236236
{
237237
/// The current monotonic time in microseconds. Must be a non-negative non-decreasing value.
238-
canard_us_t (*now)(canard_t*);
238+
canard_us_t (*now)(const canard_t*);
239239

240240
/// A new unicast message is received.
241241
/// The handler takes ownership of the payload; it must free it after use using the corresponding memory resource.
@@ -261,6 +261,7 @@ typedef struct canard_vtable_t
261261

262262
/// Reconfigure the acceptance filters of the CAN controller hardware.
263263
/// The prior configuration, if any, is replaced entirely.
264+
/// filter_count is guaranteed to not exceed the value given at initialization.
264265
/// Returns true on success, false if the filters could not be applied; another attempt will be made later.
265266
bool (*filter)(canard_t*, size_t filter_count, const canard_filter_t* filters);
266267
} canard_vtable_t;
@@ -299,10 +300,9 @@ struct canard_t
299300
/// Incremented with every enqueued transfer. Used internally but also works as a stats counter.
300301
uint64_t seqno;
301302

302-
canard_tree_t* pending[CANARD_IFACE_COUNT]; ///< Next to transmit at the head.
303+
canard_tree_t* pending[CANARD_IFACE_COUNT]; ///< Next to transmit on the left.
304+
canard_tree_t* deadline; ///< Soonest to expire on the left.
303305
canard_list_t agewise; ///< ALL transfers FIFO, oldest at the head.
304-
305-
canard_txfer_t* iter; ///< For iterative poll() scanning; NULL to restart.
306306
} tx;
307307

308308
struct

tests/src/test_api_tx.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ static void* std_alloc_mem(const canard_mem_t, const size_t size) { return std::
2020
static void std_free_mem(const canard_mem_t, const size_t, void* const pointer) { std::free(pointer); }
2121

2222
// Minimal callbacks for canard_new().
23-
static canard_us_t mock_now(canard_t* const) { return 0; }
23+
static canard_us_t mock_now(const canard_t* const) { return 0; }
2424
static bool mock_tx(canard_t* const,
2525
const canard_user_context_t,
2626
const canard_us_t,
@@ -59,9 +59,9 @@ struct tx_capture_t
5959
std::array<tx_record_t, 32> records;
6060
};
6161

62-
static tx_capture_t* capture_from(canard_t* const self) { return static_cast<tx_capture_t*>(self->user_context); }
62+
static tx_capture_t* capture_from(const canard_t* const self) { return static_cast<tx_capture_t*>(self->user_context); }
6363

64-
static canard_us_t capture_now(canard_t* const self) { return capture_from(self)->now; }
64+
static canard_us_t capture_now(const canard_t* const self) { return capture_from(self)->now; }
6565

6666
static bool capture_tx(canard_t* const self,
6767
const canard_user_context_t,

tests/src/test_intrusive_tx.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ typedef struct
1212
} test_context_t;
1313

1414
// Monotonic time callback.
15-
static canard_us_t mock_now(canard_t* const self)
15+
static canard_us_t mock_now(const canard_t* const self)
1616
{
1717
const test_context_t* const ctx = (const test_context_t*)self->user_context;
1818
return (ctx != NULL) ? ctx->now : 0;

0 commit comments

Comments
 (0)