diff --git a/repos/libports/lib/symbols/libc b/repos/libports/lib/symbols/libc index 07815ac2a..7c0c7e2b8 100644 --- a/repos/libports/lib/symbols/libc +++ b/repos/libports/lib/symbols/libc @@ -1,3 +1,4 @@ +___mb_cur_max D 50 ___runetype T ___tolower T ___toupper T @@ -7,9 +8,9 @@ __error T __flt_rounds T __fpclassifyd T __fpclassifyf T -__has_sse D 4 __h_errno T __h_errno_set T +__has_sse D 4 __inet_addr T __inet_aton T __inet_nsap_ntoa T @@ -18,7 +19,6 @@ __inet_ntop T __inet_pton T __isthreaded B 4 __mb_cur_max D 8 -___mb_cur_max D 50 __res_init T __res_query T __res_state T @@ -85,8 +85,9 @@ chroot W clearerr T clearerr_unlocked T clock T -clock_gettime W clock_getres W +clock_gettime W +clock_nanosleep W close T closedir T closelog T @@ -194,6 +195,7 @@ free T freeaddrinfo T freebsd7___semctl W freebsd7_semctl T +freeifaddrs T freelocale T freopen T fscanf T @@ -264,7 +266,6 @@ gethostbyname W gethostid T gethostname T getifaddrs T -freeifaddrs T getline T getloadavg T getlogin T @@ -456,7 +457,6 @@ mrand48 T msync T munmap T nanosleep W -clock_nanosleep W newlocale T nextwctype T nftw T @@ -480,7 +480,6 @@ pclose T perror T pipe T poll W -ppoll W popen T posix2time T posix_fadvise T @@ -506,16 +505,17 @@ posix_spawnattr_setschedpolicy T posix_spawnattr_setsigdefault T posix_spawnattr_setsigmask T posix_spawnp T +ppoll W pread T printf T pselect W psignal T pthread_atfork T pthread_attr_destroy T +pthread_attr_get_np T pthread_attr_getdetachstate T pthread_attr_getguardsize T pthread_attr_getinheritsched T -pthread_attr_get_np T pthread_attr_getschedparam T pthread_attr_getschedpolicy T pthread_attr_getscope T @@ -556,6 +556,7 @@ pthread_main_np T pthread_mutex_destroy T pthread_mutex_init T pthread_mutex_lock T +pthread_mutex_timedlock T pthread_mutex_trylock T pthread_mutex_unlock T pthread_mutexattr_destroy T @@ -630,18 +631,18 @@ seed48 T seekdir T select W sem_close T -semctl T sem_destroy T sem_getvalue T -semget W sem_init T sem_open T -semop W sem_post T sem_timedwait T sem_trywait T sem_unlink T sem_wait T +semctl T +semget W +semop W send T sendmsg W sendto T @@ -849,8 +850,8 @@ unvis T uselocale T user_from_uid T usleep W -utimes W utime W +utimes W vasprintf T vdprintf T verr T diff --git a/repos/libports/recipes/pkg/test-pthread/runtime b/repos/libports/recipes/pkg/test-pthread/runtime index 0483db5e5..4c2740696 100644 --- a/repos/libports/recipes/pkg/test-pthread/runtime +++ b/repos/libports/recipes/pkg/test-pthread/runtime @@ -3,7 +3,7 @@ - + --- returning from main --- Error: child "test-pthread" exited diff --git a/repos/libports/src/lib/libc/internal/init.h b/repos/libports/src/lib/libc/internal/init.h index 1e698ca14..a4ac81dc1 100644 --- a/repos/libports/src/lib/libc/internal/init.h +++ b/repos/libports/src/lib/libc/internal/init.h @@ -5,7 +5,7 @@ */ /* - * Copyright (C) 2016-2017 Genode Labs GmbH + * Copyright (C) 2016-2020 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. @@ -30,6 +30,7 @@ namespace Libc { struct Resume; struct Suspend; + struct Monitor; struct Select; struct Current_time; struct Clone_connection; @@ -104,10 +105,10 @@ namespace Libc { void init_socket_fs(Suspend &); /** - * Allow thread.cc to access the 'Genode::Env' (needed for the - * implementation of condition variables with timeout) + * Pthread/semaphore support */ - void init_pthread_support(Genode::Env &env, Suspend &, Resume &); + void init_pthread_support(Monitor &, Suspend &, Resume &); + void init_semaphore_support(Monitor &); struct Config_accessor : Interface { diff --git a/repos/libports/src/lib/libc/internal/kernel.h b/repos/libports/src/lib/libc/internal/kernel.h index e9b07c9d2..17aca1028 100644 --- a/repos/libports/src/lib/libc/internal/kernel.h +++ b/repos/libports/src/lib/libc/internal/kernel.h @@ -7,7 +7,7 @@ */ /* - * Copyright (C) 2016-2019 Genode Labs GmbH + * Copyright (C) 2016-2020 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. @@ -39,6 +39,7 @@ #include #include #include +#include namespace Libc { class Kernel; } @@ -58,6 +59,7 @@ struct Libc::Kernel final : Vfs::Io_response_handler, Reset_malloc_heap, Resume, Suspend, + Monitor, Select, Kernel_routine_scheduler, Current_time, @@ -218,6 +220,16 @@ struct Libc::Kernel final : Vfs::Io_response_handler, Pthread_pool _pthreads { _timer_accessor }; + Monitor::Pool _monitors { }; + + Reconstructible> _execute_monitors { + _env.ep(), *this, &Kernel::_monitors_handler }; + + void _monitors_handler() + { + _monitors.execute_monitors(); + } + Constructible _clone_connection { }; struct Resumer @@ -466,6 +478,71 @@ struct Libc::Kernel final : Vfs::Io_response_handler, } } + /** + * Monitor interface + */ + bool _monitor(Genode::Lock &mutex, Function &fn, uint64_t timeout_ms) override + { + if (_main_context()) { + + struct Job : Monitor::Job + { + Kernel &_kernel; + + uint64_t _timeout_ms; + bool _timeout_valid { _timeout_ms != 0 }; + + struct Check : Suspend_functor + { + bool const &completed; + + Check(bool const &completed) : completed(completed) { } + + bool suspend() override + { + return !completed; + } + } check { _completed }; + + Job(Monitor::Function &fn, Kernel &kernel, + Timer_accessor &timer_accessor, uint64_t timeout_ms) + : + Monitor::Job(fn, timer_accessor, 0 /* timeout handled by suspend */), + _kernel(kernel), _timeout_ms(timeout_ms) + { } + + void wait_for_completion() override + { + do { + _timeout_ms = _kernel._suspend_main(check, _timeout_ms); + _expired = _timeout_valid && !_timeout_ms; + } while (!completed() && !expired()); + } + + void complete() override + { + _completed = true; + _kernel._resume_main(); + } + } job { fn, *this, _timer_accessor, timeout_ms }; + + _monitors.monitor(mutex, job); + return job.completed(); + + } else { + Monitor::Job job { fn, _timer_accessor, timeout_ms }; + + _monitors.monitor(mutex, job); + return job.completed(); + } + } + + void _charge_monitors() override + { + if (_monitors.charge_monitors()) + Signal_transmitter(*_execute_monitors).submit(); + } + /** * Current_time interface */ diff --git a/repos/libports/src/lib/libc/internal/monitor.h b/repos/libports/src/lib/libc/internal/monitor.h new file mode 100644 index 000000000..edafda7d9 --- /dev/null +++ b/repos/libports/src/lib/libc/internal/monitor.h @@ -0,0 +1,171 @@ +/* + * \brief Monitored execution in main context + * \author Christian Helmuth + * \author Christian Prochaska + * \date 2020-01-09 + */ + +/* + * Copyright (C) 2020 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _LIBC__INTERNAL__MONITOR_H_ +#define _LIBC__INTERNAL__MONITOR_H_ + +/* Genode includes */ +#include + +/* libc-internal includes */ +#include +#include + +namespace Libc { class Monitor; }; + + +class Libc::Monitor : Interface +{ + public: + + struct Job; + struct Pool; + + protected: + + struct Function : Interface { virtual bool execute() = 0; }; + + virtual bool _monitor(Genode::Lock &, Function &, uint64_t) = 0; + virtual void _charge_monitors() = 0; + + public: + + /** + * Block until monitored execution succeeds or timeout expires + * + * The mutex must be locked when calling the monitor. It is released + * during wait for completion and re-aquired before the function + * returns. This behavior is comparable to condition variables. + * + * Returns true if execution completed, false on timeout. + */ + template + bool monitor(Genode::Lock &mutex, FN const &fn, uint64_t timeout_ms = 0) + { + struct _Function : Function + { + FN const &fn; + bool execute() override { return fn(); } + _Function(FN const &fn) : fn(fn) { } + } function { fn }; + + return _monitor(mutex, function, timeout_ms); + } + + /** + * Charge monitor to execute the monitored function + */ + void charge_monitors() { _charge_monitors(); } +}; + + +struct Libc::Monitor::Job : Timeout_handler +{ + private: + + Monitor::Function &_fn; + + protected: + + bool _completed { false }; + bool _expired { false }; + + Lock _blockade { Lock::LOCKED }; + Constructible _timeout; + + public: + + Job(Monitor::Function &fn, + Timer_accessor &timer_accessor, uint64_t timeout_ms) + : _fn(fn) + { + if (timeout_ms) { + _timeout.construct(timer_accessor, *this); + _timeout->start(timeout_ms); + } + } + + bool completed() const { return _completed; } + bool expired() const { return _expired; } + bool execute() { return _fn.execute(); } + + virtual void wait_for_completion() { _blockade.lock(); } + + virtual void complete() + { + _completed = true; + _blockade.unlock(); + } + + /** + * Timeout_handler interface + */ + void handle_timeout() override + { + _expired = true; + _blockade.unlock(); + } +}; + + +struct Libc::Monitor::Pool +{ + private: + + Registry _jobs; + + Lock _mutex; + bool _execution_pending { false }; + + public: + + void monitor(Genode::Lock &mutex, Job &job) + { + Registry::Element element { _jobs, job }; + + mutex.unlock(); + + job.wait_for_completion(); + + mutex.lock(); + } + + bool charge_monitors() + { + Lock::Guard guard { _mutex }; + + bool const charged = !_execution_pending; + _execution_pending = true; + return charged; + } + + void execute_monitors() + { + { + Lock::Guard guard { _mutex }; + + if (!_execution_pending) return; + + _execution_pending = false; + } + + _jobs.for_each([&] (Job &job) { + if (!job.completed() && !job.expired() && job.execute()) { + job.complete(); + } + }); + } +}; + +#endif /* _LIBC__INTERNAL__MONITOR_H_ */ diff --git a/repos/libports/src/lib/libc/internal/pthread.h b/repos/libports/src/lib/libc/internal/pthread.h index e5b9616f8..a40ea4099 100644 --- a/repos/libports/src/lib/libc/internal/pthread.h +++ b/repos/libports/src/lib/libc/internal/pthread.h @@ -244,6 +244,4 @@ struct pthread : Libc::Pthread }; -namespace Libc { void init_pthread_support(Env &env); } - #endif /* _LIBC__INTERNAL__PTHREAD_H_ */ diff --git a/repos/libports/src/lib/libc/internal/time.h b/repos/libports/src/lib/libc/internal/time.h new file mode 100644 index 000000000..2c4870ca2 --- /dev/null +++ b/repos/libports/src/lib/libc/internal/time.h @@ -0,0 +1,72 @@ +/* + * \brief Libc-internal time utilities + * \author Christian Helmuth + * \date 2020-01-29 + */ + +/* + * Copyright (C) 2020 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _LIBC__INTERNAL__TIME_H_ +#define _LIBC__INTERNAL__TIME_H_ + +/* libc includes */ +#include + +/* libc-internal includes */ +#include + +namespace Libc { + inline uint64_t calculate_relative_timeout_ms(timespec abs_now, timespec abs_timeout); +} + +/** + * Calculate relative timeout in milliseconds from 'abs_now' to 'abs_timeout' + * + * Returns 0 if timeout already expired. + */ +Libc::uint64_t Libc::calculate_relative_timeout_ms(timespec abs_now, timespec abs_timeout) +{ + enum { S_IN_MS = 1000ULL, S_IN_NS = 1000 * 1000 * 1000ULL }; + + if (abs_now.tv_nsec >= S_IN_NS) { + abs_now.tv_sec += abs_now.tv_nsec / S_IN_NS; + abs_now.tv_nsec = abs_now.tv_nsec % S_IN_NS; + } + if (abs_timeout.tv_nsec >= S_IN_NS) { + abs_timeout.tv_sec += abs_timeout.tv_nsec / S_IN_NS; + abs_timeout.tv_nsec = abs_timeout.tv_nsec % S_IN_NS; + } + + /* check whether absolute timeout is in the past */ + if (abs_now.tv_sec > abs_timeout.tv_sec) + return 0; + + uint64_t diff_ms = (abs_timeout.tv_sec - abs_now.tv_sec) * S_IN_MS; + uint64_t diff_ns = 0; + + if (abs_timeout.tv_nsec >= abs_now.tv_nsec) + diff_ns = abs_timeout.tv_nsec - abs_now.tv_nsec; + else { + /* check whether absolute timeout is in the past */ + if (diff_ms == 0) + return 0; + diff_ns = S_IN_NS - abs_now.tv_nsec + abs_timeout.tv_nsec; + diff_ms -= S_IN_MS; + } + + diff_ms += diff_ns / 1000 / 1000; + + /* if there is any diff then let the timeout be at least 1 MS */ + if (diff_ms == 0 && diff_ns != 0) + return 1; + + return diff_ms; +} + + +#endif /* _LIBC__INTERNAL__TIME_H_ */ diff --git a/repos/libports/src/lib/libc/internal/timed_semaphore.h b/repos/libports/src/lib/libc/internal/timed_semaphore.h deleted file mode 100644 index d7853fe9c..000000000 --- a/repos/libports/src/lib/libc/internal/timed_semaphore.h +++ /dev/null @@ -1,262 +0,0 @@ -/* - * \brief Semaphore implementation with timeout facility - * \author Stefan Kalkowski - * \date 2010-03-05 - * - * This semaphore implementation allows to block on a semaphore for a - * given time instead of blocking indefinetely. - * - * For the timeout functionality the alarm framework is used. - */ - -/* - * Copyright (C) 2010-2017 Genode Labs GmbH - * - * This file is part of the Genode OS framework, which is distributed - * under the terms of the GNU Affero General Public License version 3. - */ - -#ifndef _LIBC__INTERNAL__TIMED_SEMAPHORE_H_ -#define _LIBC__INTERNAL__TIMED_SEMAPHORE_H_ - -#include -#include -#include -#include - -namespace Libc { - - using namespace Genode; - - class Timeout_entrypoint; - class Timed_semaphore; - - /** - * Exception types - */ - class Timeout_exception : public Exception { }; - class Nonblocking_exception : public Exception { }; -} - - -/** - * Alarm thread, which counts jiffies and triggers timeout events. - */ -class Libc::Timeout_entrypoint : private Entrypoint -{ - private: - - enum { JIFFIES_STEP_MS = 10 }; - - Alarm_scheduler _alarm_scheduler { }; - - Timer::Connection _timer; - - Signal_handler _timer_handler; - - void _handle_timer() { _alarm_scheduler.handle(_timer.elapsed_ms()); } - - static size_t constexpr STACK_SIZE = 2048*sizeof(long); - - public: - - Timeout_entrypoint(Genode::Env &env) - : - Entrypoint(env, STACK_SIZE, "alarm-timer", Affinity::Location()), - _timer(env), - _timer_handler(*this, *this, &Timeout_entrypoint::_handle_timer) - { - _timer.sigh(_timer_handler); - _timer.trigger_periodic(JIFFIES_STEP_MS*1000); - } - - Alarm::Time time(void) { return _timer.elapsed_ms(); } - - void schedule_absolute(Alarm &alarm, Alarm::Time timeout) - { - _alarm_scheduler.schedule_absolute(&alarm, timeout); - } - - void discard(Alarm &alarm) { _alarm_scheduler.discard(&alarm); } -}; - - -/** - * Semaphore with timeout on down operation. - */ -class Libc::Timed_semaphore : public Semaphore -{ - private: - - typedef Semaphore::Element Element; - - Timeout_entrypoint &_timeout_ep; - - /** - * Aborts blocking on the semaphore, raised when a timeout occured. - * - * \param element the waiting-queue element associated with a timeout. - * \return true if a thread was aborted/woken up - */ - bool _abort(Element &element) - { - Lock::Guard lock_guard(Semaphore::_meta_lock); - - /* potentially, the queue is empty */ - if (++Semaphore::_cnt <= 0) { - - /* - * Iterate through the queue and find the thread, - * with the corresponding timeout. - */ - - Element *first = nullptr; - Semaphore::_queue.dequeue([&first] (Element &e) { - first = &e; }); - Element *e = first; - - while (e) { - - /* - * Wakeup the thread. - */ - if (&element == e) { - e->wake_up(); - return true; - } - - /* - * Noninvolved threads are enqueued again. - */ - Semaphore::_queue.enqueue(*e); - e = nullptr; - Semaphore::_queue.dequeue([&e] (Element &next) { - e = &next; }); - - /* - * Maybe, the alarm was triggered just after the corresponding - * thread was already dequeued, that's why we have to track - * whether we processed the whole queue. - */ - if (e == first) - break; - } - } - - /* The right element was not found, so decrease counter again */ - --Semaphore::_cnt; - return false; - } - - - /** - * Represents a timeout associated with the blocking - * operation on a semaphore. - */ - class Timeout : public Alarm - { - private: - - Timed_semaphore &_sem; /* semaphore we block on */ - Element &_element; /* queue element timeout belongs to */ - bool _triggered { false }; - Time const _start; - - public: - - Timeout(Time start, Timed_semaphore &s, Element &e) - : _sem(s), _element(e), _triggered(false), _start(start) - { } - - bool triggered(void) { return _triggered; } - Time start() { return _start; } - - protected: - - bool on_alarm(uint64_t) override - { - _triggered = _sem._abort(_element); - return false; - } - }; - - public: - - /** - * Constructor - * - * \param n initial counter value of the semphore - */ - Timed_semaphore(Timeout_entrypoint &timeout_ep, int n = 0) - : Semaphore(n), _timeout_ep(timeout_ep) { } - - /** - * Decrements semaphore and blocks when it's already zero. - * - * \param t after t milliseconds of blocking a Timeout_exception is thrown. - * if t is zero do not block, instead raise an - * Nonblocking_exception. - * \return milliseconds the caller was blocked - */ - Alarm::Time down(Alarm::Time t) - { - Semaphore::_meta_lock.lock(); - - if (--Semaphore::_cnt < 0) { - - /* If t==0 we shall not block */ - if (t == 0) { - ++_cnt; - Semaphore::_meta_lock.unlock(); - throw Nonblocking_exception(); - } - - /* - * Create semaphore queue element representing the thread - * in the wait queue. - */ - Element queue_element; - Semaphore::_queue.enqueue(queue_element); - Semaphore::_meta_lock.unlock(); - - /* Create the timeout */ - Alarm::Time const curr_time = _timeout_ep.time(); - Timeout timeout(curr_time, *this, queue_element); - _timeout_ep.schedule_absolute(timeout, curr_time + t); - - /* - * The thread is going to block on a local lock now, - * waiting for getting waked from another thread - * calling 'up()' - * */ - queue_element.block(); - - /* Deactivate timeout */ - _timeout_ep.discard(timeout); - - /* - * When we were only woken up, because of a timeout, - * throw an exception. - */ - if (timeout.triggered()) - throw Timeout_exception(); - - /* return blocking time */ - return _timeout_ep.time() - timeout.start(); - - } else { - Semaphore::_meta_lock.unlock(); - } - return 0; - } - - - /******************************** - ** Base class implementations ** - ********************************/ - - void down() { Semaphore::down(); } - void up() { Semaphore::up(); } -}; - -#endif /* _LIBC__INTERNAL__TIMED_SEMAPHORE_H_ */ diff --git a/repos/libports/src/lib/libc/internal/timer.h b/repos/libports/src/lib/libc/internal/timer.h index 23f7ea503..a0fb77b9e 100644 --- a/repos/libports/src/lib/libc/internal/timer.h +++ b/repos/libports/src/lib/libc/internal/timer.h @@ -17,6 +17,9 @@ /* Genode includes */ #include +/* libc-internal includes */ +#include + namespace Libc { class Timer; class Timer_accessor; diff --git a/repos/libports/src/lib/libc/internal/types.h b/repos/libports/src/lib/libc/internal/types.h index 39addb350..233da10b4 100644 --- a/repos/libports/src/lib/libc/internal/types.h +++ b/repos/libports/src/lib/libc/internal/types.h @@ -14,6 +14,7 @@ #ifndef _LIBC__INTERNAL__TYPES_H_ #define _LIBC__INTERNAL__TYPES_H_ +/* Genode includes */ #include #include diff --git a/repos/libports/src/lib/libc/kernel.cc b/repos/libports/src/lib/libc/kernel.cc index 222a91b08..7b8126a97 100644 --- a/repos/libports/src/lib/libc/kernel.cc +++ b/repos/libports/src/lib/libc/kernel.cc @@ -7,7 +7,7 @@ */ /* - * Copyright (C) 2016-2019 Genode Labs GmbH + * Copyright (C) 2016-2020 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. @@ -376,7 +376,8 @@ Libc::Kernel::Kernel(Genode::Env &env, Genode::Allocator &heap) { atexit(close_file_descriptors_on_exit); - init_pthread_support(env, *this, *this); + init_semaphore_support(*this); + init_pthread_support(*this, *this, *this); _env.ep().register_io_progress_handler(*this); diff --git a/repos/libports/src/lib/libc/pthread.cc b/repos/libports/src/lib/libc/pthread.cc index ef8868906..70f0a7037 100644 --- a/repos/libports/src/lib/libc/pthread.cc +++ b/repos/libports/src/lib/libc/pthread.cc @@ -1,12 +1,13 @@ /* * \brief POSIX thread implementation * \author Christian Prochaska + * \author Christian Helmuth * \date 2012-03-12 * */ /* - * Copyright (C) 2012-2017 Genode Labs GmbH + * Copyright (C) 2012-2020 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. @@ -22,47 +23,35 @@ /* libc includes */ #include #include +#include #include /* malloc, free */ /* libc-internal includes */ #include -#include #include #include #include +#include +#include using namespace Libc; -static Genode::Env *_env_ptr; /* solely needed to spawn the timeout thread for the - timed semaphore */ - -static Thread *_main_thread_ptr; - -static Suspend *_suspend_ptr; +static Thread *_main_thread_ptr; static Resume *_resume_ptr; +static Suspend *_suspend_ptr; +static Monitor *_monitor_ptr; -void Libc::init_pthread_support(Genode::Env &env, Suspend &suspend, Resume &resume) +void Libc::init_pthread_support(Monitor &monitor, Suspend &suspend, Resume &resume) { - _env_ptr = &env; _main_thread_ptr = Thread::myself(); + _monitor_ptr = &monitor; _suspend_ptr = &suspend; _resume_ptr = &resume; } -static Libc::Timeout_entrypoint &_global_timeout_ep() -{ - class Missing_call_of_init_pthread_support { }; - if (!_env_ptr) - throw Missing_call_of_init_pthread_support(); - - static Timeout_entrypoint timeout_ep { *_env_ptr }; - return timeout_ep; -} - - /************* ** Pthread ** *************/ @@ -199,23 +188,35 @@ struct pthread_mutex_attr { pthread_mutextype type; }; */ struct pthread_mutex { - pthread_t _owner { nullptr }; + pthread_t _owner { nullptr }; + unsigned _applicants { 0 }; Lock _data_mutex; + Lock _monitor_mutex; struct Missing_call_of_init_pthread_support : Exception { }; - void _suspend(Suspend_functor &func) + struct Applicant { - if (!_suspend_ptr) - throw Missing_call_of_init_pthread_support(); - _suspend_ptr->suspend(func); - } + pthread_mutex &m; - void _resume_all() + Applicant(pthread_mutex &m) : m(m) + { + Lock::Guard lock_guard(m._data_mutex); + ++m._applicants; + } + + ~Applicant() + { + Lock::Guard lock_guard(m._data_mutex); + --m._applicants; + } + }; + + Monitor & _monitor() { - if (!_resume_ptr) + if (!_monitor_ptr) throw Missing_call_of_init_pthread_support(); - _resume_ptr->resume_all(); + return *_monitor_ptr; } pthread_mutex() { } @@ -227,57 +228,95 @@ struct pthread_mutex * described IEEE Std 1003.1 POSIX.1-2017 * https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_lock.html */ - virtual int lock() = 0; - virtual int trylock() = 0; - virtual int unlock() = 0; + virtual int lock() = 0; + virtual int timedlock(timespec const &) = 0; + virtual int trylock() = 0; + virtual int unlock() = 0; }; struct Libc::Pthread_mutex_normal : pthread_mutex { - int lock() override final - { - struct Try_lock : Suspend_functor - { - bool retry { false }; /* have to try after resume */ - - Pthread_mutex_normal &_mutex; - - Try_lock(Pthread_mutex_normal &mutex) : _mutex(mutex) { } - - bool suspend() override - { - retry = _mutex.trylock() == EBUSY; - return retry; - } - } try_lock(*this); - - do { _suspend(try_lock); } while (try_lock.retry); - - return 0; - } - - int trylock() override final + int _try_lock(pthread_t thread) { Lock::Guard lock_guard(_data_mutex); if (!_owner) { - _owner = pthread_self(); + _owner = thread; return 0; } return EBUSY; } + int lock() override final + { + Lock::Guard monitor_guard(_monitor_mutex); + + pthread_t const myself = pthread_self(); + + /* fast path without lock contention */ + if (_try_lock(myself) == 0) + return 0; + + { + Applicant guard { *this }; + + _monitor().monitor(_monitor_mutex, + [&] { return _try_lock(myself) == 0; }); + } + + return 0; + } + + int timedlock(timespec const &abs_timeout) override final + { + Lock::Guard monitor_guard(_monitor_mutex); + + pthread_t const myself = pthread_self(); + + /* fast path without lock contention - does not check abstimeout according to spec */ + if (_try_lock(myself) == 0) + return 0; + + timespec abs_now; + clock_gettime(CLOCK_REALTIME, &abs_now); + + uint64_t const timeout_ms = calculate_relative_timeout_ms(abs_now, abs_timeout); + if (!timeout_ms) + return ETIMEDOUT; + + { + Applicant guard { *this }; + + auto fn = [&] { return _try_lock(myself) == 0; }; + + if (_monitor().monitor(_monitor_mutex, fn, timeout_ms)) + return 0; + else + return ETIMEDOUT; + } + + return 0; + } + + int trylock() override final + { + return _try_lock(pthread_self()); + } + int unlock() override final { + Lock::Guard monitor_guard(_monitor_mutex); Lock::Guard lock_guard(_data_mutex); if (_owner != pthread_self()) return EPERM; _owner = nullptr; - _resume_all(); + + if (_applicants) + _monitor().charge_monitors(); return 0; } @@ -286,52 +325,56 @@ struct Libc::Pthread_mutex_normal : pthread_mutex struct Libc::Pthread_mutex_errorcheck : pthread_mutex { - int lock() override final - { - /* - * We can't use trylock() as it returns EBUSY also for the - * EDEADLK case. - */ - struct Try_lock : Suspend_functor - { - bool retry { false }; /* have to try after resume */ - int result { 0 }; + enum Try_lock_result { SUCCESS, BUSY, DEADLOCK }; - Pthread_mutex_errorcheck &_mutex; - - Try_lock(Pthread_mutex_errorcheck &mutex) : _mutex(mutex) { } - - bool suspend() override - { - Lock::Guard lock_guard(_mutex._data_mutex); - - if (!_mutex._owner) { - _mutex._owner = pthread_self(); - retry = false; - result = 0; - } else if (_mutex._owner == pthread_self()) { - retry = false; - result = EDEADLK; - } else { - retry = true; - } - - return retry; - } - } try_lock(*this); - - do { _suspend(try_lock); } while (try_lock.retry); - - return try_lock.result; - } - - int trylock() override final + Try_lock_result _try_lock(pthread_t thread) { Lock::Guard lock_guard(_data_mutex); if (!_owner) { - _owner = pthread_self(); - return 0; + _owner = thread; + return SUCCESS; + } + + return _owner == thread ? DEADLOCK : BUSY; + } + + int lock() override final + { + Lock::Guard monitor_guard(_monitor_mutex); + + pthread_t const myself = pthread_self(); + + /* fast path without lock contention */ + switch (_try_lock(myself)) { + case SUCCESS: return 0; + case DEADLOCK: return EDEADLK; + case BUSY: [[fallthrough]]; + } + + { + Applicant guard { *this }; + + _monitor().monitor(_monitor_mutex, [&] { + /* DEADLOCK already handled above - just check for SUCCESS */ + return _try_lock(myself) == SUCCESS; + }); + } + + return 0; + } + + int timedlock(timespec const &) override final + { + return ENOSYS; + } + + int trylock() override final + { + switch (_try_lock(pthread_self())) { + case SUCCESS: return 0; + case DEADLOCK: return EDEADLK; + case BUSY: return EBUSY; } return EBUSY; @@ -339,13 +382,16 @@ struct Libc::Pthread_mutex_errorcheck : pthread_mutex int unlock() override final { + Lock::Guard monitor_guard(_monitor_mutex); Lock::Guard lock_guard(_data_mutex); if (_owner != pthread_self()) return EPERM; _owner = nullptr; - _resume_all(); + + if (_applicants) + _monitor().charge_monitors(); return 0; } @@ -356,37 +402,15 @@ struct Libc::Pthread_mutex_recursive : pthread_mutex { unsigned _nesting_level { 0 }; - int lock() override final - { - struct Try_lock : Suspend_functor - { - bool retry { false }; /* have to try after resume */ - - Pthread_mutex_recursive &_mutex; - - Try_lock(Pthread_mutex_recursive &mutex) : _mutex(mutex) { } - - bool suspend() override - { - retry = _mutex.trylock() == EBUSY; - return retry; - } - } try_lock(*this); - - do { _suspend(try_lock); } while (try_lock.retry); - - return 0; - } - - int trylock() override final + int _try_lock(pthread_t thread) { Lock::Guard lock_guard(_data_mutex); if (!_owner) { - _owner = pthread_self(); + _owner = thread; _nesting_level = 1; return 0; - } else if (_owner == pthread_self()) { + } else if (_owner == thread) { ++_nesting_level; return 0; } @@ -394,8 +418,39 @@ struct Libc::Pthread_mutex_recursive : pthread_mutex return EBUSY; } + int lock() override final + { + Lock::Guard monitor_guard(_monitor_mutex); + + pthread_t const myself = pthread_self(); + + /* fast path without lock contention */ + if (_try_lock(myself) == 0) + return 0; + + { + Applicant guard { *this }; + + _monitor().monitor(_monitor_mutex, + [&] { return _try_lock(myself) == 0; }); + } + + return 0; + } + + int timedlock(timespec const &) override final + { + return ENOSYS; + } + + int trylock() override final + { + return _try_lock(pthread_self()); + } + int unlock() override final { + Lock::Guard monitor_guard(_monitor_mutex); Lock::Guard lock_guard(_data_mutex); if (_owner != pthread_self()) @@ -404,7 +459,8 @@ struct Libc::Pthread_mutex_recursive : pthread_mutex --_nesting_level; if (_nesting_level == 0) { _owner = nullptr; - _resume_all(); + if (_applicants) + _monitor().charge_monitors(); } return 0; @@ -680,6 +736,20 @@ extern "C" { } + int pthread_mutex_timedlock(pthread_mutex_t *mutex, + struct timespec const *abstimeout) + { + if (!mutex) + return EINVAL; + + if (*mutex == PTHREAD_MUTEX_INITIALIZER) + pthread_mutex_init(mutex, nullptr); + + /* abstime must be non-null according to the spec */ + return (*mutex)->timedlock(*abstimeout); + } + + int pthread_mutex_unlock(pthread_mutex_t *mutex) { if (!mutex) @@ -702,13 +772,25 @@ extern "C" { struct pthread_cond { - int num_waiters; - int num_signallers; - Lock counter_lock; - Timed_semaphore signal_sem { _global_timeout_ep() }; - Semaphore handshake_sem; + int num_waiters; + int num_signallers; + pthread_mutex_t counter_mutex; + sem_t signal_sem; + sem_t handshake_sem; - pthread_cond() : num_waiters(0), num_signallers(0) { } + pthread_cond() : num_waiters(0), num_signallers(0) + { + pthread_mutex_init(&counter_mutex, nullptr); + sem_init(&signal_sem, 0, 0); + sem_init(&handshake_sem, 0, 0); + } + + ~pthread_cond() + { + sem_destroy(&handshake_sem); + sem_destroy(&signal_sem); + pthread_mutex_destroy(&counter_mutex); + } }; @@ -783,47 +865,6 @@ extern "C" { } - static uint64_t timeout_ms(struct timespec currtime, - struct timespec abstimeout) - { - enum { S_IN_MS = 1000, S_IN_NS = 1000 * 1000 * 1000 }; - - if (currtime.tv_nsec >= S_IN_NS) { - currtime.tv_sec += currtime.tv_nsec / S_IN_NS; - currtime.tv_nsec = currtime.tv_nsec % S_IN_NS; - } - if (abstimeout.tv_nsec >= S_IN_NS) { - abstimeout.tv_sec += abstimeout.tv_nsec / S_IN_NS; - abstimeout.tv_nsec = abstimeout.tv_nsec % S_IN_NS; - } - - /* check whether absolute timeout is in the past */ - if (currtime.tv_sec > abstimeout.tv_sec) - return 0; - - uint64_t diff_ms = (abstimeout.tv_sec - currtime.tv_sec) * S_IN_MS; - uint64_t diff_ns = 0; - - if (abstimeout.tv_nsec >= currtime.tv_nsec) - diff_ns = abstimeout.tv_nsec - currtime.tv_nsec; - else { - /* check whether absolute timeout is in the past */ - if (diff_ms == 0) - return 0; - diff_ns = S_IN_NS - currtime.tv_nsec + abstimeout.tv_nsec; - diff_ms -= S_IN_MS; - } - - diff_ms += diff_ns / 1000 / 1000; - - /* if there is any diff then let the timeout be at least 1 MS */ - if (diff_ms == 0 && diff_ns != 0) - return 1; - - return diff_ms; - } - - int pthread_cond_timedwait(pthread_cond_t *__restrict cond, pthread_mutex_t *__restrict mutex, const struct timespec *__restrict abstime) @@ -838,39 +879,29 @@ extern "C" { pthread_cond *c = *cond; - c->counter_lock.lock(); + pthread_mutex_lock(&c->counter_mutex); c->num_waiters++; - c->counter_lock.unlock(); + pthread_mutex_unlock(&c->counter_mutex); pthread_mutex_unlock(mutex); - if (!abstime) - c->signal_sem.down(); - else { - struct timespec currtime; - clock_gettime(CLOCK_REALTIME, &currtime); - - Alarm::Time timeout = timeout_ms(currtime, *abstime); - - try { - c->signal_sem.down(timeout); - } catch (Timeout_exception) { - result = ETIMEDOUT; - } catch (Nonblocking_exception) { - errno = ETIMEDOUT; - result = ETIMEDOUT; - } + if (!abstime) { + if (sem_wait(&c->signal_sem) == -1) + result = errno; + } else { + if (sem_timedwait(&c->signal_sem, abstime) == -1) + result = errno; } - c->counter_lock.lock(); + pthread_mutex_lock(&c->counter_mutex); if (c->num_signallers > 0) { if (result == ETIMEDOUT) /* timeout occured */ - c->signal_sem.down(); - c->handshake_sem.up(); + sem_wait(&c->signal_sem); + sem_post(&c->handshake_sem); --c->num_signallers; } c->num_waiters--; - c->counter_lock.unlock(); + pthread_mutex_unlock(&c->counter_mutex); pthread_mutex_lock(mutex); @@ -881,7 +912,7 @@ extern "C" { int pthread_cond_wait(pthread_cond_t *__restrict cond, pthread_mutex_t *__restrict mutex) { - return pthread_cond_timedwait(cond, mutex, 0); + return pthread_cond_timedwait(cond, mutex, nullptr); } @@ -892,16 +923,16 @@ extern "C" { pthread_cond *c = *cond; - c->counter_lock.lock(); + pthread_mutex_lock(&c->counter_mutex); if (c->num_waiters > c->num_signallers) { - ++c->num_signallers; - c->signal_sem.up(); - c->counter_lock.unlock(); - c->handshake_sem.down(); + ++c->num_signallers; + sem_post(&c->signal_sem); + pthread_mutex_unlock(&c->counter_mutex); + sem_wait(&c->handshake_sem); } else - c->counter_lock.unlock(); + pthread_mutex_unlock(&c->counter_mutex); - return 0; + return 0; } @@ -912,17 +943,17 @@ extern "C" { pthread_cond *c = *cond; - c->counter_lock.lock(); + pthread_mutex_lock(&c->counter_mutex); if (c->num_waiters > c->num_signallers) { int still_waiting = c->num_waiters - c->num_signallers; c->num_signallers = c->num_waiters; for (int i = 0; i < still_waiting; i++) - c->signal_sem.up(); - c->counter_lock.unlock(); + sem_post(&c->signal_sem); + pthread_mutex_unlock(&c->counter_mutex); for (int i = 0; i < still_waiting; i++) - c->handshake_sem.down(); + sem_wait(&c->handshake_sem); } else - c->counter_lock.unlock(); + pthread_mutex_unlock(&c->counter_mutex); return 0; } diff --git a/repos/libports/src/lib/libc/semaphore.cc b/repos/libports/src/lib/libc/semaphore.cc index b0ec60f8d..089768ff5 100644 --- a/repos/libports/src/lib/libc/semaphore.cc +++ b/repos/libports/src/lib/libc/semaphore.cc @@ -1,12 +1,13 @@ /* * \brief POSIX semaphore implementation * \author Christian Prochaska + * \author Christian Helmuth * \date 2012-03-12 * */ /* - * Copyright (C) 2012-2017 Genode Labs GmbH + * Copyright (C) 2012-2020 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. @@ -16,32 +17,154 @@ #include #include #include - -/* libc includes */ #include +/* libc includes */ +#include +#include + /* libc-internal includes */ +#include +#include #include +#include +#include using namespace Libc; +static Monitor *_monitor_ptr; + + +void Libc::init_semaphore_support(Monitor &monitor) +{ + _monitor_ptr = &monitor; +} + + extern "C" { /* * This class is named 'struct sem' because the 'sem_t' type is * defined as 'struct sem*' in 'semaphore.h' */ - struct sem : Genode::Semaphore + struct sem { - sem(int value) : Semaphore(value) { } + int _count; + unsigned _applicants { 0 }; + Lock _data_mutex; + Lock _monitor_mutex; + + struct Missing_call_of_init_pthread_support : Exception { }; + + struct Applicant + { + sem &s; + + Applicant(sem &s) : s(s) + { + Lock::Guard lock_guard(s._data_mutex); + ++s._applicants; + } + + ~Applicant() + { + Lock::Guard lock_guard(s._data_mutex); + --s._applicants; + } + }; + + Monitor & _monitor() + { + if (!_monitor_ptr) + throw Missing_call_of_init_pthread_support(); + return *_monitor_ptr; + } + + sem(int value) : _count(value) { } + + int trydown() + { + Lock::Guard lock_guard(_data_mutex); + + if (_count > 0) { + _count--; + return 0; + } + + return EBUSY; + } + + int down() + { + Lock::Guard monitor_guard(_monitor_mutex); + + /* fast path without contention */ + if (trydown() == 0) + return 0; + + { + Applicant guard { *this }; + + auto fn = [&] { return trydown() == 0; }; + + (void)_monitor().monitor(_monitor_mutex, fn); + } + + return 0; + } + + int down_timed(timespec const &abs_timeout) + { + Lock::Guard monitor_guard(_monitor_mutex); + + /* fast path without wait - does not check abstimeout according to spec */ + if (trydown() == 0) + return 0; + + timespec abs_now; + clock_gettime(CLOCK_REALTIME, &abs_now); + + uint64_t const timeout_ms = calculate_relative_timeout_ms(abs_now, abs_timeout); + if (!timeout_ms) + return ETIMEDOUT; + + { + Applicant guard { *this }; + + auto fn = [&] { return trydown() == 0; }; + + if (_monitor().monitor(_monitor_mutex, fn, timeout_ms)) + return 0; + else + return ETIMEDOUT; + } + } + + int up() + { + Lock::Guard monitor_guard(_monitor_mutex); + Lock::Guard lock_guard(_data_mutex); + + _count++; + + if (_applicants) + _monitor().charge_monitors(); + + return 0; + } + + int count() + { + return _count; + } }; int sem_close(sem_t *) { warning(__func__, " not implemented"); - return -1; + return Errno(ENOSYS); } @@ -55,7 +178,7 @@ extern "C" { int sem_getvalue(sem_t * __restrict sem, int * __restrict sval) { - *sval = (*sem)->cnt(); + *sval = (*sem)->count(); return 0; } @@ -77,35 +200,44 @@ extern "C" { int sem_post(sem_t *sem) { - (*sem)->up(); + if (int res = (*sem)->up()) + return Errno(res); + return 0; } - int sem_timedwait(sem_t * __restrict, const struct timespec * __restrict) + int sem_timedwait(sem_t * __restrict sem, const struct timespec * __restrict abstime) { - warning(__func__, " not implemented"); - return -1; + /* abstime must be non-null according to the spec */ + if (int res = (*sem)->down_timed(*abstime)) + return Errno(res); + + return 0; } - int sem_trywait(sem_t *) + int sem_trywait(sem_t *sem) { - warning(__func__, " not implemented"); - return -1; + if (int res = (*sem)->trydown()) + return Errno(res); + + return 0; } int sem_unlink(const char *) { warning(__func__, " not implemented"); - return -1; + return Errno(ENOSYS); } int sem_wait(sem_t *sem) { - (*sem)->down(); + if (int res = (*sem)->down()) + return Errno(res); + return 0; } diff --git a/repos/libports/src/test/pthread/main.cc b/repos/libports/src/test/pthread/main.cc index 827026abd..ba7bc48e0 100644 --- a/repos/libports/src/test/pthread/main.cc +++ b/repos/libports/src/test/pthread/main.cc @@ -6,7 +6,7 @@ */ /* - * Copyright (C) 2012-2019 Genode Labs GmbH + * Copyright (C) 2012-2020 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. @@ -19,9 +19,12 @@ #include #include #include +#include +#include /* Genode includes */ #include +#include #include @@ -49,10 +52,14 @@ static void *thread_func(void *arg) sem_post(&thread_args->thread_finished_sem); - /* sleep forever */ - sem_t sleep_sem; - sem_init(&sleep_sem, 0, 0); - sem_wait(&sleep_sem); + /* + * sleep forever + * + * The thread is going to be cancelled, but cancellation points are + * not implemented yet in most blocking libc functions, so + * Genode::sleep_forever() is called here for now. + */ + Genode::sleep_forever(); return 0; } @@ -115,10 +122,29 @@ static inline void compare_semaphore_values(int reported_value, int expected_val } +static timespec add_timespec(timespec const &a, timespec const &b) +{ + enum { NSEC_PER_SEC = 1'000'000'000ull }; + + long sec = a.tv_sec + b.tv_sec; + long nsec = a.tv_nsec + b.tv_nsec; + while (nsec >= NSEC_PER_SEC) { + nsec -= NSEC_PER_SEC; + sec++; + } + return timespec { sec, nsec }; +} + +static timespec add_timespec_ms(timespec const &a, long msec) +{ + return add_timespec(a, timespec { 0, msec*1'000'000 }); +} + struct Test_mutex_data { sem_t main_thread_ready_sem; sem_t test_thread_ready_sem; + pthread_mutex_t normal_mutex; pthread_mutex_t recursive_mutex; pthread_mutex_t errorcheck_mutex; @@ -127,6 +153,8 @@ struct Test_mutex_data sem_init(&main_thread_ready_sem, 0, 0); sem_init(&test_thread_ready_sem, 0, 0); + pthread_mutex_init(&normal_mutex, nullptr); + pthread_mutexattr_t recursive_mutex_attr; pthread_mutexattr_init(&recursive_mutex_attr); pthread_mutexattr_settype(&recursive_mutex_attr, PTHREAD_MUTEX_RECURSIVE); @@ -144,6 +172,7 @@ struct Test_mutex_data { pthread_mutex_destroy(&errorcheck_mutex); pthread_mutex_destroy(&recursive_mutex); + pthread_mutex_destroy(&normal_mutex); } }; @@ -271,6 +300,54 @@ static void *thread_mutex_func(void *arg) /* wake up main thread */ sem_post(&test_mutex_data->test_thread_ready_sem); + /* wait for main thread */ + sem_wait(&test_mutex_data->main_thread_ready_sem); + + /************************************ + ** test normal mutex with timeout ** + ************************************/ + + timespec abstimeout { 0, 0 }; + + /* lock normal mutex */ + + if (pthread_mutex_lock(&test_mutex_data->normal_mutex) != 0) { + printf("Error: could not lock normal mutex\n"); + exit(-1); + } + + /* wake up main thread */ + sem_post(&test_mutex_data->test_thread_ready_sem); + + /* wait for main thread */ + sem_wait(&test_mutex_data->main_thread_ready_sem); + + /* unlock normal mutex */ + + if (pthread_mutex_unlock(&test_mutex_data->normal_mutex) != 0) { + printf("Error: could not lock normal mutex\n"); + exit(-1); + } + + /* wake up main thread */ + sem_post(&test_mutex_data->test_thread_ready_sem); + + /* wait for main thread */ + sem_wait(&test_mutex_data->main_thread_ready_sem); + + /* try to lock locked mutex with timeout */ + + clock_gettime(CLOCK_REALTIME, &abstimeout); + abstimeout = add_timespec_ms(abstimeout, 500); + + if (pthread_mutex_timedlock(&test_mutex_data->normal_mutex, &abstimeout) != ETIMEDOUT) { + printf("Error: locking of normal mutex did not time out in test thread\n"); + exit(-1); + } + + /* wake up main thread */ + sem_post(&test_mutex_data->test_thread_ready_sem); + return nullptr; } @@ -336,6 +413,47 @@ static void test_mutex() exit(-1); } + /************************************ + ** test normal mutex with timeout ** + ************************************/ + + timespec abstimeout { 0, 0 }; + + /* wake up test thread */ + sem_post(&test_mutex_data.main_thread_ready_sem); + + /* wait for test thread - normal mutex should still be locked */ + sem_wait(&test_mutex_data.test_thread_ready_sem); + + /* try to lock locked mutex with timeout */ + + clock_gettime(CLOCK_REALTIME, &abstimeout); + abstimeout = add_timespec_ms(abstimeout, 500); + + if (pthread_mutex_timedlock(&test_mutex_data.normal_mutex, &abstimeout) != ETIMEDOUT) { + printf("Error: locking of normal mutex did not time out in main thread\n"); + exit(-1); + } + + /* wake up test thread */ + sem_post(&test_mutex_data.main_thread_ready_sem); + + /* wait for test thread - normal mutex should still be locked */ + sem_wait(&test_mutex_data.test_thread_ready_sem); + + /* lock normal mutex */ + + if (pthread_mutex_lock(&test_mutex_data.normal_mutex) != 0) { + printf("Error: could not lock normal mutex\n"); + exit(-1); + } + + /* wake up test thread */ + sem_post(&test_mutex_data.main_thread_ready_sem); + + /* wait for test thread - normal mutex should still be locked */ + sem_wait(&test_mutex_data.test_thread_ready_sem); + pthread_join(t, NULL); } @@ -542,6 +660,267 @@ static void test_lock_and_sleep() } +struct Cond +{ + pthread_cond_t _cond; + + Cond() { pthread_cond_init(&_cond, nullptr); } + + ~Cond() { pthread_cond_destroy(&_cond); } + + pthread_cond_t * cond() { return &_cond; } +}; + + +struct Test_cond +{ + Mutex _mutex; + Cond _cond; + + enum class State { + PING, PONG, SHUTDOWN, END + } _shared_state { State::PING }; + + static void *signaller_fn(void *arg) + { + ((Test_cond *)arg)->signaller(); + return nullptr; + } + + void signaller() + { + printf("signaller: started\n"); + + unsigned num_events = 0; + bool test_done = false; + while (!test_done) { + pthread_mutex_lock(_mutex.mutex()); + + switch (_shared_state) { + case State::PING: + _shared_state = State::PONG; + ++num_events; + pthread_cond_signal(_cond.cond()); + break; + case State::PONG: + _shared_state = State::PING; + ++num_events; + pthread_cond_signal(_cond.cond()); + break; + case State::SHUTDOWN: + printf("signaller: shutting down\n"); + _shared_state = State::END; + ++num_events; + pthread_cond_broadcast(_cond.cond()); + test_done = true; + break; + case State::END: + break; + } + + pthread_mutex_unlock(_mutex.mutex()); + + usleep(1000); + } + + printf("signaller: finished after %u state changes\n", num_events); + } + + static void *waiter_fn(void *arg) + { + ((Test_cond *)arg)->waiter(); + return nullptr; + } + + void waiter(bool main_thread = false) + { + char const * const note = main_thread ? "(main thread)" : ""; + + printf("waiter%s: started\n", note); + + unsigned pings = 0, pongs = 0; + unsigned long iterations = 0; + bool test_done = false; + while (!test_done) { + pthread_mutex_lock(_mutex.mutex()); + + auto handle_state = [&] { + unsigned const num_events = pings + pongs; + if (num_events == 2000) { + printf("waiter%s: request shutdown\n", note); + _shared_state = State::SHUTDOWN; + } else if (num_events % 2 == 0) { + pthread_cond_wait(_cond.cond(), _mutex.mutex()); + } + }; + + switch (_shared_state) { + case State::PING: + ++pings; + handle_state(); + break; + case State::PONG: + ++pongs; + handle_state(); + break; + case State::SHUTDOWN: + pthread_cond_wait(_cond.cond(), _mutex.mutex()); + break; + case State::END: + test_done = true; + break; + } + + pthread_mutex_unlock(_mutex.mutex()); + + usleep(3000); + ++iterations; + } + + printf("waiter%s: finished (pings=%u, pongs=%u, iterations=%lu)\n", + note, pings, pongs, iterations); + } + + Test_cond() + { + printf("main thread: test without timeouts\n"); + + pthread_t signaller_id; + if (pthread_create(&signaller_id, 0, signaller_fn, this) != 0) { + printf("error: pthread_create() failed\n"); + exit(-1); + } + pthread_t waiter1_id; + if (pthread_create(&waiter1_id, 0, waiter_fn, this) != 0) { + printf("error: pthread_create() failed\n"); + exit(-1); + } + pthread_t waiter2_id; + if (pthread_create(&waiter2_id, 0, waiter_fn, this) != 0) { + printf("error: pthread_create() failed\n"); + exit(-1); + } + + waiter(true); + pthread_join(signaller_id, nullptr); + pthread_join(waiter1_id, nullptr); + pthread_join(waiter2_id, nullptr); + } +}; + + +struct Test_cond_timed +{ + Mutex _mutex; + Cond _cond; + + enum class State { RUN, END } _shared_state { State::RUN }; + + enum { ROUNDS = 10 }; + + static void *signaller_fn(void *arg) + { + ((Test_cond_timed *)arg)->signaller(); + return nullptr; + } + + void signaller() + { + printf("signaller: started\n"); + + bool loop = true; + for (unsigned i = 1; loop; ++i) { + usleep(249*1000); + pthread_mutex_lock(_mutex.mutex()); + + if (i == ROUNDS) { + _shared_state = State::END; + loop = false; + } + pthread_cond_broadcast(_cond.cond()); + + pthread_mutex_unlock(_mutex.mutex()); + } + + printf("signaller: finished\n"); + } + + static void *waiter_fn(void *arg) + { + ((Test_cond_timed *)arg)->waiter(); + return nullptr; + } + + void waiter(bool main_thread = false) + { + char const * const note = main_thread ? "(main thread)" : ""; + + printf("waiter%s: started\n", note); + + for (bool loop = true; loop; ) { + pthread_mutex_lock(_mutex.mutex()); + + timespec ts { 0, 0 }; + clock_gettime(CLOCK_REALTIME, &ts); + + int ret; + do { + if (_shared_state == State::END) { + loop = false; + break; + } + + ts = add_timespec_ms(ts, 250); + + ret = pthread_cond_timedwait(_cond.cond(), _mutex.mutex(), &ts); + + if (ret) + printf("waiter%s: pthread_cond_timedwait: %s\n", note, strerror(ret)); + } while (ret != 0); + + pthread_mutex_unlock(_mutex.mutex()); + } + + printf("waiter%s: finished\n", note); + } + + Test_cond_timed() + { + printf("main thread: test with timeouts\n"); + + pthread_t signaller_id; + if (pthread_create(&signaller_id, 0, signaller_fn, this) != 0) { + printf("error: pthread_create() failed\n"); + exit(-1); + } + pthread_t waiter1_id; + if (pthread_create(&waiter1_id, 0, waiter_fn, this) != 0) { + printf("error: pthread_create() failed\n"); + exit(-1); + } + pthread_t waiter2_id; + if (pthread_create(&waiter2_id, 0, waiter_fn, this) != 0) { + printf("error: pthread_create() failed\n"); + exit(-1); + } + + waiter(true); + pthread_join(signaller_id, nullptr); + pthread_join(waiter1_id, nullptr); + pthread_join(waiter2_id, nullptr); + } +}; + + +static void test_cond() +{ + printf("main thread: test condition variables\n"); + + { Test_cond test; } + { Test_cond_timed test; } +} + + static void test_interplay() { enum { NUM_THREADS = 2 }; @@ -635,6 +1014,7 @@ int main(int argc, char **argv) test_mutex(); test_mutex_stress(); test_lock_and_sleep(); + test_cond(); printf("--- returning from main ---\n"); return 0;