This is an automated email from the ASF dual-hosted git repository.
sjanc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-nimble.git
The following commit(s) were added to refs/heads/master by this push:
new 613761c65 npl/linux: Honor finite event queue timeouts
613761c65 is described below
commit 613761c65aea043b6596a7510e2107a369391872
Author: lewisjoshua1986 <[email protected]>
AuthorDate: Sat Sep 5 03:09:53 2026 -0500
npl/linux: Honor finite event queue timeouts
The Linux queue currently treats every nonzero timeout as an
indefinite condition wait. An empty queue therefore never returns
when a caller requests a finite timeout.
Use a CLOCK_MONOTONIC condition variable and a normalized absolute
deadline for finite waits. Reuse the deadline after spurious wakeups
and preserve nonblocking reads and BLE_NPL_TIME_FOREVER behavior.
Add a watchdog-bounded regression covering empty-queue expiration,
queued events, producer wakeups, and reuse after timeout. Register
it in the Linux NPL test target.
---
porting/npl/linux/src/wqueue.h | 40 ++++++-
porting/npl/linux/test/Makefile | 5 +
porting/npl/linux/test/test_npl_eventq_timeout.c | 146 +++++++++++++++++++++++
3 files changed, 187 insertions(+), 4 deletions(-)
diff --git a/porting/npl/linux/src/wqueue.h b/porting/npl/linux/src/wqueue.h
index 61540562e..416146fa4 100644
--- a/porting/npl/linux/src/wqueue.h
+++ b/porting/npl/linux/src/wqueue.h
@@ -23,6 +23,8 @@
#define __wqueue_h__
#include <pthread.h>
+#include <stdint.h>
+#include <time.h>
#include <list>
template <typename T> class wqueue
@@ -35,10 +37,15 @@ template <typename T> class wqueue
public:
wqueue()
{
+ pthread_condattr_t cond_attr;
+
pthread_mutexattr_init(&m_mutex_attr);
pthread_mutexattr_settype(&m_mutex_attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&m_mutex, &m_mutex_attr);
- pthread_cond_init(&m_condv, NULL);
+ pthread_condattr_init(&cond_attr);
+ pthread_condattr_setclock(&cond_attr, CLOCK_MONOTONIC);
+ pthread_cond_init(&m_condv, &cond_attr);
+ pthread_condattr_destroy(&cond_attr);
}
~wqueue() {
@@ -54,10 +61,35 @@ public:
}
T get(uint32_t tmo) {
+ struct timespec deadline;
+ int rc;
+
pthread_mutex_lock(&m_mutex);
- if (tmo) {
- while (m_queue.size() == 0) {
- pthread_cond_wait(&m_condv, &m_mutex);
+ if (tmo && tmo != UINT32_MAX && m_queue.empty()) {
+ if (clock_gettime(CLOCK_MONOTONIC, &deadline) != 0) {
+ pthread_mutex_unlock(&m_mutex);
+ return NULL;
+ }
+
+ /* Linux NPL ticks are milliseconds. Keep one absolute deadline
+ * so spurious wakeups cannot extend the requested timeout.
+ */
+ deadline.tv_sec += tmo / 1000;
+ deadline.tv_nsec += (tmo % 1000) * 1000000;
+ if (deadline.tv_nsec >= 1000000000) {
+ deadline.tv_sec++;
+ deadline.tv_nsec -= 1000000000;
+ }
+ }
+
+ while (tmo && m_queue.empty()) {
+ if (tmo == UINT32_MAX) {
+ rc = pthread_cond_wait(&m_condv, &m_mutex);
+ } else {
+ rc = pthread_cond_timedwait(&m_condv, &m_mutex, &deadline);
+ }
+ if (rc != 0) {
+ break;
}
}
diff --git a/porting/npl/linux/test/Makefile b/porting/npl/linux/test/Makefile
index cab378d32..6563b3d35 100644
--- a/porting/npl/linux/test/Makefile
+++ b/porting/npl/linux/test/Makefile
@@ -73,6 +73,7 @@ all: depend \
test_npl_task.exe \
test_npl_callout.exe \
test_npl_eventq.exe \
+ test_npl_eventq_timeout.exe \
test_npl_sem.exe \
$(NULL)
@@ -82,6 +83,9 @@ test_npl_task.exe: test_npl_task.o $(OBJS)
test_npl_eventq.exe: test_npl_eventq.o $(OBJS)
$(LD) -o $@ $^ $(LDFLAGS) $(LIBS)
+test_npl_eventq_timeout.exe: test_npl_eventq_timeout.o $(OBJS)
+ $(LD) -o $@ $^ $(LDFLAGS) $(LIBS)
+
test_npl_callout.exe: test_npl_callout.o $(OBJS)
$(LD) -o $@ $^ $(LDFLAGS) $(LIBS)
@@ -92,6 +96,7 @@ test: all
./test_npl_task.exe
./test_npl_callout.exe
./test_npl_eventq.exe
+ ./test_npl_eventq_timeout.exe
./test_npl_sem.exe
show_objs:
diff --git a/porting/npl/linux/test/test_npl_eventq_timeout.c
b/porting/npl/linux/test/test_npl_eventq_timeout.c
new file mode 100644
index 000000000..a1dcb17dc
--- /dev/null
+++ b/porting/npl/linux/test/test_npl_eventq_timeout.c
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "nimble/nimble_npl.h"
+#include "test_util.h"
+
+static struct ble_npl_eventq eventq;
+static struct ble_npl_event event;
+
+static void
+watchdog(int signo)
+{
+ static const char message[] = "FAILED: event queue wait did not finish\n";
+ ssize_t written;
+
+ (void)signo;
+ written = write(STDERR_FILENO, message, sizeof(message) - 1);
+ (void)written;
+ _exit(EXIT_FAILURE);
+}
+
+static uint64_t
+monotonic_ns(void)
+{
+ struct timespec now;
+
+ SuccessOrQuit(clock_gettime(CLOCK_MONOTONIC, &now), "clock_gettime
failed");
+ return (uint64_t)now.tv_sec * 1000000000 + now.tv_nsec;
+}
+
+static void
+test_ready(ble_npl_time_t timeout)
+{
+ ble_npl_eventq_put(&eventq, &event);
+ VerifyOrQuit(ble_npl_event_is_queued(&event), "event not queued");
+ VerifyOrQuit(ble_npl_eventq_get(&eventq, timeout) == &event,
+ "queued event not returned");
+ VerifyOrQuit(!ble_npl_event_is_queued(&event), "event still marked
queued");
+ VerifyOrQuit(ble_npl_eventq_get(&eventq, 0) == NULL,
+ "event returned more than once");
+}
+
+static void
+test_timeout(ble_npl_time_t timeout)
+{
+ uint64_t start;
+ uint64_t elapsed;
+
+ start = monotonic_ns();
+ VerifyOrQuit(ble_npl_eventq_get(&eventq, timeout) == NULL,
+ "empty queue returned an event");
+ elapsed = monotonic_ns() - start;
+ VerifyOrQuit(elapsed >= (uint64_t)timeout * 1000000,
+ "queue wait returned before its deadline");
+
+ /* A timeout must leave the queue unlocked and usable. */
+ test_ready(0);
+}
+
+static void *
+delayed_put(void *arg)
+{
+ struct timespec delay = { .tv_sec = 0, .tv_nsec = 20000000 };
+ int rc;
+
+ (void)arg;
+ do {
+ rc = nanosleep(&delay, &delay);
+ } while (rc == -1 && errno == EINTR);
+ SuccessOrQuit(rc, "nanosleep failed");
+
+ ble_npl_eventq_put(&eventq, &event);
+ return NULL;
+}
+
+static void
+test_wakeup(ble_npl_time_t timeout)
+{
+ pthread_t producer;
+ uint64_t start;
+
+ SuccessOrQuit(pthread_create(&producer, NULL, delayed_put, NULL),
+ "producer creation failed");
+ start = monotonic_ns();
+ VerifyOrQuit(ble_npl_eventq_get(&eventq, timeout) == &event,
+ "waiting consumer did not receive event");
+ if (timeout != BLE_NPL_TIME_FOREVER) {
+ VerifyOrQuit(monotonic_ns() - start < (uint64_t)timeout * 1000000,
+ "consumer waited until deadline despite queued event");
+ }
+ SuccessOrQuit(pthread_join(producer, NULL), "producer join failed");
+ VerifyOrQuit(!ble_npl_event_is_queued(&event), "event still marked
queued");
+ VerifyOrQuit(ble_npl_eventq_get(&eventq, 0) == NULL,
+ "event returned more than once");
+}
+
+int
+main(void)
+{
+ struct sigaction action = { 0 };
+
+ /* Bound failures even when a finite timeout incorrectly waits forever. */
+ action.sa_handler = watchdog;
+ SuccessOrQuit(sigemptyset(&action.sa_mask), "sigemptyset failed");
+ SuccessOrQuit(sigaction(SIGALRM, &action, NULL), "sigaction failed");
+ alarm(10);
+
+ ble_npl_eventq_init(&eventq);
+ ble_npl_event_init(&event, NULL, NULL);
+
+ VerifyOrQuit(ble_npl_eventq_get(&eventq, 0) == NULL,
+ "nonblocking read of empty queue failed");
+ test_ready(0);
+ test_ready(20);
+ test_ready(BLE_NPL_TIME_FOREVER);
+ test_timeout(20);
+ test_timeout(1250);
+ test_wakeup(5000);
+ test_wakeup(BLE_NPL_TIME_FOREVER);
+
+ alarm(0);
+ printf("All event queue timeout tests passed\n");
+ return PASS;
+}