The Pedigree Project 0.1
primitive-regressions.cc
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#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/machine/IrqHandler.h"
11#include "pedigree/kernel/machine/IrqHandlerRegistry.h"
12#include "pedigree/kernel/process/Completion.h"
13#include "pedigree/kernel/process/ConditionVariable.h"
14#include "pedigree/kernel/process/MemoryPressureManager.h"
15#include "pedigree/kernel/process/Mutex.h"
16#include "pedigree/kernel/process/OperationBarrier.h"
17#include "pedigree/kernel/process/Scheduler.h"
18#include "pedigree/kernel/process/Semaphore.h"
19#include "pedigree/kernel/process/Thread.h"
20#include "pedigree/kernel/process/WaitQueue.h"
21#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
22#include "pedigree/kernel/processor/Processor.h"
23#include "pedigree/kernel/time/Time.h"
24#include "pedigree/kernel/utilities/Buffer.h"
25#include "pedigree/kernel/utilities/Mailbox.h"
26#include "pedigree/kernel/utilities/MemoryPool.h"
28#include "pedigree/kernel/utilities/RingBuffer.h"
29#include "pedigree/kernel/utilities/Vector.h"
30
31namespace {
32bool check(bool condition, const char* test, const char* detail) {
33 if (condition) {
34 return true;
35 }
36
37 ERROR("HOSTED-WAIT-TEST: FAIL " << test << ": " << detail);
38 return false;
39}
40
41bool waitUntilQueued(Thread* thread, size_t debugState) {
42 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
43 while (Time::getTicks() < deadline) {
44 Thread::WaitDebugInfo info = {};
45 uintptr_t debugAddress = 0;
46 if (thread->getWaitDebugInfo(info) && info.queue && info.queued &&
47 thread->getDebugState(debugAddress) == debugState) {
48 return true;
49 }
51 }
52 return false;
53}
54
55class NestedRemovalProbe final : public IrqHandler {
56 public:
57 NestedRemovalProbe(IrqHandlerRegistry& registry, uint8_t key)
58 : registry(registry),
59 key(key),
60 nested(nullptr),
61 calls(0),
62 removed(false),
63 nestedRetired(true) {}
64
65 IrqDisposition irq(irq_id_t) override {
66 ++calls;
67 if (nested) {
68 const bool interruptsWereEnabled = Processor::getInterrupts();
70 const bool published = registry.publishThreadedDispatch(nested->key, 1);
71 Processor::setInterrupts(interruptsWereEnabled);
73 nestedRetired = published && registry.dispatchThreaded(nested->key, 1, result) &&
74 result.handled &&
75 registry.unregisterHandler(nested->key, nested) ==
76 IrqHandlerRegistry::UnregisterResult::NotFound;
77 }
78 removed =
79 registry.unregisterHandler(key, this) == IrqHandlerRegistry::UnregisterResult::Deferred;
80 return IrqDisposition::Handled;
81 }
82
83 IrqHandlerRegistry& registry;
84 uint8_t key;
85 NestedRemovalProbe* nested;
86 size_t calls;
87 bool removed;
88 bool nestedRetired;
89};
90
91bool registryNestedRemoval() {
92 constexpr const char* Test = "registry-nested-removal";
93 static IrqHandlerRegistry registry;
94 NestedRemovalProbe outer(registry, 2);
95 NestedRemovalProbe inner(registry, 3);
96 outer.nested = &inner;
97 const bool registered = registry.registerThreadedHandler(outer.key, &outer) &&
98 registry.registerThreadedHandler(inner.key, &inner);
99 const bool interruptsWereEnabled = Processor::getInterrupts();
101 const bool published = registered && registry.publishThreadedDispatch(outer.key, 1);
102 Processor::setInterrupts(interruptsWereEnabled);
104 const bool dispatched = published && registry.dispatchThreaded(outer.key, 1, result);
105 const bool reused = registry.registerThreadedHandler(outer.key, &outer) &&
106 registry.registerThreadedHandler(inner.key, &inner);
107 const bool cleaned = registry.unregisterHandler(outer.key, &outer) ==
108 IrqHandlerRegistry::UnregisterResult::Completed &&
109 registry.unregisterHandler(inner.key, &inner) ==
110 IrqHandlerRegistry::UnregisterResult::Completed;
111 const bool passed =
112 check(registered && dispatched && result.handled && outer.calls == 1 && inner.calls == 1 &&
113 outer.removed && inner.removed && outer.nestedRetired && reused && cleaned,
114 Test, "nested callback removal leaked admission or retirement");
115 if (passed) {
116 NOTICE("HOSTED-WAIT-TEST: PASS " << Test);
117 }
118 return passed;
119}
120
121bool radixTreeExportedAbi() {
122 RadixTree<void*> tree;
123 const String key("hosted-radix-tree-abi");
124 void* const expected = &tree;
125
126 void* value = expected;
127 const bool missing = !tree.lookup(key, value) && value == nullptr;
128
129 tree.insert(key, expected);
130 value = nullptr;
131 const bool found = tree.lookup(key, value) && value == expected;
132
133 tree.remove(key);
134 value = expected;
135 const bool removed = !tree.lookup(key, value) && value == nullptr;
136
137 const bool passed = check(missing && found && removed, "radix-tree-exported-abi",
138 "module-to-kernel bool-and-output lookup contract failed");
139 if (passed) {
140 NOTICE("HOSTED-WAIT-TEST: PASS radix-tree-exported-abi");
141 }
142 return passed;
143}
144
145bool semaphoreDrainAvailable() {
146 Semaphore semaphore(3);
147 const size_t firstDrain = semaphore.drainAvailable();
148 const bool emptyAfterFirstDrain = !semaphore.tryAcquire();
149 semaphore.release(2);
150 const size_t secondDrain = semaphore.drainAvailable();
151 const size_t emptyDrain = semaphore.drainAvailable();
152
153 const bool passed =
154 check(firstDrain == 3 && emptyAfterFirstDrain && secondDrain == 2 && emptyDrain == 0 &&
155 !semaphore.tryAcquire(),
156 "semaphore-drain-available", "available items were not removed exactly once");
157 if (passed) {
158 NOTICE("HOSTED-WAIT-TEST: PASS semaphore-drain-available");
159 }
160 return passed;
161}
162
163struct CompletionContext {
164 explicit CompletionContext(Completion* completion)
165 : completion(completion), entered(0), completed(0) {}
166
167 Completion* completion;
168 Atomic<size_t> entered;
169 Atomic<size_t> completed;
170};
171
172struct TerminalCompletionContext {
173 explicit TerminalCompletionContext(Semaphore* completion)
174 : completion(completion), entered(0), acquired(0), returned(0) {}
175
176 Semaphore* completion;
177 Atomic<size_t> entered;
178 Atomic<size_t> acquired;
179 Atomic<size_t> returned;
180};
181
182struct OperationBarrierContext {
183 explicit OperationBarrierContext(OperationBarrier* barrier)
184 : barrier(barrier), workEntered(0), workFinished(0), closeFinished(0), releaseWork(0) {}
185
186 OperationBarrier* barrier;
187 Atomic<size_t> workEntered;
188 Atomic<size_t> workFinished;
189 Atomic<size_t> closeFinished;
190 Semaphore releaseWork;
191};
192
193int waitForCompletion(void* parameter) {
194 CompletionContext* context = reinterpret_cast<CompletionContext*>(parameter);
195 context->entered += 1;
196 if (context->completion->wait()) {
197 context->completed += 1;
198 }
199 return 0;
200}
201
202int waitForTerminalCompletion(void* parameter) {
203 TerminalCompletionContext* context = reinterpret_cast<TerminalCompletionContext*>(parameter);
204 context->entered += 1;
205 if (context->completion->acquireForCompletion()) {
206 context->acquired += 1;
207 }
208 context->returned += 1;
209 return 0;
210}
211
212int runAdmittedOperation(void* parameter) {
213 OperationBarrierContext* context = reinterpret_cast<OperationBarrierContext*>(parameter);
214 context->workEntered += 1;
215 const bool released = context->releaseWork.acquireForCompletion();
216 (void)released;
217 context->workFinished += 1;
218 context->barrier->leave();
219 return 0;
220}
221
222int closeOperationBarrier(void* parameter) {
223 OperationBarrierContext* context = reinterpret_cast<OperationBarrierContext*>(parameter);
224 context->barrier->closeAndWait();
225 context->closeFinished += 1;
226 return 0;
227}
228
229bool completionLifecycle() {
230 bool passed = true;
231
232 Completion latched;
233 passed &=
234 check(latched.complete(), "completion-lifecycle", "the first early completion was rejected");
235 passed &=
236 check(!latched.complete(), "completion-lifecycle", "duplicate early completion was accepted");
237 passed &=
238 check(latched.wait(), "completion-lifecycle", "complete-before-wait did not stay latched");
239
240 Completion delayed;
241 CompletionContext context(&delayed);
242 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), waitForCompletion, &context,
243 nullptr, false, true);
244 waiter->setName("hosted Completion waiter");
245
246 const bool queued = waitUntilQueued(waiter, Thread::SemWait);
247 const bool completed = delayed.complete();
248 const bool duplicateRejected = !delayed.complete();
249 const bool joined = waiter->join();
250
251 passed &= check(context.entered == 1 && queued, "completion-lifecycle",
252 "waiter-before-complete did not publish its wait");
253 passed &= check(completed && duplicateRejected, "completion-lifecycle",
254 "delayed completion was not exactly-once");
255 passed &= check(joined && context.completed == 1, "completion-lifecycle",
256 "the delayed waiter did not wake exactly once");
257
258 if (passed) {
259 NOTICE("HOSTED-WAIT-TEST: PASS completion-lifecycle");
260 }
261 return passed;
262}
263
264bool terminalCompletionBarrier() {
265 Semaphore completion(0);
266 TerminalCompletionContext context(&completion);
267 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), waitForTerminalCompletion,
268 &context, nullptr, false, true);
269 waiter->setName("hosted terminal completion waiter");
270
271 const bool queued = waitUntilQueued(waiter, Thread::SemWait);
272 waiter->setUnwindState(Thread::TerminateThread);
273
274 // Let the terminal wake reach acquireForCompletion(). It must re-enrol
275 // rather than abandon storage still owned by the producer.
276 for (size_t i = 0; i < 8; ++i) {
278 }
279 const bool deferred = context.entered == 1 && context.returned == 0;
280
281 completion.release();
282 const bool joined = waiter->join();
283
284 const bool passed = check(
285 queued && deferred && joined && context.acquired == 1 && context.returned == 1,
286 "terminal-completion-barrier", "terminal teardown escaped an unfinished completion barrier");
287 if (passed) {
288 NOTICE("HOSTED-WAIT-TEST: PASS terminal-completion-barrier");
289 }
290 return passed;
291}
292
293bool operationBarrierLifecycle() {
294 OperationBarrier barrier;
295 OperationBarrierContext context(&barrier);
296 Process* process = Scheduler::instance().getKernelProcess();
297
298 const bool admitted = barrier.tryEnter();
299 const bool secondAdmitted = barrier.tryEnter();
300 Thread* worker = new Thread(process, runAdmittedOperation, &context, nullptr, false, true);
301 worker->setName("hosted admitted operation");
302
303 while (!context.workEntered) {
305 }
306
307 Thread* closer = new Thread(process, closeOperationBarrier, &context, nullptr, false, true);
308 closer->setName("hosted operation barrier closer");
309
310 const bool closeQueued = waitUntilQueued(closer, Thread::CallbackDrain);
311 const bool closeBlocked = closeQueued && context.closeFinished == 0 && context.workFinished == 0;
312 const bool lateRejected = !barrier.tryEnter();
313 barrier.leave();
314 const bool partialReleaseBlocked = !barrier.isClosedAndDrained() && context.closeFinished == 0;
315
316 context.releaseWork.release();
317 const bool workerJoined = worker->join();
318 const bool closerJoined = closer->join();
319 const bool drained = barrier.isClosedAndDrained();
320
321 const bool passed = check(admitted && secondAdmitted && closeBlocked && lateRejected &&
322 partialReleaseBlocked && workerJoined && closerJoined &&
323 context.workFinished == 1 && context.closeFinished == 1 && drained,
324 "operation-barrier-lifecycle",
325 "close did not reject late work and drain the admitted operation");
326 if (passed) {
327 NOTICE("HOSTED-WAIT-TEST: PASS operation-barrier-lifecycle");
328 }
329 return passed;
330}
331
332struct ConditionTimeoutContext {
333 ConditionTimeoutContext(ConditionVariable* condition, Thread* waiter, Time::Timestamp signalAt)
334 : condition(condition), waiter(waiter), signalAt(signalAt), published(0), signals(0) {}
335
336 ConditionVariable* condition;
337 Thread* waiter;
338 Time::Timestamp signalAt;
339 Atomic<size_t> published;
340 Atomic<size_t> signals;
341};
342
343Atomic<size_t> g_ZeroTimeoutPublications(0);
344Thread* g_ZeroTimeoutThread = nullptr;
345
346void observeZeroTimeoutPublication(WaitQueue*, Thread* thread, const WaitQueue::Channel&,
347 size_t debugState) {
348 if (thread == g_ZeroTimeoutThread && debugState == Thread::CondWait) {
349 g_ZeroTimeoutPublications += 1;
350 }
351}
352
353int delayedConditionSignal(void* parameter) {
354 ConditionTimeoutContext* context = reinterpret_cast<ConditionTimeoutContext*>(parameter);
355 if (waitUntilQueued(context->waiter, Thread::CondWait)) {
356 context->published += 1;
357 }
358
359 while (Time::getTicks() < context->signalAt) {
361 }
362 context->condition->signal();
363 context->signals += 1;
364 return 0;
365}
366
367bool conditionVariableTimeoutAccounting(Thread* thread) {
368 ConditionVariable condition;
369 Mutex mutex;
370 bool passed = true;
371
372 constexpr Time::Timestamp InitialTimeout = 250 * Time::Multiplier::Millisecond;
373 Time::Timestamp remaining = InitialTimeout;
374 ConditionTimeoutContext context(&condition, thread,
375 Time::getTicks() + (20 * Time::Multiplier::Millisecond));
376 Thread* signaler = new Thread(Scheduler::instance().getKernelProcess(), delayedConditionSignal,
377 &context, nullptr, false, true);
378 signaler->setName("hosted timed ConditionVariable signaler");
379
380 passed &= check(mutex.acquire(), "condition-variable-timeout",
381 "the signalled wait mutex could not be acquired");
382 ConditionVariable::Error error = ConditionVariable::NoError;
383 const bool signalled = condition.wait(mutex, remaining, error);
384 const bool mutexHeldAfterSignal = mutex.isOwnedByCurrentThread();
385 mutex.release();
386 const bool signalerJoined = signaler->join();
387
388 passed &= check(signalled && error == ConditionVariable::NoError, "condition-variable-timeout",
389 "the delayed signal was reported as an error");
390 passed &= check(context.published == 1 && context.signals == 1 && signalerJoined,
391 "condition-variable-timeout",
392 "the delayed signal did not observe one published waiter");
393 passed &= check(mutexHeldAfterSignal, "condition-variable-timeout",
394 "the signalled wait did not reacquire its mutex");
395 passed &= check(remaining > 0 && remaining < InitialTimeout, "condition-variable-timeout",
396 "remaining timeout underflowed or used incompatible clock units");
397
398 Time::Timestamp expiring = 20 * Time::Multiplier::Millisecond;
399 passed &= check(mutex.acquire(), "condition-variable-timeout",
400 "the expiring wait mutex could not be acquired");
401 error = ConditionVariable::NoError;
402 const bool timedWait = condition.wait(mutex, expiring, error);
403 const bool mutexHeldAfterTimeout = mutex.isOwnedByCurrentThread();
404 mutex.release();
405
406 passed &= check(!timedWait && error == ConditionVariable::TimedOut, "condition-variable-timeout",
407 "an expired wait did not report TimedOut");
408 passed &= check(expiring == 0, "condition-variable-timeout",
409 "an expired wait retained a nonzero timeout");
410 passed &= check(mutexHeldAfterTimeout, "condition-variable-timeout",
411 "the expired wait did not reacquire its mutex");
412
413 Time::Timestamp immediate = 0;
414 passed &= check(mutex.acquire(), "condition-variable-timeout",
415 "the immediate wait mutex could not be acquired");
416 const size_t alarmCreatesBeforeImmediate = Time::getHostedAlarmCreateCount();
417 g_ZeroTimeoutPublications = 0;
418 g_ZeroTimeoutThread = thread;
419 WaitQueue::setBeforeBlockHook(observeZeroTimeoutPublication);
420 error = ConditionVariable::NoError;
421 const bool immediateWait = condition.wait(mutex, immediate, error);
422 WaitQueue::setBeforeBlockHook(nullptr);
423 g_ZeroTimeoutThread = nullptr;
424 const bool mutexHeldAfterImmediate = mutex.isOwnedByCurrentThread();
425 mutex.release();
426
427 passed &= check(!immediateWait && error == ConditionVariable::TimedOut && immediate == 0,
428 "condition-variable-timeout", "zero did not request an immediate timeout");
429 passed &= check(mutexHeldAfterImmediate, "condition-variable-timeout",
430 "the immediate timeout released its mutex");
431 passed &= check(g_ZeroTimeoutPublications == 0 &&
432 Time::getHostedAlarmCreateCount() == alarmCreatesBeforeImmediate,
433 "condition-variable-timeout",
434 "the immediate timeout published a waiter or allocated an alarm");
435
436 if (passed) {
437 NOTICE("HOSTED-WAIT-TEST: PASS condition-variable-timeout");
438 }
439 return passed;
440}
441
442struct MemoryPoolContext {
443 explicit MemoryPoolContext(MemoryPool* pool) : pool(pool), entered(0), returned(0), result(0) {}
444
445 MemoryPool* pool;
446 Atomic<size_t> entered;
447 Atomic<size_t> returned;
448 Atomic<uintptr_t> result;
449};
450
451int allocateFromExhaustedPool(void* parameter) {
452 MemoryPoolContext* context = reinterpret_cast<MemoryPoolContext*>(parameter);
453 context->entered += 1;
454 context->result = context->pool->allocate();
455 context->returned += 1;
456 return 0;
457}
458
459bool memoryPoolBlockingAndStride() {
460 constexpr size_t BufferSize = 512;
461 MemoryPool pool("hosted-memory-pool-regression");
462 bool passed = true;
463 if (!pool.initialise(1, BufferSize)) {
464 return check(false, "memory-pool-lifecycle", "a one-page hosted pool could not be initialised");
465 }
466
467 const size_t bufferCount = PhysicalMemoryManager::getPageSize() / BufferSize;
468 pool.acquireHostedMappingLock();
469 const bool compactedWhileMapping = pool.trim();
470 pool.releaseHostedMappingLock();
471 passed &= check(!compactedWhileMapping, "memory-pool-lifecycle",
472 "memory-pressure compaction waited on its own mapping lock");
473
474 Vector<uintptr_t> buffers;
475 for (size_t i = 0; i < bufferCount; ++i) {
476 const uintptr_t buffer = pool.allocateNow();
477 buffers.pushBack(buffer);
478 passed &= check(buffer != 0, "memory-pool-lifecycle",
479 "the pool exhausted before its advertised capacity");
480 if (i) {
481 passed &= check(buffer == buffers[0] + (i * BufferSize), "memory-pool-lifecycle",
482 "buffers did not use the configured fixed stride");
483 }
484 passed &= check((buffer % BufferSize) == 0, "memory-pool-lifecycle",
485 "a buffer did not retain its configured alignment");
486 }
487 passed &= check(pool.allocateNow() == 0, "memory-pool-lifecycle",
488 "nonblocking allocation succeeded after exhaustion");
489
490 MemoryPoolContext context(&pool);
491 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), allocateFromExhaustedPool,
492 &context, nullptr, false, true);
493 waiter->setName("hosted MemoryPool waiter");
494 const bool queued = waitUntilQueued(waiter, Thread::CondWait);
495
496 const size_t freedIndex = bufferCount / 2;
497 const uintptr_t freed = buffers[freedIndex];
498 pool.free(freed);
499 const bool joined = waiter->join();
500 const uintptr_t reused = context.result;
501
502 passed &= check(context.entered == 1 && queued, "memory-pool-lifecycle",
503 "the exhausted-pool allocation did not block");
504 passed &= check(joined && reused == freed, "memory-pool-lifecycle",
505 "free did not wake the waiter with the released buffer");
506
507 for (size_t i = 0; i < buffers.count(); ++i) {
508 if (i != freedIndex) {
509 pool.free(buffers[i]);
510 }
511 }
512 if (reused) {
513 pool.free(reused);
514 }
515
516 if (passed) {
517 NOTICE("HOSTED-WAIT-TEST: PASS memory-pool-lifecycle");
518 }
519 return passed;
520}
521
522bool memoryPoolCloseAndDrain() {
523 constexpr size_t BufferSize = 512;
524 MemoryPool* pool = new MemoryPool("hosted-memory-pool-close-regression");
525 bool passed = true;
526 if (!pool->initialise(1, BufferSize)) {
527 delete pool;
528 return check(false, "memory-pool-close-drain",
529 "a one-page hosted pool could not be initialised");
530 }
531
532 const size_t bufferCount = PhysicalMemoryManager::getPageSize() / BufferSize;
533 for (size_t i = 0; i < bufferCount; ++i) {
534 passed &= check(pool->allocateNow() != 0, "memory-pool-close-drain",
535 "the close regression could not exhaust the pool");
536 }
537
538 MemoryPoolContext context(pool);
539 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), allocateFromExhaustedPool,
540 &context, nullptr, false, true);
541 waiter->setName("hosted MemoryPool close waiter");
542 const bool queued = waitUntilQueued(waiter, Thread::CondWait);
543
544 // Destruction must wake this already-entered allocation and wait until it
545 // has stopped touching the pool's mutex and condition variable.
546 delete pool;
547 const bool joined = waiter->join();
548
549 passed &= check(context.entered == 1 && queued, "memory-pool-close-drain",
550 "the exhausted allocation was not active before close");
551 passed &= check(joined && context.result == 0, "memory-pool-close-drain",
552 "close did not drain the blocked allocation with a null result");
553
554 if (passed) {
555 NOTICE("HOSTED-WAIT-TEST: PASS memory-pool-close-drain");
556 }
557 return passed;
558}
559
560bool memoryPoolTerminalDrain() {
561 constexpr size_t BufferSize = 512;
562 MemoryPool* pool = new MemoryPool("hosted-memory-pool-terminal-regression");
563 bool passed = true;
564 if (!pool->initialise(1, BufferSize)) {
565 delete pool;
566 return check(false, "memory-pool-terminal-drain",
567 "a one-page hosted pool could not be initialised");
568 }
569
570 const size_t bufferCount = PhysicalMemoryManager::getPageSize() / BufferSize;
571 for (size_t i = 0; i < bufferCount; ++i) {
572 passed &= check(pool->allocateNow() != 0, "memory-pool-terminal-drain",
573 "the terminal regression could not exhaust the pool");
574 }
575
576 MemoryPoolContext context(pool);
577 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), allocateFromExhaustedPool,
578 &context, nullptr, false, true);
579 waiter->setName("hosted MemoryPool terminal waiter");
580 const bool queued = waitUntilQueued(waiter, Thread::CondWait);
581
582 waiter->setUnwindState(Thread::TerminateThread);
583 const bool joined = waiter->join();
584
585 // The terminal request returns through the protected operation scope so
586 // its ordinary RAII cleanup retires the pool reference before teardown.
587 delete pool;
588
589 passed &= check(
590 context.entered == 1 && queued && joined && context.returned == 1 && context.result == 0,
591 "memory-pool-terminal-drain", "terminal cancellation skipped or corrupted operation cleanup");
592
593 if (passed) {
594 NOTICE("HOSTED-WAIT-TEST: PASS memory-pool-terminal-drain");
595 }
596 return passed;
597}
598
599class CountingPressureHandler : public MemoryPressureHandler {
600 public:
601 CountingPressureHandler() : calls(0) {}
602
603 const char* getMemoryPressureDescription() override {
604 return "hosted registry-reentry peer";
605 }
606
607 bool compact() override {
608 calls += 1;
609 return false;
610 }
611
612 Atomic<size_t> calls;
613};
614
615class BlockingPressureHandler : public MemoryPressureHandler {
616 public:
617 BlockingPressureHandler(MemoryPressureManager* manager, MemoryPressureHandler* reentryPeer)
618 : entered(0),
619 releaseCallback(0),
620 calls(0),
621 reentries(0),
622 manager(manager),
623 reentryPeer(reentryPeer) {}
624
625 const char* getMemoryPressureDescription() override {
626 return "hosted callback-lifetime regression";
627 }
628
629 bool compact() override {
630 calls += 1;
631
632 // Registry mutation from inside a callback must not recurse on a lock.
633 manager->removeHandler(reentryPeer);
634 manager->registerHandler(MemoryPressureManager::LowPriority, reentryPeer);
635 reentries += 1;
636
637 entered.release();
638 const bool released = releaseCallback.acquireForCompletion();
639 assert(released);
640 return false;
641 }
642
643 Semaphore entered;
644 Semaphore releaseCallback;
645 Atomic<size_t> calls;
646 Atomic<size_t> reentries;
647 MemoryPressureManager* manager;
648 MemoryPressureHandler* reentryPeer;
649};
650
651struct PressureManagerContext {
652 PressureManagerContext(MemoryPressureManager* manager, MemoryPressureHandler* handler)
653 : manager(manager),
654 handler(handler),
655 compactEntered(0),
656 compactReturned(0),
657 removeEntered(0),
658 removeReturned(0) {}
659
660 MemoryPressureManager* manager;
661 MemoryPressureHandler* handler;
662 Atomic<size_t> compactEntered;
663 Atomic<size_t> compactReturned;
664 Atomic<size_t> removeEntered;
665 Atomic<size_t> removeReturned;
666};
667
668int compactPressureManager(void* parameter) {
669 PressureManagerContext* context = reinterpret_cast<PressureManagerContext*>(parameter);
670 context->compactEntered += 1;
671 context->manager->compact();
672 context->compactReturned += 1;
673 return 0;
674}
675
676int removePressureHandler(void* parameter) {
677 PressureManagerContext* context = reinterpret_cast<PressureManagerContext*>(parameter);
678 context->removeEntered += 1;
679 context->manager->removeHandler(context->handler);
680 context->removeReturned += 1;
681 return 0;
682}
683
684bool memoryPressureCallbackBarrier() {
685 MemoryPressureManager manager;
686 CountingPressureHandler reentryPeer;
687 BlockingPressureHandler handler(&manager, &reentryPeer);
688 PressureManagerContext context(&manager, &handler);
689 bool passed = true;
690
691 manager.registerHandler(MemoryPressureManager::LowPriority, &reentryPeer);
692 manager.registerHandler(MemoryPressureManager::HighestPriority, &handler);
693
694 Thread* compactor = new Thread(Scheduler::instance().getKernelProcess(), compactPressureManager,
695 &context, nullptr, false, true);
696 compactor->setName("hosted pressure compactor");
697
698 const bool callbackEntered = handler.entered.acquire(1, 0, 500000);
699 Thread* remover = new Thread(Scheduler::instance().getKernelProcess(), removePressureHandler,
700 &context, nullptr, false, true);
701 remover->setName("hosted pressure remover");
702 const Time::Timestamp removerDeadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
703 while (context.removeEntered != 1 && Time::getTicks() < removerDeadline) {
705 }
706
707 Thread* followingCompactor = new Thread(Scheduler::instance().getKernelProcess(),
708 compactPressureManager, &context, nullptr, false, true);
709 followingCompactor->setName("hosted pressure following compactor");
710 const Time::Timestamp followerDeadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
711 while (context.compactEntered < 2 && Time::getTicks() < followerDeadline) {
713 }
714
715 bool removerWaitPublished = false;
716 bool compactorWaitPublished = false;
717 const Time::Timestamp publicationDeadline =
718 Time::getTicks() + (500 * Time::Multiplier::Millisecond);
719 while (!(removerWaitPublished && compactorWaitPublished) &&
720 Time::getTicks() < publicationDeadline) {
721 Thread::WaitDebugInfo waitInfo;
722 removerWaitPublished = remover->getStatus() == Thread::Sleeping &&
723 remover->getWaitDebugInfo(waitInfo) && waitInfo.channelOwner == &handler;
724 compactorWaitPublished = followingCompactor->getStatus() == Thread::Sleeping &&
725 followingCompactor->getWaitDebugInfo(waitInfo) &&
726 waitInfo.channelOwner == &manager;
727 if (!(removerWaitPublished && compactorWaitPublished)) {
729 }
730 }
731
732 passed &=
733 check(callbackEntered && context.removeEntered == 1 && context.removeReturned == 0 &&
734 handler.reentries == 1 && context.compactEntered == 2 &&
735 context.compactReturned == 0 && removerWaitPublished && compactorWaitPublished,
736 "memory-pressure-callback-barrier",
737 "callback reentry failed or a lifetime wait was not published");
738
739 // Always release the callback before joining, including failure paths.
740 handler.releaseCallback.release();
741 const bool compactJoined = compactor->join();
742 const bool followingCompactJoined = followingCompactor->join();
743 const bool removeJoined = remover->join();
744
745 passed &= check(
746 compactJoined && followingCompactJoined && removeJoined && context.compactReturned == 2 &&
747 context.removeReturned == 1 && handler.calls == 1,
748 "memory-pressure-callback-barrier", "the callback and removal did not complete exactly once");
749
750 const size_t peerCallsBefore = reentryPeer.calls;
751 passed &=
752 check(!manager.compact() && handler.calls == 1 && reentryPeer.calls == peerCallsBefore + 1,
753 "memory-pressure-callback-barrier",
754 "a removed handler ran again or the reentrant registration was lost");
755 manager.removeHandler(&reentryPeer);
756
757 manager.registerHandler(MemoryPressureManager::LowPriority, &reentryPeer);
758 const size_t callsBeforeAtomicAttempt = reentryPeer.calls;
759 const bool interruptsWereEnabled = Processor::getInterrupts();
761 const bool atomicCompactResult = manager.compact();
762 Processor::setInterrupts(interruptsWereEnabled);
763 manager.removeHandler(&reentryPeer);
764 passed &= check(!atomicCompactResult && reentryPeer.calls == callsBeforeAtomicAttempt,
765 "memory-pressure-callback-barrier",
766 "an atomic-context pressure pass entered a blocking callback");
767
768 for (size_t i = 0; i < 3; ++i) {
769 const size_t callsBefore = reentryPeer.calls;
770 manager.registerHandler(MemoryPressureManager::LowPriority, &reentryPeer);
771 const bool compactResult = manager.compact();
772 manager.removeHandler(&reentryPeer);
773 const size_t callsAfterRemoval = reentryPeer.calls;
774 const bool emptyCompactResult = manager.compact();
775
776 passed &= check(!compactResult && !emptyCompactResult && callsAfterRemoval == callsBefore + 1 &&
777 reentryPeer.calls == callsAfterRemoval,
778 "memory-pressure-callback-barrier",
779 "repeated register/compact/remove was not stable");
780 }
781
782 if (passed) {
783 NOTICE(
784 "HOSTED-WAIT-TEST: PASS "
785 "memory-pressure-callback-barrier");
786 }
787 return passed;
788}
789
790struct RingBufferCloseContext {
791 enum Operation {
792 Read,
793 Write,
794 };
795
796 RingBufferCloseContext(RingBuffer<char>* buffer, Operation operation)
797 : buffer(buffer),
798 operation(operation),
799 entered(0),
800 returned(0),
801 succeeded(1),
802 error(RingBuffer<char>::NoError) {}
803
804 RingBuffer<char>* buffer;
805 Operation operation;
806 Atomic<size_t> entered;
807 Atomic<size_t> returned;
808 Atomic<size_t> succeeded;
809 Atomic<size_t> error;
810};
811
812int runBlockingRingBufferOperation(void* parameter) {
813 RingBufferCloseContext* context = reinterpret_cast<RingBufferCloseContext*>(parameter);
814 context->entered += 1;
815
816 if (context->operation == RingBufferCloseContext::Read) {
817 char value = 0;
818 Time::Timestamp timeout = Time::Infinity;
819 RingBuffer<char>::Error error = RingBuffer<char>::NoError;
820 context->succeeded = context->buffer->read(value, timeout, error) ? 1 : 0;
821 context->error = error;
822 } else {
823 Time::Timestamp timeout = Time::Infinity;
824 context->error = context->buffer->write('b', timeout);
825 context->succeeded = context->error == RingBuffer<char>::NoError ? 1 : 0;
826 }
827
828 context->returned += 1;
829 return 0;
830}
831
832bool runRingBufferCloseCase(RingBufferCloseContext::Operation operation, bool fill) {
833 RingBuffer<char>* buffer = new RingBuffer<char>(1);
834 bool passed = true;
835 if (fill) {
836 passed &= buffer->write('a') == RingBuffer<char>::NoError;
837 }
838
839 RingBufferCloseContext context(buffer, operation);
840 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(),
841 runBlockingRingBufferOperation, &context, nullptr, false, true);
842 waiter->setName("hosted RingBuffer close waiter");
843 const bool queued = waitUntilQueued(waiter, Thread::CondWait);
844
845 delete buffer;
846 const bool joined = waiter->join();
847 return passed && context.entered == 1 && queued && joined && context.returned == 1 &&
848 context.succeeded == 0 && context.error == RingBuffer<char>::Closed;
849}
850
851bool ringBufferCloseAndDrain() {
852 bool passed = true;
853 passed &=
854 check(runRingBufferCloseCase(RingBufferCloseContext::Read, false), "ringbuffer-close-drain",
855 "close did not wake and drain a blocked reader with Closed");
856 passed &=
857 check(runRingBufferCloseCase(RingBufferCloseContext::Write, true), "ringbuffer-close-drain",
858 "close did not wake and drain a full-buffer writer with Closed");
859
860 RingBuffer<char>* buffer = new RingBuffer<char>(1);
861 Semaphore monitor(0, false);
862 buffer->monitor(&monitor);
863 delete buffer;
864 passed &= check(monitor.tryAcquire(), "ringbuffer-close-drain",
865 "close did not wake a registered readiness monitor");
866
867 if (passed) {
868 NOTICE("HOSTED-WAIT-TEST: PASS ringbuffer-close-drain");
869 }
870 return passed;
871}
872
873bool runRingBufferTerminalCase(RingBufferCloseContext::Operation operation, bool fill) {
874 RingBuffer<char>* buffer = new RingBuffer<char>(1);
875 bool passed = true;
876 if (fill) {
877 passed &= buffer->write('a') == RingBuffer<char>::NoError;
878 }
879
880 RingBufferCloseContext context(buffer, operation);
881 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(),
882 runBlockingRingBufferOperation, &context, nullptr, false, true);
883 waiter->setName("hosted RingBuffer terminal waiter");
884 const bool queued = waitUntilQueued(waiter, Thread::CondWait);
885
886 waiter->setUnwindState(Thread::TerminateThread);
887 const bool joined = waiter->join();
888 delete buffer;
889
890 return passed && context.entered == 1 && queued && joined && context.returned == 1 &&
891 context.succeeded == 0 && context.error == RingBuffer<char>::ThreadTerminating;
892}
893
894bool ringBufferTerminalDrain() {
895 bool passed = true;
896 passed &=
897 check(runRingBufferTerminalCase(RingBufferCloseContext::Read, false),
898 "ringbuffer-terminal-drain", "a terminated blocking read retained an active operation");
899 passed &= check(runRingBufferTerminalCase(RingBufferCloseContext::Write, true),
900 "ringbuffer-terminal-drain",
901 "a terminated blocking write retained an active operation");
902
903 if (passed) {
904 NOTICE("HOSTED-WAIT-TEST: PASS ringbuffer-terminal-drain");
905 }
906 return passed;
907}
908
909struct MailboxWaitContext {
910 enum Operation { Read, Write, Final };
911
912 MailboxWaitContext(Mailbox<char, 2>* mailbox, Operation operation, char value)
913 : mailbox(mailbox), operation(operation), value(value), returned(0), succeeded(0) {}
914
915 Mailbox<char, 2>* mailbox;
916 Operation operation;
917 char value;
918 Atomic<size_t> returned;
919 Atomic<size_t> succeeded;
920};
921
922int runMailboxWaiter(void* parameter) {
923 MailboxWaitContext* context = reinterpret_cast<MailboxWaitContext*>(parameter);
924 Time::Timestamp timeout = Time::Infinity;
925 if (context->operation == MailboxWaitContext::Read) {
926 Mailbox<char, 2>::Error error = Mailbox<char, 2>::NoError;
927 context->succeeded = context->mailbox->read(context->value, timeout, error) ? 1 : 0;
928 } else if (context->operation == MailboxWaitContext::Write) {
929 context->succeeded =
930 context->mailbox->write(context->value, timeout) == Mailbox<char, 2>::NoError ? 1 : 0;
931 } else {
932 context->succeeded = context->mailbox->closeWritesWithFinal(context->value) ? 1 : 0;
933 }
934 context->returned += 1;
935 return 0;
936}
937
938bool runMailboxBatchWakeCase(bool writers) {
939 Mailbox<char, 2> mailbox(2);
940 char values[] = {'a', 'b'};
941 Time::Timestamp timeout = 500 * Time::Multiplier::Millisecond;
942 bool passed = !writers || mailbox.write(values, 2, timeout) == 2;
943 const MailboxWaitContext::Operation operation =
944 writers ? MailboxWaitContext::Write : MailboxWaitContext::Read;
945 MailboxWaitContext contexts[] = {{&mailbox, operation, 'c'}, {&mailbox, operation, 'd'}};
946 Thread* waiters[2];
947 bool queued = true;
948 for (size_t i = 0; i < 2; ++i) {
949 waiters[i] = new Thread(Scheduler::instance().getKernelProcess(), runMailboxWaiter,
950 &contexts[i], nullptr, false, true);
951 waiters[i]->setName("hosted Mailbox batch waiter");
952 queued &= waitUntilQueued(waiters[i], Thread::CondWait);
953 }
954
955 timeout = 500 * Time::Multiplier::Millisecond;
956 const size_t transferred =
957 writers ? mailbox.read(values, 2, timeout) : mailbox.write(values, 2, timeout);
958 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
959 while ((!contexts[0].returned || !contexts[1].returned) && Time::getTicks() < deadline) {
961 }
962 const bool completed = contexts[0].returned == 1 && contexts[1].returned == 1;
963
964 // Close only after measuring progress, so it cannot hide a missed batch wake.
965 mailbox.close();
966 for (Thread* waiter : waiters) {
967 passed &= waiter->join();
968 }
969 if (writers) {
970 for (char& value : values) {
971 passed &= mailbox.takeAfterClose(value);
972 }
973 } else {
974 values[0] = contexts[0].value;
975 values[1] = contexts[1].value;
976 }
977 const char first = writers ? 'c' : 'a';
978 const char second = writers ? 'd' : 'b';
979 return passed && queued && transferred == 2 && completed && contexts[0].succeeded == 1 &&
980 contexts[1].succeeded == 1 &&
981 ((values[0] == first && values[1] == second) ||
982 (values[0] == second && values[1] == first));
983}
984
985bool mailboxBatchWake() {
986 bool passed = true;
987 passed &= check(runMailboxBatchWakeCase(false), "mailbox-batch-wake",
988 "one batch publication did not wake both queued readers");
989 passed &= check(runMailboxBatchWakeCase(true), "mailbox-batch-wake",
990 "one batch removal did not wake both queued writers");
991 if (passed) {
992 NOTICE("HOSTED-WAIT-TEST: PASS mailbox-batch-wake");
993 }
994 return passed;
995}
996
997bool mailboxPendingFinal() {
998 Mailbox<char, 2> mailbox(2);
999 const char initial[] = {'a', 'b'};
1000 Time::Timestamp timeout = 500 * Time::Multiplier::Millisecond;
1001 bool passed = mailbox.write(initial, 2, timeout) == 2;
1002 MailboxWaitContext context(&mailbox, MailboxWaitContext::Final, 'z');
1003 Thread* closer = new Thread(Scheduler::instance().getKernelProcess(), runMailboxWaiter, &context,
1004 nullptr, false, true);
1005 closer->setName("hosted Mailbox pending final");
1006 const bool queued = waitUntilQueued(closer, Thread::CondWait);
1007
1008 char values[2] = {};
1009 timeout = 500 * Time::Multiplier::Millisecond;
1010 const size_t received = mailbox.read(values, 2, timeout);
1011 char final = 0;
1012 Mailbox<char, 2>::Error error = Mailbox<char, 2>::NoError;
1013 timeout = 500 * Time::Multiplier::Millisecond;
1014 const bool receivedFinal = mailbox.read(final, timeout, error);
1015 char extra = 0;
1016 Time::Timestamp zero = 0;
1017 const bool reachedEnd = !mailbox.read(extra, zero, error) && error == Mailbox<char, 2>::Closed;
1018 mailbox.close();
1019 const bool joined = closer->join();
1020
1021 passed &= check(queued && received == 2 && values[0] == 'a' && values[1] == 'b' &&
1022 receivedFinal && final == 'z' && reachedEnd && joined &&
1023 context.returned == 1 && context.succeeded == 1,
1024 "mailbox-pending-final", "a full queue exposed EOF before its final item");
1025 if (passed) {
1026 NOTICE("HOSTED-WAIT-TEST: PASS mailbox-pending-final");
1027 }
1028 return passed;
1029}
1030
1031struct BufferCloseContext {
1032 enum Operation {
1033 Read,
1034 Write,
1035 CanRead,
1036 CanWrite,
1037 };
1038
1039 BufferCloseContext(Buffer<char>* buffer, Operation operation)
1040 : buffer(buffer), operation(operation), entered(0), returned(0), result(1) {}
1041
1042 Buffer<char>* buffer;
1043 Operation operation;
1044 Atomic<size_t> entered;
1045 Atomic<size_t> returned;
1046 Atomic<size_t> result;
1047};
1048
1049struct BufferTryWriteContext {
1050 explicit BufferTryWriteContext(Buffer<char>* buffer)
1051 : buffer(buffer), entered(0), returned(0), result(1) {}
1052
1053 Buffer<char>* buffer;
1054 Atomic<size_t> entered;
1055 Atomic<size_t> returned;
1056 Atomic<size_t> result;
1057};
1058
1059int runBufferTryWrite(void* parameter) {
1060 BufferTryWriteContext* context = reinterpret_cast<BufferTryWriteContext*>(parameter);
1061 const char input[] = {'a', 'b'};
1062 context->entered += 1;
1063 context->result = context->buffer->tryWrite(input, sizeof(input)) ? 1 : 0;
1064 context->returned += 1;
1065 return 0;
1066}
1067
1068int runBlockingBufferOperation(void* parameter) {
1069 BufferCloseContext* context = reinterpret_cast<BufferCloseContext*>(parameter);
1070 context->entered += 1;
1071
1072 char value = 'x';
1073 switch (context->operation) {
1074 case BufferCloseContext::Read:
1075 context->result = context->buffer->read(&value, 1, true);
1076 break;
1077 case BufferCloseContext::Write:
1078 context->result = context->buffer->write(&value, 1, true);
1079 break;
1080 case BufferCloseContext::CanRead:
1081 context->result = context->buffer->canRead(true) ? 1 : 0;
1082 break;
1083 case BufferCloseContext::CanWrite:
1084 context->result = context->buffer->canWrite(true) ? 1 : 0;
1085 break;
1086 }
1087 context->returned += 1;
1088 return 0;
1089}
1090
1091bool runBufferCloseCase(BufferCloseContext::Operation operation, bool fill) {
1092 Buffer<char>* buffer = new Buffer<char>(1);
1093 bool passed = true;
1094 if (fill) {
1095 const char initial = 'a';
1096 passed &= buffer->write(&initial, 1, false) == 1;
1097 }
1098
1099 BufferCloseContext context(buffer, operation);
1100 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), runBlockingBufferOperation,
1101 &context, nullptr, false, true);
1102 waiter->setName("hosted Buffer close waiter");
1103 const bool queued = waitUntilQueued(waiter, Thread::CondWait);
1104
1105 delete buffer;
1106 const bool joined = waiter->join();
1107 return passed && context.entered == 1 && queued && joined && context.returned == 1 &&
1108 context.result == 0;
1109}
1110
1111bool bufferCloseAndDrain() {
1112 bool passed = true;
1113 passed &= check(runBufferCloseCase(BufferCloseContext::Read, false), "buffer-close-drain",
1114 "close did not drain an already-entered blocking read");
1115 passed &= check(runBufferCloseCase(BufferCloseContext::Write, true), "buffer-close-drain",
1116 "close did not drain an already-entered blocking write");
1117 passed &= check(runBufferCloseCase(BufferCloseContext::CanRead, false), "buffer-close-drain",
1118 "close did not drain an already-entered canRead");
1119 passed &= check(runBufferCloseCase(BufferCloseContext::CanWrite, true), "buffer-close-drain",
1120 "close did not drain an already-entered canWrite");
1121
1122 if (passed) {
1123 NOTICE("HOSTED-WAIT-TEST: PASS buffer-close-drain");
1124 }
1125 return passed;
1126}
1127
1128bool bufferTryWriteDoesNotWaitForLock() {
1129 Buffer<char> buffer(2);
1130 BufferTryWriteContext context(&buffer);
1131 buffer.acquireHostedOperationLock();
1132 Thread* writer = new Thread(Scheduler::instance().getKernelProcess(), runBufferTryWrite, &context,
1133 nullptr, false, true);
1134 writer->setName("hosted Buffer try-write contention");
1135
1136 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
1137 while (!context.returned && Time::getTicks() < deadline) {
1139 }
1140 const bool returnedWhileLocked = context.entered == 1 && context.returned == 1;
1141 buffer.releaseHostedOperationLock();
1142 const bool joined = writer->join();
1143
1144 const bool passed =
1145 check(returnedWhileLocked && joined && context.result == 0 && buffer.getDataSize() == 0,
1146 "buffer-try-write", "tryWrite waited for the lock or changed the buffer on failure");
1147 if (passed) {
1148 NOTICE("HOSTED-WAIT-TEST: PASS buffer-try-write");
1149 }
1150 return passed;
1151}
1152
1153bool runBufferTerminalCase(BufferCloseContext::Operation operation, bool fill) {
1154 Buffer<char>* buffer = new Buffer<char>(1);
1155 bool passed = true;
1156 if (fill) {
1157 const char initial = 'a';
1158 passed &= buffer->write(&initial, 1, false) == 1;
1159 }
1160
1161 BufferCloseContext context(buffer, operation);
1162 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), runBlockingBufferOperation,
1163 &context, nullptr, false, true);
1164 waiter->setName("hosted Buffer terminal waiter");
1165 const bool queued = waitUntilQueued(waiter, Thread::CondWait);
1166
1167 waiter->setUnwindState(Thread::TerminateThread);
1168 const bool joined = waiter->join();
1169 delete buffer;
1170
1171 return passed && context.entered == 1 && queued && joined && context.returned == 1 &&
1172 context.result == 0;
1173}
1174
1175bool bufferTerminalDrain() {
1176 bool passed = true;
1177 passed &= check(runBufferTerminalCase(BufferCloseContext::Read, false), "buffer-terminal-drain",
1178 "a terminated blocking read retained an active operation");
1179 passed &= check(runBufferTerminalCase(BufferCloseContext::Write, true), "buffer-terminal-drain",
1180 "a terminated blocking write retained an active operation");
1181 passed &= check(runBufferTerminalCase(BufferCloseContext::CanRead, false),
1182 "buffer-terminal-drain", "a terminated canRead retained an active operation");
1183 passed &= check(runBufferTerminalCase(BufferCloseContext::CanWrite, true),
1184 "buffer-terminal-drain", "a terminated canWrite retained an active operation");
1185
1186 if (passed) {
1187 NOTICE("HOSTED-WAIT-TEST: PASS buffer-terminal-drain");
1188 }
1189 return passed;
1190}
1191
1192bool terminalOperationAdmissionScope() {
1193 bool passed = true;
1194
1195 Buffer<char>* buffer = new Buffer<char>(1);
1196 BufferCloseContext bufferContext(buffer, BufferCloseContext::Read);
1197 buffer->acquireHostedOperationLock();
1198 Thread* bufferWaiter =
1199 new Thread(Scheduler::instance().getKernelProcess(), runBlockingBufferOperation,
1200 &bufferContext, nullptr, false, true);
1201 bufferWaiter->setName("hosted Buffer operation-admission waiter");
1202 const bool bufferQueued = waitUntilQueued(bufferWaiter, Thread::SemWait);
1204 for (size_t i = 0; i < 8; ++i) {
1206 }
1207 const bool bufferStayedBlocked = bufferContext.returned == 0;
1208 buffer->releaseHostedOperationLock();
1209 const bool bufferJoined = bufferWaiter->join();
1210 const bool bufferRetired = buffer->getHostedActiveOperationCount() == 0;
1211 delete buffer;
1212
1213 passed &= check(bufferQueued && bufferStayedBlocked && bufferJoined &&
1214 bufferContext.returned == 1 && bufferContext.result == 0 && bufferRetired,
1215 "terminal-operation-admission",
1216 "Buffer termination escaped the admission mutex or leaked its pin");
1217
1218 constexpr size_t BufferSize = 512;
1219 MemoryPool* pool = new MemoryPool("hosted-terminal-operation-admission");
1220 if (!pool->initialise(1, BufferSize)) {
1221 delete pool;
1222 return check(false, "terminal-operation-admission",
1223 "the MemoryPool admission fixture could not initialise");
1224 }
1225
1226 const size_t bufferCount = PhysicalMemoryManager::getPageSize() / BufferSize;
1227 for (size_t i = 0; i < bufferCount; ++i) {
1228 passed &= pool->allocateNow() != 0;
1229 }
1230
1231 MemoryPoolContext poolContext(pool);
1232 pool->acquireHostedOperationLock();
1233 Thread* poolWaiter = new Thread(Scheduler::instance().getKernelProcess(),
1234 allocateFromExhaustedPool, &poolContext, nullptr, false, true);
1235 poolWaiter->setName("hosted MemoryPool operation-admission waiter");
1236 const bool poolQueued = waitUntilQueued(poolWaiter, Thread::SemWait);
1238 for (size_t i = 0; i < 8; ++i) {
1240 }
1241 const bool poolStayedBlocked = poolContext.returned == 0;
1242 pool->releaseHostedOperationLock();
1243 const bool poolJoined = poolWaiter->join();
1244 const bool poolRetired = pool->getHostedActiveOperationCount() == 0;
1245 delete pool;
1246
1247 passed &= check(poolQueued && poolStayedBlocked && poolJoined && poolContext.returned == 1 &&
1248 poolContext.result == 0 && poolRetired,
1249 "terminal-operation-admission",
1250 "MemoryPool termination escaped the admission mutex or leaked its pin");
1251
1252 if (passed) {
1253 NOTICE("HOSTED-WAIT-TEST: PASS terminal-operation-admission");
1254 }
1255 return passed;
1256}
1257
1258struct TerminalTimeoutContext {
1259 TerminalTimeoutContext(Semaphore* semaphore, ConditionVariable* condition, Mutex* mutex)
1260 : semaphore(semaphore),
1261 condition(condition),
1262 mutex(mutex),
1263 semaphoreEntered(0),
1264 semaphoreReturned(0),
1265 semaphoreInterrupted(0),
1266 semaphoreDestructed(0),
1267 conditionEntered(0),
1268 conditionReturned(0),
1269 conditionTerminal(0),
1270 conditionMutexHeld(0),
1271 conditionDestructed(0),
1272 delayEntered(0),
1273 delayReturned(0),
1274 delayInterrupted(0),
1275 delayDestructed(0) {}
1276
1277 Semaphore* semaphore;
1278 ConditionVariable* condition;
1279 Mutex* mutex;
1280 Atomic<size_t> semaphoreEntered;
1281 Atomic<size_t> semaphoreReturned;
1282 Atomic<size_t> semaphoreInterrupted;
1283 Atomic<size_t> semaphoreDestructed;
1284 Atomic<size_t> conditionEntered;
1285 Atomic<size_t> conditionReturned;
1286 Atomic<size_t> conditionTerminal;
1287 Atomic<size_t> conditionMutexHeld;
1288 Atomic<size_t> conditionDestructed;
1289 Atomic<size_t> delayEntered;
1290 Atomic<size_t> delayReturned;
1291 Atomic<size_t> delayInterrupted;
1292 Atomic<size_t> delayDestructed;
1293};
1294
1295class TerminalTimeoutStackCanary {
1296 public:
1297 explicit TerminalTimeoutStackCanary(Atomic<size_t>* destructed) : m_Destructed(destructed) {}
1298
1299 ~TerminalTimeoutStackCanary() {
1300 *m_Destructed += 1;
1301 }
1302
1303 private:
1304 Atomic<size_t>* m_Destructed;
1305};
1306
1307int terminalTimedSemaphoreWait(void* parameter) {
1308 TerminalTimeoutContext* context = reinterpret_cast<TerminalTimeoutContext*>(parameter);
1309 TerminalTimeoutStackCanary stackCanary(&context->semaphoreDestructed);
1310 context->semaphoreEntered += 1;
1311 Semaphore::SemaphoreError error = Semaphore::NoError;
1312 const bool acquired = context->semaphore->acquireWithError(1, 0, 500000, error);
1313 context->semaphoreInterrupted =
1314 !acquired && error == Semaphore::Interrupted ? static_cast<size_t>(1) : 0;
1315 context->semaphoreReturned += 1;
1316 return 0;
1317}
1318
1319int terminalTimedConditionWait(void* parameter) {
1320 TerminalTimeoutContext* context = reinterpret_cast<TerminalTimeoutContext*>(parameter);
1321 TerminalTimeoutStackCanary stackCanary(&context->conditionDestructed);
1322 context->mutex->acquire();
1323 context->conditionEntered += 1;
1324 Time::Timestamp timeout = 500 * Time::Multiplier::Millisecond;
1325 ConditionVariable::Error error = ConditionVariable::NoError;
1326 const bool signalled = context->condition->wait(*context->mutex, timeout, error);
1327 context->conditionTerminal =
1328 !signalled && error == ConditionVariable::TerminationDeferred ? static_cast<size_t>(1) : 0;
1329 context->conditionMutexHeld = context->mutex->isOwnedByCurrentThread() ? 1 : 0;
1330 context->conditionReturned += 1;
1331 if (context->conditionMutexHeld) {
1332 context->mutex->release();
1333 }
1334 return 0;
1335}
1336
1337int terminalDelay(void* parameter) {
1338 TerminalTimeoutContext* context = reinterpret_cast<TerminalTimeoutContext*>(parameter);
1339 TerminalTimeoutStackCanary stackCanary(&context->delayDestructed);
1340 context->delayEntered += 1;
1341 context->delayInterrupted = Time::delay(500 * Time::Multiplier::Millisecond) ? 0 : 1;
1342 context->delayReturned += 1;
1343 return 0;
1344}
1345
1346bool terminalTimeoutCleanup() {
1347 Semaphore semaphore(0);
1348 ConditionVariable condition;
1349 Mutex mutex;
1350 TerminalTimeoutContext context(&semaphore, &condition, &mutex);
1351 Process* process = Scheduler::instance().getKernelProcess();
1352
1353 const size_t semaphoreCreates = Semaphore::getHostedTimeoutCreateCount();
1354 const size_t semaphoreDestroys = Semaphore::getHostedTimeoutDestroyCount();
1355 const size_t alarmCreates = Time::getHostedAlarmCreateCount();
1356 const size_t alarmDestroys = Time::getHostedAlarmDestroyCount();
1357 const Time::Timestamp deadline = Time::getTicks() + (550 * Time::Multiplier::Millisecond);
1358
1359 Thread* semaphoreWaiter =
1360 new Thread(process, terminalTimedSemaphoreWait, &context, nullptr, false, true);
1361 Thread* conditionWaiter =
1362 new Thread(process, terminalTimedConditionWait, &context, nullptr, false, true);
1363 Thread* delayWaiter = new Thread(process, terminalDelay, &context, nullptr, false, true);
1364
1365 const bool semaphoreQueued = waitUntilQueued(semaphoreWaiter, Thread::SemWait);
1366 const bool conditionQueued = waitUntilQueued(conditionWaiter, Thread::CondWait);
1367 const bool delayQueued = waitUntilQueued(delayWaiter, Thread::EventWait);
1368
1369 semaphoreWaiter->setUnwindState(Thread::TerminateThread);
1370 conditionWaiter->setUnwindState(Thread::TerminateThread);
1372
1373 const bool semaphoreJoined = semaphoreWaiter->join();
1374 const bool conditionJoined = conditionWaiter->join();
1375 const bool delayJoined = delayWaiter->join();
1376
1377 bool passed = true;
1378 passed &= check(semaphoreQueued && conditionQueued && delayQueued, "terminal-timeout-cleanup",
1379 "not every timed waiter published its wait");
1380 passed &=
1381 check(semaphoreJoined && conditionJoined && delayJoined && context.semaphoreEntered == 1 &&
1382 context.conditionEntered == 1 && context.delayEntered == 1 &&
1383 context.semaphoreReturned == 1 && context.semaphoreInterrupted == 1 &&
1384 context.semaphoreDestructed == 1 && context.conditionReturned == 1 &&
1385 context.conditionTerminal == 1 && context.conditionMutexHeld == 1 &&
1386 context.conditionDestructed == 1 && context.delayReturned == 1 &&
1387 context.delayInterrupted == 1 && context.delayDestructed == 1,
1388 "terminal-timeout-cleanup",
1389 "a terminal timed wait did not return through ordinary stack cleanup");
1390 passed &=
1391 check(Semaphore::getHostedTimeoutCreateCount() == semaphoreCreates + 1 &&
1392 Semaphore::getHostedTimeoutDestroyCount() == semaphoreDestroys + 1 &&
1393 Time::getHostedAlarmCreateCount() == alarmCreates + 2 &&
1394 Time::getHostedAlarmDestroyCount() == alarmDestroys + 2,
1395 "terminal-timeout-cleanup", "a cancelled timeout event was not destroyed exactly once");
1396
1397 // Advance beyond every original deadline. A stale timer target would now
1398 // deliver into a joined and freed Thread; hosted ASan makes that fatal.
1399 while (Time::getTicks() < deadline) {
1401 }
1402
1403 if (passed) {
1404 NOTICE("HOSTED-WAIT-TEST: PASS terminal-timeout-cleanup");
1405 }
1406 return passed;
1407}
1408} // namespace
1409
1410bool runHostedPrimitiveRegressions(Thread* thread) {
1411 return registryNestedRemoval() && radixTreeExportedAbi() && semaphoreDrainAvailable() &&
1412 completionLifecycle() && terminalCompletionBarrier() && operationBarrierLifecycle() &&
1413 conditionVariableTimeoutAccounting(thread) && memoryPoolBlockingAndStride() &&
1414 memoryPoolCloseAndDrain() && memoryPoolTerminalDrain() &&
1415 memoryPressureCallbackBarrier() && bufferCloseAndDrain() &&
1416 bufferTryWriteDoesNotWaitForLock() && bufferTerminalDrain() &&
1417 terminalOperationAdmissionScope() && terminalTimeoutCleanup();
1418}
1419
1420bool runHostedRingBufferRegressions() {
1421 return ringBufferCloseAndDrain() && ringBufferTerminalDrain() && mailboxBatchWake() &&
1422 mailboxPendingFinal();
1423}
Implements a Radix Tree, a kind of Trie with compressed keys.
size_t write(const T *buffer, size_t count, bool block=true)
Definition Buffer.cc:103
size_t getDataSize()
Definition Buffer.cc:470
bool complete()
Definition Completion.cc:43
MUST_USE_RESULT bool wait(WaitQueue::StackDiscardCleanup onStackDiscard=nullptr, void *stackDiscardContext=nullptr)
Definition Completion.cc:16
MUST_USE_RESULT bool wait(Mutex &mutex, Time::Timestamp &timeout, Error &error, WaitQueue::StackDiscardCleanup onStackDiscard=nullptr, void *stackDiscardContext=nullptr)
bool publishThreadedDispatch(uint8_t irq, size_t dispatchGeneration)
bool registerThreadedHandler(uint8_t irq, IrqHandler *handler)
UnregisterResult unregisterHandler(uint8_t irq, IrqHandlerBase *handler)
bool dispatchThreaded(uint8_t irq, size_t dispatchGeneration, ThreadedDispatchResult &result, IrqHandler *onlyHandler=nullptr, irq_id_t callbackId=0)
virtual IrqDisposition irq(irq_id_t number)=0
void close()
Definition Mailbox.h:114
Error write(const T &obj, Time::Timestamp &timeout)
Writes one object, waiting for space up to the supplied timeout.
Definition Mailbox.h:154
uintptr_t allocateNow()
bool initialise(size_t poolSize, size_t bufferSize=1024)
virtual bool compact()=0
virtual const char * getMemoryPressureDescription()=0
void registerHandler(size_t prio, MemoryPressureHandler *pHandler)
void removeHandler(MemoryPressureHandler *pHandler)
Definition Mutex.h:56
MUST_USE_RESULT bool tryEnter()
static bool getInterrupts()
static void setInterrupts(bool bEnable)
A key/value dictionary for string keys.
Definition RadixTree.h:46
bool monitor(Thread *pThread, Event *pEvent)
monitor - add a new Event to be fired when something happens
Definition RingBuffer.h:80
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:236
void setUnwindState(UnwindType ut)
Definition Thread.cc:3580
@ TerminateThread
Exit only this thread during Process exit.
Definition Thread.h:519
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3163
bool join()
Definition Thread.cc:2746
Status getStatus() const
Definition Thread.h:433
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:574
A vector / dynamic array.
Definition Vector.h:33
IrqDisposition
Definition IrqHandler.h:31
void remove(const String &key)
Definition RadixTree.h:505
MUST_USE_RESULT bool lookup(const String &key, T &value) const
Definition RadixTree.h:454
void insert(const String &key, const T &value)
Definition RadixTree.h:353
void pushBack(const T &value)
Definition Vector.h:275
size_t count() const
Definition Vector.h:270