tighten transport failure semantics

This commit is contained in:
Jay
2026-08-17 17:33:44 -04:00
parent 3c95f3d7be
commit 73e5680fc3
11 changed files with 144 additions and 29 deletions
+14 -5
View File
@@ -168,6 +168,7 @@ static void write_binary_packet_until_sent(
size_t packet_size)
{
while (true) {
const bool was_pending = sender->pending;
const trikke_transport_status_t status =
trikke_transport_sender_step(
sender, &context->transport, packet, packet_size);
@@ -175,12 +176,20 @@ static void write_binary_packet_until_sent(
return;
}
if (status == TRIKKE_TRANSPORT_FATAL) {
// Preserve the in-flight packet and stop consuming the queue. A
// fatal backend invariant is not safely recoverable or retryable.
// The binary stream is terminal at this point, so a final text
// diagnostic cannot corrupt later frames. Preserve the packet and
// stop consuming the sample queue.
esp_log_level_set(TAG, ESP_LOG_ERROR);
ESP_LOGE(TAG, "fatal transport invariant; output task suspended");
while (true) {
vTaskDelay(portMAX_DELAY);
vTaskSuspend(NULL);
}
}
if (status == TRIKKE_TRANSPORT_PENDING && !was_pending) {
// Poll once immediately after acceptance. Subsequent pending polls
// are paced so a future nonblocking backend cannot busy-spin.
continue;
}
vTaskDelay(pdMS_TO_TICKS(TRIKKE_TRANSPORT_RETRY_DELAY_MS));
}
}
@@ -349,11 +358,11 @@ void app_main(void)
s_context.sample_queue =
xQueueCreate(TRIKKE_SAMPLE_QUEUE_DEPTH, sizeof(trikke_wire_sample_t));
if (s_context.sample_queue == NULL) {
trikke_usb_transport_deinit(&s_context.usb_transport);
ESP_LOGE(TAG, "sample queue allocation failed");
l3g4200d_deinit(&s_context.gyroscope);
adxl345_deinit(&s_context.accelerometer);
i2c_del_master_bus(bus);
trikke_usb_transport_deinit(&s_context.usb_transport);
return;
}
@@ -370,11 +379,11 @@ void app_main(void)
vTaskDelete(acquisition_task_handle);
}
vQueueDelete(s_context.sample_queue);
trikke_usb_transport_deinit(&s_context.usb_transport);
ESP_LOGE(TAG, "telemetry task creation failed");
l3g4200d_deinit(&s_context.gyroscope);
adxl345_deinit(&s_context.accelerometer);
i2c_del_master_bus(bus);
trikke_usb_transport_deinit(&s_context.usb_transport);
return;
}
+14 -5
View File
@@ -24,20 +24,29 @@ trikke_transport_status_t trikke_transport_sender_step(
return TRIKKE_TRANSPORT_FATAL;
}
const trikke_transport_status_t status = sender->pending
const bool was_pending = sender->pending;
const trikke_transport_status_t status = was_pending
? transport->poll(transport->context)
: transport->begin(transport->context, packet, packet_size);
if (!status_is_valid(status)) {
sender->pending = false;
sender->pending = was_pending;
return TRIKKE_TRANSPORT_FATAL;
}
if (status == TRIKKE_TRANSPORT_PENDING) {
sender->pending = true;
} else {
// RETRY from poll is allowed only when the backend has discarded or
// otherwise resolved the old transfer and knows resubmission is safe.
} else if (status == TRIKKE_TRANSPORT_COMPLETE) {
sender->pending = false;
} else if (status == TRIKKE_TRANSPORT_RETRY) {
if (was_pending) {
// Once accepted, generic transport code cannot prove that retrying
// the whole frame is duplicate-safe. Fail closed and keep ownership.
sender->pending = true;
return TRIKKE_TRANSPORT_FATAL;
}
sender->pending = false;
} else {
sender->pending = was_pending;
}
return status;
}
+6 -1
View File
@@ -36,7 +36,12 @@ void trikke_transport_sender_init(trikke_transport_sender_t *sender);
// Advances one bounded transport operation. The packet storage must remain valid
// and unchanged from the first PENDING result through COMPLETE. While pending,
// only poll is called: an ambiguous timeout can never duplicate a frame.
// only poll is called: an ambiguous timeout can never duplicate a frame. RETRY
// is valid only from begin, where it guarantees that no bytes were accepted.
//
// COMPLETE is backend-specific. USB uses endpoint drain, which does not prove
// application receipt. A reliable BLE backend must reserve COMPLETE for an
// application acknowledgement covering this exact frame.
trikke_transport_status_t trikke_transport_sender_step(
trikke_transport_sender_t *sender,
const trikke_transport_t *transport,
+3
View File
@@ -26,6 +26,9 @@ static trikke_transport_status_t usb_begin_packet(
return TRIKKE_TRANSPORT_PENDING;
}
if (written == 0) {
// Arguments and lifecycle were validated above, and this backend is the
// driver's sole owner. Under that invariant, zero means the all-or-none
// ring submission timed out without accepting this frame.
return TRIKKE_TRANSPORT_RETRY;
}
return TRIKKE_TRANSPORT_FATAL;