diff --git a/CHANGELOG b/CHANGELOG
index 00c0191..4312d54 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,7 +3,10 @@
*******************************************************************************
=== 1.0.37 ===
-
+* Implemented immediate wake-up of a thread sleeping in ipc::Thread::sleep()
+ on ipc::Thread::cancel(); the sleep is no longer split into 100 ms intervals.
+* The ipc/Thread.h header no longer includes platform headers (Windows or
+ pthread); the platform-specific state of ipc::Thread is kept in Thread.cpp.
=== 1.0.36 ===
* Updated build scripts.
diff --git a/include/lsp-plug.in/ipc/Thread.h b/include/lsp-plug.in/ipc/Thread.h
index 8a75a14..af6ff96 100644
--- a/include/lsp-plug.in/ipc/Thread.h
+++ b/include/lsp-plug.in/ipc/Thread.h
@@ -1,6 +1,6 @@
/*
- * Copyright (C) 2025 Linux Studio Plugins Project
- * (C) 2025 Vladimir Sadovnikov
+ * Copyright (C) 2026 Linux Studio Plugins Project
+ * (C) 2026 Vladimir Sadovnikov
*
* This file is part of lsp-runtime-lib
* Created on: 25 февр. 2019 г.
@@ -27,12 +27,6 @@
#include
#include
-#if defined(PLATFORM_WINDOWS)
- #include
-#else
- #include
-#endif /* PLATFORM_WINDOWS */
-
#include
#include
@@ -59,6 +53,11 @@ namespace lsp
constexpr thread_id_t INVALID_THREAD_ID = 0;
+ namespace detail
+ {
+ struct thread_impl_t;
+ } /* namespace detail */
+
/**
* Thread class
*/
@@ -81,31 +80,26 @@ namespace lsp
volatile bool bCancelled;
volatile status_t nResult;
-#if defined(PLATFORM_WINDOWS)
- HANDLE hThread; // Windows threads
-#else
- pthread_t hThread; // POSIX threads
-#endif /* PLATFORM_WINDOWS */
+ detail::thread_impl_t *pImpl; // Platform-specific thread handle and cancellation wake-up
protected:
binding_t sBinding;
private:
-#if defined(PLATFORM_WINDOWS)
- static DWORD WINAPI thread_launcher(_In_ LPVOID lpParameter);
-#else
- static void *thread_launcher(void *arg);
-#endif /* PLATFORM_WINDOWS */
+ friend struct detail::thread_impl_t;
+ Thread(const Thread &src); // Deny copying
Thread & operator = (const Thread &src); // Deny copying
+ void init_impl();
+
public:
explicit Thread();
explicit Thread(thread_proc_t proc);
explicit Thread(thread_proc_t proc, void *arg);
explicit Thread(IRunnable *runnable);
- virtual ~Thread();
+ virtual ~Thread(); // The thread must be joined before it is destroyed
public:
/**
diff --git a/src/main/ipc/Thread.cpp b/src/main/ipc/Thread.cpp
index d00bb59..59187d2 100644
--- a/src/main/ipc/Thread.cpp
+++ b/src/main/ipc/Thread.cpp
@@ -30,6 +30,13 @@
#if defined(PLATFORM_WINDOWS)
#include
#include
+ #include
+#else
+ #include
+#endif /* PLATFORM_WINDOWS */
+
+#if defined(PLATFORM_WINDOWS)
+ // Nothing
#elif defined(PLATFORM_LINUX)
#include
#include
@@ -56,20 +63,78 @@ namespace lsp
{
namespace ipc
{
+ namespace detail
+ {
+ struct thread_impl_t
+ {
+ #if defined(PLATFORM_WINDOWS)
+ HANDLE hThread; // Windows threads
+ CRITICAL_SECTION sLock; // Lock for cancellation wake-up
+ CONDITION_VARIABLE sCancel; // Cancellation wake-up for sleep()
+
+ static DWORD WINAPI launcher(_In_ LPVOID lpParameter);
+ #else
+ pthread_t hThread; // POSIX threads
+ pthread_mutex_t sLock; // Lock for cancellation wake-up
+ pthread_cond_t sCancel; // Cancellation wake-up for sleep()
+ clockid_t nClockId; // Clock used by sleep()
+
+ static void *launcher(void *arg);
+ #endif /* PLATFORM_WINDOWS */
+ };
+ } /* namespace detail */
+
__thread Thread *Thread::pThis = NULL;
-
- #if defined(PLATFORM_WINDOWS)
- #define CLR_HANDLE(hThread) hThread = INVALID_HANDLE_VALUE;
- #else
- #define CLR_HANDLE(hThread) hThread = 0;
- #endif
+
+ #if !defined(PLATFORM_WINDOWS)
+ static void add_millis(struct timespec *ts, wsize_t millis)
+ {
+ const time_t max_sec = time_t((~uint64_t(0)) >> (65 - sizeof(time_t) * 8)); // Maximum value of time_t
+ const uint64_t sec = millis / 1000;
+ ts->tv_nsec += (millis % 1000) * 1000000;
+ ts->tv_sec += ts->tv_nsec / 1000000000;
+ ts->tv_nsec %= 1000000000;
+
+ // Wait until cancelled if the deadline does not fit into time_t
+ if (sec > uint64_t(max_sec - ts->tv_sec))
+ {
+ ts->tv_sec = max_sec;
+ ts->tv_nsec = 999999999;
+ }
+ else
+ ts->tv_sec += sec;
+ }
+ #endif /* PLATFORM_WINDOWS */
+
+ #if defined(PLATFORM_MACOSX)
+ // Convert the current time to the time left until the deadline, false if it has been reached
+ static bool time_left(struct timespec *left, const struct timespec *deadline)
+ {
+ if ((left->tv_sec > deadline->tv_sec) ||
+ ((left->tv_sec == deadline->tv_sec) && (left->tv_nsec >= deadline->tv_nsec)))
+ return false;
+
+ left->tv_sec = deadline->tv_sec - left->tv_sec;
+ left->tv_nsec = deadline->tv_nsec - left->tv_nsec;
+ if (left->tv_nsec < 0)
+ {
+ left->tv_nsec += 1000000000;
+ --left->tv_sec;
+ }
+
+ // Limit the relative wait to one day, the deadline is checked again after it
+ if (left->tv_sec >= 86400)
+ left->tv_sec = 86400;
+ return true;
+ }
+ #endif /* PLATFORM_MACOSX */
Thread::Thread()
{
atomic_store(&enState, TS_CREATED);
nResult = STATUS_OK;
bCancelled = false;
- CLR_HANDLE(hThread);
+ init_impl();
sBinding.proc = NULL;
sBinding.arg = NULL;
sBinding.runnable = NULL;
@@ -80,7 +145,7 @@ namespace lsp
atomic_store(&enState, TS_CREATED);
nResult = STATUS_OK;
bCancelled = false;
- CLR_HANDLE(hThread);
+ init_impl();
sBinding.proc = proc;
sBinding.arg = NULL;
sBinding.runnable = NULL;
@@ -91,7 +156,7 @@ namespace lsp
atomic_store(&enState, TS_CREATED);
nResult = STATUS_OK;
bCancelled = false;
- CLR_HANDLE(hThread);
+ init_impl();
sBinding.proc = proc;
sBinding.runnable = NULL;
sBinding.arg = arg;
@@ -102,7 +167,7 @@ namespace lsp
atomic_store(&enState, TS_CREATED);
nResult = STATUS_OK;
bCancelled = false;
- CLR_HANDLE(hThread);
+ init_impl();
sBinding.proc = NULL;
sBinding.arg = NULL;
sBinding.runnable = runnable;
@@ -110,12 +175,47 @@ namespace lsp
Thread::~Thread()
{
+ if (pImpl == NULL)
+ return;
+
#if defined(PLATFORM_WINDOWS)
- if (hThread != INVALID_HANDLE_VALUE)
- CloseHandle(hThread);
+ if (pImpl->hThread != INVALID_HANDLE_VALUE)
+ CloseHandle(pImpl->hThread);
+ DeleteCriticalSection(&pImpl->sLock);
+ #else
+ pthread_cond_destroy(&pImpl->sCancel);
+ pthread_mutex_destroy(&pImpl->sLock);
#endif /* PLATFORM_WINDOWS */
- CLR_HANDLE(hThread);
+ delete pImpl;
+ pImpl = NULL;
+ }
+
+ void Thread::init_impl()
+ {
+ pImpl = new detail::thread_impl_t;
+ if (pImpl == NULL)
+ return;
+
+ #if defined(PLATFORM_WINDOWS)
+ pImpl->hThread = INVALID_HANDLE_VALUE;
+ InitializeCriticalSection(&pImpl->sLock);
+ InitializeConditionVariable(&pImpl->sCancel);
+ #else
+ pthread_mutex_init(&pImpl->sLock, NULL);
+
+ // Use CLOCK_MONOTONIC for sleep() if possible, CLOCK_REALTIME otherwise. MacOS has no
+ // pthread_condattr_setclock() and waits for the time left until CLOCK_MONOTONIC deadline.
+ pthread_condattr_t attr;
+ pthread_condattr_init(&attr);
+ pImpl->nClockId = CLOCK_MONOTONIC;
+ #if !defined(PLATFORM_MACOSX)
+ if (pthread_condattr_setclock(&attr, CLOCK_MONOTONIC) != 0)
+ pImpl->nClockId = CLOCK_REALTIME;
+ #endif /* PLATFORM_MACOSX */
+ pthread_cond_init(&pImpl->sCancel, &attr);
+ pthread_condattr_destroy(&attr);
+ #endif /* PLATFORM_WINDOWS */
}
status_t Thread::run()
@@ -138,22 +238,35 @@ namespace lsp
return STATUS_BAD_STATE;
}
+ // Set the flag and wake up the thread if it sleeps. The thread has been started,
+ // so pImpl is not NULL.
+ #if defined(PLATFORM_WINDOWS)
+ EnterCriticalSection(&pImpl->sLock);
+ bCancelled = true;
+ WakeConditionVariable(&pImpl->sCancel);
+ LeaveCriticalSection(&pImpl->sLock);
+ #else
+ pthread_mutex_lock(&pImpl->sLock);
bCancelled = true;
+ pthread_cond_signal(&pImpl->sCancel);
+ pthread_mutex_unlock(&pImpl->sLock);
+ #endif /* PLATFORM_WINDOWS */
+
return STATUS_OK;
}
#if defined(PLATFORM_WINDOWS)
- DWORD WINAPI Thread::thread_launcher(_In_ LPVOID lpParameter)
+ DWORD WINAPI detail::thread_impl_t::launcher(_In_ LPVOID lpParameter)
{
Thread *_this = reinterpret_cast(lpParameter);
- pThis = _this;
+ Thread::pThis = _this;
// Wait until we are ready to launch
while (!atomic_cas(&_this->enState, TS_PENDING, TS_RUNNING)) {}
// Execute the thread
status_t res = _this->run();
- pThis = NULL;
+ Thread::pThis = NULL;
// Commit the 'FINISHED' status
int state;
@@ -168,12 +281,15 @@ namespace lsp
status_t Thread::start()
{
+ if (pImpl == NULL)
+ return STATUS_NO_MEM;
+
DWORD tid;
- HANDLE thandle = CreateThread(NULL, 0, thread_launcher, this, 0, &tid);
+ HANDLE thandle = CreateThread(NULL, 0, detail::thread_impl_t::launcher, this, 0, &tid);
if (thandle == INVALID_HANDLE_VALUE)
return STATUS_UNKNOWN_ERR;
- hThread = thandle;
+ pImpl->hThread = thandle;
atomic_store(&enState, TS_PENDING);
return STATUS_OK;
}
@@ -187,7 +303,7 @@ namespace lsp
case TS_PENDING:
case TS_RUNNING:
{
- DWORD res = WaitForSingleObject(hThread, INFINITE);
+ DWORD res = WaitForSingleObject(pImpl->hThread, INFINITE);
if ((res != WAIT_OBJECT_0) && (res != WAIT_ABANDONED))
return STATUS_UNKNOWN_ERR;
break;
@@ -216,15 +332,36 @@ namespace lsp
if (pThis->bCancelled)
return STATUS_CANCELLED;
- while (millis > 0)
+ // Wait until the timeout expires or the thread is cancelled
+ Thread *self = pThis;
+ detail::thread_impl_t *impl = self->pImpl;
+ status_t res = STATUS_OK;
+ wsize_t left = millis;
+ LARGE_INTEGER freq, start, now;
+ QueryPerformanceFrequency(&freq);
+ QueryPerformanceCounter(&start);
+
+ EnterCriticalSection(&impl->sLock);
+ while ((!self->bCancelled) && (left > 0))
{
- if (pThis->bCancelled)
- return STATUS_CANCELLED;
+ const DWORD timeout = (left >= INFINITE) ? INFINITE - 1 : DWORD(left);
+ if ((!SleepConditionVariableCS(&impl->sCancel, &impl->sLock, timeout)) &&
+ (GetLastError() != ERROR_TIMEOUT))
+ {
+ res = STATUS_UNKNOWN_ERR;
+ break;
+ }
- DWORD interval = (millis > 100) ? 100 : millis;
- Sleep(interval);
- millis -= interval;
+ // Compute the time left, the timeout may expire early and wake-ups may be spurious
+ QueryPerformanceCounter(&now);
+ const wsize_t passed = wsize_t(now.QuadPart - start.QuadPart) * 1000 / wsize_t(freq.QuadPart);
+ left = (passed < millis) ? millis - passed : 0;
}
+ if (self->bCancelled)
+ res = STATUS_CANCELLED;
+ LeaveCriticalSection(&impl->sLock);
+
+ return res;
}
return STATUS_OK;
@@ -236,10 +373,10 @@ namespace lsp
}
#else
- void *Thread::thread_launcher(void *arg)
+ void *detail::thread_impl_t::launcher(void *arg)
{
Thread *_this = reinterpret_cast(arg);
- pThis = _this;
+ Thread::pThis = _this;
// Cleanup cancellation state
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
@@ -264,11 +401,14 @@ namespace lsp
status_t Thread::start()
{
+ if (pImpl == NULL)
+ return STATUS_NO_MEM;
+
pthread_t tid;
- if (pthread_create(&tid, NULL, &thread_launcher, this) != 0)
+ if (pthread_create(&tid, NULL, &detail::thread_impl_t::launcher, this) != 0)
return STATUS_UNKNOWN_ERR;
- hThread = tid;
+ pImpl->hThread = tid;
atomic_store(&enState, TS_PENDING);
return STATUS_OK;
}
@@ -281,7 +421,7 @@ namespace lsp
return STATUS_BAD_STATE;
case TS_PENDING:
case TS_RUNNING:
- if (pthread_join(hThread, NULL) != 0)
+ if (pthread_join(pImpl->hThread, NULL) != 0)
return STATUS_UNKNOWN_ERR;
break;
case TS_FINISHED:
@@ -314,28 +454,51 @@ namespace lsp
if (pThis->bCancelled)
return STATUS_CANCELLED;
- while (millis > 0)
- {
- if (pThis->bCancelled)
- return STATUS_CANCELLED;
+ // Compute the deadline
+ Thread *self = pThis;
+ detail::thread_impl_t *impl = self->pImpl;
+ status_t res = STATUS_OK;
+ struct timespec deadline;
- wsize_t interval = (millis > 100) ? 100 : millis;
- req.tv_sec = 0;
- req.tv_nsec = interval * 1000000;
+ if (::clock_gettime(impl->nClockId, &deadline) != 0)
+ return STATUS_UNKNOWN_ERR;
+ add_millis(&deadline, millis);
- while (::nanosleep(&req, &rem) != 0)
+ // Wait until the deadline is reached or the thread is cancelled
+ pthread_mutex_lock(&impl->sLock);
+ while (!self->bCancelled)
+ {
+ #if defined(PLATFORM_MACOSX)
+ struct timespec left;
+ if (::clock_gettime(CLOCK_MONOTONIC, &left) != 0)
{
- int code = errno;
- if (code != EINTR)
- return STATUS_UNKNOWN_ERR;
-
- if (pThis->bCancelled)
- return STATUS_CANCELLED;
- req = rem;
+ res = STATUS_UNKNOWN_ERR;
+ break;
}
-
- millis -= interval;
+ if (!time_left(&left, &deadline))
+ break;
+ const int code = pthread_cond_timedwait_relative_np(&impl->sCancel, &impl->sLock, &left);
+ if ((code != 0) && (code != ETIMEDOUT))
+ {
+ res = STATUS_UNKNOWN_ERR;
+ break;
+ }
+ #else
+ const int code = pthread_cond_timedwait(&impl->sCancel, &impl->sLock, &deadline);
+ if (code == ETIMEDOUT)
+ break;
+ else if (code != 0)
+ {
+ res = STATUS_UNKNOWN_ERR;
+ break;
+ }
+ #endif /* PLATFORM_MACOSX */
}
+ if (self->bCancelled)
+ res = STATUS_CANCELLED;
+ pthread_mutex_unlock(&impl->sLock);
+
+ return res;
}
return STATUS_OK;
diff --git a/src/test/utest/ipc/thread_sleep.cpp b/src/test/utest/ipc/thread_sleep.cpp
new file mode 100644
index 0000000..40bda19
--- /dev/null
+++ b/src/test/utest/ipc/thread_sleep.cpp
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2026 Linux Studio Plugins Project
+ * (C) 2026 Vladimir Sadovnikov
+ *
+ * This file is part of lsp-runtime-lib
+ * Created on: 15 окт. 2026 г.
+ *
+ * lsp-runtime-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * lsp-runtime-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with lsp-runtime-lib. If not, see .
+ */
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#if defined(PLATFORM_WINDOWS)
+ #include
+#else
+ #include
+#endif /* PLATFORM_WINDOWS */
+
+using namespace lsp;
+
+namespace
+{
+ static const wssize_t NANOS_PER_MILLI = 1000000;
+ static const wssize_t NANOS_PER_SECOND = 1000000000;
+
+ // Time of the monotonic clock in nanoseconds, does not depend on system time changes
+ static wssize_t monotonic_nanos()
+ {
+ #if defined(PLATFORM_WINDOWS)
+ LARGE_INTEGER freq, now;
+ QueryPerformanceFrequency(&freq);
+ QueryPerformanceCounter(&now);
+ return (now.QuadPart / freq.QuadPart) * NANOS_PER_SECOND +
+ ((now.QuadPart % freq.QuadPart) * NANOS_PER_SECOND) / freq.QuadPart;
+ #else
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return wssize_t(ts.tv_sec) * NANOS_PER_SECOND + ts.tv_nsec;
+ #endif /* PLATFORM_WINDOWS */
+ }
+} /* namespace */
+
+UTEST_BEGIN("runtime.ipc", thread_sleep)
+
+ UTEST_TIMELIMIT(30)
+
+ class SleepThread: public ipc::Thread
+ {
+ public:
+ wsize_t nInterval; // Sleep interval, milliseconds
+ wssize_t nAlign; // Start of the sleep within a second of the monotonic clock, milliseconds
+ int nEntered; // Set just before the call of sleep()
+ wssize_t nStart; // Start of the sleep, nanoseconds
+ wssize_t nEnd; // End of the sleep, nanoseconds
+ ssize_t nSwitches;
+ status_t nStatus;
+
+ public:
+ explicit SleepThread(wsize_t interval, wssize_t align = -1)
+ {
+ nInterval = interval;
+ nAlign = align;
+ nEntered = 0;
+ nStart = 0;
+ nEnd = 0;
+ nSwitches = -1;
+ nStatus = STATUS_OK;
+ }
+
+ static ssize_t voluntary_switches()
+ {
+ ssize_t result = -1;
+ #if defined(PLATFORM_LINUX)
+ char path[64], line[128];
+ snprintf(path, sizeof(path), "/proc/self/task/%lu/status", (unsigned long)(current_thread_id()));
+ FILE *fd = fopen(path, "r");
+ if (fd == NULL)
+ return result;
+ while (fgets(line, sizeof(line), fd) != NULL)
+ {
+ if (strncmp(line, "voluntary_ctxt_switches:", 24) == 0)
+ {
+ result = atol(&line[24]);
+ break;
+ }
+ }
+ fclose(fd);
+ #endif /* PLATFORM_LINUX */
+ return result;
+ }
+
+ virtual status_t run()
+ {
+ // Wait until the monotonic clock is in the window [nAlign, nAlign + 50) ms of a second
+ while (nAlign >= 0)
+ {
+ const wssize_t millis = (monotonic_nanos() % NANOS_PER_SECOND) / NANOS_PER_MILLI;
+ const wssize_t delay = (nAlign + 1000 - millis) % 1000;
+ if (delay == 0)
+ break;
+ if (delay > 950)
+ break;
+ sleep(delay);
+ }
+
+ const ssize_t before = voluntary_switches();
+ nStart = monotonic_nanos();
+ atomic_store(&nEntered, 1);
+ nStatus = sleep(nInterval);
+ nEnd = monotonic_nanos();
+ const ssize_t after = voluntary_switches();
+ if ((before >= 0) && (after >= 0))
+ nSwitches = after - before;
+ return STATUS_OK;
+ }
+ };
+
+ UTEST_MAIN
+ {
+ // Cancel the sleeping thread. With the sleep split into 100 ms intervals, the thread
+ // would wake up 70..80 ms after cancel() for these offsets. The 40 ms limit of the
+ // wake-up leaves room for the scheduling delays of a loaded system or of valgrind.
+ static const wsize_t intervals[] = { 10000, 10000, ~wsize_t(0) };
+ static const wsize_t offsets[] = { 20, 30, 130 };
+ static const wssize_t max_latency = 40 * NANOS_PER_MILLI;
+ for (size_t i=0; i<3; ++i)
+ {
+ SleepThread t(intervals[i]);
+ UTEST_ASSERT(t.start() == STATUS_OK);
+ while (atomic_load(&t.nEntered) == 0)
+ ipc::Thread::sleep(1);
+ ipc::Thread::sleep(offsets[i]);
+ UTEST_ASSERT(!t.finished());
+
+ const wssize_t cancel = monotonic_nanos();
+ UTEST_ASSERT(t.cancel() == STATUS_OK);
+ UTEST_ASSERT(t.join() == STATUS_OK);
+ const wssize_t joined = monotonic_nanos();
+
+ printf("Cancelled sleep: interval=%llu ms, status=%d, slept before cancel=%.3f ms, wake-up after cancel=%.3f ms, join after cancel=%.3f ms\n",
+ (unsigned long long)intervals[i], int(t.nStatus),
+ double(cancel - t.nStart) / NANOS_PER_MILLI,
+ double(t.nEnd - cancel) / NANOS_PER_MILLI,
+ double(joined - cancel) / NANOS_PER_MILLI);
+ UTEST_ASSERT(t.nStatus == STATUS_CANCELLED);
+ // The thread was inside sleep() when cancelled
+ UTEST_ASSERT(cancel - t.nStart >= wssize_t(offsets[i]) * NANOS_PER_MILLI);
+ UTEST_ASSERT(t.nEnd >= cancel);
+ UTEST_ASSERT(t.nEnd - cancel <= max_latency);
+ }
+
+ // Sleep without cancel() should last the whole interval without waking up
+ {
+ SleepThread t(320);
+ UTEST_ASSERT(t.start() == STATUS_OK);
+ UTEST_ASSERT(t.join() == STATUS_OK);
+
+ const wssize_t elapsed = t.nEnd - t.nStart;
+ printf("Full sleep: status=%d, slept=%.3f ms, voluntary context switches=%d\n",
+ int(t.nStatus), double(elapsed) / NANOS_PER_MILLI, int(t.nSwitches));
+ UTEST_ASSERT(t.nStatus == STATUS_OK);
+ UTEST_ASSERT(elapsed >= 320 * NANOS_PER_MILLI);
+ #if defined(PLATFORM_LINUX)
+ if (t.nSwitches >= 0)
+ UTEST_ASSERT(t.nSwitches <= 2);
+ else
+ printf("Voluntary context switch check skipped: /proc not readable\n");
+ #endif /* PLATFORM_LINUX */
+ }
+
+ // Sleep that ends in the next second of the monotonic clock (with the nanosecond carry)
+ // and in the same second (without it)
+ static const wssize_t aligns[] = { 900, 100 };
+ for (size_t i=0; i<2; ++i)
+ {
+ SleepThread t(150, aligns[i]);
+ UTEST_ASSERT(t.start() == STATUS_OK);
+ UTEST_ASSERT(t.join() == STATUS_OK);
+
+ const wssize_t start = (t.nStart % NANOS_PER_SECOND) / NANOS_PER_MILLI;
+ const wssize_t elapsed = t.nEnd - t.nStart;
+ printf("Aligned sleep: status=%d, start=%d ms of the second, slept=%.3f ms\n",
+ int(t.nStatus), int(start), double(elapsed) / NANOS_PER_MILLI);
+ UTEST_ASSERT(t.nStatus == STATUS_OK);
+ UTEST_ASSERT(elapsed >= 150 * NANOS_PER_MILLI);
+ UTEST_ASSERT(elapsed <= 250 * NANOS_PER_MILLI);
+ #if !defined(PLATFORM_WINDOWS)
+ UTEST_ASSERT((start >= aligns[i]) && (start < aligns[i] + 50));
+ #endif /* PLATFORM_WINDOWS */
+ }
+ }
+
+UTEST_END