Skip to content
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

v4 tx poll #239

Merged
merged 15 commits into from
Dec 9, 2024
124 changes: 103 additions & 21 deletions MIGRATION_v3.x_to_v4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,24 @@ These changes do not affect wire compatibility.
```
- **Usage**: After popping a transmission queue item using `canardTxPop`, use `canardTxFree` to deallocate its memory.

- **`canardTxPoll`**:
- **Description**: A helper function simplifies the transmission process by combining frame retrieval, transmission, and cleanup into a single function.
- **Prototype**:
```c
int8_t canardTxPoll(
struct CanardTxQueue* const que,
const struct CanardInstance* const ins,
const CanardMicrosecond now_usec,
void* const user_reference,
const CanardTxFrameHandler frame_handler
```
- **Purpose**: Streamlines the process of handling frames from the TX queue.
- **Functionality**:
- Retrieves the next frame to be transmitted.
- Invokes a user-provided `frame_handler` to transmit the frame.
- Manages frame cleanup based on the handler's return value.
- Automatically drops timed-out frames if `now_usec` is provided.

### Modified Functions

Several functions have updated prototypes and usage patterns:
Expand All @@ -49,7 +67,7 @@ Several functions have updated prototypes and usage patterns:
- **Changes**:
- Replaces `CanardMemoryAllocate` and `CanardMemoryFree` function pointers with a `CanardMemoryResource` struct.

2. **`canardTxInit`**:
1. **`canardTxInit`**:
- **Old Prototype**:
```c
CanardTxQueue canardTxInit(
Expand All @@ -66,7 +84,7 @@ Several functions have updated prototypes and usage patterns:
- **Changes**:
- Adds a `CanardMemoryResource` parameter for memory allocation of payload data.

3. **`canardTxPush`**:
1. **`canardTxPush`**:
- **Old Prototype**:
```c
int32_t canardTxPush(
Expand All @@ -84,12 +102,17 @@ Several functions have updated prototypes and usage patterns:
const struct CanardInstance* const ins,
const CanardMicrosecond tx_deadline_usec,
const struct CanardTransferMetadata* const metadata,
const struct CanardPayload payload);
const struct CanardPayload payload,
const CanardMicrosecond now_usec);
```
- **Changes**:
- Replaces `payload_size` and `payload` with a single `CanardPayload` struct.
- Adds a `now_usec` parameter for handling timed-out frames.
- **Purpose**: Allows the library to automatically drop frames that have exceeded their transmission deadlines (`tx_deadline_usec`).
- **Behavior**: If `now_usec` is greater than `tx_deadline_usec`, the frames already in the TX queue will be dropped, and the `dropped_frames` counter in `CanardTxQueueStats` will be incremented.
- **Optional Feature**: Passing `0` for `now_usec` disables automatic dropping, maintaining previous behavior.

4. **`canardTxPeek`** and **`canardTxPop`**:
1. **`canardTxPeek`** and **`canardTxPop`**:
- The functions now return and accept pointers to mutable `struct CanardTxQueueItem` instead of const pointers.

### Removed Functions
Expand Down Expand Up @@ -122,6 +145,16 @@ Several functions have updated prototypes and usage patterns:
struct CanardInstance { ... };
```

- **Function pointers**:
- **Added** `CanardTxFrameHandler` function pointer type.
```c
typedef int8_t (*CanardTxFrameHandler)(
void* const user_reference,
const CanardMicrosecond deadline_usec,
struct CanardMutableFrame* const frame
);
```

### Struct Modifications

- **`CanardFrame`**:
Expand Down Expand Up @@ -152,6 +185,7 @@ Several functions have updated prototypes and usage patterns:

- **`CanardTxQueue`**:
- Includes a `CanardMemoryResource` for payload data allocation.
- Includes a `CanardTxQueueStats` for tracking number of dropped frames.

- **`CanardMemoryResource`** and **`CanardMemoryDeleter`**:
- New structs to encapsulate memory allocation and deallocation functions along with user references.
Expand All @@ -177,17 +211,37 @@ Several functions have updated prototypes and usage patterns:
void* const pointer);
```

## Automatic Dropping of Timed-Out Frames

#### Description

Frames in the TX queue that have exceeded their `tx_deadline_usec` can now be automatically dropped when `now_usec` is provided to `canardTxPush()` or `canardTxPoll()`.

- **Benefit**: Reduces the need for manual management of timed-out frames.
serges147 marked this conversation as resolved.
Show resolved Hide resolved
- **Optional**: Feature can be disabled by passing `0` for `now_usec`.

#### Migration Steps

1. **Enable or Disable Automatic Dropping**:

- **Enable**: Provide the current time to `now_usec` in both `canardTxPush()` and `canardTxPoll()`.
- **Disable**: Pass `0` to `now_usec` to retain manual control.

2. **Adjust Application Logic**:

- Monitor the `dropped_frames` counter in `CanardTxQueueStats` if tracking of dropped frames is required.

## Migration Steps

1. **Update Type Definitions**:
- Replace all `typedef`-based enum and struct types with direct `struct` and `enum` declarations.
- For example, change `CanardInstance` to `struct CanardInstance`.

2. **Adjust Memory Management Code**:
1. **Adjust Memory Management Code**:
- Replace separate memory allocation and deallocation function pointers with `CanardMemoryResource` and `CanardMemoryDeleter` structs.
- Update function calls and definitions accordingly.

3. **Modify Function Calls**:
1. **Modify Function Calls**:
- Update all function calls to match the new prototypes.
- **`canardInit`**:
- Before:
Expand Down Expand Up @@ -223,30 +277,58 @@ Several functions have updated prototypes and usage patterns:
.size = payload_size,
.data = payload
};
canardTxPush(que, ins, tx_deadline_usec, metadata, payload_struct);
canardTxPush(que, ins, tx_deadline_usec, metadata, payload_struct, now_usec);
```

4. **Handle New Functions**:
1. **Handle New Functions**:
- Use `canardTxFree` to deallocate transmission queue items after popping them.
- Example:
```c
struct CanardTxQueueItem* item = canardTxPeek(&tx_queue);
while (item != NULL) {
// Transmit the frame...
canardTxPop(&tx_queue, item);
canardTxFree(&tx_queue, &canard_instance, item);
item = canardTxPeek(&tx_queue);
}
```
- Example:
```c
struct CanardTxQueueItem* item = canardTxPeek(&tx_queue);
while (item != NULL) {
// Transmit the frame...
canardTxPop(&tx_queue, item);
canardTxFree(&tx_queue, &canard_instance, item);
item = canardTxPeek(&tx_queue);
}
```

5. **Update Struct Field Access**:
- If currently using `canardTxPeek()`, `canardTxPop()`, and `canardTxFree()`, consider replacing that logic with `canardTxPoll()` for simplicity.
- Define a function matching the `CanardTxFrameHandler` signature:
```c
int8_t myFrameHandler(
void* const user_reference,
const CanardMicrosecond deadline_usec,
struct CanardMutableFrame* frame
) {
// Implement transmission logic here
// Return positive value on success - the frame will be released
// Return zero to retry later - the frame will stay in the TX queue
// Return negative value on failure - whole transfer (including this frame) will be dropped
}
```
- Example:
```c
// Before
struct CanardTxQueueItem* item = canardTxPeek(queue);
if (item != NULL) {
// Handle deadline
// Transmit item->frame
item = canardTxPop(queue, item);
canardTxFree(queue, instance, item);
}

// After
int8_t result = canardTxPoll(queue, instance, now_usec, user_reference, myFrameHandler);
```
1. **Update Struct Field Access**:
- Adjust your code to access struct fields directly, considering the changes in struct definitions.
- For example, access `payload.size` instead of `payload_size`.

6. **Adjust Memory Allocation Logic**:
1. **Adjust Memory Allocation Logic**:
- Ensure that your memory allocation and deallocation functions conform to the new prototypes.
- Pay attention to the additional `size` parameter in the deallocation function.

7. **Test Thoroughly**:
1. **Test Thoroughly**:
- After making the changes, thoroughly test your application to ensure that it functions correctly with the new library version.
- Pay special attention to memory management and potential leaks.
94 changes: 77 additions & 17 deletions libcanard/canard.c
Original file line number Diff line number Diff line change
Expand Up @@ -592,32 +592,52 @@ CANARD_PRIVATE int32_t txPushMultiFrame(struct CanardTxQueue* const que,
return out;
}

CANARD_PRIVATE void txPopAndFreeTransfer(struct CanardTxQueue* const que,
const struct CanardInstance* const ins,
struct CanardTxQueueItem* const tx_item,
const bool drop_whole_transfer)
serges147 marked this conversation as resolved.
Show resolved Hide resolved
{
struct CanardTxQueueItem* next_tx_item = tx_item;
struct CanardTxQueueItem* tx_item_to_free = canardTxPop(que, next_tx_item);
while (NULL != tx_item_to_free)
{
next_tx_item = tx_item_to_free->next_in_transfer;
canardTxFree(que, ins, tx_item_to_free);

if (!drop_whole_transfer)
{
break;
}
que->stats.dropped_frames++;

tx_item_to_free = canardTxPop(que, next_tx_item);
}
}

/// Flushes expired transfers by comparing deadline timestamps of the pending transfers with the current time.
CANARD_PRIVATE void txFlushExpiredTransfers(struct CanardTxQueue* const que,
const struct CanardInstance* const ins,
const CanardMicrosecond now_usec)
{
serges147 marked this conversation as resolved.
Show resolved Hide resolved
struct CanardTxQueueItem* tx_item = NULL;
while (NULL != (tx_item = MUTABLE_CONTAINER_OF( //
struct CanardTxQueueItem,
cavlFindExtremum(que->deadline_root, false),
deadline_base)))
struct CanardTxQueueItem* tx_item = MUTABLE_CONTAINER_OF( //
struct CanardTxQueueItem,
cavlFindExtremum(que->deadline_root, false),
deadline_base);
while (NULL != tx_item)
{
if (now_usec <= tx_item->tx_deadline_usec)
{
// The queue is sorted by deadline, so we can stop here.
break;
}

// All frames of the transfer are released at once b/c they all have the same deadline.
struct CanardTxQueueItem* tx_item_to_free = NULL;
while (NULL != (tx_item_to_free = canardTxPop(que, tx_item)))
{
tx_item = tx_item_to_free->next_in_transfer;
canardTxFree(que, ins, tx_item_to_free);
// All frames of the transfer are dropped at once b/c they all have the same deadline.
txPopAndFreeTransfer(que, ins, tx_item, true); // drop the whole transfer

que->stats.dropped_frames++;
}
tx_item = MUTABLE_CONTAINER_OF( //
struct CanardTxQueueItem,
cavlFindExtremum(que->deadline_root, false),
deadline_base);
}
}

Expand Down Expand Up @@ -1157,8 +1177,6 @@ int32_t canardTxPush(struct CanardTxQueue* const que,
txFlushExpiredTransfers(que, ins, now_usec);
}

(void) now_usec;

int32_t out = -CANARD_ERROR_INVALID_ARGUMENT;
if ((ins != NULL) && (que != NULL) && (metadata != NULL) && ((payload.data != NULL) || (0U == payload.size)))
{
Expand Down Expand Up @@ -1202,8 +1220,6 @@ struct CanardTxQueueItem* canardTxPeek(const struct CanardTxQueue* const que)
struct CanardTxQueueItem* out = NULL;
if (que != NULL)
{
// 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.
struct CanardTreeNode* const priority_node = cavlFindExtremum(que->priority_root, false);
out = MUTABLE_CONTAINER_OF(struct CanardTxQueueItem, priority_node, priority_base);
}
Expand Down Expand Up @@ -1242,6 +1258,50 @@ void canardTxFree(struct CanardTxQueue* const que,
}
}

int8_t canardTxPoll(struct CanardTxQueue* const que,
const struct CanardInstance* const ins,
const CanardMicrosecond now_usec,
void* const user_reference,
const CanardTxFrameHandler frame_handler)
{
serges147 marked this conversation as resolved.
Show resolved Hide resolved
int8_t out = 0;

// Before peeking a frame to transmit, we need to try to flush any expired transfers.
// This will not only ensure ASAP freeing of the queue capacity, but also makes sure that the following
// `canardTxPeek` will return a not expired item (if any), so we don't need to check the deadline again.
// The flushing is done by comparing deadline timestamps of the pending transfers with the current time,
// which makes sense only if the current time is known (bigger than zero).
if (now_usec > 0)
{
txFlushExpiredTransfers(que, ins, now_usec);
}

if (frame_handler != NULL)
{
struct CanardTxQueueItem* const tx_item = canardTxPeek(que);
if (tx_item != NULL)
{
// No need to check the deadline again, as we have already flushed all expired transfers.
out = frame_handler(user_reference, tx_item->tx_deadline_usec, &tx_item->frame);

// We gonna release (pop and free) the frame if the handler returned:
// - either a positive value - the frame has been successfully accepted by the handler;
// - or a negative value - the frame has been rejected by the handler due to a failure.
// Zero value means that the handler cannot accept the frame at the moment, so we keep it in the queue.
if (out != 0)
{
// In case of a failure, it makes sense to drop the whole transfer immediately
// b/c at least this frame has been rejected, so the whole transfer is useless.
const bool drop_whole_transfer = (out < 0);
txPopAndFreeTransfer(que, ins, tx_item, drop_whole_transfer);
serges147 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

CANARD_ASSERT(out <= 1);
return out;
}

int8_t canardRxAccept(struct CanardInstance* const ins,
const CanardMicrosecond timestamp_usec,
const struct CanardFrame* const frame,
Expand Down
Loading
Loading