Merge pull request #15442 from maribu/mutex_abort
core/mutex: Add mutex_cancel
This commit is contained in:
commit
229291ef01
@ -126,6 +126,18 @@ typedef struct {
|
|||||||
list_node_t queue;
|
list_node_t queue;
|
||||||
} mutex_t;
|
} mutex_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A cancellation structure for use with @ref mutex_lock_cancelable
|
||||||
|
* and @ref mutex_cancel
|
||||||
|
*
|
||||||
|
* @note The contents of this structure are internal.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
mutex_t *mutex; /**< The mutex to lock */
|
||||||
|
thread_t *thread; /**< The thread trying to lock the mutex */
|
||||||
|
uint8_t cancelled; /**< Flag whether the mutex has been cancelled */
|
||||||
|
} mutex_cancel_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Static initializer for mutex_t.
|
* @brief Static initializer for mutex_t.
|
||||||
* @details This initializer is preferable to mutex_init().
|
* @details This initializer is preferable to mutex_init().
|
||||||
@ -158,6 +170,23 @@ static inline void mutex_init(mutex_t *mutex)
|
|||||||
mutex->queue.next = NULL;
|
mutex->queue.next = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize a mutex cancellation structure
|
||||||
|
* @param mutex The mutex that the calling thread wants to lock
|
||||||
|
* @return The cancellation structure for use with @ref mutex_lock_cancelable
|
||||||
|
* and @ref mutex_cancel
|
||||||
|
*
|
||||||
|
* @note This function is considered internal. Out of tree users should be
|
||||||
|
* aware that breaking API changes or removal of this API without
|
||||||
|
* an deprecation period might happen.
|
||||||
|
*/
|
||||||
|
static inline mutex_cancel_t mutex_cancel_init(mutex_t *mutex)
|
||||||
|
{
|
||||||
|
mutex_cancel_t result = { mutex, thread_get_active(), 0 };
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Tries to get a mutex, non-blocking.
|
* @brief Tries to get a mutex, non-blocking.
|
||||||
*
|
*
|
||||||
@ -174,10 +203,11 @@ static inline int mutex_trylock(mutex_t *mutex)
|
|||||||
{
|
{
|
||||||
unsigned irq_state = irq_disable();
|
unsigned irq_state = irq_disable();
|
||||||
int retval = 0;
|
int retval = 0;
|
||||||
|
|
||||||
if (mutex->queue.next == NULL) {
|
if (mutex->queue.next == NULL) {
|
||||||
mutex->queue.next = MUTEX_LOCKED;
|
mutex->queue.next = MUTEX_LOCKED;
|
||||||
retval = 1;
|
retval = 1;
|
||||||
};
|
}
|
||||||
irq_restore(irq_state);
|
irq_restore(irq_state);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
@ -195,6 +225,30 @@ static inline int mutex_trylock(mutex_t *mutex)
|
|||||||
*/
|
*/
|
||||||
void mutex_lock(mutex_t *mutex);
|
void mutex_lock(mutex_t *mutex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Locks a mutex, blocking. This function can be canceled.
|
||||||
|
*
|
||||||
|
* @param[in,out] mc Mutex cancellation structure to work on
|
||||||
|
*
|
||||||
|
* @retval 0 The mutex was locked by the caller
|
||||||
|
* @retval -ECANCELED The mutex was ***NOT*** locked, operation was
|
||||||
|
* canceled. See @ref mutex_cancel
|
||||||
|
*
|
||||||
|
* @note This function is considered internal. Out of tree users should be
|
||||||
|
* aware that breaking API changes or removal of this API without
|
||||||
|
* an deprecation period might happen.
|
||||||
|
*
|
||||||
|
* @pre Must be called in thread context
|
||||||
|
* @pre @p mc has been initialized with @ref mutex_cancel_init by the
|
||||||
|
* calling thread.
|
||||||
|
* @pre @p mc has ***NOT*** been used for previous calls to
|
||||||
|
* this function. (Reinitialize before reusing!)
|
||||||
|
*
|
||||||
|
* @post The mutex referred to by @p mc is locked and held by the calling
|
||||||
|
* thread, unless `-ECANCELED` was returned.
|
||||||
|
*/
|
||||||
|
int mutex_lock_cancelable(mutex_cancel_t *mc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Unlocks the mutex.
|
* @brief Unlocks the mutex.
|
||||||
*
|
*
|
||||||
@ -215,6 +269,71 @@ void mutex_unlock(mutex_t *mutex);
|
|||||||
*/
|
*/
|
||||||
void mutex_unlock_and_sleep(mutex_t *mutex);
|
void mutex_unlock_and_sleep(mutex_t *mutex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Cancels a call to @ref mutex_lock_cancelable
|
||||||
|
*
|
||||||
|
* @param[in,out] mc Mutex cancellation structure referring to the
|
||||||
|
* thread calling @ref mutex_lock_cancelable and to
|
||||||
|
* the mutex to cancel the operation on
|
||||||
|
*
|
||||||
|
* @note This function is considered internal. Out of tree users should be
|
||||||
|
* aware that breaking API changes or removal of this API without
|
||||||
|
* an deprecation period might happen.
|
||||||
|
*
|
||||||
|
* @pre @p mc is used to cancel at most one call to
|
||||||
|
* @ref mutex_lock_cancelable. (You can reinitialize the same memory
|
||||||
|
* to safely reuse it.)
|
||||||
|
* @warning You ***MUST NOT*** call this function once the thread referred to by
|
||||||
|
* @p mc re-uses the mutex object referred to by @p mc (not counting
|
||||||
|
* the call to @ref mutex_lock_cancelable @p mc was used in).
|
||||||
|
* @note It is safe to call this function from IRQ context, e.g. from a timer
|
||||||
|
* interrupt.
|
||||||
|
* @note It is safe to call this function more than once on the same @p mc
|
||||||
|
* while it is still valid (see the warning above). The first call will
|
||||||
|
* cancel the operation and subsequent calls will have no effect.
|
||||||
|
*
|
||||||
|
* @details If @p thread is currently running (or pending), a subsequent call
|
||||||
|
* from @p thread to @ref mutex_lock_cancelable will also fail
|
||||||
|
*
|
||||||
|
* Canonical use:
|
||||||
|
*
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
|
||||||
|
* static void timeout_cb(void *_arg) {
|
||||||
|
* mutex_cancel(arg);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* int ztimer_mutex_lock_timeout(ztimer_clock_t *clock, mutex_t *mutex,
|
||||||
|
* uint32_t timeout)
|
||||||
|
* {
|
||||||
|
* mutex_cancel_t mc = mutex_cancel_init(mutex);
|
||||||
|
* ztimer_t t;
|
||||||
|
* t.callback = timeout_cb;
|
||||||
|
* t.arg = &mc;
|
||||||
|
* ztimer_set(clock, &t, timeout);
|
||||||
|
* if (0 == mutex_lock_cancelable(mutex)) {
|
||||||
|
* ztimer_remove(clock, &t);
|
||||||
|
* return 0;
|
||||||
|
* }
|
||||||
|
* return -ECANCELED;
|
||||||
|
* }
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
*
|
||||||
|
* In the above example a simple implementation of how to implement mutex
|
||||||
|
* locking with a timeout is given. There are two corner cases:
|
||||||
|
*
|
||||||
|
* 1. The call to @ref mutex_cancel could occur *before* the call to
|
||||||
|
* @ref mutex_lock_cancelable. (E.g. for `timeout == 0`.)
|
||||||
|
* 2. The call to @ref mutex_cancel could occur right after the mutex was
|
||||||
|
* *successfully* obtained, but before `ztimer_remove()` was executed.
|
||||||
|
*
|
||||||
|
* In the first corner case the cancellation is stored in @p mc. Hence, the
|
||||||
|
* subsequent call to @ref mutex_lock_cancelable gets indeed canceled. In the
|
||||||
|
* second corner case the cancellation is also stored in @p mc but never used -
|
||||||
|
* the mutex cancellation structure @p mc is not allowed to be reused without
|
||||||
|
* reinitialization.
|
||||||
|
*/
|
||||||
|
void mutex_cancel(mutex_cancel_t *mc);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
63
core/mutex.c
63
core/mutex.c
@ -82,6 +82,39 @@ void mutex_lock(mutex_t *mutex)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int mutex_lock_cancelable(mutex_cancel_t *mc)
|
||||||
|
{
|
||||||
|
unsigned irq_state = irq_disable();
|
||||||
|
|
||||||
|
DEBUG("PID[%" PRIkernel_pid "] mutex_lock_cancelable()\n",
|
||||||
|
thread_getpid());
|
||||||
|
|
||||||
|
if (mc->cancelled) {
|
||||||
|
DEBUG("PID[%" PRIkernel_pid "] mutex_lock_cancelable cancelled "
|
||||||
|
"early.\n", thread_getpid());
|
||||||
|
irq_restore(irq_state);
|
||||||
|
return -ECANCELED;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_t *mutex = mc->mutex;
|
||||||
|
if (mutex->queue.next == NULL) {
|
||||||
|
/* mutex is unlocked. */
|
||||||
|
mutex->queue.next = MUTEX_LOCKED;
|
||||||
|
DEBUG("PID[%" PRIkernel_pid "] mutex_lock_cancelable() early out.\n",
|
||||||
|
thread_getpid());
|
||||||
|
irq_restore(irq_state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_block(mutex, irq_state);
|
||||||
|
if (mc->cancelled) {
|
||||||
|
DEBUG("PID[%" PRIkernel_pid "] mutex_lock_cancelable() "
|
||||||
|
"cancelled.\n", thread_getpid());
|
||||||
|
}
|
||||||
|
return (mc->cancelled) ? -ECANCELED: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void mutex_unlock(mutex_t *mutex)
|
void mutex_unlock(mutex_t *mutex)
|
||||||
{
|
{
|
||||||
unsigned irqstate = irq_disable();
|
unsigned irqstate = irq_disable();
|
||||||
@ -148,3 +181,33 @@ void mutex_unlock_and_sleep(mutex_t *mutex)
|
|||||||
irq_restore(irqstate);
|
irq_restore(irqstate);
|
||||||
thread_yield_higher();
|
thread_yield_higher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mutex_cancel(mutex_cancel_t *mc)
|
||||||
|
{
|
||||||
|
unsigned irq_state = irq_disable();
|
||||||
|
mc->cancelled = 1;
|
||||||
|
|
||||||
|
mutex_t *mutex = mc->mutex;
|
||||||
|
thread_t *thread = mc->thread;
|
||||||
|
if (thread_is_active(thread)) {
|
||||||
|
/* thread is still running or about to run, so it will check
|
||||||
|
* `mc-cancelled` in time */
|
||||||
|
irq_restore(irq_state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((mutex->queue.next != MUTEX_LOCKED)
|
||||||
|
&& (mutex->queue.next != NULL)
|
||||||
|
&& list_remove(&mutex->queue, (list_node_t *)&thread->rq_entry)) {
|
||||||
|
/* Thread was queued and removed from list, wake it up */
|
||||||
|
if (mutex->queue.next == NULL) {
|
||||||
|
mutex->queue.next = MUTEX_LOCKED;
|
||||||
|
}
|
||||||
|
sched_set_status(thread, STATUS_PENDING);
|
||||||
|
irq_restore(irq_state);
|
||||||
|
sched_switch(thread->priority);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
irq_restore(irq_state);
|
||||||
|
}
|
||||||
|
|||||||
5
tests/mutex_cancel/Makefile
Normal file
5
tests/mutex_cancel/Makefile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
include ../Makefile.tests_common
|
||||||
|
|
||||||
|
USEMODULE += xtimer
|
||||||
|
|
||||||
|
include $(RIOTBASE)/Makefile.include
|
||||||
79
tests/mutex_cancel/main.c
Normal file
79
tests/mutex_cancel/main.c
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg
|
||||||
|
*
|
||||||
|
* This file is subject to the terms and conditions of the GNU Lesser
|
||||||
|
* General Public License v2.1. See the file LICENSE in the top level
|
||||||
|
* directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ingroup tests
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @brief Test application for testing the mutex_cancel function in
|
||||||
|
* core_mutex
|
||||||
|
*
|
||||||
|
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "mutex.h"
|
||||||
|
#include "test_utils/expect.h"
|
||||||
|
#include "thread.h"
|
||||||
|
#include "xtimer.h"
|
||||||
|
|
||||||
|
static mutex_t testlock = MUTEX_INIT;
|
||||||
|
|
||||||
|
static void cb_unlock(void *mutex)
|
||||||
|
{
|
||||||
|
mutex_unlock(mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cb_cancel(void *mc)
|
||||||
|
{
|
||||||
|
mutex_cancel(mc);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
xtimer_t xt;
|
||||||
|
puts(
|
||||||
|
"Test Application for mutex_cancel / mutex_lock_cancelable\n"
|
||||||
|
"=========================================================\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
printf("%s: ", "Test without cancellation");
|
||||||
|
mutex_cancel_t mc = mutex_cancel_init(&testlock);
|
||||||
|
expect(mutex_lock_cancelable(&mc) == 0);
|
||||||
|
puts("OK");
|
||||||
|
|
||||||
|
printf("%s: ", "Test early cancellation");
|
||||||
|
mc = mutex_cancel_init(&testlock);
|
||||||
|
mutex_cancel(&mc);
|
||||||
|
expect(mutex_lock_cancelable(&mc) == -ECANCELED);
|
||||||
|
puts("OK");
|
||||||
|
|
||||||
|
printf("%s: ", "Verify no side effects on subsequent calls");
|
||||||
|
mc = mutex_cancel_init(&testlock);
|
||||||
|
xt.callback = cb_unlock;
|
||||||
|
xt.arg = &testlock;
|
||||||
|
xtimer_set(&xt, US_PER_MS * 10);
|
||||||
|
expect(mutex_lock_cancelable(&mc) == 0);
|
||||||
|
puts("OK");
|
||||||
|
|
||||||
|
printf("%s: ", "Test late cancellation");
|
||||||
|
mc = mutex_cancel_init(&testlock);
|
||||||
|
xt.callback = cb_cancel;
|
||||||
|
xt.arg = &mc;
|
||||||
|
xtimer_set(&xt, US_PER_MS * 10);
|
||||||
|
expect(mutex_lock_cancelable(&mc) == -ECANCELED);
|
||||||
|
puts("OK");
|
||||||
|
|
||||||
|
puts("TEST PASSED");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
20
tests/mutex_cancel/tests/01-run.py
Executable file
20
tests/mutex_cancel/tests/01-run.py
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg
|
||||||
|
#
|
||||||
|
# This file is subject to the terms and conditions of the GNU Lesser
|
||||||
|
# General Public License v2.1. See the file LICENSE in the top level
|
||||||
|
# directory for more details.
|
||||||
|
|
||||||
|
# @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from testrunner import run
|
||||||
|
|
||||||
|
|
||||||
|
def testfunc(child):
|
||||||
|
child.expect("TEST PASSED")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(run(testfunc))
|
||||||
Loading…
x
Reference in New Issue
Block a user