Skip to content

Commit

Permalink
centralize clock handling for pthread_cond_timedwait (PortAudio#877)
Browse files Browse the repository at this point in the history
* Ensure that the clock that is configured on the cond var object is the one that is used to compute the wait deadline.

* Ensure that the most appropriate clock is selected on each platform based on availability CLOCK_BOOTTIME > CLOCK_MONOTONIC > CLOCK_REALTIME (on Mac use gettimeofday because other clock interfaces are either not available or broken).

NB: use GetSystemTimeAsFileTime on Windows. should be much faster than GetSystemTime+SystemTimeToFileTime according to http://www.windowstimestamp.com/description. thanks s09bQ5

Used directly or indirectly by pa_jack, pa_alsa, pa_asihpi

Resolves PortAudio#795, PortAudio#807.
  • Loading branch information
RossBencina authored Feb 23, 2024
1 parent 45c12e8 commit 4600d81
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 26 deletions.
9 changes: 8 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,13 @@ find_package(JACK)
cmake_dependent_option(PA_USE_JACK "Enable support for JACK Audio Connection Kit" ON JACK_FOUND OFF)
if(PA_USE_JACK)
target_link_libraries(PortAudio PRIVATE JACK::jack)
target_sources(PortAudio PRIVATE src/hostapi/jack/pa_jack.c)
target_sources(PortAudio PRIVATE
src/hostapi/jack/pa_jack.c
src/os/unix/pa_pthread_util.c
src/os/unix/pa_pthread_util.h
)
set(PORTAUDIO_PUBLIC_HEADERS "${PORTAUDIO_PUBLIC_HEADERS}" include/pa_jack.h)
target_include_directories(PortAudio PRIVATE src/os/unix) # for pa_pthread_util.h
target_compile_definitions(PortAudio PUBLIC PA_USE_JACK=1)
set(PKGCONFIG_CFLAGS "${PKGCONFIG_CFLAGS} -DPA_USE_JACK=1")
set(PKGCONFIG_REQUIRES_PRIVATE "${PKGCONFIG_REQUIRES_PRIVATE} jack")
Expand Down Expand Up @@ -288,6 +293,8 @@ elseif(UNIX)
src/os/unix/pa_unix_hostapis.c
src/os/unix/pa_unix_util.c
src/os/unix/pa_unix_util.h
src/os/unix/pa_pthread_util.c
src/os/unix/pa_pthread_util.h
)
target_include_directories(PortAudio PRIVATE src/os/unix)
target_link_libraries(PortAudio PRIVATE m)
Expand Down
4 changes: 2 additions & 2 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -16238,7 +16238,7 @@ else
fi

CFLAGS="$CFLAGS $mac_arches $mac_sysroot $mac_version_min"
OTHER_OBJS="src/os/unix/pa_unix_hostapis.o src/os/unix/pa_unix_util.o src/hostapi/coreaudio/pa_mac_core.o src/hostapi/coreaudio/pa_mac_core_utilities.o src/hostapi/coreaudio/pa_mac_core_blocking.o src/common/pa_ringbuffer.o"
OTHER_OBJS="src/os/unix/pa_unix_hostapis.o src/os/unix/pa_unix_util.o src/os/unix/pa_pthread_util.o src/hostapi/coreaudio/pa_mac_core.o src/hostapi/coreaudio/pa_mac_core_utilities.o src/hostapi/coreaudio/pa_mac_core_blocking.o src/common/pa_ringbuffer.o"
PADLL="libportaudio.dylib"
;;

Expand Down Expand Up @@ -16597,7 +16597,7 @@ fi
;;
esac

OTHER_OBJS="$OTHER_OBJS src/os/unix/pa_unix_hostapis.o src/os/unix/pa_unix_util.o"
OTHER_OBJS="$OTHER_OBJS src/os/unix/pa_unix_hostapis.o src/os/unix/pa_unix_util.o src/os/unix/pa_pthread_util.o"
esac
CFLAGS="$CFLAGS $THREAD_CFLAGS"

Expand Down
4 changes: 2 additions & 2 deletions configure.in
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ case "${host_os}" in
SHARED_FLAGS="$LIBS -dynamiclib $mac_arches $mac_sysroot $mac_version_min"
AX_CHECK_COMPILE_FLAG([-std=c11], [CFLAGS="-std=c11 $CFLAGS"], [CFLAGS="-std=c99 $CFLAGS"])
CFLAGS="$CFLAGS $mac_arches $mac_sysroot $mac_version_min"
OTHER_OBJS="src/os/unix/pa_unix_hostapis.o src/os/unix/pa_unix_util.o src/hostapi/coreaudio/pa_mac_core.o src/hostapi/coreaudio/pa_mac_core_utilities.o src/hostapi/coreaudio/pa_mac_core_blocking.o src/common/pa_ringbuffer.o"
OTHER_OBJS="src/os/unix/pa_unix_hostapis.o src/os/unix/pa_unix_util.o src/os/unix/pa_pthread_util.o src/hostapi/coreaudio/pa_mac_core.o src/hostapi/coreaudio/pa_mac_core_utilities.o src/hostapi/coreaudio/pa_mac_core_blocking.o src/common/pa_ringbuffer.o"
PADLL="libportaudio.dylib"
;;

Expand Down Expand Up @@ -464,7 +464,7 @@ case "${host_os}" in
;;
esac

OTHER_OBJS="$OTHER_OBJS src/os/unix/pa_unix_hostapis.o src/os/unix/pa_unix_util.o"
OTHER_OBJS="$OTHER_OBJS src/os/unix/pa_unix_hostapis.o src/os/unix/pa_unix_util.o src/os/unix/pa_pthread_util.o"
esac
CFLAGS="$CFLAGS $THREAD_CFLAGS"

Expand Down
25 changes: 19 additions & 6 deletions src/hostapi/jack/pa_jack.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
#include <jack/jack.h>

#include "pa_util.h"
#include "pa_pthread_util.h"
#include "pa_hostapi.h"
#include "pa_stream.h"
#include "pa_process.h"
Expand Down Expand Up @@ -168,6 +169,7 @@ typedef struct

pthread_mutex_t mtx;
pthread_cond_t cond;
PaUtilClockId condClockId;
unsigned long inputBase, outputBase;

/* For dealing with the process thread */
Expand Down Expand Up @@ -758,14 +760,18 @@ PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi,
int activated = 0;
jack_status_t jackStatus = 0;
*hostApi = NULL; /* Initialize to NULL */
pthread_condattr_t cattr;

UNLESS( jackHostApi = (PaJackHostApiRepresentation*)
PaUtil_AllocateZeroInitializedMemory( sizeof(PaJackHostApiRepresentation) ), paInsufficientMemory );
UNLESS( jackHostApi->deviceInfoMemory = PaUtil_CreateAllocationGroup(), paInsufficientMemory );

mainThread_ = pthread_self();
ASSERT_CALL( pthread_mutex_init( &jackHostApi->mtx, NULL ), 0 );
ASSERT_CALL( pthread_cond_init( &jackHostApi->cond, NULL ), 0 );

ASSERT_CALL( pthread_condattr_init( &cattr ), 0 );
jackHostApi->condClockId = PaPthreadUtil_NegotiateCondAttrClock( &cattr );
ASSERT_CALL( pthread_cond_init( &jackHostApi->cond, &cattr), 0 );

/* Try to become a client of the JACK server. If we cannot do
* this, then this API cannot be used.
Expand Down Expand Up @@ -1049,13 +1055,20 @@ static PaError WaitCondition( PaJackHostApiRepresentation *hostApi )
{
PaError result = paNoError;
int err = 0;
PaTime pt = PaUtil_GetTime();
struct timespec ts;

ts.tv_sec = (time_t) floor( pt + 10 * 60 /* 10 minutes */ );
ts.tv_nsec = (long) ((pt - floor( pt )) * 1000000000);
/* XXX: Best enclose in loop, in case of spurious wakeups? */
err = pthread_cond_timedwait( &hostApi->cond, &hostApi->mtx, &ts );
if( PaPthreadUtil_GetTime( hostApi->condClockId, &ts ) == 0 )
{
ts.tv_sec += 10 * 60; /* 10 minutes */

/* XXX: Best enclose in loop, in case of spurious wakeups? */
err = pthread_cond_timedwait( &hostApi->cond, &hostApi->mtx, &ts );
}
else
{
/* XXX: Best enclose in loop, in case of spurious wakeups? */
err = pthread_cond_wait( &hostApi->cond, &hostApi->mtx );
}

/* Make sure we didn't time out */
UNLESS( err != ETIMEDOUT, paTimedOut );
Expand Down
136 changes: 136 additions & 0 deletions src/os/unix/pa_pthread_util.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* $Id$
* PortAudio Portable Real-Time Audio Library
* Latest Version at: http://www.portaudio.com
*
* Based on the Open Source API proposed by Ross Bencina
* Copyright (c) 1999-2024 Ross Bencina, Phil Burk
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/

#if !PAUTIL_USE_POSIX_ADVANCED_REALTIME && (defined(WIN32) || defined(_WIN32))
#include <windows.h>
#else
#include <errno.h>
#endif

#include "pa_pthread_util.h"
#include "pa_debugprint.h"

PaUtilClockId PaPthreadUtil_NegotiateCondAttrClock( pthread_condattr_t *cattr )
{
#if PAUTIL_USE_POSIX_ADVANCED_REALTIME
/* Set most suitable timeout clock on the condattr and return its clock id.
If a clock can't be set, return the default clock id.
*/
clockid_t clockId;

/* try each potential clockid in order of preference until one succeeds: */
#if defined(CLOCK_BOOTTIME )
if( pthread_condattr_setclock( cattr, CLOCK_BOOTTIME ) == 0 )
return CLOCK_BOOTTIME;
#endif

#if defined(CLOCK_MONOTONIC)
if( pthread_condattr_setclock( cattr, CLOCK_MONOTONIC ) == 0 )
return CLOCK_MONOTONIC;
#endif

#if defined(CLOCK_REALTIME)
if( pthread_condattr_setclock( cattr, CLOCK_REALTIME ) == 0 )
return CLOCK_REALTIME;
#endif

/* fall back to returning the current clock id */
if ( pthread_condattr_getclock( cattr, &clockId) == 0 )
return clockId;

/* fall back to returning the default expected clock id */
PA_DEBUG(( "%s: could not configure condattr clock\n", __FUNCTION__));
return CLOCK_REALTIME;
#else /* not PAUTIL_USE_POSIX_ADVANCED_REALTIME */
return 0; /* dummy value */
#endif
}

int PaPthreadUtil_GetTime( PaUtilClockId clockId, struct timespec *ts )
{
#if PAUTIL_USE_POSIX_ADVANCED_REALTIME
if ( clock_gettime(clockId, ts) == 0 )
{
return 0; /* success */
}
PA_DEBUG(( "%s: clock_gettime failed with errno %d\n", __FUNCTION__, errno));
ts->tv_sec = 0;
ts->tv_nsec = 0;
return -1; /* failure */

#else /* not PAUTIL_USE_POSIX_ADVANCED_REALTIME */

#if defined(WIN32) || defined(_WIN32)
/* On Windows, the most likely pthreads implementations are pthreads4w,
and winpthread via mingw-w64. Both use Unix time derived from Win32 SystemTime as the time base:
https://sourceforge.net/p/pthreads4w/code/ci/master/tree/ptw32_timespec.c
https://sourceforge.net/p/mingw-w64/mingw-w64/ci/master/tree/mingw-w64-libraries/winpthreads/src/misc.c
The conversion from SystemTime to Unix time is based on this code: https://stackoverflow.com/a/26085827
with reference to the pthreads4w code linked above.
*/
FILETIME ft;
UINT64 t1601; /* 100ns units since 00:00:00 UTC January 1, 1601 */
UINT64 t1970; /* 100ns units since 00:00:00 UTC January 1, 1970 */

GetSystemTimeAsFileTime( &ft );
t1601 = ((UINT64)ft.dwHighDateTime << 32) + (UINT64)ft.dwLowDateTime;
/* The following constant is 134774 days in 100ns ticks. i.e. 134774*24*3600*10000000 */
#define SYSTEM_TIME_TO_UNIX_TIME_OFFSET (((UINT64)27111902UL << 32) + (UINT64)3577643008UL)
t1970 = t1601 - SYSTEM_TIME_TO_UNIX_TIME_OFFSET;

ts->tv_sec = (time_t) (t1970 / (UINT64)10000000UL);
ts->tv_nsec = (long) ((t1970 - ((UINT64)ts->tv_sec * (UINT64)10000000UL)) * (UINT64)100UL);
return 0; /* success */
#else
/* fall back to gettimeofday for Apple and when clock_gettime is unavailable */
struct timeval tv;
if ( gettimeofday(&tv, NULL) == 0 )
{
ts->tv_sec = tv.tv_sec;
ts->tv_nsec = tv.tv_usec * 1000UL;
return 0; /* success */
}
PA_DEBUG(( "%s: gettimeofday failed with errno %d\n", __FUNCTION__, errno));
ts->tv_sec = 0;
ts->tv_nsec = 0;
return -1; /* failure */
#endif

#endif
}
104 changes: 104 additions & 0 deletions src/os/unix/pa_pthread_util.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* $Id$
* PortAudio Portable Real-Time Audio Library
* Latest Version at: http://www.portaudio.com
*
* Based on the Open Source API proposed by Ross Bencina
* Copyright (c) 1999-2024 Ross Bencina, Phil Burk
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/

/** @file
@ingroup unix_src
*/

#ifndef PA_PTHREAD_UTIL_H
#define PA_PTHREAD_UTIL_H

#include <time.h> /* timespec, clock_gettime */
#include <sys/types.h> /* clockid_t */
#if !(defined(WIN32) || defined(_WIN32))
#include <sys/time.h> /* gettimeofday on macos */
#endif
#include <pthread.h>

#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */

/*
Use the presence of CLOCK_REALTIME as a proxy for the availability of
pthread_condattr_setclock, pthread_condattr_getclock and clock_gettime.
Otherewise use a fallback path.
On Apple, stick with default unix time using gettimeofday,
since CLOCK_MONOTONIC is known to be buggy:
https://discussions.apple.com/thread/253778121?sortBy=best
And clock_gettime is not available pre-Sierra:
https://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x
https://stackoverflow.com/a/21352348
*/
#if defined(CLOCK_REALTIME) && !defined(__APPLE__)
#define PAUTIL_USE_POSIX_ADVANCED_REALTIME 1
#else
#define PAUTIL_USE_POSIX_ADVANCED_REALTIME 0
#endif

#if PAUTIL_USE_POSIX_ADVANCED_REALTIME

#define PaUtilClockId clockid_t

#else

#define PaUtilClockId int /* dummy type */

#endif

/** Negotiate the most suitable clock for condvar timeouts, set the clock
* on cattr and return the clock's id.
*/
PaUtilClockId PaPthreadUtil_NegotiateCondAttrClock( pthread_condattr_t *cattr );

/** Get the current time according to the clock referred to by clockId, as
* previously returned by PaPthreadUtil_NegotiateCondAttrTimeoutClock().
*
* Returns 0 upon success, -1 otherwise.
*/
int PaPthreadUtil_GetTime( PaUtilClockId clockId, struct timespec *ts );

#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
Loading

0 comments on commit 4600d81

Please sign in to comment.