mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-17 23:55:04 +00:00
Rename system_event api functions and add subscribe mechanism
This commit is contained in:
parent
d2c69ee7e8
commit
8e7f997597
@ -17,12 +17,12 @@ static void on_boot_completed(struct SystemEvent* /*event*/, void* /*context*/)
|
||||
}
|
||||
|
||||
static error_t start() {
|
||||
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed, nullptr);
|
||||
system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed, nullptr);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed);
|
||||
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, on_boot_completed);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,8 @@
|
||||
|
||||
## Higher Priority
|
||||
|
||||
- Improve Setup: Show "Step done" screen
|
||||
- Improve Setup: Add keyboard/keypad navigation explanation
|
||||
- display.h API: get_backlight does not change ref counting, but it should
|
||||
- bluetooth: various getters for child devices do not change ref counting, but they should
|
||||
- Improve kernel_init.cpp (and other modules): create driver_ensure_added() and driver_ensure_destructed()
|
||||
|
||||
@ -133,7 +133,7 @@ static void statusbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj)
|
||||
|
||||
if (!statusbar_data.time_update_timer->isRunning()) {
|
||||
statusbar_data.time_update_timer->start();
|
||||
system_event_subscribe(KERNEL_EVENT_TIME_CHANGED, onTimeChanged, nullptr);
|
||||
system_event_callback_add(KERNEL_EVENT_TIME_CHANGED, onTimeChanged, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -120,7 +120,7 @@ bool RtcTimeService::onStart(ServiceContext& serviceContext) {
|
||||
system_event_emit(KERNEL_EVENT_TIME_CHANGED, nullptr, 0);
|
||||
}
|
||||
|
||||
if (system_event_subscribe(KERNEL_EVENT_TIME_CHANGED, &RtcTimeService::onTimeChangedTrampoline, this) == ERROR_NONE) {
|
||||
if (system_event_callback_add(KERNEL_EVENT_TIME_CHANGED, &RtcTimeService::onTimeChangedTrampoline, this) == ERROR_NONE) {
|
||||
timeEventSubscribed = true;
|
||||
}
|
||||
|
||||
@ -129,7 +129,7 @@ bool RtcTimeService::onStart(ServiceContext& serviceContext) {
|
||||
|
||||
void RtcTimeService::onStop(ServiceContext& serviceContext) {
|
||||
if (timeEventSubscribed) {
|
||||
system_event_unsubscribe(KERNEL_EVENT_TIME_CHANGED, &RtcTimeService::onTimeChangedTrampoline);
|
||||
system_event_callback_remove(KERNEL_EVENT_TIME_CHANGED, &RtcTimeService::onTimeChangedTrampoline);
|
||||
timeEventSubscribed = false;
|
||||
}
|
||||
|
||||
|
||||
@ -510,7 +510,7 @@ public:
|
||||
LOG_W(TAG, "No WiFi device found");
|
||||
}
|
||||
|
||||
if (system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, onBootCompleted, nullptr) == ERROR_NONE) {
|
||||
if (system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, onBootCompleted, nullptr) == ERROR_NONE) {
|
||||
state.bootEventSubscribed = true;
|
||||
}
|
||||
|
||||
@ -531,7 +531,7 @@ public:
|
||||
state.autoConnectTimer = nullptr; // Must release as it holds a reference via its callback.
|
||||
|
||||
if (state.bootEventSubscribed) {
|
||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, onBootCompleted);
|
||||
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, onBootCompleted);
|
||||
state.bootEventSubscribed = false;
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/freertos/freertos.h>
|
||||
#include <tactility/freertos/task.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@ -73,7 +75,7 @@ struct ServiceStoppedEvent {
|
||||
|
||||
/**
|
||||
* @param[in] event the event being delivered; only valid for the duration of the call
|
||||
* @param[in] context the context pointer passed to system_event_subscribe()
|
||||
* @param[in] context the context pointer passed to system_event_callback_add()
|
||||
*/
|
||||
typedef void (*system_event_callback_t)(struct SystemEvent* event, void* context);
|
||||
|
||||
@ -82,7 +84,7 @@ typedef void (*system_event_callback_t)(struct SystemEvent* event, void* context
|
||||
* @warning Does not work in ISR context.
|
||||
* @warning @a callback is invoked synchronously, on the caller's task, from within
|
||||
* system_event_emit(). The internal subscription lock is not held during the call, so
|
||||
* @a callback may itself call system_event_subscribe(), system_event_unsubscribe() or
|
||||
* @a callback may itself call system_event_callback_add(), system_event_callback_remove() or
|
||||
* system_event_emit() without deadlocking - but a subscribe/unsubscribe made from within
|
||||
* a callback only takes effect for events emitted after the current system_event_emit()
|
||||
* call returns, since that call already snapshotted the subscriptions it will invoke.
|
||||
@ -91,7 +93,7 @@ typedef void (*system_event_callback_t)(struct SystemEvent* event, void* context
|
||||
* @param[in] context an opaque pointer passed back to @a callback unmodified
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t system_event_subscribe(
|
||||
error_t system_event_callback_add(
|
||||
enum SystemEventType type,
|
||||
system_event_callback_t callback,
|
||||
void *context
|
||||
@ -100,11 +102,11 @@ error_t system_event_subscribe(
|
||||
/**
|
||||
* Remove a previously added subscription.
|
||||
* @warning Does not work in ISR context.
|
||||
* @param[in] type the event type passed to the matching system_event_subscribe() call
|
||||
* @param[in] callback the callback passed to the matching system_event_subscribe() call
|
||||
* @param[in] type the event type passed to the matching system_event_callback_add() call
|
||||
* @param[in] callback the callback passed to the matching system_event_callback_add() call
|
||||
* @return ERROR_NONE on success, ERROR_NOT_FOUND if no matching subscription exists
|
||||
*/
|
||||
error_t system_event_unsubscribe(
|
||||
error_t system_event_callback_remove(
|
||||
enum SystemEventType type,
|
||||
system_event_callback_t callback
|
||||
);
|
||||
@ -125,6 +127,59 @@ error_t system_event_emit(
|
||||
size_t data_len
|
||||
);
|
||||
|
||||
/** Size of the largest type-specific event struct documented in SystemEventType, i.e. the
|
||||
* embedded buffer size needed by SystemEventSubscription to hold any event's payload by value. */
|
||||
#define SYSTEM_EVENT_MAX_DATA_SIZE (sizeof(struct NetworkConnectedEvent))
|
||||
|
||||
/**
|
||||
* gps.h-style poll subscription: caller-owned node, registered with system_event_subscribe()
|
||||
* and polled with system_event_await(). Unlike system_event_callback_t, the payload is copied
|
||||
* by value into @a data (up to SYSTEM_EVENT_MAX_DATA_SIZE bytes) so it remains valid after
|
||||
* system_event_emit() returns.
|
||||
* @warning Fields other than `type` are for internal use only; do not read or write them
|
||||
* directly.
|
||||
*/
|
||||
struct SystemEventSubscription {
|
||||
/** Event type to subscribe to; set by the caller before system_event_subscribe(). */
|
||||
enum SystemEventType type;
|
||||
|
||||
TaskHandle_t task;
|
||||
|
||||
uint64_t timestamp;
|
||||
uint8_t data[SYSTEM_EVENT_MAX_DATA_SIZE];
|
||||
size_t data_len;
|
||||
|
||||
uint32_t sequence;
|
||||
uint32_t consumed_sequence;
|
||||
|
||||
struct SystemEventSubscription* next;
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a poll subscription for events of @a sub->type.
|
||||
* @warning Does not work in ISR context.
|
||||
* @param[in,out] sub subscription to register; caller sets @a sub->type beforehand, owns the
|
||||
* storage, and must keep it alive (and stationary) until unsubscribed
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t system_event_subscribe(struct SystemEventSubscription* sub);
|
||||
|
||||
/**
|
||||
* Remove a previously registered poll subscription.
|
||||
* @warning Does not work in ISR context.
|
||||
* @param[in] sub subscription to remove, as passed to system_event_subscribe()
|
||||
* @return ERROR_NONE on success, ERROR_NOT_FOUND if no matching subscription exists
|
||||
*/
|
||||
error_t system_event_unsubscribe(struct SystemEventSubscription* sub);
|
||||
|
||||
/**
|
||||
* Blocks the calling task until a new event arrives for @a sub, or timeout elapses.
|
||||
* @param[in,out] sub subscription to wait on, as passed to system_event_subscribe()
|
||||
* @param[in] timeout max ticks to wait
|
||||
* @return ERROR_NONE if an event arrived, ERROR_TIMEOUT if the timeout elapsed
|
||||
*/
|
||||
error_t system_event_await(struct SystemEventSubscription* sub, TickType_t timeout);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <tactility/time.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <vector>
|
||||
|
||||
@ -27,9 +28,16 @@ struct KernelEventMutex {
|
||||
|
||||
static KernelEventMutex subscriptions_mutex;
|
||||
|
||||
// Intrusive singly-linked list of poll subscriptions (system_event_subscribe()/_unsubscribe()/
|
||||
// _await()), separate from the callback-based `subscriptions` vector above. Guarded by its own
|
||||
// mutex since notifying a poll subscriber never invokes caller code (just a memcpy and an
|
||||
// xTaskNotifyGive), so there is no reentrancy concern requiring a snapshot-then-unlock dance.
|
||||
static SystemEventSubscription* poll_subscriptions = nullptr;
|
||||
static KernelEventMutex poll_subscriptions_mutex;
|
||||
|
||||
extern "C" {
|
||||
|
||||
error_t system_event_subscribe(
|
||||
error_t system_event_callback_add(
|
||||
SystemEventType type,
|
||||
system_event_callback_t callback,
|
||||
void* context
|
||||
@ -41,7 +49,7 @@ error_t system_event_subscribe(
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t system_event_unsubscribe(
|
||||
error_t system_event_callback_remove(
|
||||
SystemEventType type,
|
||||
system_event_callback_t callback
|
||||
) {
|
||||
@ -59,20 +67,37 @@ error_t system_event_unsubscribe(
|
||||
return result;
|
||||
}
|
||||
|
||||
error_t system_event_emit(
|
||||
enum SystemEventType type,
|
||||
// Copies `data` into every current poll subscriber of `type` and wakes its waiting task.
|
||||
// Held entirely under the lock: unlike the callback path, this never invokes caller code
|
||||
// (just a memcpy and an xTaskNotifyGive), so there is nothing that could reenter and deadlock.
|
||||
static void notify_poll_subscribers(
|
||||
SystemEventType type,
|
||||
uint64_t timestamp,
|
||||
const void* data,
|
||||
size_t data_len
|
||||
) {
|
||||
SystemEvent event = {
|
||||
.type = type,
|
||||
.timestamp = get_micros_since_boot(),
|
||||
.data = data,
|
||||
.data_len = data_len,
|
||||
};
|
||||
mutex_lock(&poll_subscriptions_mutex.handle);
|
||||
|
||||
for (SystemEventSubscription* sub = poll_subscriptions; sub != nullptr; sub = sub->next) {
|
||||
if (sub->type == type) {
|
||||
sub->timestamp = timestamp;
|
||||
if (data_len > 0) {
|
||||
std::memcpy(sub->data, data, std::min(data_len, static_cast<size_t>(SYSTEM_EVENT_MAX_DATA_SIZE)));
|
||||
}
|
||||
sub->data_len = data_len;
|
||||
sub->sequence++;
|
||||
xTaskNotifyGive(sub->task);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&poll_subscriptions_mutex.handle);
|
||||
}
|
||||
|
||||
static error_t notify_listeners(
|
||||
SystemEvent& event
|
||||
) {
|
||||
// Snapshot matching subscriptions under the lock, then invoke after unlocking: a
|
||||
// callback calling system_event_subscribe(), system_event_unsubscribe() or
|
||||
// callback calling system_event_callback_add(), system_event_callback_remove() or
|
||||
// system_event_emit() would otherwise deadlock against this same (non-recursive)
|
||||
// mutex, and a slow callback would block every other thread's subscribe/unsubscribe
|
||||
// for the duration of this emit.
|
||||
@ -86,7 +111,7 @@ error_t system_event_emit(
|
||||
|
||||
size_t match_count = 0;
|
||||
for (const auto& subscription : subscriptions) {
|
||||
if (subscription.type == type) {
|
||||
if (subscription.type == event.type) {
|
||||
match_count++;
|
||||
}
|
||||
}
|
||||
@ -101,7 +126,7 @@ error_t system_event_emit(
|
||||
|
||||
size_t matched_count = 0;
|
||||
for (const auto& subscription : subscriptions) {
|
||||
if (subscription.type == type) {
|
||||
if (subscription.type == event.type) {
|
||||
matching[matched_count++] = subscription;
|
||||
}
|
||||
}
|
||||
@ -117,4 +142,66 @@ error_t system_event_emit(
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t system_event_emit(
|
||||
SystemEventType type,
|
||||
const void* data,
|
||||
size_t data_len
|
||||
) {
|
||||
SystemEvent event = {
|
||||
.type = type,
|
||||
.timestamp = get_micros_since_boot(),
|
||||
.data = data,
|
||||
.data_len = data_len,
|
||||
};
|
||||
|
||||
notify_poll_subscribers(type, event.timestamp, data, data_len);
|
||||
auto error = notify_listeners(event);
|
||||
if (error != ERROR_NONE) { return error; }
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t system_event_subscribe(SystemEventSubscription* sub) {
|
||||
sub->task = xTaskGetCurrentTaskHandle();
|
||||
sub->sequence = 0;
|
||||
sub->consumed_sequence = 0;
|
||||
sub->data_len = 0;
|
||||
|
||||
mutex_lock(&poll_subscriptions_mutex.handle);
|
||||
sub->next = poll_subscriptions;
|
||||
poll_subscriptions = sub;
|
||||
mutex_unlock(&poll_subscriptions_mutex.handle);
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t system_event_unsubscribe(SystemEventSubscription* sub) {
|
||||
error_t result = ERROR_NOT_FOUND;
|
||||
|
||||
mutex_lock(&poll_subscriptions_mutex.handle);
|
||||
for (SystemEventSubscription** link = &poll_subscriptions; *link != nullptr; link = &(*link)->next) {
|
||||
if (*link == sub) {
|
||||
*link = sub->next;
|
||||
result = ERROR_NONE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&poll_subscriptions_mutex.handle);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
error_t system_event_await(SystemEventSubscription* sub, TickType_t timeout) {
|
||||
uint32_t old_sequence = sub->sequence;
|
||||
|
||||
while (sub->sequence == old_sequence) {
|
||||
if (ulTaskNotifyTake(pdTRUE, timeout) == 0) {
|
||||
return ERROR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
sub->consumed_sequence = sub->sequence;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
#include "doctest.h"
|
||||
|
||||
#include <tactility/concurrent/thread.h>
|
||||
#include <tactility/delay.h>
|
||||
#include <tactility/system_event.h>
|
||||
#include <tactility/time.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
// system_event_emit() snapshots matching subscriptions under the lock, then invokes them
|
||||
// after unlocking (see the @warning on system_event_subscribe() in system_event.h), so a
|
||||
// callback calling system_event_subscribe()/_unsubscribe()/_emit() must not deadlock -
|
||||
// after unlocking (see the @warning on system_event_callback_add() in system_event.h), so a
|
||||
// callback calling system_event_callback_add()/_unsubscribe()/_emit() must not deadlock -
|
||||
// covered below, mirroring DeviceListenerTest.cpp's reentrancy test.
|
||||
|
||||
struct RecordedCall {
|
||||
@ -39,8 +41,8 @@ TEST_CASE("system_event_emit invokes every subscriber registered for that type")
|
||||
int context_a = 1;
|
||||
int context_b = 2;
|
||||
|
||||
CHECK_EQ(system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a), ERROR_NONE);
|
||||
CHECK_EQ(system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b, &context_b), ERROR_NONE);
|
||||
CHECK_EQ(system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a), ERROR_NONE);
|
||||
CHECK_EQ(system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, listener_b, &context_b), ERROR_NONE);
|
||||
|
||||
CHECK_EQ(system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0), ERROR_NONE);
|
||||
|
||||
@ -51,14 +53,14 @@ TEST_CASE("system_event_emit invokes every subscriber registered for that type")
|
||||
REQUIRE_EQ(calls_b.size(), 1);
|
||||
CHECK_EQ(calls_b[0].context, &context_b);
|
||||
|
||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
||||
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
||||
}
|
||||
|
||||
TEST_CASE("system_event_emit only invokes subscribers registered for the emitted type") {
|
||||
reset_calls();
|
||||
int context_a = 1;
|
||||
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a);
|
||||
system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a);
|
||||
|
||||
system_event_emit(KERNEL_EVENT_TIME_CHANGED, nullptr, 0);
|
||||
CHECK_EQ(calls_a.size(), 0);
|
||||
@ -66,7 +68,7 @@ TEST_CASE("system_event_emit only invokes subscribers registered for the emitted
|
||||
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||
CHECK_EQ(calls_a.size(), 1);
|
||||
|
||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||
}
|
||||
|
||||
TEST_CASE("system_event_emit passes the data pointer and length through unchanged") {
|
||||
@ -74,7 +76,7 @@ TEST_CASE("system_event_emit passes the data pointer and length through unchange
|
||||
int context_a = 1;
|
||||
struct Payload { int value; } payload { 42 };
|
||||
|
||||
system_event_subscribe(KERNEL_EVENT_TIME_CHANGED, listener_a, &context_a);
|
||||
system_event_callback_add(KERNEL_EVENT_TIME_CHANGED, listener_a, &context_a);
|
||||
system_event_emit(KERNEL_EVENT_TIME_CHANGED, &payload, sizeof(payload));
|
||||
|
||||
REQUIRE_EQ(calls_a.size(), 1);
|
||||
@ -82,13 +84,13 @@ TEST_CASE("system_event_emit passes the data pointer and length through unchange
|
||||
CHECK_EQ(calls_a[0].data_len, sizeof(payload));
|
||||
CHECK_EQ(static_cast<const Payload*>(calls_a[0].data)->value, 42);
|
||||
|
||||
system_event_unsubscribe(KERNEL_EVENT_TIME_CHANGED, listener_a);
|
||||
system_event_callback_remove(KERNEL_EVENT_TIME_CHANGED, listener_a);
|
||||
}
|
||||
|
||||
TEST_CASE("system_event_emit with no data passes a null pointer and zero length") {
|
||||
reset_calls();
|
||||
int context_a = 1;
|
||||
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a);
|
||||
system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a);
|
||||
|
||||
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||
|
||||
@ -96,50 +98,50 @@ TEST_CASE("system_event_emit with no data passes a null pointer and zero length"
|
||||
CHECK_EQ(calls_a[0].data, nullptr);
|
||||
CHECK_EQ(calls_a[0].data_len, 0);
|
||||
|
||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||
}
|
||||
|
||||
TEST_CASE("system_event_unsubscribe stops further notifications for that callback only") {
|
||||
TEST_CASE("system_event_callback_remove stops further notifications for that callback only") {
|
||||
reset_calls();
|
||||
int context_a = 1;
|
||||
int context_b = 2;
|
||||
|
||||
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a);
|
||||
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b, &context_b);
|
||||
system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a);
|
||||
system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, listener_b, &context_b);
|
||||
|
||||
CHECK_EQ(system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a), ERROR_NONE);
|
||||
CHECK_EQ(system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, listener_a), ERROR_NONE);
|
||||
|
||||
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||
|
||||
CHECK_EQ(calls_a.size(), 0);
|
||||
CHECK_EQ(calls_b.size(), 1);
|
||||
|
||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
||||
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
||||
}
|
||||
|
||||
TEST_CASE("system_event_unsubscribe on an unregistered callback returns ERROR_NOT_FOUND and is a no-op") {
|
||||
TEST_CASE("system_event_callback_remove on an unregistered callback returns ERROR_NOT_FOUND and is a no-op") {
|
||||
reset_calls();
|
||||
int context_b = 2;
|
||||
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b, &context_b);
|
||||
system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, listener_b, &context_b);
|
||||
|
||||
// listener_a was never added for this type, so removing it must not disturb listener_b.
|
||||
CHECK_EQ(system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a), ERROR_NOT_FOUND);
|
||||
CHECK_EQ(system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, listener_a), ERROR_NOT_FOUND);
|
||||
|
||||
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||
CHECK_EQ(calls_b.size(), 1);
|
||||
|
||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
||||
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
||||
}
|
||||
|
||||
TEST_CASE("system_event_unsubscribe matches on (type, callback), not the callback alone") {
|
||||
TEST_CASE("system_event_callback_remove matches on (type, callback), not the callback alone") {
|
||||
reset_calls();
|
||||
int context_a = 1;
|
||||
|
||||
// Same callback subscribed for two different event types.
|
||||
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a);
|
||||
system_event_subscribe(KERNEL_EVENT_TIME_CHANGED, listener_a, &context_a);
|
||||
system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a);
|
||||
system_event_callback_add(KERNEL_EVENT_TIME_CHANGED, listener_a, &context_a);
|
||||
|
||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||
|
||||
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||
CHECK_EQ(calls_a.size(), 0);
|
||||
@ -147,7 +149,7 @@ TEST_CASE("system_event_unsubscribe matches on (type, callback), not the callbac
|
||||
system_event_emit(KERNEL_EVENT_TIME_CHANGED, nullptr, 0);
|
||||
CHECK_EQ(calls_a.size(), 1);
|
||||
|
||||
system_event_unsubscribe(KERNEL_EVENT_TIME_CHANGED, listener_a);
|
||||
system_event_callback_remove(KERNEL_EVENT_TIME_CHANGED, listener_a);
|
||||
}
|
||||
|
||||
TEST_CASE("system_event_emit with no subscribers for that type returns ERROR_NONE") {
|
||||
@ -157,7 +159,7 @@ TEST_CASE("system_event_emit with no subscribers for that type returns ERROR_NON
|
||||
TEST_CASE("system_event_emit stamps the event with the current boot-relative time") {
|
||||
reset_calls();
|
||||
int context_a = 1;
|
||||
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a);
|
||||
system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a);
|
||||
|
||||
auto before = static_cast<uint64_t>(get_micros_since_boot());
|
||||
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||
@ -167,7 +169,7 @@ TEST_CASE("system_event_emit stamps the event with the current boot-relative tim
|
||||
CHECK_GE(calls_a[0].timestamp, before);
|
||||
CHECK_LE(calls_a[0].timestamp, after);
|
||||
|
||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||
}
|
||||
|
||||
static bool reentrant_add_triggered = false;
|
||||
@ -179,10 +181,10 @@ static void reentrant_listener(SystemEvent* event, void* context) {
|
||||
// Subscribing from within a notification must not deadlock: emit() releases the
|
||||
// lock before invoking callbacks, so this only blocks briefly on the (already
|
||||
// unlocked) mutex.
|
||||
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b, context);
|
||||
system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, listener_b, context);
|
||||
// Also exercise unsubscribe() and a nested emit() of a different type from within
|
||||
// a callback - all must complete without deadlocking.
|
||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, reentrant_listener);
|
||||
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, reentrant_listener);
|
||||
system_event_emit(KERNEL_EVENT_TIME_CHANGED, nullptr, 0);
|
||||
}
|
||||
}
|
||||
@ -192,8 +194,8 @@ TEST_CASE("system_event_emit is safe when a callback subscribes, unsubscribes an
|
||||
reentrant_add_triggered = false;
|
||||
int context_a = 1;
|
||||
|
||||
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, reentrant_listener, &context_a);
|
||||
system_event_subscribe(KERNEL_EVENT_TIME_CHANGED, listener_b, &context_a);
|
||||
system_event_callback_add(KERNEL_EVENT_BOOT_COMPLETED, reentrant_listener, &context_a);
|
||||
system_event_callback_add(KERNEL_EVENT_TIME_CHANGED, listener_b, &context_a);
|
||||
|
||||
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||
|
||||
@ -210,6 +212,68 @@ TEST_CASE("system_event_emit is safe when a callback subscribes, unsubscribes an
|
||||
CHECK_EQ(calls_a.size(), 1);
|
||||
CHECK_EQ(calls_b.size(), 2);
|
||||
|
||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
||||
system_event_unsubscribe(KERNEL_EVENT_TIME_CHANGED, listener_b);
|
||||
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
||||
system_event_callback_remove(KERNEL_EVENT_TIME_CHANGED, listener_b);
|
||||
}
|
||||
|
||||
// gps.h-style poll subscription: system_event_subscribe()/_await()/_unsubscribe().
|
||||
//
|
||||
// system_event_await() only detects sequence increments that happen *after* it starts
|
||||
// waiting (same as gps_api_event_await()), so the emit must be started from another task
|
||||
// while this one is already blocked in await() - emitting first and awaiting after would
|
||||
// race the notification the same way it would with any FreeRTOS task-notify consumer.
|
||||
|
||||
TEST_CASE("system_event_subscribe/_await deliver the event payload by value") {
|
||||
SystemEventSubscription sub {};
|
||||
sub.type = KERNEL_EVENT_NETWORK_CONNECTED;
|
||||
CHECK_EQ(system_event_subscribe(&sub), ERROR_NONE);
|
||||
|
||||
NetworkConnectedEvent connected { .device = nullptr, .ipv4_addr = 0x0A000001, .gateway = 0x0A0000FE };
|
||||
auto* thread = thread_alloc_full(
|
||||
"system-event-emitter",
|
||||
4096,
|
||||
[](void* context) {
|
||||
delay_millis(20);
|
||||
auto* connected_ptr = static_cast<NetworkConnectedEvent*>(context);
|
||||
system_event_emit(KERNEL_EVENT_NETWORK_CONNECTED, connected_ptr, sizeof(*connected_ptr));
|
||||
return 0;
|
||||
},
|
||||
&connected,
|
||||
-1
|
||||
);
|
||||
CHECK_EQ(thread_start(thread), ERROR_NONE);
|
||||
|
||||
CHECK_EQ(system_event_await(&sub, pdMS_TO_TICKS(2000)), ERROR_NONE);
|
||||
|
||||
const auto* received = reinterpret_cast<const NetworkConnectedEvent*>(sub.data);
|
||||
CHECK_EQ(received->ipv4_addr, connected.ipv4_addr);
|
||||
CHECK_EQ(received->gateway, connected.gateway);
|
||||
CHECK_EQ(sub.data_len, sizeof(connected));
|
||||
|
||||
CHECK_EQ(thread_join(thread, 2, 1), ERROR_NONE);
|
||||
thread_free(thread);
|
||||
|
||||
CHECK_EQ(system_event_unsubscribe(&sub), ERROR_NONE);
|
||||
CHECK_EQ(system_event_unsubscribe(&sub), ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
TEST_CASE("system_event_await times out when no matching event has arrived") {
|
||||
SystemEventSubscription sub {};
|
||||
sub.type = KERNEL_EVENT_TIME_CHANGED;
|
||||
system_event_subscribe(&sub);
|
||||
|
||||
CHECK_EQ(system_event_await(&sub, 0), ERROR_TIMEOUT);
|
||||
|
||||
system_event_unsubscribe(&sub);
|
||||
}
|
||||
|
||||
TEST_CASE("system_event_emit does not notify a poll subscriber of a different type") {
|
||||
SystemEventSubscription sub {};
|
||||
sub.type = KERNEL_EVENT_BOOT_COMPLETED;
|
||||
system_event_subscribe(&sub);
|
||||
|
||||
system_event_emit(KERNEL_EVENT_TIME_CHANGED, nullptr, 0);
|
||||
CHECK_EQ(system_event_await(&sub, 0), ERROR_TIMEOUT);
|
||||
|
||||
system_event_unsubscribe(&sub);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user