mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-21 17:35:06 +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() {
|
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;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static error_t stop() {
|
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;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
## Higher Priority
|
## 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
|
- 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
|
- 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()
|
- 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()) {
|
if (!statusbar_data.time_update_timer->isRunning()) {
|
||||||
statusbar_data.time_update_timer->start();
|
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);
|
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;
|
timeEventSubscribed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +129,7 @@ bool RtcTimeService::onStart(ServiceContext& serviceContext) {
|
|||||||
|
|
||||||
void RtcTimeService::onStop(ServiceContext& serviceContext) {
|
void RtcTimeService::onStop(ServiceContext& serviceContext) {
|
||||||
if (timeEventSubscribed) {
|
if (timeEventSubscribed) {
|
||||||
system_event_unsubscribe(KERNEL_EVENT_TIME_CHANGED, &RtcTimeService::onTimeChangedTrampoline);
|
system_event_callback_remove(KERNEL_EVENT_TIME_CHANGED, &RtcTimeService::onTimeChangedTrampoline);
|
||||||
timeEventSubscribed = false;
|
timeEventSubscribed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -510,7 +510,7 @@ public:
|
|||||||
LOG_W(TAG, "No WiFi device found");
|
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;
|
state.bootEventSubscribed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -531,7 +531,7 @@ public:
|
|||||||
state.autoConnectTimer = nullptr; // Must release as it holds a reference via its callback.
|
state.autoConnectTimer = nullptr; // Must release as it holds a reference via its callback.
|
||||||
|
|
||||||
if (state.bootEventSubscribed) {
|
if (state.bootEventSubscribed) {
|
||||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, onBootCompleted);
|
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, onBootCompleted);
|
||||||
state.bootEventSubscribed = false;
|
state.bootEventSubscribed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <tactility/error.h>
|
#include <tactility/error.h>
|
||||||
|
#include <tactility/freertos/freertos.h>
|
||||||
|
#include <tactility/freertos/task.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
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] 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);
|
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 Does not work in ISR context.
|
||||||
* @warning @a callback is invoked synchronously, on the caller's task, from within
|
* @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
|
* 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
|
* 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()
|
* 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.
|
* 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
|
* @param[in] context an opaque pointer passed back to @a callback unmodified
|
||||||
* @return ERROR_NONE on success
|
* @return ERROR_NONE on success
|
||||||
*/
|
*/
|
||||||
error_t system_event_subscribe(
|
error_t system_event_callback_add(
|
||||||
enum SystemEventType type,
|
enum SystemEventType type,
|
||||||
system_event_callback_t callback,
|
system_event_callback_t callback,
|
||||||
void *context
|
void *context
|
||||||
@ -100,11 +102,11 @@ error_t system_event_subscribe(
|
|||||||
/**
|
/**
|
||||||
* Remove a previously added subscription.
|
* Remove a previously added subscription.
|
||||||
* @warning Does not work in ISR context.
|
* @warning Does not work in ISR context.
|
||||||
* @param[in] type the event type 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_subscribe() 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
|
* @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,
|
enum SystemEventType type,
|
||||||
system_event_callback_t callback
|
system_event_callback_t callback
|
||||||
);
|
);
|
||||||
@ -125,6 +127,59 @@ error_t system_event_emit(
|
|||||||
size_t data_len
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
#include <tactility/time.h>
|
#include <tactility/time.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
#include <new>
|
#include <new>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -27,9 +28,16 @@ struct KernelEventMutex {
|
|||||||
|
|
||||||
static KernelEventMutex subscriptions_mutex;
|
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" {
|
extern "C" {
|
||||||
|
|
||||||
error_t system_event_subscribe(
|
error_t system_event_callback_add(
|
||||||
SystemEventType type,
|
SystemEventType type,
|
||||||
system_event_callback_t callback,
|
system_event_callback_t callback,
|
||||||
void* context
|
void* context
|
||||||
@ -41,7 +49,7 @@ error_t system_event_subscribe(
|
|||||||
return ERROR_NONE;
|
return ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
error_t system_event_unsubscribe(
|
error_t system_event_callback_remove(
|
||||||
SystemEventType type,
|
SystemEventType type,
|
||||||
system_event_callback_t callback
|
system_event_callback_t callback
|
||||||
) {
|
) {
|
||||||
@ -59,20 +67,37 @@ error_t system_event_unsubscribe(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
error_t system_event_emit(
|
// Copies `data` into every current poll subscriber of `type` and wakes its waiting task.
|
||||||
enum SystemEventType type,
|
// 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,
|
const void* data,
|
||||||
size_t data_len
|
size_t data_len
|
||||||
) {
|
) {
|
||||||
SystemEvent event = {
|
mutex_lock(&poll_subscriptions_mutex.handle);
|
||||||
.type = type,
|
|
||||||
.timestamp = get_micros_since_boot(),
|
|
||||||
.data = data,
|
|
||||||
.data_len = data_len,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
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
|
// 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)
|
// system_event_emit() would otherwise deadlock against this same (non-recursive)
|
||||||
// mutex, and a slow callback would block every other thread's subscribe/unsubscribe
|
// mutex, and a slow callback would block every other thread's subscribe/unsubscribe
|
||||||
// for the duration of this emit.
|
// for the duration of this emit.
|
||||||
@ -86,7 +111,7 @@ error_t system_event_emit(
|
|||||||
|
|
||||||
size_t match_count = 0;
|
size_t match_count = 0;
|
||||||
for (const auto& subscription : subscriptions) {
|
for (const auto& subscription : subscriptions) {
|
||||||
if (subscription.type == type) {
|
if (subscription.type == event.type) {
|
||||||
match_count++;
|
match_count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,7 +126,7 @@ error_t system_event_emit(
|
|||||||
|
|
||||||
size_t matched_count = 0;
|
size_t matched_count = 0;
|
||||||
for (const auto& subscription : subscriptions) {
|
for (const auto& subscription : subscriptions) {
|
||||||
if (subscription.type == type) {
|
if (subscription.type == event.type) {
|
||||||
matching[matched_count++] = subscription;
|
matching[matched_count++] = subscription;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,4 +142,66 @@ error_t system_event_emit(
|
|||||||
return ERROR_NONE;
|
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"
|
} // extern "C"
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
|
#include <tactility/concurrent/thread.h>
|
||||||
|
#include <tactility/delay.h>
|
||||||
#include <tactility/system_event.h>
|
#include <tactility/system_event.h>
|
||||||
#include <tactility/time.h>
|
#include <tactility/time.h>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// system_event_emit() snapshots matching subscriptions under the lock, then invokes them
|
// 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
|
// after unlocking (see the @warning on system_event_callback_add() in system_event.h), so a
|
||||||
// callback calling system_event_subscribe()/_unsubscribe()/_emit() must not deadlock -
|
// callback calling system_event_callback_add()/_unsubscribe()/_emit() must not deadlock -
|
||||||
// covered below, mirroring DeviceListenerTest.cpp's reentrancy test.
|
// covered below, mirroring DeviceListenerTest.cpp's reentrancy test.
|
||||||
|
|
||||||
struct RecordedCall {
|
struct RecordedCall {
|
||||||
@ -39,8 +41,8 @@ TEST_CASE("system_event_emit invokes every subscriber registered for that type")
|
|||||||
int context_a = 1;
|
int context_a = 1;
|
||||||
int context_b = 2;
|
int context_b = 2;
|
||||||
|
|
||||||
CHECK_EQ(system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a, &context_a), ERROR_NONE);
|
CHECK_EQ(system_event_callback_add(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_b, &context_b), ERROR_NONE);
|
||||||
|
|
||||||
CHECK_EQ(system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0), 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);
|
REQUIRE_EQ(calls_b.size(), 1);
|
||||||
CHECK_EQ(calls_b[0].context, &context_b);
|
CHECK_EQ(calls_b[0].context, &context_b);
|
||||||
|
|
||||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, listener_a);
|
||||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("system_event_emit only invokes subscribers registered for the emitted type") {
|
TEST_CASE("system_event_emit only invokes subscribers registered for the emitted type") {
|
||||||
reset_calls();
|
reset_calls();
|
||||||
int context_a = 1;
|
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);
|
system_event_emit(KERNEL_EVENT_TIME_CHANGED, nullptr, 0);
|
||||||
CHECK_EQ(calls_a.size(), 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);
|
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||||
CHECK_EQ(calls_a.size(), 1);
|
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") {
|
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;
|
int context_a = 1;
|
||||||
struct Payload { int value; } payload { 42 };
|
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));
|
system_event_emit(KERNEL_EVENT_TIME_CHANGED, &payload, sizeof(payload));
|
||||||
|
|
||||||
REQUIRE_EQ(calls_a.size(), 1);
|
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(calls_a[0].data_len, sizeof(payload));
|
||||||
CHECK_EQ(static_cast<const Payload*>(calls_a[0].data)->value, 42);
|
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") {
|
TEST_CASE("system_event_emit with no data passes a null pointer and zero length") {
|
||||||
reset_calls();
|
reset_calls();
|
||||||
int context_a = 1;
|
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);
|
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, nullptr);
|
||||||
CHECK_EQ(calls_a[0].data_len, 0);
|
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();
|
reset_calls();
|
||||||
int context_a = 1;
|
int context_a = 1;
|
||||||
int context_b = 2;
|
int context_b = 2;
|
||||||
|
|
||||||
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_subscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b, &context_b);
|
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);
|
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||||
|
|
||||||
CHECK_EQ(calls_a.size(), 0);
|
CHECK_EQ(calls_a.size(), 0);
|
||||||
CHECK_EQ(calls_b.size(), 1);
|
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();
|
reset_calls();
|
||||||
int context_b = 2;
|
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.
|
// 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);
|
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||||
CHECK_EQ(calls_b.size(), 1);
|
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();
|
reset_calls();
|
||||||
int context_a = 1;
|
int context_a = 1;
|
||||||
|
|
||||||
// Same callback subscribed for two different event types.
|
// Same callback subscribed for two different event types.
|
||||||
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_subscribe(KERNEL_EVENT_TIME_CHANGED, 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);
|
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
||||||
CHECK_EQ(calls_a.size(), 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);
|
system_event_emit(KERNEL_EVENT_TIME_CHANGED, nullptr, 0);
|
||||||
CHECK_EQ(calls_a.size(), 1);
|
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") {
|
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") {
|
TEST_CASE("system_event_emit stamps the event with the current boot-relative time") {
|
||||||
reset_calls();
|
reset_calls();
|
||||||
int context_a = 1;
|
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());
|
auto before = static_cast<uint64_t>(get_micros_since_boot());
|
||||||
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
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_GE(calls_a[0].timestamp, before);
|
||||||
CHECK_LE(calls_a[0].timestamp, after);
|
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;
|
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
|
// Subscribing from within a notification must not deadlock: emit() releases the
|
||||||
// lock before invoking callbacks, so this only blocks briefly on the (already
|
// lock before invoking callbacks, so this only blocks briefly on the (already
|
||||||
// unlocked) mutex.
|
// 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
|
// Also exercise unsubscribe() and a nested emit() of a different type from within
|
||||||
// a callback - all must complete without deadlocking.
|
// 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);
|
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;
|
reentrant_add_triggered = false;
|
||||||
int context_a = 1;
|
int context_a = 1;
|
||||||
|
|
||||||
system_event_subscribe(KERNEL_EVENT_BOOT_COMPLETED, reentrant_listener, &context_a);
|
system_event_callback_add(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_TIME_CHANGED, listener_b, &context_a);
|
||||||
|
|
||||||
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
|
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_a.size(), 1);
|
||||||
CHECK_EQ(calls_b.size(), 2);
|
CHECK_EQ(calls_b.size(), 2);
|
||||||
|
|
||||||
system_event_unsubscribe(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
system_event_callback_remove(KERNEL_EVENT_BOOT_COMPLETED, listener_b);
|
||||||
system_event_unsubscribe(KERNEL_EVENT_TIME_CHANGED, 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