Friday, October 02, 2020

Clock Time

POSIX threads (a.k.a. pthreads) - the standard that defines an API and an execution model for supporting threads in a programming language - are old news. In the past twenty years I've written or helped write three different pthread-based frameworks in C++ for different jobs or gigs, and used several other frameworks that others had written. Today finds me finishing up writing yet another pthread framework (YAPTF), this time in C, for my own use. It will be a part of Diminuto, my C-based systems programming library whose open source is available on GitHub. This is something I should have done a long time ago, instead of hacking little separate bits of pthread code into virtually every non-trivial C project that I wrote.

Here's the thing: it wasn't until I was writing a unit test for this new feature that I finally realized why the pthreads API function

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

uses the absolute wall clock time (e.g. 2020-10-02T17:38:24.03894879UTC) instead of a relative duration (e.g. 0.50000000s) to specify a timeout (abstime) for the condition that the caller is asking to wait for. Timeouts in other POSIX standard features, like the select(2) system call, are specified as a duration.

It finally dawned on me it was because this allows you to specify a consistent fixed timeout for complex situations that may have to receive multiple pthread signals to satisfy their pthread wait condition.

Here's a code snippet from the unit test. Below, my function diminuto_thread_wait_until calls pthread_cond_timedwait in the pthread library. (Click on the image to see a larger version.)

Screen Shot 2020-10-02 at 11.25.11 AM

The test initializes the thread object (a C struct instance) with a pointer to the function, body7, that implements the thread logic. The test starts the thread with an argument of just a null pointer that is passed to the function. Then the test acquires the current clock time and (effectively) adds one second to it. The test then enters a critical section which locks a pthread mutex associated with the thread. Inside the critical section, if the thread has not yet entered the running state (something the framework changes), the test waits until it receives a pthread signal on the condition associated with the thread from my framework indicating that the thread state has changed. Then it checks again. When the thread is in the running state, the test exits the critical section, unlocking the mutex.

No matter how many times the test waits, the thread state changes, a signal is sent, and the test wakes up to check the state again, the unit test will never wait longer than a total of one second. If more than a total of one second elapses, the wait will return the error code ETIMEDOUT instead of zero, and the test will fail.

This eliminates all the bookkeeping I've had to do in real-time operating systems that have non-POSIX threading models. In those RTOSes, I had to keep track myself of how much time had elapsed, subtract that duration from the total timeout, check that I hadn't used up all the timeout, and if not then wait again. It's not that hard (in fact, Diminuto has features that make this pretty easy), but it does call for more code.

I confess I felt a warm glow inside when I figured this out. There is a joy in understanding a problem that people smarter than you have solved.

No comments: