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

960 lines
30 KiB
C

/**
* \file
* \brief Waitset and low-level event handling mechanism
*
* A "wait set" is a collection of channels to wait on, much like an
* FDSET in POSIX. There should be a default, static wait set for each
* dispatcher. Threads which wait for events specify the wait set they
* are waiting on.
*/
/*
* Copyright (c) 2009-2012, ETH Zurich.
* Copyright (c) 2015, Hewlett Packard Enterprise Development LP.
* 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, Universitaetstr. 6, CH-8092 Zurich. Attn: Systems Group.
*/
#include <aos/aos.h>
#include <aos/waitset.h>
#include <aos/waitset_chan.h>
#include <aos/threads.h>
#include <aos/dispatch.h>
#include "threads_priv.h"
#include "waitset_chan_priv.h"
#include <stdio.h>
#include <string.h>
/// Dequeue a chanstate from a queue
static void dequeue(struct waitset_chanstate **queue, struct waitset_chanstate *chan)
{
if (chan->next == chan) {
assert(chan->prev == chan);
assert(*queue == chan);
*queue = NULL;
} else {
chan->prev->next = chan->next;
chan->next->prev = chan->prev;
if (*queue == chan) {
*queue = chan->next;
}
}
chan->prev = chan->next = NULL;
}
/// Enqueue a chanstate on a queue
static void enqueue(struct waitset_chanstate **queue, struct waitset_chanstate *chan)
{
if (*queue == NULL) {
*queue = chan;
chan->next = chan->prev = chan;
} else {
chan->next = *queue;
chan->prev = (*queue)->prev;
chan->next->prev = chan;
chan->prev->next = chan;
}
}
/// Dequeue a chanstate from polled queue
static void dequeue_polled(struct waitset_chanstate **queue,
struct waitset_chanstate *chan)
{
if (chan->polled_next == chan) {
assert(chan->polled_prev == chan);
assert(*queue == chan);
*queue = NULL;
} else {
chan->polled_prev->polled_next = chan->polled_next;
chan->polled_next->polled_prev = chan->polled_prev;
if (*queue == chan) {
*queue = chan->polled_next;
}
}
chan->polled_prev = chan->polled_next = NULL;
}
/// Enqueue a chanstate on polled queue
static void enqueue_polled(struct waitset_chanstate **queue,
struct waitset_chanstate *chan)
{
if (*queue == NULL) {
*queue = chan;
chan->polled_next = chan->polled_prev = chan;
} else {
chan->polled_next = *queue;
chan->polled_prev = (*queue)->polled_prev;
chan->polled_next->polled_prev = chan;
chan->polled_prev->polled_next = chan;
}
}
/**
* \brief Initialise a new waitset
*/
void waitset_init(struct waitset *ws)
{
assert(ws != NULL);
ws->pending = ws->polled = ws->idle = ws->waiting = NULL;
ws->waiting_threads = NULL;
}
/**
* \brief Destroy a previously initialised waitset
*/
errval_t waitset_destroy(struct waitset *ws)
{
assert(ws != NULL);
// FIXME: do we want to support cancelling all the pending events/channels?
if (ws->pending || ws->waiting_threads) {
return LIB_ERR_WAITSET_IN_USE;
}
// remove idle and polled channels from waitset
struct waitset_chanstate *chan, *next;
for (chan = ws->idle; chan != NULL; chan = next) {
next = chan->next;
assert(chan->state == CHAN_IDLE);
assert(chan->waitset == ws);
chan->waitset = NULL;
chan->next = chan->prev = NULL;
if (next == ws->idle) {
break;
}
}
ws->idle = NULL;
for (chan = ws->polled; chan != NULL; chan = next) {
next = chan->next;
assert(chan->state == CHAN_POLLED);
assert(chan->waitset == ws);
chan->waitset = NULL;
chan->next = chan->prev = NULL;
if (next == ws->polled) {
break;
}
}
ws->polled = NULL;
return SYS_ERR_OK;
}
/// Check if the thread can receive the event
static bool waitset_can_receive(struct waitset_chanstate *chan,
struct thread *thread)
{
bool res = false;
if (chan->wait_for) // if a thread is waiting for this specific event
res = chan->wait_for == thread;
else
res = (chan->token & 1 && !thread->token) // incoming token is a request
// and a thread is not waiting for a token
|| (!chan->token && chan != thread->channel) // there's no token
// and a thread is not waiting specifically for that event
|| (chan->token == thread->token && chan == thread->channel);
// there is a token and it matches thread's token and event
return res;
}
/// Returns a channel with a pending event on the given waitset matching
/// our thread
static struct waitset_chanstate *get_pending_event_disabled(struct waitset *ws,
struct waitset_chanstate *waitfor, struct waitset_chanstate *waitfor2)
{
struct thread *me = thread_self_disabled();
if (waitfor) { // channel that we wait for
if ((waitfor->state == CHAN_PENDING || waitfor->state == CHAN_WAITING)
&& waitset_can_receive(waitfor, me)) {
return waitfor;
}
if (waitfor2 && (waitfor2->state == CHAN_PENDING || waitfor2->state == CHAN_WAITING)
&& waitset_can_receive(waitfor2, me)) {
return waitfor2;
}
return NULL;
}
struct waitset_chanstate *chan;
// check a waiting queue for matching event
for (chan = ws->waiting; chan; ) {
if (waitset_can_receive(chan, me)) {
assert_disabled(chan->state == CHAN_WAITING);
return chan;
}
chan = chan->next;
if (chan == ws->waiting)
break;
}
// check a pending queue for matching event
for (chan = ws->pending; chan;) {
if (waitset_can_receive(chan, me)) {
assert_disabled(chan->state == CHAN_PENDING);
return chan;
}
chan = chan->next;
if (chan == ws->pending)
break;
}
return NULL;
}
/// Check polled channels
void poll_channels_disabled(dispatcher_handle_t handle) {
struct dispatcher_generic *dp = get_dispatcher_generic(handle);
struct waitset_chanstate *chan, *prev_head;
if (!dp->polled_channels)
return;
chan = dp->polled_channels;
do {
bool chan_ready = false;
switch (chan->chantype) {
case CHANTYPE_UMP_IN:
// TODO: To add UMP channel support to waitsets,
// set chan_ready to true here if channel has a message ready
break;
default:
assert_disabled(!ws_chantype_is_polled(chan->chantype));
assert_disabled(!"invalid channel type to poll!");
}
/* Triggering events on the channel might dequeue the channel
* So we advance now and remember the current head, as it might change
*/
chan = chan->polled_next;
prev_head = dp->polled_channels;
if (chan_ready) {
/* We already advanced so we use prev pointer here */
errval_t err = waitset_chan_trigger_disabled(chan->polled_prev, handle);
/* Shouldn't fail */
assert_disabled(err_is_ok(err));
}
/* Circular queue, stop if we arrived at the start again
* If channel was dequeued there are 3 situations:
* 1: The dequeued channel wasn't the head
* This means prev_head == dp->polled_channels and chan is in the queue still so we're fine
* 2: The dequeued channel was the head but there are more nodes.
* This means prev_head == dequeued channel && chan != prev_head, so we're fine
* 3: The dequeued channel was the only node (and the queue is now empty)
* This means prev_head == dequeued channel && chan == dequeued channel, so we'll exit the loop
*/
} while (chan != prev_head);
}
/// Re-register a channel (if persistent)
static void reregister_channel(struct waitset *ws, struct waitset_chanstate *chan,
dispatcher_handle_t handle)
{
assert_disabled(chan->waitset == ws);
if (chan->state == CHAN_PENDING) {
dequeue(&ws->pending, chan);
} else {
assert_disabled(chan->state == CHAN_WAITING);
dequeue(&ws->waiting, chan);
}
chan->token = 0;
if (ws_chantype_is_polled(chan->chantype)) {
enqueue(&ws->polled, chan);
enqueue_polled(&get_dispatcher_generic(handle)->polled_channels, chan);
chan->state = CHAN_POLLED;
} else {
enqueue(&ws->idle, chan);
chan->state = CHAN_IDLE;
}
}
/// Find a thread that is able to receive an event
static struct thread * find_recipient(struct waitset *ws,
struct waitset_chanstate *channel, struct thread *me)
{
struct thread *t = ws->waiting_threads;
if (!t)
return NULL;
do {
if (waitset_can_receive(channel, t))
return t;
t = t->next;
} while (t != ws->waiting_threads);
return ws->waiting_threads;
}
/// Wake up other thread if there's more pending events
static void wake_up_other_thread(dispatcher_handle_t handle, struct waitset *ws)
{
if (ws->pending && ws->waiting_threads) {
struct thread *t;
t = thread_unblock_one_disabled(handle, &ws->waiting_threads, NULL);
assert_disabled(t == NULL); // shouldn't see a remote thread
}
}
/**
* \brief Get next pending event
*
* Check if there is a pending event that matches current thread and return it.
* Pending events are in a pending queue and in a waiting queue.
* A pending event then will be removed from a pending/waiting queue and become
* unregistered or, if it's persistent, will be re-registered to an idle queue
* or a polled queue (UMP channels) of a waitset.
* If there's no pending event, block this thread.
* If there's a pending event but it doesn't match our thread, don't remove it
* from a pending queue and wake up a matching thread.
* If there's no matching thread, add it to a waiting queue.
*
* \param ws Waitset with sources of events
* \param retchannel Holder of returned event
* \param retclosure Holder of returned closure
* \param waitfor Specific event that we're waiting for (can be NULL)
* \param handle Dispatcher's handle
* \param debug Debug mode (not used)
*/
errval_t get_next_event_disabled(struct waitset *ws,
struct waitset_chanstate **retchannel, struct event_closure *retclosure,
struct waitset_chanstate *waitfor, struct waitset_chanstate *waitfor2,
dispatcher_handle_t handle, bool debug)
{
struct waitset_chanstate * chan;
// debug_printf("%s: %p %p %p %p\n", __func__, __builtin_return_address(0), __builtin_return_address(1), __builtin_return_address(2), __builtin_return_address(3));
for (;;) {
chan = get_pending_event_disabled(ws, waitfor, waitfor2); // get our event
if (chan) {
*retchannel = chan;
*retclosure = chan->closure;
chan->wait_for = NULL;
chan->token = 0;
if (chan->persistent) {
reregister_channel(ws, chan, handle);
} else {
waitset_chan_deregister_disabled(chan, handle);
}
wake_up_other_thread(handle, ws);
// debug_printf("%s.%d: %p\n", __func__, __LINE__, retclosure->handler);
return SYS_ERR_OK;
}
chan = ws->pending; // check a pending queue
if (!chan) { // if nothing then wait
thread_block_disabled(handle, &ws->waiting_threads);
disp_disable();
} else { // something but it's not our event
if (!ws->waiting_threads) { // no other thread interested in
dequeue(&ws->pending, chan);
enqueue(&ws->waiting, chan);
chan->state = CHAN_WAITING;
chan->waitset = ws;
} else {
// find a matching thread
struct thread *t;
for (t = ws->waiting_threads; t; ) {
if (waitset_can_receive(chan, t)) { // match found, wake it
ws->waiting_threads = t;
t = thread_unblock_one_disabled(handle,
&ws->waiting_threads, chan);
assert_disabled(t == NULL); // shouldn't see a remote thread
break;
}
t = t->next;
if (t == ws->waiting_threads) { // no recipient found
dequeue(&ws->pending, chan);
enqueue(&ws->waiting, chan);
chan->state = CHAN_WAITING;
chan->waitset = ws;
break;
}
}
}
}
}
}
/**
* \brief Wait for (block) and return next event on given waitset
*
* Wait until something happens, either activity on some channel, or a deferred
* call, and then return the corresponding closure. This is the core of the
* event-handling system.
*
* \param ws Waitset
* \param retclosure Pointer to storage space for returned event closure
*/
errval_t get_next_event(struct waitset *ws, struct event_closure *retclosure)
{
dispatcher_handle_t handle = disp_disable();
struct waitset_chanstate *channel;
errval_t err = get_next_event_disabled(ws, &channel, retclosure, NULL, NULL,
handle, false);
disp_enable(handle);
return err;
}
/**
* \brief Check if there is an event pending on given waitset
*
* This is essentially a non-blocking variant of get_next_event(). It should be
* used with great care, to avoid the creation of busy-waiting loops.
*
* \param ws Waitset
*
* \returns LIB_ERR_NO_EVENT if nothing is pending
*/
static errval_t check_for_event_disabled(struct waitset *ws, dispatcher_handle_t handle)
{
struct waitset_chanstate *chan;
poll_channels_disabled(handle);
chan = get_pending_event_disabled(ws, NULL, NULL);
if (chan != NULL) {
return SYS_ERR_OK;
}
return LIB_ERR_NO_EVENT;
}
errval_t check_for_event(struct waitset *ws)
{
errval_t err;
assert(ws != NULL);
dispatcher_handle_t handle = disp_disable();
err = check_for_event_disabled(ws, handle);
disp_enable(handle);
return err;
}
/**
* \brief Wait for (block) and dispatch next event on given waitset
*
* Wait until something happens, either activity on some channel, or deferred
* call, and then call the corresponding closure.
*
* \param ws Waitset
*/
errval_t event_dispatch(struct waitset *ws)
{
struct event_closure closure;
errval_t err = get_next_event(ws, &closure);
if (err_is_fail(err)) {
return err;
}
assert(closure.handler != NULL);
closure.handler(closure.arg);
return SYS_ERR_OK;
}
errval_t event_dispatch_debug(struct waitset *ws)
{
struct event_closure closure;
struct waitset_chanstate *channel;
dispatcher_handle_t handle = disp_disable();
errval_t err = get_next_event_disabled(ws, &channel, &closure, NULL, NULL,
handle, true);
disp_enable(handle);
if (err_is_fail(err)) {
return err;
}
assert(closure.handler != NULL);
closure.handler(closure.arg);
return SYS_ERR_OK;
}
/**
* \brief Dispatch events until a specific event is received
*
* Wait for events and dispatch them. If a specific event comes, don't call
* a closure, just return.
*
* \param ws Waitset
* \param waitfor Event, that we are waiting for
* \param error_var Error variable that can be changed by closures
*/
errval_t wait_for_channel(struct waitset *ws, struct waitset_chanstate *waitfor,
errval_t *error_var)
{
assert(waitfor->waitset == ws);
thread_set_local_trigger(NULL);
for (;;) {
struct event_closure closure;
struct waitset_chanstate *channel, *trigger;
trigger = thread_get_local_trigger();
dispatcher_handle_t handle = disp_disable();
errval_t err = get_next_event_disabled(ws, &channel, &closure, waitfor,
trigger ? trigger: waitfor->trigger, handle, false);
disp_enable(handle);
if (err_is_fail(err)) {
assert(0);
return err;
}
if (channel == waitfor)
return SYS_ERR_OK;
assert(!channel->wait_for);
assert(closure.handler != NULL);
closure.handler(closure.arg);
if (err_is_fail(*error_var))
return *error_var;
}
}
/**
* \brief check and dispatch next event on given waitset
*
* Check if there is any pending activity on some channel, or deferred
* call, and then call the corresponding closure.
*
* Do not wait! In case of no pending events, return err LIB_ERR_NO_EVENT.
*
* \param ws Waitset
*/
errval_t event_dispatch_non_block(struct waitset *ws)
{
struct waitset_chanstate *channel;
struct event_closure closure;
assert(ws != NULL);
// are there any pending events on the waitset?
dispatcher_handle_t handle = disp_disable();
errval_t err = check_for_event_disabled(ws, handle);
if (err_is_fail(err)) {
disp_enable(handle);
return err;
}
err = get_next_event_disabled(ws, &channel, &closure, NULL, NULL, handle,
false);
if (err_is_fail(err))
return err;
disp_enable(handle);
assert(closure.handler != NULL);
closure.handler(closure.arg);
return SYS_ERR_OK;
}
/**
* \privatesection
* "Private" functions that are called only by the channel implementations
*/
/**
* \brief Initialise per-channel waitset state
*
* \param chan Channel state
* \param chantype Channel type
*/
void waitset_chanstate_init(struct waitset_chanstate *chan,
enum ws_chantype chantype)
{
assert(chan != NULL);
chan->waitset = NULL;
chan->chantype = chantype;
chan->state = CHAN_UNREGISTERED;
#ifndef NDEBUG
chan->prev = chan->next = NULL;
#endif
chan->persistent = false;
chan->token = 0;
chan->wait_for = NULL;
chan->trigger = NULL;
}
/**
* \brief Destroy previously-initialised per-channel waitset state
* \param chan Channel state
*/
void waitset_chanstate_destroy(struct waitset_chanstate *chan)
{
assert(chan != NULL);
if (chan->waitset != NULL) {
errval_t err = waitset_chan_deregister(chan);
assert(err_is_ok(err)); // can't fail if registered
}
}
/**
* \brief Register a closure to be called when a channel is triggered
*
* In the Future, call the closure on a thread associated with the waitset
* when the channel is triggered. Only one closure may be registered per
* channel state at any one time.
* This function must only be called when disabled.
*
* \param ws Waitset
* \param chan Waitset's per-channel state
* \param closure Event handler
*/
errval_t waitset_chan_register_disabled(struct waitset *ws,
struct waitset_chanstate *chan,
struct event_closure closure)
{
if (chan->waitset != NULL) {
return LIB_ERR_CHAN_ALREADY_REGISTERED;
}
chan->waitset = ws;
chan->token = 0;
// channel must not already be registered!
assert_disabled(chan->next == NULL && chan->prev == NULL);
assert_disabled(chan->state == CHAN_UNREGISTERED);
// this is probably insane! :)
// assert_disabled(closure.handler != NULL);
// store closure
chan->closure = closure;
// enqueue this channel on the waitset's queue of idle channels
enqueue(&ws->idle, chan);
chan->state = CHAN_IDLE;
return SYS_ERR_OK;
}
/**
* \brief Register a closure on a channel, and mark the channel as polled
*
* In the Future, call the closure on a thread associated with the waitset
* when the channel is triggered. Only one closure may be registered per
* channel state at any one time. Additionally, mark the channel as polled.
* This function must only be called when disabled.
*
* \param ws Waitset
* \param chan Waitset's per-channel state
* \param closure Event handler
* \param disp Current dispatcher pointer
*/
errval_t waitset_chan_register_polled_disabled(struct waitset *ws,
struct waitset_chanstate *chan,
struct event_closure closure,
dispatcher_handle_t handle)
{
if (chan->waitset != NULL) {
return LIB_ERR_CHAN_ALREADY_REGISTERED;
}
if (!ws_chantype_is_polled(chan->chantype)) {
return LIB_ERR_CHAN_NOT_POLLED;
}
chan->waitset = ws;
chan->token = 0;
// channel must not already be registered!
assert_disabled(chan->next == NULL && chan->prev == NULL);
assert_disabled(chan->state == CHAN_UNREGISTERED);
// store closure
chan->closure = closure;
// enqueue this channel on the waitset's queue of polled channels
enqueue(&ws->polled, chan);
chan->state = CHAN_POLLED;
enqueue_polled(&get_dispatcher_generic(handle)->polled_channels, chan);
return SYS_ERR_OK;
}
/**
* \brief Register a closure to be called when a channel is triggered
*
* In the Future, call the closure on a thread associated with the waitset
* when the channel is triggered. Only one closure may be registered per
* channel state at any one time.
* This function must only be called when enabled.
*
* \param ws Waitset
* \param chan Waitset's per-channel state
* \param closure Event handler
*/
errval_t waitset_chan_register(struct waitset *ws, struct waitset_chanstate *chan,
struct event_closure closure)
{
dispatcher_handle_t handle = disp_disable();
errval_t err = waitset_chan_register_disabled(ws, chan, closure);
disp_enable(handle);
return err;
}
/**
* \brief Register a closure on a channel, and mark the channel as polled
*
* In the Future, call the closure on a thread associated with the waitset
* when the channel is triggered. Only one closure may be registered per
* channel state at any one time. Additionally, mark the channel as polled.
* This function must only be called when enabled. It is equivalent to
* calling waitset_chan_register() followed by waitset_chan_start_polling().
*
* \param ws Waitset
* \param chan Waitset's per-channel state
* \param closure Event handler
*/
errval_t waitset_chan_register_polled(struct waitset *ws,
struct waitset_chanstate *chan,
struct event_closure closure)
{
dispatcher_handle_t handle = disp_disable();
errval_t err = waitset_chan_register_polled_disabled(ws, chan, closure, handle);
disp_enable(handle);
return err;
}
/**
* \brief Cancel a previous callback registration
*
* Remove the registration for a callback on the given channel.
* This function must only be called when disabled.
*
* \param chan Waitset's per-channel state
*/
errval_t waitset_chan_deregister_disabled(struct waitset_chanstate *chan,
dispatcher_handle_t handle)
{
assert_disabled(chan != NULL);
struct waitset *ws = chan->waitset;
if (ws == NULL) {
return LIB_ERR_CHAN_NOT_REGISTERED;
}
// remove this channel from the queue in which it is waiting
chan->waitset = NULL;
assert_disabled(chan->next != NULL && chan->prev != NULL);
switch (chan->state) {
case CHAN_IDLE:
dequeue(&ws->idle, chan);
break;
case CHAN_POLLED:
dequeue(&ws->polled, chan);
dequeue_polled(&get_dispatcher_generic(handle)->polled_channels, chan);
break;
case CHAN_PENDING:
dequeue(&ws->pending, chan);
break;
case CHAN_WAITING:
dequeue(&ws->waiting, chan);
break;
default:
assert_disabled(!"invalid channel state in deregister");
}
chan->state = CHAN_UNREGISTERED;
chan->wait_for = NULL;
return SYS_ERR_OK;
}
/**
* \brief Cancel a previous callback registration
*
* Remove the registration for a callback on the given channel.
* This function must only be called when enabled.
*
* \param chan Waitset's per-channel state
*/
errval_t waitset_chan_deregister(struct waitset_chanstate *chan)
{
dispatcher_handle_t handle = disp_disable();
errval_t err = waitset_chan_deregister_disabled(chan, handle);
disp_enable(handle);
return err;
}
/**
* \brief Migrate callback registrations to a new waitset.
*
* \param chan Old waitset's per-channel state to migrate
* \param new_ws New waitset to migrate to
*/
void waitset_chan_migrate(struct waitset_chanstate *chan,
struct waitset *new_ws)
{
struct waitset *ws = chan->waitset;
// Only when registered
if(ws == NULL) {
return;
}
switch(chan->state) {
case CHAN_IDLE:
dequeue(&ws->idle, chan);
enqueue(&new_ws->idle, chan);
break;
case CHAN_POLLED:
dequeue(&ws->polled, chan);
enqueue(&new_ws->polled, chan);
break;
case CHAN_PENDING:
dequeue(&ws->pending, chan);
enqueue(&new_ws->pending, chan);
break;
case CHAN_WAITING:
dequeue(&ws->waiting, chan);
enqueue(&new_ws->waiting, chan);
break;
case CHAN_UNREGISTERED:
// Do nothing
break;
}
// Remember new waitset association
chan->waitset = new_ws;
}
/**
* \brief Trigger an event callback on a channel
*
* Marks the given channel as having a pending event, causing some future call
* to get_next_event() to return the registered closure.
* This function must only be called when disabled.
*
* \param chan Waitset's per-channel state
* \param disp Current dispatcher pointer
*/
errval_t waitset_chan_trigger_disabled(struct waitset_chanstate *chan,
dispatcher_handle_t handle)
{
assert_disabled(chan != NULL);
struct waitset *ws = chan->waitset;
assert_disabled(ws != NULL);
assert_disabled(chan->prev != NULL && chan->next != NULL);
// no-op if already pending
if (chan->state == CHAN_PENDING) {
return SYS_ERR_OK;
}
// remove from previous queue (either idle or polled)
if (chan->state == CHAN_IDLE) {
dequeue(&ws->idle, chan);
} else {
assert_disabled(chan->state == CHAN_POLLED);
dequeue(&ws->polled, chan);
dequeue_polled(&get_dispatcher_generic(handle)->polled_channels, chan);
}
// else mark channel pending and move to end of pending event queue
enqueue(&ws->pending, chan);
chan->state = CHAN_PENDING;
// is there a thread blocked on this waitset? if so, awaken it with the event
struct thread *thread = find_recipient(ws, chan, thread_self_disabled());
if (thread) {
struct thread *t;
ws->waiting_threads = thread;
t = thread_unblock_one_disabled(handle, &ws->waiting_threads, chan);
assert_disabled(t == NULL);
}
return SYS_ERR_OK;
}
/**
* \brief Trigger an event callback on a channel
*
* Marks the given channel as having a pending event, causing some future call
* to get_next_event() to return the registered closure.
* This function must only be called when enabled.
*
* \param chan Waitset's per-channel state
* \param disp Current dispatcher pointer
*/
errval_t waitset_chan_trigger(struct waitset_chanstate *chan)
{
dispatcher_handle_t handle = disp_disable();
errval_t err = waitset_chan_trigger_disabled(chan, handle);
disp_enable(handle);
return err;
}
/**
* \brief Trigger a specific event callback on an unregistered channel
*
* This function is equivalent to waitset_chan_register_disabled() immediately
* followed by waitset_chan_trigger_disabled(), but avoids unneccessary queue
* manipulation. This function must only be called when disabled.
*
* \param ws Waitset
* \param chan Waitset's per-channel state
* \param closure Event handler
* \param disp Current dispatcher pointer
*/
errval_t waitset_chan_trigger_closure_disabled(struct waitset *ws,
struct waitset_chanstate *chan,
struct event_closure closure,
dispatcher_handle_t handle)
{
assert_disabled(chan != NULL);
assert_disabled(ws != NULL);
// check if already registered
if (chan->waitset != NULL || chan->state != CHAN_UNREGISTERED) {
return LIB_ERR_CHAN_ALREADY_REGISTERED;
}
assert_disabled(chan->prev == NULL && chan->next == NULL);
// set closure
chan->closure = closure;
// mark channel pending and place on end of pending event queue
chan->waitset = ws;
enqueue(&ws->pending, chan);
// if (first)
// ws->pending = chan;
chan->state = CHAN_PENDING;
// is there a thread blocked on this waitset? if so, awaken it with the event
struct thread *thread = find_recipient(ws, chan, thread_self_disabled());
if (thread) {
struct thread *t;
ws->waiting_threads = thread;
t = thread_unblock_one_disabled(handle, &ws->waiting_threads, chan);
assert_disabled(t == NULL);
}
return SYS_ERR_OK;
}
/**
* \brief Trigger a specific event callback on an unregistered channel
*
* This function is equivalent to waitset_chan_register()
* followed by waitset_chan_trigger(), but avoids unneccessary queue
* manipulation. This function must only be called when enabled.
*
* \param ws Waitset
* \param chan Waitset's per-channel state
* \param closure Event handler
*/
errval_t waitset_chan_trigger_closure(struct waitset *ws,
struct waitset_chanstate *chan,
struct event_closure closure)
{
dispatcher_handle_t disp = disp_disable();
errval_t err = waitset_chan_trigger_closure_disabled(ws, chan, closure, disp);
disp_enable(disp);
return err;
}