-
-
Notifications
You must be signed in to change notification settings - Fork 195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
introduce deadline queue #sonar #238
Conversation
libcanard/canard.c
Outdated
@@ -326,18 +332,39 @@ CANARD_PRIVATE struct CanardTxQueueItem* txAllocateQueueItem(struct CanardTxQueu | |||
return out; | |||
} | |||
|
|||
#define CONTAINER_OF(type, ptr, member) ((type*) (((ptr) == NULL) ? NULL : ((char*) (ptr) - offsetof(type, member)))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be near the top. Also, there is risk of this expression accidentally casting away a const qualifier on the pointer. Maybe we should have const and mutable editions of this macro, where the const one would use const type*
and const char*
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should have const and mutable editions of this macro, where the const one would use const type* and const char*?
Good point - I will move it to the top, and make two versions: CONTAINER_OF
& CONST_CONTAINER_OF
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Usually we assume immutability by default, like with the payload containers, for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean to have CONTAINER_OF
(const version) and MUTABLE_CONTAINER_OF
(mutable version) ?
libcanard/canard.h
Outdated
/// Holds the statistics of a transmission queue. | ||
struct CanardTxQueueStats | ||
{ | ||
/// Holds number of dropped TX frames (due to timeout when `now > deadline`). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not just timeout. If the media fails to accept a frame, we discard it, right? Along with every other frame in the transfer chain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Such business logic was out of lizard scope. Yes, at libcyphal I did it as you described. At flux_grip and telega I'm not 100% sure, but it seems that there is no notion of error there - just boolean either accepted or not accepted ifc->push
. If it was not accepted then it stays in the queue - for retry purposes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but if we are designing a template method ("template" in the sense of design patterns, not type-generic programming), then it makes sense to support fallible media, right?
Quality Gate failedFailed conditions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The migration guide requires updating.
@@ -502,7 +519,9 @@ struct CanardTxQueue canardTxInit(const size_t capacity, | |||
/// frames (so all frames will have the same timestamp value). This feature is intended to facilitate transmission | |||
/// deadline tracking, i.e., aborting frames that could not be transmitted before the specified deadline. | |||
/// Therefore, normally, the timestamp value should be in the future. | |||
/// The library itself, however, does not use or check this value in any way, so it can be zero if not needed. | |||
/// The library uses `now > deadline` comparison to determine which frames timed out, and so could |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think here we could use a clarification that we're talking about frames that were enqueued earlier, not the ones that are added by this call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
already fixed in the next pr branch (sshirokov/v4_tx_poll
)
@@ -306,6 +306,13 @@ struct CanardMemoryResource | |||
CanardMemoryAllocate allocate; ///< Shall be a valid pointer. | |||
}; | |||
|
|||
/// Holds the statistics of a transmission queue. | |||
struct CanardTxQueueStats |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we name it just CanardStats, with the possibility of future extension with other metrics? Then dropped_frames
becomes tx_dropped_frames
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, if you have redundant media, you can see such stats per TX queue. If we move CanardTxQueueStats
out of CanardTxQueue
(to the CanaradInstance
I assume as more generic CanardStats
is proposed) then we will loose such distinction.
Also, under Udpard there is no such thing as "UdpardInstance" as you know of course - my point is that under Udpard we have to keep it per TX queue (I assume that we want similar improvements at Udpard as well, namely: two trees, flush on push, udpardTxPoll
).
So, I propose to keep it as I did. The only thing what maybe could be valueable is to split dropped_frames
to two separate counters:
dropped_expired_frames
- incremented by thecanardTxPush
andcanardTxPoll
for each expired frame.dropped_failed_frames
- incremented by the newcanardTxPoll
when frame handler returns negative result.
Currently both cases increment the very samedropped_frames
.
@@ -1114,7 +1204,8 @@ struct CanardTxQueueItem* canardTxPeek(const struct CanardTxQueue* const que) | |||
{ | |||
// Paragraph 6.7.2.1.15 of the C standard says: | |||
// A pointer to a structure object, suitably converted, points to its initial member, and vice versa. | |||
out = (struct CanardTxQueueItem*) (void*) cavlFindExtremum(que->root, false); | |||
struct CanardTreeNode* const priority_node = cavlFindExtremum(que->priority_root, false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment above (citing the standard) is no longer applicable so it should be removed.
const CanardMicrosecond now_usec) | ||
{ | ||
struct CanardTxQueueItem* tx_item = NULL; | ||
while (NULL != (tx_item = MUTABLE_CONTAINER_OF( // |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assignment in an expression violates some MISRA C rule whose number I forgot (ChatGPT can tell you which one); please split this into the longer form.
break; | ||
} | ||
|
||
// All frames of the transfer are released at once b/c they all have the same deadline. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need this special case here? All frames in a transfer have the same timeout (IIRC it is mentioned in the docs, too), so either all or none will be kept even if you don't include this special case. On the other hand, one might argue that the current solution is more robust because it doesn't make assumptions about timeout equality. So maybe we do need this special case, after all.
There is also a related question: do we really need to locate the extremum at every iteration? This looks like an avoidable Shliemel the painter problem. What we have right now is good enough but perhaps an explanatory comment is warranted, saying that it should be possible to avoid extremum call at every iteration by simply fetching the neighboring node from the current one before deleting it. Obviously this will only work if we rely on the assumption that the timeout is the same for all frames in the transfer (otherwise the loop below will invalidate the tree linkage).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that such flushing is more like for robustness and "self healing" for a special corner case, namely when media stopped to work for some time (f.e. stopped to answer/callback at all), whole TX capacity exhausted, then media recovered but it won't be engaged/triggered anymore b/c we cannot push anything new due to full capacity. Flushing by timeout will resolve such deadlock.
really need to locate the extremum
I think it is okey, especially if you think about nominal use case in which there will be only one call to cavlFindExtremum
, it will return NULL
(b/c normally everything properly transmitted before deadline), and that is it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While the current solution is acceptable, I must point out that in real-time programming, one should focus not only on the general case but also on the worst case.
Yes, it will be done in the next pr when new |
No description provided.