diff --git a/common/wscript b/common/wscript index bc23ced1..b1f1b73e 100644 --- a/common/wscript +++ b/common/wscript @@ -83,6 +83,7 @@ def build(bld): 'JackDebugClient.cpp', 'timestamps.c', 'promiscuous.c', + '../posix/JackPosixCommon.cpp', '../posix/JackPosixThread.cpp', '../posix/JackPosixProcessSync.cpp', '../posix/JackPosixMutex.cpp', @@ -113,6 +114,7 @@ def build(bld): 'JackDebugClient.cpp', 'timestamps.c', 'promiscuous.c', + '../posix/JackPosixCommon.cpp', '../posix/JackPosixThread.cpp', '../posix/JackFifo.cpp', '../posix/JackPosixProcessSync.cpp', @@ -128,6 +130,7 @@ def build(bld): 'JackDebugClient.cpp', 'timestamps.c', 'promiscuous.c', + '../posix/JackPosixCommon.cpp', '../posix/JackPosixProcessSync.cpp', '../posix/JackPosixThread.cpp', '../posix/JackPosixMutex.cpp', diff --git a/posix/JackPosixCommon.cpp b/posix/JackPosixCommon.cpp new file mode 100644 index 00000000..97666bfc --- /dev/null +++ b/posix/JackPosixCommon.cpp @@ -0,0 +1,50 @@ +#include "JackPosixCommon.h" +#include "JackConstants.h" +#include "JackTools.h" +#include "JackError.h" +#include +#include +#include +#include +#include + +namespace Jack +{ + +void JackPosixTools::TimespecAdd(const struct timespec *left, + const struct timespec *right, struct timespec *result) +{ + assert(result != NULL); + result->tv_sec = left->tv_sec + right->tv_sec; + result->tv_nsec = left->tv_nsec + right->tv_nsec; + if (result->tv_nsec >= 1000000000) { + result->tv_nsec -= 1000000000; + result->tv_sec += 1; + } +} + +void JackPosixTools::TimespecSub(const struct timespec *left, + const struct timespec *right, struct timespec *result) +{ + assert(result != NULL); + result->tv_sec = left->tv_sec - right->tv_sec; + result->tv_nsec = left->tv_nsec - right->tv_nsec; + if (result->tv_nsec < 0) { + result->tv_nsec += 1000000000; + result->tv_sec -= 1; + } +} + +int JackPosixTools::TimespecCmp(const struct timespec *left, + const struct timespec *right) +{ + if (left->tv_sec != right->tv_sec) + return left->tv_sec < right->tv_sec ? -1 : 1; + if (left->tv_nsec != right->tv_nsec) + return left->tv_nsec < right->tv_nsec ? -1 : 1; + return 0; +} + +} // end of namespace + + diff --git a/posix/JackPosixCommon.h b/posix/JackPosixCommon.h new file mode 100644 index 00000000..54c529a9 --- /dev/null +++ b/posix/JackPosixCommon.h @@ -0,0 +1,30 @@ +#ifndef __JackPosixCommon__ +#define __JackPosixCommon__ + +#include "jslist.h" +#include "JackCompilerDeps.h" +#include "JackError.h" + +#include +#include +#include + +namespace Jack +{ + +struct SERVER_EXPORT JackPosixTools +{ + static void TimespecAdd(const struct timespec *left, + const struct timespec *right, struct timespec *result); + + static void TimespecSub(const struct timespec *left, + const struct timespec *right, struct timespec *result); + + static int TimespecCmp(const struct timespec *left, + const struct timespec *right); +}; + +} // end of namespace + +#endif + diff --git a/posix/JackPosixProcessSync.cpp b/posix/JackPosixProcessSync.cpp index 04c8785f..e107ea95 100644 --- a/posix/JackPosixProcessSync.cpp +++ b/posix/JackPosixProcessSync.cpp @@ -19,6 +19,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "JackPosixProcessSync.h" #include "JackError.h" +#include "JackPosixCommon.h" namespace Jack { @@ -103,73 +104,94 @@ void JackPosixProcessSync::LockedWait() } } -bool JackPosixProcessSync::TimedWait(long usec) -{ - ThrowIf(!pthread_equal(pthread_self(), fOwner), JackException("JackPosixProcessSync::TimedWait: a thread has to have locked a mutex before it can wait")); - fOwner = 0; - - struct timeval T0, T1; - timespec time; - struct timeval now; - int res; - - jack_log("JackPosixProcessSync::TimedWait time out = %ld", usec); - gettimeofday(&T0, 0); - - gettimeofday(&now, 0); - unsigned int next_date_usec = now.tv_usec + usec; - time.tv_sec = now.tv_sec + (next_date_usec / 1000000); - time.tv_nsec = (next_date_usec % 1000000) * 1000; - - res = pthread_cond_timedwait(&fCond, &fMutex, &time); - if (res != 0) { - jack_error("JackPosixProcessSync::TimedWait error usec = %ld err = %s", usec, strerror(res)); - } else { - fOwner = pthread_self(); - } - - gettimeofday(&T1, 0); - jack_log("JackPosixProcessSync::TimedWait finished delta = %5.1lf", - (1e6 * T1.tv_sec - 1e6 * T0.tv_sec + T1.tv_usec - T0.tv_usec)); - - return (res == 0); -} - // TO DO : check thread consistency? bool JackPosixProcessSync::LockedTimedWait(long usec) { - struct timeval T0, T1; - timespec time; - struct timeval now; - int res1, res2; + int res1, res2, res3; + struct timespec rel_timeout, now_mono, end_mono, now_real, end_real; + struct timespec diff_mono, final_time; + int old_errno; + bool mono_available = true; + double delta; res1 = pthread_mutex_lock(&fMutex); if (res1 != 0) { - jack_error("JackPosixProcessSync::LockedTimedWait error err = %s", usec, strerror(res1)); - } - - jack_log("JackPosixProcessSync::TimedWait time out = %ld", usec); - gettimeofday(&T0, 0); - - gettimeofday(&now, 0); - unsigned int next_date_usec = now.tv_usec + usec; - time.tv_sec = now.tv_sec + (next_date_usec / 1000000); - time.tv_nsec = (next_date_usec % 1000000) * 1000; - res2 = pthread_cond_timedwait(&fCond, &fMutex, &time); - if (res2 != 0) { - jack_error("JackPosixProcessSync::LockedTimedWait error usec = %ld err = %s", usec, strerror(res2)); + jack_error("JackPosixProcessSync::LockedTimedWait error err = %s", + usec, strerror(res1)); + } + + jack_log("JackPosixProcessSync::LockedTimedWait time out = %ld", usec); + /* Convert usec argument to timespec */ + rel_timeout.tv_sec = usec / 1000000; + rel_timeout.tv_nsec = (usec % 1000000) * 1000; + + /* Calculate absolute monotonic timeout */ + res3 = clock_gettime(CLOCK_MONOTONIC, &now_mono); + if (res3 != 0) { + mono_available = false; + } + JackPosixTools::TimespecAdd(&now_mono, &rel_timeout, &end_mono); + + /* pthread_cond_timedwait() is affected by abrupt time jumps, i.e. when the + * system time is changed. To protect against this, measure the time + * difference between and after the sem_timedwait() call and if it suggests + * that there has been a time jump, restart the call. */ + if (mono_available) { + for (;;) { + /* Calculate absolute realtime timeout, assuming no steps */ + res3 = clock_gettime(CLOCK_REALTIME, &now_real); + assert(res3 == 0); + JackPosixTools::TimespecSub(&end_mono, &now_mono, &diff_mono); + JackPosixTools::TimespecAdd(&now_real, &diff_mono, &end_real); + + res2 = pthread_cond_timedwait(&fCond, &fMutex, &end_real); + if (res2 != ETIMEDOUT) { + break; + } + + /* Compare with monotonic timeout, in case a step happened */ + old_errno = errno; + res3 = clock_gettime(CLOCK_MONOTONIC, &now_mono); + assert(res3 == 0); + errno = old_errno; + if (JackPosixTools::TimespecCmp(&now_mono, &end_mono) >= 0) { + break; + } + } + } else { + /* CLOCK_MONOTONIC is not supported, do not check for time skips. */ + res3 = clock_gettime(CLOCK_REALTIME, &now_real); + assert(res3 == 0); + JackPosixTools::TimespecAdd(&now_real, &rel_timeout, &end_real); + res2 = pthread_cond_timedwait(&fCond, &fMutex, &end_real); } - gettimeofday(&T1, 0); res1 = pthread_mutex_unlock(&fMutex); if (res1 != 0) { - jack_error("JackPosixProcessSync::LockedTimedWait error err = %s", usec, strerror(res1)); + jack_error("JackPosixProcessSync::LockedTimedWait error err = %s", + strerror(res1)); } - jack_log("JackPosixProcessSync::TimedWait finished delta = %5.1lf", - (1e6 * T1.tv_sec - 1e6 * T0.tv_sec + T1.tv_usec - T0.tv_usec)); + if (res2 != 0) { + jack_error("JackPosixProcessSync::LockedTimedWait error usec = %ld err = %s", + usec, strerror(res2)); + } - return (res2 == 0); + old_errno = errno; + if (mono_available) { + res3 = clock_gettime(CLOCK_MONOTONIC, &final_time); + assert(res3 == 0); + delta = 1e6 * final_time.tv_sec - 1e6 * now_mono.tv_sec + + (final_time.tv_nsec - now_mono.tv_nsec) / 1000; + } else { + res3 = clock_gettime(CLOCK_REALTIME, &final_time); + assert(res3 == 0); + delta = 1e6 * final_time.tv_sec - 1e6 * now_real.tv_sec + + (final_time.tv_nsec - now_real.tv_nsec) / 1000; + } + errno = old_errno; + jack_log("JackPosixProcessSync::LockedTimedWait finished delta = %5.1lf", delta); + return res2 == 0; } diff --git a/posix/JackPosixProcessSync.h b/posix/JackPosixProcessSync.h index 956d39c3..286c4d90 100644 --- a/posix/JackPosixProcessSync.h +++ b/posix/JackPosixProcessSync.h @@ -52,7 +52,6 @@ class JackPosixProcessSync : public JackBasePosixMutex pthread_cond_destroy(&fCond); } - bool TimedWait(long usec); bool LockedTimedWait(long usec); void Wait(); diff --git a/posix/JackPosixSemaphore.cpp b/posix/JackPosixSemaphore.cpp index 49012312..4d4185ea 100644 --- a/posix/JackPosixSemaphore.cpp +++ b/posix/JackPosixSemaphore.cpp @@ -21,6 +21,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "JackTools.h" #include "JackConstants.h" #include "JackError.h" +#include "JackPosixCommon.h" #include #include #include @@ -122,29 +123,80 @@ bool JackPosixSemaphore::Wait() bool JackPosixSemaphore::TimedWait(long usec) { - int res; - struct timeval now; - timespec time; + int res; + struct timespec rel_timeout, now_mono, end_mono, now_real, end_real; + struct timespec diff_mono; if (!fSemaphore) { jack_error("JackPosixSemaphore::TimedWait name = %s already deallocated!!", fName); return false; } - gettimeofday(&now, 0); - time.tv_sec = now.tv_sec + usec / 1000000; - long tv_usec = (now.tv_usec + (usec % 1000000)); - time.tv_sec += tv_usec / 1000000; - time.tv_nsec = (tv_usec % 1000000) * 1000; - - while ((res = sem_timedwait(fSemaphore, &time)) < 0) { - jack_error("JackPosixSemaphore::TimedWait err = %s", strerror(errno)); - jack_log("JackPosixSemaphore::TimedWait now : %ld %ld ", now.tv_sec, now.tv_usec); - jack_log("JackPosixSemaphore::TimedWait next : %ld %ld ", time.tv_sec, time.tv_nsec/1000); - if (errno != EINTR) { - break; + + /* Convert usec argument to timespec */ + rel_timeout.tv_sec = usec / 1000000; + rel_timeout.tv_nsec = (usec % 1000000) * 1000; + + /* Calculate absolute monotonic timeout */ + res = clock_gettime(CLOCK_MONOTONIC, &now_mono); + if (res != 0) { + /* CLOCK_MONOTONIC is not supported, do not check for time skips. */ + res = clock_gettime(CLOCK_REALTIME, &now_real); + assert(res == 0); + JackPosixTools::TimespecAdd(&now_real, &rel_timeout, &end_real); + while ((res = sem_timedwait(fSemaphore, &end_real)) < 0) { + if (errno != EINTR) { + break; + } + } + if (res == 0) { + return true; + } else { + goto err; } } - return (res == 0); + JackPosixTools::TimespecAdd(&now_mono, &rel_timeout, &end_mono); + + /* sem_timedwait() is affected by abrupt time jumps, i.e. when the system + * time is changed. To protect against this, measure the time difference + * between and after the sem_timedwait() call and if it suggests that there + * has been a time jump, restart the call. */ + for (;;) { + /* Calculate absolute realtime timeout, assuming no steps */ + res = clock_gettime(CLOCK_REALTIME, &now_real); + assert(res == 0); + JackPosixTools::TimespecSub(&end_mono, &now_mono, &diff_mono); + JackPosixTools::TimespecAdd(&now_real, &diff_mono, &end_real); + + while ((res = sem_timedwait(fSemaphore, &end_real)) < 0) { + if (errno != EINTR) { + break; + } + } + if (res == 0) { + return true; + } + if (errno != ETIMEDOUT) { + goto err; + } + + /* Compare with monotonic timeout, in case a step happened */ + int old_errno = errno; + res = clock_gettime(CLOCK_MONOTONIC, &now_mono); + assert(res == 0); + errno = old_errno; + if (JackPosixTools::TimespecCmp(&now_mono, &end_mono) >= 0) { + goto err; + } + } + return true; + +err: + jack_error("JackPosixSemaphore::TimedWait err = %s", strerror(errno)); + jack_log("JackPosixSemaphore::TimedWait now : %ld %ld ", now_real.tv_sec, + now_real.tv_nsec / 1000); + jack_log("JackPosixSemaphore::TimedWait next : %ld %ld ", end_real.tv_sec, + end_real.tv_nsec / 1000); + return false; } // Server side : publish the semaphore in the global namespace