The Pedigree Project 0.1
trace-context.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/LockGuard.h"
3#include "pedigree/kernel/process/Scheduler.h"
4#include "pedigree/kernel/process/Thread.h"
5#include "pedigree/kernel/utilities/assert.h"
6#include "pedigree/kernel/utilities/utility.h"
7
8#include "PosixSubsystem.h"
9#include "trace-context.h"
10
11namespace {
12size_t localId(const Thread& thread) {
13 return const_cast<Thread&>(thread).getId();
14}
15} // namespace
16
17// Reuse the task tokens' existing lifetime and creating-thread identity. All
18// fanout is pinned under this policy lock and delivered after releasing it.
20 public:
21 static void publish(PreparedTraceTask& task, const TraceTaskRef& creator) {
22 LockGuard<Mutex> guard(s_Lock);
23 task.m_Token->m_ParentDeathCreator = creator;
24 task.m_ParentDeathNext = s_Tasks;
25 if (s_Tasks) {
26 s_Tasks->m_ParentDeathPrevious = &task;
27 }
28 s_Tasks = &task;
29 }
30
31 static bool arm(const TraceTaskRef& task, int signal) {
32 LockGuard<Mutex> guard(s_Lock);
33 if (!task->live()) {
34 return false;
35 }
36 const bool armed = signal && task->m_ParentDeathCreator && task->m_ParentDeathCreator->live();
37 if (armed != task->m_ParentDeathArmed) {
38 if (armed) {
39 ++s_ArmedCount;
40 } else {
41 --s_ArmedCount;
42 }
43 }
44 task->m_ParentDeathArmed = armed;
45 __atomic_store_n(&task->m_ParentDeathSignal, signal, __ATOMIC_RELEASE);
46 return true;
47 }
48
49 static void retire(PreparedTraceTask& node) {
50 const TraceTaskRef& task = node.m_Token;
51 struct Delivery {
52 TraceTaskRef task;
53 int signal;
54 };
55 Vector<Delivery> deliveries;
56 {
57 LockGuard<Mutex> guard(s_Lock);
58 if (node.m_ParentDeathPrevious) {
59 node.m_ParentDeathPrevious->m_ParentDeathNext = node.m_ParentDeathNext;
60 } else {
61 s_Tasks = node.m_ParentDeathNext;
62 }
63 if (node.m_ParentDeathNext) {
64 node.m_ParentDeathNext->m_ParentDeathPrevious = node.m_ParentDeathPrevious;
65 }
66 node.m_ParentDeathNext = node.m_ParentDeathPrevious = nullptr;
67 if (task->m_ParentDeathArmed) {
68 task->m_ParentDeathArmed = false;
69 --s_ArmedCount;
70 }
71 // Most tasks never request this feature. Their exit is constant-time.
72 if (!s_ArmedCount) {
73 return;
74 }
75 for (auto* child = s_Tasks; child; child = child->m_ParentDeathNext) {
76 if (child->m_Token->m_ParentDeathCreator != task || !child->m_Token->m_ParentDeathArmed) {
77 continue;
78 }
79 child->m_Token->m_ParentDeathArmed = false;
80 --s_ArmedCount;
81 const int signal = child->m_Token->parentDeathSignal();
82 if (signal) {
83 // The owning task node cannot release its token until retirement
84 // removes it under this same policy lock.
85 deliveries.pushBack({child->m_Token, signal});
86 }
87 }
88 }
89 for (const auto& delivery : deliveries) {
91 if (posix_trace_acquire_task(delivery.task, target) != TraceStatus::Success) {
92 continue;
93 }
94 auto* subsystem = static_cast<PosixSubsystem*>(target->getParent()->getSubsystem());
95 subsystem->sendSignal(target.get(), delivery.signal, false, true);
96 }
97 }
98
99 private:
100 static Mutex s_Lock;
101 static PreparedTraceTask* s_Tasks;
102 static size_t s_ArmedCount;
103};
104Mutex ParentDeathSignals::s_Lock;
105PreparedTraceTask* ParentDeathSignals::s_Tasks = nullptr;
106size_t ParentDeathSignals::s_ArmedCount = 0;
107
108TraceTaskToken::Snapshot TraceTaskToken::snapshot() const {
109 LockGuard<Spinlock> guard(m_Lock);
110 return m_Identity;
111}
112
113bool TraceTaskToken::live() const {
114 return snapshot().state == State::Live;
115}
116
117void TraceTaskToken::publish(const Thread& thread) {
118 LockGuard<Spinlock> guard(m_Lock);
119 assert(m_Identity.state == State::Staged);
120 m_Identity.processId = thread.getParent()->getId();
121 m_Identity.localThreadId = localId(thread);
122 m_Identity.linuxTaskId = thread.getTaskId();
123 m_Identity.state = State::Live;
124}
125
126void TraceTaskToken::promote(const Thread& thread) {
127 LockGuard<Spinlock> guard(m_Lock);
128 if (m_Identity.state != State::Live)
129 return;
130 assert(m_Identity.processId == thread.getParent()->getId());
131 assert(m_Identity.localThreadId == localId(thread));
132 m_Identity.linuxTaskId = thread.getTaskId();
133}
134
135void TraceTaskToken::close() {
136 LockGuard<Spinlock> guard(m_Lock);
137 m_Identity.state = State::Closed;
138}
139
140PosixTraceContext::~PosixTraceContext() {
141 close();
142 // The core creation scope and current syscall retain the owning subsystem.
143 assert(!m_ThreadCreations && !m_EnrollmentPending);
144}
145
146bool PosixTraceContext::valid() const {
147 return !__atomic_load_n(&m_Closed, __ATOMIC_ACQUIRE);
148}
149
150void PosixTraceContext::attach(Process& process) {
151 LockGuard<Mutex> guard(m_AdmissionLock);
152 assert(valid());
153 assert(!m_Attached || m_ProcessId == process.getId());
154 m_ProcessId = process.getId();
155 m_Attached = true;
156}
157
158TraceStatus PosixTraceContext::prepareTask(UniquePointer<PreparedTraceTask>& result) {
159 assert(!result);
160 {
161 LockGuard<Mutex> guard(m_AdmissionLock);
162 if (!m_Attached || !valid())
163 return TraceStatus::Missing;
164 }
166 if (!prepared)
167 return TraceStatus::NoMemory;
168 prepared.get()->m_Token = TraceTaskRef::tryAllocate();
169 if (!prepared.get()->m_Token)
170 return TraceStatus::NoMemory;
171 result = pedigree_std::move(prepared);
172 return TraceStatus::Success;
173}
174
175TraceStatus PosixTraceContext::publishTask(UniquePointer<PreparedTraceTask>& prepared,
176 Thread& thread) {
177 assert(prepared && prepared.get()->m_Token);
178 LockGuard<Mutex> guard(m_AdmissionLock);
179 if (!m_Attached || !valid() || thread.getParent()->getId() != m_ProcessId ||
181 return TraceStatus::Missing;
182 // Shutdown publishes TerminateThread before calling the retirement hook.
183 // Its earlier hook therefore cannot be undone by delayed publication.
184 if (tokenUnlocked(localId(thread)))
185 return TraceStatus::Denied;
186 prepared.get()->m_Token->publish(thread);
187 ParentDeathSignals::publish(*prepared.get(), m_Creator);
188 auto* node = prepared.releaseOwnership();
189 node->m_Next = m_Tasks;
190 m_Tasks = node;
191 return TraceStatus::Success;
192}
193
194TraceTaskRef PosixTraceContext::tokenUnlocked(size_t id) const {
195 for (auto* task = m_Tasks; task; task = task->m_Next) {
196 const auto identity = task->m_Token->snapshot();
197 if (identity.state == TraceTaskToken::State::Live && identity.localThreadId == id)
198 return task->m_Token;
199 }
200 return {};
201}
202
203bool PosixTraceContext::ownsTokenUnlocked(const TraceTaskRef& token) const {
204 for (auto* task = m_Tasks; task; task = task->m_Next) {
205 if (task->m_Token == token)
206 return token->live();
207 }
208 return false;
209}
210
211bool PosixTraceContext::taskToken(const Thread& thread, TraceTaskRef& result) const {
212 assert(!result);
213 TraceTaskRef acquired;
214 {
215 LockGuard<Mutex> guard(m_AdmissionLock);
216 if (!m_Attached || !valid() || thread.getParent()->getId() != m_ProcessId)
217 return false;
218 acquired = tokenUnlocked(localId(thread));
219 }
220 result = pedigree_std::move(acquired);
221 return bool(result);
222}
223
224void PosixTraceContext::setCreator(const TraceTaskRef& token) {
225 LockGuard<Mutex> guard(m_AdmissionLock);
226 assert(m_Attached && valid() && !m_Tasks && !m_Creator);
227 m_Creator = token;
228}
229
230TraceTaskRef PosixTraceContext::creator() const {
231 LockGuard<Mutex> guard(m_AdmissionLock);
232 return valid() ? m_Creator : TraceTaskRef{};
233}
234
235bool PosixTraceContext::setParentDeathSignal(const Thread& thread, int signal) {
236 TraceTaskRef task;
237 if (!taskToken(thread, task)) {
238 return false;
239 }
240 return ParentDeathSignals::arm(task, signal);
241}
242
243void PosixTraceContext::promoteExec(const Thread& thread) {
244 TraceTaskRef token;
245 {
246 LockGuard<Mutex> guard(m_AdmissionLock);
247 if (!valid())
248 return;
249 assert(thread.getParent()->getId() == m_ProcessId);
250 token = tokenUnlocked(localId(thread));
251 assert(bool(token));
252 token->promote(thread);
253 }
254}
255
256bool PosixTraceContext::acquireIncoming(TraceRelationRef& result) const {
257 assert(!result);
258 LockGuard<Spinlock> guard(m_RelationLock);
259 if (!valid())
260 return false;
261 result = m_Incoming;
262 return bool(result);
263}
264
265bool PosixTraceContext::selectStop(size_t parentPid, bool stopped, bool continued, bool consume,
266 PosixWait::Report& report) {
267 LockGuard<Spinlock> guard(m_RelationLock);
268 if (!valid() || !m_Incoming)
269 return false;
270 const auto owner = m_Incoming->tracer()->snapshot();
271 return owner.state == TraceTaskToken::State::Live && owner.processId == parentPid &&
272 m_Incoming->selectStop(stopped, continued, consume, report);
273}
274
275bool PosixTraceContext::hasIncoming() const {
276 LockGuard<Spinlock> guard(m_RelationLock);
277 return bool(m_Incoming);
278}
279
280void PosixTraceContext::clearIncoming(const PosixTraceRelation* relation) {
281 TraceRelationRef retired;
282 {
283 LockGuard<Spinlock> guard(m_RelationLock);
284 if (m_Incoming.get() == relation)
285 retired = pedigree_std::move(m_Incoming);
286 }
287}
288
289void PosixTraceContext::retireTask(const Thread& thread) {
290 if (!valid())
291 return;
292 PreparedTraceTask* retired = nullptr;
293 TraceRelationRef incoming;
294 {
295 LockGuard<Mutex> guard(m_AdmissionLock);
296 if (!valid() || thread.getParent()->getId() != m_ProcessId)
297 return;
298 auto** position = &m_Tasks;
299 while (*position && (*position)->m_Token->snapshot().localThreadId != localId(thread))
300 position = &(*position)->m_Next;
301 if (!*position)
302 return;
303 retired = *position;
304 *position = retired->m_Next;
305 retired->m_Token->close();
306 LockGuard<Spinlock> publication(m_RelationLock);
307 if (m_Incoming && m_Incoming->tracee() == retired->m_Token)
308 incoming = pedigree_std::move(m_Incoming);
309 }
310 ParentDeathSignals::retire(*retired);
311 if (incoming)
312 incoming->taskClosed(retired->m_Token);
313 for (;;) {
314 OwnedRelation* outgoing = nullptr;
315 {
316 LockGuard<Mutex> guard(m_AdmissionLock);
317 auto** position = &m_Outgoing;
318 while (*position && (*position)->relation->tracer() != retired->m_Token)
319 position = &(*position)->next;
320 if (*position) {
321 outgoing = *position;
322 *position = outgoing->next;
323 }
324 }
325 if (!outgoing)
326 break;
327 outgoing->relation->taskClosed(retired->m_Token);
328 delete outgoing;
329 }
330 delete retired;
331}
332
333void PosixTraceContext::close() {
334 // The once-only, IRQ-enabled subsystem exit boundary performs first close.
335 // Later terminal destructor fallbacks must not acquire a sleeping lock.
336 if (!valid())
337 return;
338 PreparedTraceTask* tasks = nullptr;
339 OwnedRelation* outgoing = nullptr;
340 TraceRelationRef incoming;
342 {
343 LockGuard<Mutex> guard(m_AdmissionLock);
344 if (!valid())
345 return;
346 __atomic_store_n(&m_Closed, true, __ATOMIC_RELEASE);
347 tasks = m_Tasks;
348 m_Tasks = nullptr;
349 outgoing = m_Outgoing;
350 m_Outgoing = nullptr;
351 for (auto* task = tasks; task; task = task->m_Next)
352 task->m_Token->close();
353 creator = pedigree_std::move(m_Creator);
354 LockGuard<Spinlock> publication(m_RelationLock);
355 incoming = pedigree_std::move(m_Incoming);
356 }
357 if (incoming)
358 incoming->taskClosed(incoming->tracee());
359 while (outgoing) {
360 auto* next = outgoing->next;
361 outgoing->relation->taskClosed(outgoing->relation->tracer());
362 delete outgoing;
363 outgoing = next;
364 }
365 while (tasks) {
366 auto* next = tasks->m_Next;
367 ParentDeathSignals::retire(*tasks);
368 delete tasks;
369 tasks = next;
370 }
371}
372
373TraceStatus posix_trace_acquire_task(const TraceTaskRef& token, Process::ThreadLease& result) {
374 assert(!result);
375 if (!token)
376 return TraceStatus::Missing;
377 const auto identity = token->snapshot();
378 if (identity.state != TraceTaskToken::State::Live)
379 return TraceStatus::Missing;
381 if (!Scheduler::instance().acquireProcessById(process, identity.processId) ||
382 process->getType() != Process::Posix)
383 return TraceStatus::Missing;
385 if (!process->acquireThreadById(task, identity.localThreadId) || !task->acceptingEvents() ||
387 return TraceStatus::Missing;
388 auto* subsystem = static_cast<PosixSubsystem*>(process->getSubsystem());
389 TraceTaskRef actual;
390 if (!subsystem || !subsystem->traceContext().taskToken(*task.get(), actual) || actual != token ||
391 !token->live())
392 return TraceStatus::Missing;
393 result = pedigree_std::move(task);
394 return TraceStatus::Success;
395}
Definition Mutex.h:56
virtual void sendSignal(Thread *pThread, int signal, bool yield=true, bool processDirected=false)
size_t getId()
Definition Process.h:499
MUST_USE_RESULT bool acquireThreadById(ThreadLease &lease, size_t id)
Definition Process.cc:1446
static Scheduler & instance()
Definition Scheduler.h:96
static SharedPointer< TraceTaskToken > tryAllocate(Args...)
T * get() const
@ Continue
No unwind necessary, carry on as normal.
Definition Thread.h:517
@ TerminateThread
Exit only this thread during Process exit.
Definition Thread.h:519
UnwindType getUnwindState()
Definition Thread.h:535
Process * getParent() const
Definition Thread.h:340
T * releaseOwnership() noexcept
Definition Pointers.h:106
A vector / dynamic array.
Definition Vector.h:33
void pushBack(const T &value)
Definition Vector.h:275