aos/lib/aos/event_queue.c
Daniel Schwyn 6d444bf552 Main handout
Signed-off-by: Daniel Schwyn <daniel.schwyn@inf.ethz.ch>
2022-03-03 14:57:51 +01:00

281 lines
7.2 KiB
C

/**
* \file
* \brief Event queue implementatino
*
* This code implements a thread-safe queue of pending events which are
* serviced by a single waitset.
*
* [!] WARNING: current realization of event queues is unsuitable for
* cross-core operation. Crux of the issue: trigger channel for waitset
* belonging to *another* core but waitsets are assumed to be local to a
* dispatcher.
*
* Example scenario:
* - Consumer C (on core 0) calls get_next_event and blocks on
* ws->waiting_threads.
* - Producer P (on core 1) invokes event_queue_add which in turn triggers
* channel. P disables its dispatcher, unblocks a thread and C is returned.
* Code assumes remote wakeup should not occur but C is on core 0.
*
* For further details see:
* https://lists.inf.ethz.ch/mailman/private/aos/2013/002746.html
*/
/*
* Copyright (c) 2010, 2012, 2015, ETH Zurich.
* All rights reserved.
*
* This file is distributed under the terms in the attached LICENSE file.
* If you do not find this file, copies can be found by writing to:
* ETH Zurich D-INFK, Universitaetsstrasse 6, CH-8092 Zurich. Attn: Systems Group.
*/
#include <aos/aos.h>
#include <aos/event_queue.h>
#include <aos/waitset_chan.h>
/**
* \brief Initialise a new event queue
*
* \param q Storage for event queue
* \param waitset Waitset that will service the queue
* \param mode Operating mode for the queue
*/
void event_queue_init(struct event_queue *q, struct waitset *waitset,
enum event_queue_mode mode)
{
waitset_chanstate_init(&q->waitset_state, CHANTYPE_EVENT_QUEUE);
thread_mutex_init(&q->mutex);
q->head = q->tail = NULL;
q->waitset = waitset;
q->mode = mode;
}
static struct event_queue_node *next_event(struct event_queue *q)
{
// dequeue the next node from the head
struct event_queue_node *qn = q->head;
if (qn == NULL) {
return NULL;
}
assert(qn->prev == NULL);
if (qn->next == NULL) {
assert(q->tail == qn);
q->head = q->tail = NULL;
} else {
qn->next->prev = NULL;
q->head = qn->next;
}
return qn;
}
static void event_queue_runner(void *arg)
{
struct event_queue *q = arg;
errval_t err;
assert(q->mode == EVENT_QUEUE_CONTINUOUS);
thread_mutex_lock(&q->mutex);
// dequeue the next node from the head
struct event_queue_node *qn = next_event(q);
if (qn == NULL) {
// an event was cancelled while we were pending
thread_mutex_unlock(&q->mutex);
return;
}
if (q->head != NULL) {
// queue is non-empty: trigger ourselves again
// (note: event registrations are single shot)
struct event_closure self = {
.handler = event_queue_runner,
.arg = arg
};
err = waitset_chan_trigger_closure(q->waitset, &q->waitset_state, self);
assert(err_is_ok(err)); // shouldn't fail
}
qn->run = true;
thread_mutex_unlock(&q->mutex);
// run closure
qn->event.handler(qn->event.arg);
}
/**
* \brief Cancel the runner if mode is continuous and queue is empty.
*
* Must hold this event_queue's mutex.
*
* \param q The event queue
*/
static errval_t
event_queue_cancel_runner(struct event_queue *q)
{
errval_t err = SYS_ERR_OK;
if (q->head == NULL && q->mode == EVENT_QUEUE_CONTINUOUS) {
assert(q->tail == NULL);
err = waitset_chan_deregister(&q->waitset_state);
if (err_is_fail(err)) {
// can fail if the event already fired, but this is ok
if (err_no(err) == LIB_ERR_CHAN_NOT_REGISTERED) {
err = SYS_ERR_OK;
}
}
}
return err;
}
/**
* \brief Add a new event to an event queue
*
* \param q Event queue
* \param qn Storage for queue node (uninitialised)
* \param event Event closure
*/
void event_queue_add(struct event_queue *q, struct event_queue_node *qn,
struct event_closure event)
{
errval_t err;
qn->event = event;
qn->run = false;
thread_mutex_lock(&q->mutex);
// enqueue at tail
if (q->tail == NULL) {
assert(q->head == NULL);
qn->next = qn->prev = NULL;
q->head = q->tail = qn;
// was empty: need to trigger queue runner if in continuous mode
if (q->mode == EVENT_QUEUE_CONTINUOUS) {
struct event_closure runner = {
.handler = event_queue_runner,
.arg = q
};
err = waitset_chan_trigger_closure(q->waitset, &q->waitset_state,
runner);
// apparently there's a situation when we dont really need to
// trigger the queue runner here, as we can get an
// LIB_ERR_CHAN_ALREADY REGISTERED error from the call.
// -SG, 2013-07-31
assert(err_is_ok(err) ||
err_no(err) == LIB_ERR_CHAN_ALREADY_REGISTERED);
}
} else {
assert(q->tail != qn); // don't re-enqueue the same node!
assert(q->tail->next == NULL);
q->tail->next = qn;
qn->prev = q->tail;
qn->next = NULL;
q->tail = qn;
// runner is already active if it needs to be, don't need to do anything
}
thread_mutex_unlock(&q->mutex);
}
/**
* \brief Cancel an event previously added to an event queue
*
* \param q Event queue
* \param qn Queue node which was previously added to #q by event_queue_add()
*/
errval_t event_queue_cancel(struct event_queue *q, struct event_queue_node *qn)
{
errval_t err = SYS_ERR_OK;
if (qn->run) {
return LIB_ERR_EVENT_ALREADY_RUN;
}
thread_mutex_lock(&q->mutex);
if (qn->run) {
thread_mutex_unlock(&q->mutex);
return LIB_ERR_EVENT_ALREADY_RUN;
}
// dequeue
if (qn->next == NULL) {
assert(q->tail == qn);
q->tail = qn->prev;
} else {
qn->next->prev = qn->prev;
}
if (qn->prev == NULL) {
assert(q->head == qn);
q->head = qn->next;
} else {
qn->prev->next = qn->next;
}
// if the queue is now empty, we should cancel the runner
err = event_queue_cancel_runner(q);
thread_mutex_unlock(&q->mutex);
return err;
}
/**
* \brief Flush all pending events from the event queue.
*
* \param q Event queue.
*/
errval_t
event_queue_flush(struct event_queue *q) {
errval_t err = SYS_ERR_OK;
thread_mutex_lock(&q->mutex);
struct event_queue_node *qn;
do {
qn = next_event(q);
} while (qn);
err = event_queue_cancel_runner(q);
thread_mutex_unlock(&q->mutex);
return err;
}
/**
* \brief Trigger the next event on a queue which is operating in one-shot mode
*
* Must not be called before the previously-triggered event has run.
*
* \param q Event queue
*/
errval_t event_queue_trigger(struct event_queue *q)
{
assert(q->mode == EVENT_QUEUE_ONESHOT);
thread_mutex_lock(&q->mutex);
struct event_queue_node *qn = next_event(q);
if (qn == NULL) {
thread_mutex_unlock(&q->mutex);
return LIB_ERR_EVENT_QUEUE_EMPTY;
}
qn->run = true;
thread_mutex_unlock(&q->mutex);
// trigger closure on waitset
return waitset_chan_trigger_closure(q->waitset, &q->waitset_state, qn->event);
}