SuperTinyKernel™ RTOS 1.08.x
Lightweight, high-performance, deterministic, bare-metal C++ RTOS for resource-constrained embedded systems. MIT Open Source License.
Loading...
Searching...
No Matches
stk_sync_msgqueue.h
Go to the documentation of this file.
1/*
2 * SuperTinyKernel(TM) RTOS: Lightweight High-Performance Deterministic C++ RTOS for Embedded Systems.
3 *
4 * Source: https://github.com/SuperTinyKernel-RTOS
5 *
6 * Copyright (c) 2022-2026 Neutron Code Limited <stk@neutroncode.com>. All Rights Reserved.
7 * License: MIT License, see LICENSE for a full text.
8 */
9
10#ifndef STK_SYNC_MSGQUEUE_H_
11#define STK_SYNC_MSGQUEUE_H_
12
13#include <cstddef> // for std::max_align_t
14
15#include "stk_sync_cv.h"
16
22
23namespace stk {
24namespace sync {
25
63{
64public:
67 static const size_t CAPACITY_MAX = 0xFFFEU;
68
75 explicit MessageQueue(uint8_t *buf, size_t capacity, size_t msg_size);
76
84
99 EWaitResult PutEx(const void *msg_ptr, Timeout timeout_ticks = WAIT_INFINITE);
100
114 bool Put(const void *msg_ptr, Timeout timeout_ticks = WAIT_INFINITE)
115 {
116 return (PutEx(msg_ptr, timeout_ticks) == WAIT_RESULT_SIGNAL);
117 }
118
127 bool TryPut(const void *msg_ptr) { return Put(msg_ptr, NO_WAIT); }
128
140 EWaitResult PutFrontEx(const void *msg_ptr, Timeout timeout_ticks = WAIT_INFINITE);
141
157 bool PutFront(const void *msg_ptr, Timeout timeout_ticks = WAIT_INFINITE)
158 {
159 return (PutFrontEx(msg_ptr, timeout_ticks) == WAIT_RESULT_SIGNAL);
160 }
161
171 bool TryPutFront(const void *msg_ptr) { return PutFront(msg_ptr, NO_WAIT); }
172
188 EWaitResult GetEx(void *msg_ptr, Timeout timeout_ticks = WAIT_INFINITE);
189
204 bool Get(void *msg_ptr, Timeout timeout_ticks = WAIT_INFINITE)
205 {
206 return (GetEx(msg_ptr, timeout_ticks) == WAIT_RESULT_SIGNAL);
207 }
208
217 bool TryGet(void *msg_ptr) { return Get(msg_ptr, NO_WAIT); }
218
231 EWaitResult PeekEx(void *msg_ptr, Timeout timeout_ticks = WAIT_INFINITE);
232
252 bool Peek(void *msg_ptr, Timeout timeout_ticks = WAIT_INFINITE)
253 {
254 return (PeekEx(msg_ptr, timeout_ticks) == WAIT_RESULT_SIGNAL);
255 }
256
266 bool TryPeek(void *msg_ptr) { return Peek(msg_ptr, NO_WAIT); }
267
279 EWaitResult PeekFrontEx(void *msg_ptr, Timeout timeout_ticks = WAIT_INFINITE);
280
300 bool PeekFront(void *msg_ptr, Timeout timeout_ticks = WAIT_INFINITE)
301 {
302 return (PeekFrontEx(msg_ptr, timeout_ticks) == WAIT_RESULT_SIGNAL);
303 }
304
314 bool TryPeekFront(void *msg_ptr) { return PeekFront(msg_ptr, NO_WAIT); }
315
324 void Reset();
325
330 size_t GetCapacity() const { return m_capacity; }
331
336 size_t GetMsgSize() const { return m_msg_size; }
337
343 size_t GetCount() const { return m_count; }
344
349 size_t GetSpace() const { return (m_capacity - m_count); }
350
355 uint8_t *GetBuffer() { return m_buffer; }
356
361 bool IsEmpty() const { return (m_count == 0U); }
362
367 bool IsFull() const { return (m_count == m_capacity); }
368
377 bool IsStorageValid() const { return (m_buffer != nullptr); }
378
379private:
381
382 // Get pointer to the raw storage for slot index \a idx.
383 uint8_t *Slot(size_t idx) const { return m_buffer + (idx * m_msg_size); }
384
385 // Advance a ring-buffer index forward by one with wrap-around.
386 size_t Next(size_t idx) const { return (idx + 1U) % m_capacity; }
387
388 // Retreat a ring-buffer index backward by one with wrap-around.
389 // Uses a branch instead of modular arithmetic to avoid unsigned underflow.
390 size_t Prev(size_t idx) const { return (idx == 0U) ? (m_capacity - 1U) : (idx - 1U); }
391
392 // Wait until the queue has a free slot or the wait fails. Caller MUST hold the critical
393 // section \a cs. Returns WAIT_RESULT_SIGNAL if a free slot is available, otherwise the
394 // failed wait outcome (timeout, cancellation, kernel failure).
395 EWaitResult WaitNotFull(IMutex &cs, Timeout timeout_ticks);
396
397 // Wait until the queue has a message or the wait fails. Caller MUST hold the critical
398 // section \a cs. Returns WAIT_RESULT_SIGNAL if a message is available, otherwise the
399 // failed wait outcome (timeout, cancellation, kernel failure).
400 EWaitResult WaitNotEmpty(IMutex &cs, Timeout timeout_ticks);
401
402 uint8_t *m_buffer;
403 const size_t m_capacity;
404 const size_t m_msg_size;
405 size_t m_count;
406 size_t m_head;
407 size_t m_tail;
410};
411
412// ---------------------------------------------------------------------------
413// Constructor
414// ---------------------------------------------------------------------------
415
416inline MessageQueue::MessageQueue(uint8_t *buf, size_t capacity, size_t msg_size)
417: m_buffer(buf),
418 m_capacity(capacity),
419 m_msg_size(msg_size),
420 m_count(0U),
421 m_head(0U),
422 m_tail(0U)
423{
424 STK_ASSERT(buf != nullptr);
425 STK_ASSERT(capacity >= 1U);
426 STK_ASSERT(capacity <= CAPACITY_MAX);
427 STK_ASSERT(msg_size >= 1U);
428}
429
430// ---------------------------------------------------------------------------
431// WaitNotFull (private helper, caller must hold the critical section)
432// ---------------------------------------------------------------------------
433
435{
437
438 const bool timed_wait = (timeout_ticks != WAIT_INFINITE) && (timeout_ticks != NO_WAIT);
439
440 // capture an absolute deadline once, before entering the wait loop; a different
441 // producer can steal the just-freed slot between NotifyOne_CS() and this task
442 // re-acquiring the critical section (Mesa-style wakeup, not a direct handoff),
443 // so re-passing the original timeout_ticks on each retry would silently restart it
444 const Timeout deadline = (timed_wait ?
445 static_cast<Timeout>(GetTicks() + timeout_ticks) : timeout_ticks);
446
447 while (m_count == m_capacity)
448 {
449 Timeout remaining = deadline;
450 if (timed_wait)
451 {
452 const Timeout now = static_cast<Timeout>(GetTicks());
453 remaining = (now >= deadline ? NO_WAIT : (deadline - now));
454 }
455
456 result = m_cv_not_full.WaitEx(cs, remaining);
457 if (result != WAIT_RESULT_SIGNAL)
458 {
459 // timeout, cancellation or kernel failure
460 break;
461 }
462 }
463
464 return result;
465}
466
467// ---------------------------------------------------------------------------
468// WaitNotEmpty (private helper, caller must hold the critical section)
469// ---------------------------------------------------------------------------
470
472{
474
475 const bool timed_wait = (timeout_ticks != WAIT_INFINITE) && (timeout_ticks != NO_WAIT);
476
477 // capture an absolute deadline once, before entering the wait loop; a different
478 // consumer can steal the just-produced message (or drain the queue via Get()/TryGet())
479 // between NotifyOne_CS() and this task re-acquiring the critical section (Mesa-style
480 // wakeup, not a direct handoff), so re-passing the original timeout_ticks on each
481 // retry would silently restart it
482 const Timeout deadline = (timed_wait ?
483 static_cast<Timeout>(GetTicks() + timeout_ticks) : timeout_ticks);
484
485 while (m_count == 0U)
486 {
487 Timeout remaining = deadline;
488 if (timed_wait)
489 {
490 const Timeout now = static_cast<Timeout>(GetTicks());
491 remaining = (now >= deadline ? NO_WAIT : (deadline - now));
492 }
493
494 result = m_cv_not_empty.WaitEx(cs, remaining);
495 if (result != WAIT_RESULT_SIGNAL)
496 {
497 // timeout, cancellation or kernel failure
498 break;
499 }
500 }
501
502 return result;
503}
504
505// ---------------------------------------------------------------------------
506// PutEx
507// ---------------------------------------------------------------------------
508
509inline EWaitResult MessageQueue::PutEx(const void *msg_ptr, Timeout timeout_ticks)
510{
511 STK_ASSERT(msg_ptr != nullptr); // API contract: msg_ptr must not be null
512 STK_ASSERT(m_count <= (CAPACITY_MAX - 1U)); // API contract: must not exceed capacity
513
515
516 const EWaitResult result = WaitNotFull(cs_, timeout_ticks);
517
518 if (result == WAIT_RESULT_SIGNAL)
519 {
520 STK_MEMCPY(Slot(m_head), msg_ptr, m_msg_size);
521 m_head = Next(m_head);
522 m_count++;
523
524 m_cv_not_empty.NotifyOne_CS();
525 }
526
527 return result;
528}
529
530// ---------------------------------------------------------------------------
531// PutFrontEx
532// ---------------------------------------------------------------------------
533
534inline EWaitResult MessageQueue::PutFrontEx(const void *msg_ptr, Timeout timeout_ticks)
535{
536 STK_ASSERT(msg_ptr != nullptr); // API contract: msg_ptr must not be null
537 STK_ASSERT(m_count <= (CAPACITY_MAX - 1U)); // API contract: must not exceed capacity
538
540
541 const EWaitResult result = WaitNotFull(cs_, timeout_ticks);
542
543 if (result == WAIT_RESULT_SIGNAL)
544 {
545 // retreat the tail pointer to claim the slot that Get() would read next,
546 // then write the message there; this makes the new message the head of
547 // the logical sequence without touching m_head at all
548 m_tail = Prev(m_tail);
549 STK_MEMCPY(Slot(m_tail), msg_ptr, m_msg_size);
550 m_count++;
551
552 m_cv_not_empty.NotifyOne_CS();
553 }
554
555 return result;
556}
557
558// ---------------------------------------------------------------------------
559// GetEx
560// ---------------------------------------------------------------------------
561
562inline EWaitResult MessageQueue::GetEx(void *msg_ptr, Timeout timeout_ticks)
563{
564 STK_ASSERT(msg_ptr != nullptr); // API contract: msg_ptr must not be null
565
567
568 const EWaitResult result = WaitNotEmpty(cs_, timeout_ticks);
569
570 if (result == WAIT_RESULT_SIGNAL)
571 {
572 STK_MEMCPY(msg_ptr, Slot(m_tail), m_msg_size);
573 m_tail = Next(m_tail);
574 m_count--;
575
576 m_cv_not_full.NotifyOne_CS();
577 }
578
579 return result;
580}
581
582// ---------------------------------------------------------------------------
583// PeekEx
584// ---------------------------------------------------------------------------
585
586inline EWaitResult MessageQueue::PeekEx(void *msg_ptr, Timeout timeout_ticks)
587{
588 STK_ASSERT(msg_ptr != nullptr); // API contract: msg_ptr must not be null
589
591
592 const EWaitResult result = WaitNotEmpty(cs_, timeout_ticks);
593
594 if (result == WAIT_RESULT_SIGNAL)
595 {
596 // copy from the tail slot without advancing the index or decrementing
597 // the count, so the message remains available for the next Get()
598 STK_MEMCPY(msg_ptr, Slot(m_tail), m_msg_size);
599
600 // the message was not consumed: pass the wake-up on to the next waiter (if any),
601 // otherwise a Get() waiter queued behind this Peek() would stay blocked although
602 // a message is available (Put() notifies only one waiter)
603 m_cv_not_empty.NotifyOne_CS();
604 }
605
606 return result;
607}
608
609// ---------------------------------------------------------------------------
610// PeekFrontEx
611// ---------------------------------------------------------------------------
612
613inline EWaitResult MessageQueue::PeekFrontEx(void *msg_ptr, Timeout timeout_ticks)
614{
615 STK_ASSERT(msg_ptr != nullptr); // API contract: msg_ptr must not be null
616
618
619 const EWaitResult result = WaitNotEmpty(cs_, timeout_ticks);
620
621 if (result == WAIT_RESULT_SIGNAL)
622 {
623 // the front-inserted message is at m_tail (PutFront retreats m_tail then
624 // writes, so the newly placed message is always at the current m_tail);
625 // for a pure-Put queue this is equally correct: m_tail is the oldest slot
626 STK_MEMCPY(msg_ptr, Slot(m_tail), m_msg_size);
627
628 // the message was not consumed: pass the wake-up on to the next waiter (if any),
629 // see PeekEx()
630 m_cv_not_empty.NotifyOne_CS();
631 }
632
633 return result;
634}
635
636// ---------------------------------------------------------------------------
637// Reset
638// ---------------------------------------------------------------------------
639
641{
642 const ScopedCriticalSection cs_;
643
644 m_count = 0U;
645 m_head = 0U;
646 m_tail = 0U;
647
648 // wake all blocked producers: the queue is now entirely empty
649 m_cv_not_full.NotifyAll_CS();
650}
651
652// ---------------------------------------------------------------------------
653
690template <size_t N, size_t MSG>
692{
693public:
694 STK_STATIC_ASSERT_DESC(N >= 1U, "MessageQueueT: capacity N must be at least 1");
695 STK_STATIC_ASSERT_DESC(N <= CAPACITY_MAX, "MessageQueueT: capacity N must not exceed CAPACITY_MAX");
696 STK_STATIC_ASSERT_DESC(MSG >= 1U, "MessageQueueT: message size MSG must be at least 1");
697
702
710
711private:
713
714 alignas(alignof(std::max_align_t)) uint8_t m_storage[N * MSG];
715};
716
717} // namespace sync
718} // namespace stk
719
720#endif /* STK_SYNC_MSGQUEUE_H_ */
static void STK_MEMCPY(void *const dest, const void *const src, const size_t size)
Implementation of STK_MEMCPY.
Definition stk_arch.h:731
#define STK_NONCOPYABLE_CLASS(TYPE)
Disables copy construction and assignment for a class.
Definition stk_defs.h:708
#define STK_ASSERT(e)
Runtime assertion. Halts execution if the expression e evaluates to false.
Definition stk_defs.h:516
#define STK_STATIC_ASSERT_DESC(X, DESC)
Compile-time assertion with a custom error description. Produces a compilation error if X is false.
Definition stk_defs.h:536
#define STK_VIRT_DTOR
Makes destructors virtual and compliant to strict rules if STK_STRICT_COMPLIANCY=0.
Definition stk_defs.h:261
Implementation of synchronization primitive: stk::sync::ConditionVariable.
Namespace of STK package.
static constexpr Timeout NO_WAIT
Timeout value: return immediately if the synchronization object is not yet signaled (non-blocking pol...
Definition stk_common.h:220
EWaitResult
Wait result (see IKernelService::Wait).
Definition stk_common.h:123
@ WAIT_RESULT_SIGNAL
The wake was caused by a signal.
Definition stk_common.h:125
int32_t Timeout
Timeout time (ticks).
Definition stk_common.h:156
static Ticks GetTicks()
Get number of ticks elapsed since kernel start.
Definition stk_helper.h:438
static constexpr Timeout WAIT_INFINITE
Timeout value: block indefinitely until the synchronization object is signaled.
Definition stk_common.h:214
Synchronization primitives for task coordination and resource protection.
Traceable object.
Definition stk_common.h:533
Interface for mutex synchronization primitive.
Definition stk_common.h:722
RAII-style low-level synchronization primitive for atomic code execution. Used as building brick for ...
Definition stk_sync_cs.h:54
Condition Variable primitive for signaling between tasks based on specific predicates.
Definition stk_sync_cv.h:68
bool TryPeek(void *msg_ptr)
Attempt to peek at the next message without blocking.
bool TryPeekFront(void *msg_ptr)
Attempt to peek at the front message without blocking.
bool IsEmpty() const
Check whether the queue is currently empty.
bool PeekFront(void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Peek at the front of the queue (the message Get() returns next) without removing it.
size_t GetCapacity() const
Get the maximum number of messages the queue can hold.
static const size_t CAPACITY_MAX
Max capacity supported (number of messages).
EWaitResult PutEx(const void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Put a message into the back of the queue (FIFO order), preserving the full wait outcome.
bool TryPutFront(const void *msg_ptr)
Attempt to put a message into the front of the queue without blocking.
size_t m_tail
read index (next slot to be read by Get())
size_t Next(size_t idx) const
size_t GetMsgSize() const
Get the size of each message in bytes.
bool TryPut(const void *msg_ptr)
Attempt to put a message into the back of the queue without blocking.
EWaitResult PeekFrontEx(void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Peek at the front of the queue without removing it, preserving the full wait outcome.
~MessageQueue()=default
Destructor.
size_t GetSpace() const
Get the number of free slots currently available.
EWaitResult WaitNotFull(IMutex &cs, Timeout timeout_ticks)
uint8_t * Slot(size_t idx) const
const size_t m_capacity
maximum number of messages stored in the queue
EWaitResult PutFrontEx(const void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Put a message into the front of the queue (LIFO / priority-insert order), preserving the full wait ou...
size_t Prev(size_t idx) const
size_t GetCount() const
Get the current number of messages in the queue.
bool TryGet(void *msg_ptr)
Attempt to get a message from the queue without blocking.
uint8_t * GetBuffer()
Get pointer to the message buffer.
size_t m_head
write index (next slot to be written by Put())
bool Peek(void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Peek at the next message to be delivered (the one Get() returns next) without removing it.
EWaitResult PeekEx(void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Peek at the next message to be delivered without removing it, preserving the full wait outcome.
EWaitResult GetEx(void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Get a message from the queue, preserving the full wait outcome.
uint8_t * m_buffer
flat byte ring-buffer: capacity slots of msg_size bytes each
ConditionVariable m_cv_not_full
signaled by Get()/Reset() when the queue is no longer full
EWaitResult WaitNotEmpty(IMutex &cs, Timeout timeout_ticks)
bool PutFront(const void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Put a message into the front of the queue (LIFO / priority-insert order).
bool IsStorageValid() const
Verify that the backing storage is valid and the pool is ready for use.
size_t m_count
current number of messages stored in the queue
bool Put(const void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Put a message into the back of the queue (FIFO order).
bool Get(void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Get a message from the queue.
bool IsFull() const
Check whether the queue is currently full.
ConditionVariable m_cv_not_empty
signaled by Put() when the queue transitions from empty
void Reset()
Discard all messages and reset the queue to the empty state.
MessageQueue(uint8_t *buf, size_t capacity, size_t msg_size)
Constructor.
const size_t m_msg_size
size of each message in bytes
uint8_t m_storage[N *MSG]
flat byte ring-buffer: N slots of MSG bytes each
~MessageQueueT()=default
Destructor.