pthread: Add support for PTHREAD_COND_INITIALIZER

Includes unit test for condition variables in C (previous test was C++ only)
pull/7041/head
Angus Gratton 2021-04-23 10:01:37 +10:00
rodzic e6d15a0975
commit a6dea64106
5 zmienionych plików z 160 dodań i 9 usunięć

Wyświetl plik

@ -66,7 +66,7 @@ typedef struct {
static SemaphoreHandle_t s_threads_mux = NULL;
static portMUX_TYPE s_mutex_init_lock = portMUX_INITIALIZER_UNLOCKED;
portMUX_TYPE pthread_lazy_init_lock = portMUX_INITIALIZER_UNLOCKED; // Used for mutexes and cond vars
static SLIST_HEAD(esp_thread_list_head, esp_pthread_entry) s_threads_list
= SLIST_HEAD_INITIALIZER(s_threads_list);
static pthread_key_t s_pthread_cfg_key;
@ -581,6 +581,10 @@ int pthread_mutex_destroy(pthread_mutex_t *mutex)
if (!mutex) {
return EINVAL;
}
if ((intptr_t) *mutex == PTHREAD_MUTEX_INITIALIZER) {
return 0; // Static mutex was never initialized
}
mux = (esp_pthread_mutex_t *)*mutex;
if (!mux) {
return EINVAL;
@ -634,11 +638,11 @@ static int pthread_mutex_init_if_static(pthread_mutex_t *mutex)
{
int res = 0;
if ((intptr_t) *mutex == PTHREAD_MUTEX_INITIALIZER) {
portENTER_CRITICAL(&s_mutex_init_lock);
portENTER_CRITICAL(&pthread_lazy_init_lock);
if ((intptr_t) *mutex == PTHREAD_MUTEX_INITIALIZER) {
res = pthread_mutex_init(mutex, NULL);
}
portEXIT_CRITICAL(&s_mutex_init_lock);
portEXIT_CRITICAL(&pthread_lazy_init_lock);
}
return res;
}

Wyświetl plik

@ -26,6 +26,7 @@
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/list.h"
#include "pthread_internal.h"
#include <sys/queue.h>
#include <sys/time.h>
@ -43,12 +44,32 @@ typedef struct esp_pthread_cond {
TAILQ_HEAD(, esp_pthread_cond_waiter) waiter_list; ///< head of the list of semaphores
} esp_pthread_cond_t;
int pthread_cond_signal(pthread_cond_t *cv)
static int s_check_and_init_if_static(pthread_cond_t *cv)
{
int res = 0;
if (cv == NULL || *cv == (pthread_cond_t) 0) {
return EINVAL;
}
if (*cv == PTHREAD_COND_INITIALIZER) {
portENTER_CRITICAL(&pthread_lazy_init_lock);
if (*cv == PTHREAD_COND_INITIALIZER) {
res = pthread_cond_init(cv, NULL);
}
portEXIT_CRITICAL(&pthread_lazy_init_lock);
}
return res;
}
int pthread_cond_signal(pthread_cond_t *cv)
{
int res = s_check_and_init_if_static(cv);
if (res) {
return res;
}
esp_pthread_cond_t *cond = (esp_pthread_cond_t *) *cv;
_lock_acquire_recursive(&cond->lock);
@ -64,8 +85,9 @@ int pthread_cond_signal(pthread_cond_t *cv)
int pthread_cond_broadcast(pthread_cond_t *cv)
{
if (cv == NULL || *cv == (pthread_cond_t) 0) {
return EINVAL;
int res = s_check_and_init_if_static(cv);
if (res) {
return res;
}
esp_pthread_cond_t *cond = (esp_pthread_cond_t *) *cv;
@ -90,8 +112,9 @@ int pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *mut, const struc
int ret;
TickType_t timeout_ticks;
if (cv == NULL || *cv == (pthread_cond_t) 0) {
return EINVAL;
int res = s_check_and_init_if_static(cv);
if (res) {
return res;
}
esp_pthread_cond_t *cond = (esp_pthread_cond_t *) *cv;
@ -180,8 +203,14 @@ int pthread_cond_destroy(pthread_cond_t *cv)
if (cv == NULL || *cv == (pthread_cond_t) 0) {
return EINVAL;
}
if (*cv == PTHREAD_COND_INITIALIZER) {
return 0; // never initialized
}
esp_pthread_cond_t *cond = (esp_pthread_cond_t *) *cv;
if (!cond) {
return EINVAL;
}
_lock_acquire_recursive(&cond->lock);
if (!TAILQ_EMPTY(&cond->waiter_list)) {

Wyświetl plik

@ -14,3 +14,5 @@
#pragma once
void pthread_internal_local_storage_destructor_callback(void);
extern portMUX_TYPE pthread_lazy_init_lock;

Wyświetl plik

@ -0,0 +1,115 @@
#include <pthread.h>
#include "unity.h"
typedef struct {
pthread_cond_t *cond;
pthread_mutex_t *mutex;
unsigned delay_ms;
} thread_args_t;
static void *thread_signals(void *arg)
{
const thread_args_t *targs = (thread_args_t *)arg;
int r;
r = pthread_mutex_lock(targs->mutex);
TEST_ASSERT_EQUAL_INT(0, r);
r = pthread_cond_signal(targs->cond);
TEST_ASSERT_EQUAL_INT(0, r);
r = pthread_mutex_unlock(targs->mutex);
TEST_ASSERT_EQUAL_INT(0, r);
usleep(targs->delay_ms * 1000);
r = pthread_mutex_lock(targs->mutex);
TEST_ASSERT_EQUAL_INT(0, r);
r = pthread_cond_broadcast(targs->cond);
TEST_ASSERT_EQUAL_INT(0, r);
r = pthread_mutex_unlock(targs->mutex);
TEST_ASSERT_EQUAL_INT(0, r);
return NULL;
}
static void *thread_waits(void *arg)
{
const thread_args_t *targs = (thread_args_t *)arg;
int r;
r = pthread_mutex_lock(targs->mutex);
TEST_ASSERT_EQUAL_INT(0, r);
r = pthread_cond_wait(targs->cond, targs->mutex);
TEST_ASSERT_EQUAL_INT(0, r);
r = pthread_mutex_unlock(targs->mutex);
TEST_ASSERT_EQUAL_INT(0, r);
usleep(targs->delay_ms * 1000);
r = pthread_mutex_lock(targs->mutex);
TEST_ASSERT_EQUAL_INT(0, r);
struct timespec two_seconds;
clock_gettime(CLOCK_REALTIME, &two_seconds);
two_seconds.tv_sec += 2;
r = pthread_cond_timedwait(targs->cond, targs->mutex, &two_seconds);
TEST_ASSERT_EQUAL_INT(0, r);
r = pthread_mutex_unlock(targs->mutex);
TEST_ASSERT_EQUAL_INT(0, r);
return NULL;
}
#define NUM_THREADS 3
TEST_CASE("pthread cond wait", "[pthread]")
{
int r;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
struct {
thread_args_t args;
pthread_t thread;
} wait[NUM_THREADS];
struct {
thread_args_t args;
pthread_t thread;
} signal[NUM_THREADS];
wait[0].args.delay_ms = 50;
wait[1].args.delay_ms = 100;
wait[2].args.delay_ms = 200;
signal[0].args.delay_ms = 30;
signal[1].args.delay_ms = 150;
signal[2].args.delay_ms = 500; // highest delay, ensure that broadcast will be received by all waiter threads
for (int i = 0; i < NUM_THREADS; i++) {
wait[i].args.cond = &cond;
wait[i].args.mutex = &mutex;
signal[i].args.cond = &cond;
signal[i].args.mutex = &mutex;
r = pthread_create(&signal[i].thread, NULL, thread_signals, &signal[i].args);
TEST_ASSERT_EQUAL_INT(0, r);
r = pthread_create(&wait[i].thread, NULL, thread_waits, &wait[i].args);
TEST_ASSERT_EQUAL_INT(0, r);
}
for (int i = 0; i < NUM_THREADS; i++) {
r = pthread_join(signal[i].thread, NULL);
TEST_ASSERT_EQUAL_INT(0, r);
pthread_join(wait[i].thread, NULL);
TEST_ASSERT_EQUAL_INT(0, r);
}
pthread_mutex_destroy(&cond);
pthread_mutex_destroy(&mutex);
}

Wyświetl plik

@ -92,6 +92,8 @@ Condition Variables
* ``pthread_cond_wait()``
* ``pthread_cond_timedwait()``
Static initializer constant ``PTHREAD_COND_INITIALIZER`` is supported.
.. note:: These functions can be called from tasks created using either pthread or FreeRTOS APIs
Thread-Specific Data
@ -113,7 +115,6 @@ The ``pthread.h`` header is a standard header and includes additional APIs and f
* ``pthread_cancel()`` returns ``ENOSYS`` if called.
* ``pthread_condattr_init()`` returns ``ENOSYS`` if called.
* ``PTHREAD_COND_INITIALIZER`` static initializer constant is not implemented and will crash if passed to a function.
Other POSIX Threads functions (not listed here) are not implemented and will produce either a compiler or a linker error if referenced from an ESP-IDF application. If you identify a useful API that you would like to see implemented in ESP-IDF, please open a `feature request on GitHub <https://github.com/espressif/esp-idf/issues>` with the details.