The Pedigree Project 0.1
IntrusiveMpscQueue.h
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#ifndef KERNEL_UTILITIES_INTRUSIVEMPSCQUEUE_H
9#define KERNEL_UTILITIES_INTRUSIVEMPSCQUEUE_H
10#include "pedigree/kernel/compiler.h"
11
12#include <config.h>
13
14template <typename Node, Node* Node::* NextMember>
16
32template <typename Node, Node* Node::* NextMember>
34 static_assert(__atomic_always_lock_free(sizeof(Node*), nullptr),
35 "IntrusiveMpscQueue requires lock-free pointer atomics");
36 static_assert(alignof(Node) >= alignof(Node*),
37 "IntrusiveMpscQueue nodes require natural pointer alignment");
38 static_assert(NextMember != nullptr, "IntrusiveMpscQueue requires a valid intrusive link member");
39
40 public:
41 enum class PopResult {
42 Item,
43 Empty,
44 Transient,
45 };
46
51 explicit IntrusiveMpscQueue(Node& stub) : m_Head(&stub), m_Tail(&stub), m_Stub(&stub) {
52 storeNext(&stub, nullptr, __ATOMIC_RELAXED);
53 }
54
56 void push(Node& node) {
57 Node* previous = beginPush(node);
58 finishPush(node, previous);
59 }
60
70 MUST_USE_RESULT PopResult pop(Node*& out) {
71 out = nullptr;
72
73 Node* tail = m_Tail;
74 Node* next = loadNext(tail);
75
76 if (tail == m_Stub) {
77 if (!next) {
78 if (loadHead() == tail) {
79 return PopResult::Empty;
80 }
81 return PopResult::Transient;
82 }
83
84 m_Tail = next;
85 tail = next;
86 next = loadNext(tail);
87 }
88
89 if (next) {
90 m_Tail = next;
91 out = tail;
92 return PopResult::Item;
93 }
94
95 if (tail != loadHead()) {
96 return PopResult::Transient;
97 }
98
99 // Moving the producer head onto the permanent stub prevents a later
100 // producer from retaining the returned node as its predecessor.
101 push(*m_Stub);
102 next = loadNext(tail);
103 if (!next) {
104 // A producer can win the head exchange immediately before the stub
105 // rotation and still be responsible for this final link.
106 return PopResult::Transient;
107 }
108
109 m_Tail = next;
110 out = tail;
111 return PopResult::Item;
112 }
113
114 private:
115 friend class IntrusiveMpscQueueTestAccess<Node, NextMember>;
116
117 IntrusiveMpscQueue(const IntrusiveMpscQueue&) = delete;
118 IntrusiveMpscQueue& operator=(const IntrusiveMpscQueue&) = delete;
119
120 Node* beginPush(Node& node) {
121 // Clearing the reused link before the release exchange prevents the
122 // consumer from following a link left over from an earlier lifetime.
123 storeNext(&node, nullptr, __ATOMIC_RELAXED);
124 return __atomic_exchange_n(&m_Head, &node, __ATOMIC_ACQ_REL);
125 }
126
127 static void finishPush(Node& node, Node* previous) {
128 // This is the payload publication edge consumed by loadNext().
129 storeNext(previous, &node, __ATOMIC_RELEASE);
130 }
131
132 Node* loadHead() const {
133 return __atomic_load_n(&m_Head, __ATOMIC_ACQUIRE);
134 }
135
136 static Node* loadNext(Node* node) {
137 return __atomic_load_n(&(node->*NextMember), __ATOMIC_ACQUIRE);
138 }
139
140 static void storeNext(Node* node, Node* next, int memoryOrder) {
141 __atomic_store_n(&(node->*NextMember), next, memoryOrder);
142 }
143
144 Node* m_Head;
145 Node* m_Tail;
146 Node* const m_Stub;
147};
148
149#if defined(TESTSUITE) || (HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS)
151template <typename Node, Node* Node::* NextMember>
153 public:
155
156 struct Publication {
157 Node* node;
158 Node* previous;
159 };
160
161 static Publication beginPush(Queue& queue, Node& node) {
162 return Publication{&node, queue.beginPush(node)};
163 }
164
165 static void finishPush(Queue& queue, const Publication& publication) {
166 queue.finishPush(*publication.node, publication.previous);
167 }
168
169 static bool consumerSeesLastNode(const Queue& queue) {
170 return queue.m_Tail != queue.m_Stub && !queue.loadNext(queue.m_Tail) &&
171 queue.m_Tail == queue.loadHead();
172 }
173
174 static void rotateStub(Queue& queue) {
175 queue.push(*queue.m_Stub);
176 }
177};
178#endif
179
180#endif
void push(Node &node)
MUST_USE_RESULT PopResult pop(Node *&out)
IntrusiveMpscQueue(Node &stub)