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"
32bool check(
bool condition,
const char* test,
const char* detail) {
37 ERROR(
"HOSTED-WAIT-TEST: FAIL " << test <<
": " << detail);
41bool waitUntilQueued(
Thread* thread,
size_t debugState) {
42 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
43 while (Time::getTicks() < deadline) {
45 uintptr_t debugAddress = 0;
55class NestedRemovalProbe final :
public IrqHandler {
63 nestedRetired(true) {}
70 const bool published = registry.publishThreadedDispatch(nested->key, 1);
73 nestedRetired = published && registry.dispatchThreaded(nested->key, 1, result) &&
75 registry.unregisterHandler(nested->key, nested) ==
76 IrqHandlerRegistry::UnregisterResult::NotFound;
79 registry.unregisterHandler(key,
this) == IrqHandlerRegistry::UnregisterResult::Deferred;
80 return IrqDisposition::Handled;
85 NestedRemovalProbe* nested;
91bool registryNestedRemoval() {
92 constexpr const char* Test =
"registry-nested-removal";
94 NestedRemovalProbe outer(registry, 2);
95 NestedRemovalProbe inner(registry, 3);
96 outer.nested = &inner;
104 const bool dispatched = published && registry.
dispatchThreaded(outer.key, 1, result);
108 IrqHandlerRegistry::UnregisterResult::Completed &&
110 IrqHandlerRegistry::UnregisterResult::Completed;
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");
116 NOTICE(
"HOSTED-WAIT-TEST: PASS " << Test);
121bool radixTreeExportedAbi() {
123 const String key(
"hosted-radix-tree-abi");
124 void*
const expected = &tree;
126 void* value = expected;
127 const bool missing = !tree.
lookup(key, value) && value ==
nullptr;
129 tree.
insert(key, expected);
131 const bool found = tree.
lookup(key, value) && value == expected;
135 const bool removed = !tree.
lookup(key, value) && value ==
nullptr;
137 const bool passed = check(missing && found && removed,
"radix-tree-exported-abi",
138 "module-to-kernel bool-and-output lookup contract failed");
140 NOTICE(
"HOSTED-WAIT-TEST: PASS radix-tree-exported-abi");
145bool semaphoreDrainAvailable() {
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();
154 check(firstDrain == 3 && emptyAfterFirstDrain && secondDrain == 2 && emptyDrain == 0 &&
155 !semaphore.tryAcquire(),
156 "semaphore-drain-available",
"available items were not removed exactly once");
158 NOTICE(
"HOSTED-WAIT-TEST: PASS semaphore-drain-available");
163struct CompletionContext {
164 explicit CompletionContext(
Completion* completion)
165 : completion(completion), entered(0), completed(0) {}
172struct TerminalCompletionContext {
173 explicit TerminalCompletionContext(
Semaphore* completion)
174 : completion(completion), entered(0), acquired(0), returned(0) {}
182struct OperationBarrierContext {
184 : barrier(barrier), workEntered(0), workFinished(0), closeFinished(0), releaseWork(0) {}
193int waitForCompletion(
void* parameter) {
194 CompletionContext* context =
reinterpret_cast<CompletionContext*
>(parameter);
195 context->entered += 1;
196 if (context->completion->wait()) {
197 context->completed += 1;
202int waitForTerminalCompletion(
void* parameter) {
203 TerminalCompletionContext* context =
reinterpret_cast<TerminalCompletionContext*
>(parameter);
204 context->entered += 1;
205 if (context->completion->acquireForCompletion()) {
206 context->acquired += 1;
208 context->returned += 1;
212int runAdmittedOperation(
void* parameter) {
213 OperationBarrierContext* context =
reinterpret_cast<OperationBarrierContext*
>(parameter);
214 context->workEntered += 1;
215 const bool released = context->releaseWork.acquireForCompletion();
217 context->workFinished += 1;
218 context->barrier->leave();
222int closeOperationBarrier(
void* parameter) {
223 OperationBarrierContext* context =
reinterpret_cast<OperationBarrierContext*
>(parameter);
224 context->barrier->closeAndWait();
225 context->closeFinished += 1;
229bool completionLifecycle() {
234 check(latched.
complete(),
"completion-lifecycle",
"the first early completion was rejected");
236 check(!latched.
complete(),
"completion-lifecycle",
"duplicate early completion was accepted");
238 check(latched.
wait(),
"completion-lifecycle",
"complete-before-wait did not stay latched");
241 CompletionContext context(&delayed);
243 nullptr,
false,
true);
244 waiter->setName(
"hosted Completion waiter");
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();
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");
259 NOTICE(
"HOSTED-WAIT-TEST: PASS completion-lifecycle");
264bool terminalCompletionBarrier() {
266 TerminalCompletionContext context(&completion);
268 &context,
nullptr,
false,
true);
269 waiter->setName(
"hosted terminal completion waiter");
271 const bool queued = waitUntilQueued(
waiter, Thread::SemWait);
276 for (
size_t i = 0; i < 8; ++i) {
279 const bool deferred = context.entered == 1 && context.returned == 0;
281 completion.release();
282 const bool joined =
waiter->join();
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");
288 NOTICE(
"HOSTED-WAIT-TEST: PASS terminal-completion-barrier");
293bool operationBarrierLifecycle() {
295 OperationBarrierContext context(&barrier);
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");
303 while (!context.workEntered) {
307 Thread* closer =
new Thread(process, closeOperationBarrier, &context,
nullptr,
false,
true);
308 closer->setName(
"hosted operation barrier closer");
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();
314 const bool partialReleaseBlocked = !barrier.isClosedAndDrained() && context.closeFinished == 0;
316 context.releaseWork.release();
317 const bool workerJoined =
worker->join();
318 const bool closerJoined = closer->
join();
319 const bool drained = barrier.isClosedAndDrained();
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");
327 NOTICE(
"HOSTED-WAIT-TEST: PASS operation-barrier-lifecycle");
332struct ConditionTimeoutContext {
334 : condition(condition),
waiter(
waiter), signalAt(signalAt), published(0), signals(0) {}
338 Time::Timestamp signalAt;
344Thread* g_ZeroTimeoutThread =
nullptr;
348 if (thread == g_ZeroTimeoutThread && debugState == Thread::CondWait) {
349 g_ZeroTimeoutPublications += 1;
353int delayedConditionSignal(
void* parameter) {
354 ConditionTimeoutContext* context =
reinterpret_cast<ConditionTimeoutContext*
>(parameter);
355 if (waitUntilQueued(context->waiter, Thread::CondWait)) {
356 context->published += 1;
359 while (Time::getTicks() < context->signalAt) {
362 context->condition->signal();
363 context->signals += 1;
367bool conditionVariableTimeoutAccounting(
Thread* thread) {
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));
377 &context,
nullptr,
false,
true);
378 signaler->setName(
"hosted timed ConditionVariable signaler");
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();
386 const bool signalerJoined = signaler->
join();
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");
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();
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");
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();
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");
437 NOTICE(
"HOSTED-WAIT-TEST: PASS condition-variable-timeout");
442struct MemoryPoolContext {
443 explicit MemoryPoolContext(
MemoryPool* pool) : pool(pool), entered(0), returned(0), result(0) {}
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;
459bool memoryPoolBlockingAndStride() {
460 constexpr size_t BufferSize = 512;
461 MemoryPool pool(
"hosted-memory-pool-regression");
463 if (!pool.initialise(1, BufferSize)) {
464 return check(
false,
"memory-pool-lifecycle",
"a one-page hosted pool could not be initialised");
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");
475 for (
size_t i = 0; i < bufferCount; ++i) {
476 const uintptr_t buffer = pool.allocateNow();
478 passed &= check(buffer != 0,
"memory-pool-lifecycle",
479 "the pool exhausted before its advertised capacity");
481 passed &= check(buffer == buffers[0] + (i * BufferSize),
"memory-pool-lifecycle",
482 "buffers did not use the configured fixed stride");
484 passed &= check((buffer % BufferSize) == 0,
"memory-pool-lifecycle",
485 "a buffer did not retain its configured alignment");
487 passed &= check(pool.allocateNow() == 0,
"memory-pool-lifecycle",
488 "nonblocking allocation succeeded after exhaustion");
490 MemoryPoolContext context(&pool);
492 &context,
nullptr,
false,
true);
493 waiter->setName(
"hosted MemoryPool waiter");
494 const bool queued = waitUntilQueued(
waiter, Thread::CondWait);
496 const size_t freedIndex = bufferCount / 2;
497 const uintptr_t freed = buffers[freedIndex];
499 const bool joined =
waiter->join();
500 const uintptr_t reused = context.result;
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");
507 for (
size_t i = 0; i < buffers.
count(); ++i) {
508 if (i != freedIndex) {
509 pool.free(buffers[i]);
517 NOTICE(
"HOSTED-WAIT-TEST: PASS memory-pool-lifecycle");
522bool memoryPoolCloseAndDrain() {
523 constexpr size_t BufferSize = 512;
528 return check(
false,
"memory-pool-close-drain",
529 "a one-page hosted pool could not be initialised");
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");
538 MemoryPoolContext context(pool);
540 &context,
nullptr,
false,
true);
541 waiter->setName(
"hosted MemoryPool close waiter");
542 const bool queued = waitUntilQueued(
waiter, Thread::CondWait);
547 const bool joined =
waiter->join();
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");
555 NOTICE(
"HOSTED-WAIT-TEST: PASS memory-pool-close-drain");
560bool memoryPoolTerminalDrain() {
561 constexpr size_t BufferSize = 512;
566 return check(
false,
"memory-pool-terminal-drain",
567 "a one-page hosted pool could not be initialised");
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");
576 MemoryPoolContext context(pool);
578 &context,
nullptr,
false,
true);
579 waiter->setName(
"hosted MemoryPool terminal waiter");
580 const bool queued = waitUntilQueued(
waiter, Thread::CondWait);
583 const bool joined =
waiter->join();
590 context.entered == 1 && queued && joined && context.returned == 1 && context.result == 0,
591 "memory-pool-terminal-drain",
"terminal cancellation skipped or corrupted operation cleanup");
594 NOTICE(
"HOSTED-WAIT-TEST: PASS memory-pool-terminal-drain");
601 CountingPressureHandler() : calls(0) {}
604 return "hosted registry-reentry peer";
623 reentryPeer(reentryPeer) {}
626 return "hosted callback-lifetime regression";
633 manager->removeHandler(reentryPeer);
634 manager->registerHandler(MemoryPressureManager::LowPriority, reentryPeer);
638 const bool released = releaseCallback.acquireForCompletion();
651struct PressureManagerContext {
668int compactPressureManager(
void* parameter) {
669 PressureManagerContext* context =
reinterpret_cast<PressureManagerContext*
>(parameter);
670 context->compactEntered += 1;
671 context->manager->compact();
672 context->compactReturned += 1;
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;
684bool memoryPressureCallbackBarrier() {
686 CountingPressureHandler reentryPeer;
687 BlockingPressureHandler handler(&manager, &reentryPeer);
688 PressureManagerContext context(&manager, &handler);
691 manager.
registerHandler(MemoryPressureManager::LowPriority, &reentryPeer);
692 manager.
registerHandler(MemoryPressureManager::HighestPriority, &handler);
695 &context,
nullptr,
false,
true);
696 compactor->setName(
"hosted pressure compactor");
698 const bool callbackEntered = handler.entered.acquire(1, 0, 500000);
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) {
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) {
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) {
722 removerWaitPublished = remover->
getStatus() == Thread::Sleeping &&
724 compactorWaitPublished = followingCompactor->
getStatus() == Thread::Sleeping &&
726 waitInfo.channelOwner == &manager;
727 if (!(removerWaitPublished && compactorWaitPublished)) {
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");
740 handler.releaseCallback.release();
741 const bool compactJoined = compactor->
join();
742 const bool followingCompactJoined = followingCompactor->
join();
743 const bool removeJoined = remover->
join();
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");
750 const size_t peerCallsBefore = reentryPeer.calls;
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");
757 manager.
registerHandler(MemoryPressureManager::LowPriority, &reentryPeer);
758 const size_t callsBeforeAtomicAttempt = reentryPeer.calls;
761 const bool atomicCompactResult = manager.
compact();
764 passed &= check(!atomicCompactResult && reentryPeer.calls == callsBeforeAtomicAttempt,
765 "memory-pressure-callback-barrier",
766 "an atomic-context pressure pass entered a blocking callback");
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();
773 const size_t callsAfterRemoval = reentryPeer.calls;
774 const bool emptyCompactResult = manager.
compact();
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");
784 "HOSTED-WAIT-TEST: PASS "
785 "memory-pressure-callback-barrier");
790struct RingBufferCloseContext {
798 operation(operation),
812int runBlockingRingBufferOperation(
void* parameter) {
813 RingBufferCloseContext* context =
reinterpret_cast<RingBufferCloseContext*
>(parameter);
814 context->entered += 1;
816 if (context->operation == RingBufferCloseContext::Read) {
818 Time::Timestamp timeout = Time::Infinity;
820 context->succeeded = context->buffer->read(value, timeout, error) ? 1 : 0;
821 context->error = error;
823 Time::Timestamp timeout = Time::Infinity;
824 context->error = context->buffer->write(
'b', timeout);
828 context->returned += 1;
832bool runRingBufferCloseCase(RingBufferCloseContext::Operation operation,
bool fill) {
839 RingBufferCloseContext context(buffer, operation);
841 runBlockingRingBufferOperation, &context,
nullptr,
false,
true);
842 waiter->setName(
"hosted RingBuffer close waiter");
843 const bool queued = waitUntilQueued(
waiter, Thread::CondWait);
846 const bool joined =
waiter->join();
847 return passed && context.entered == 1 && queued && joined && context.returned == 1 &&
851bool ringBufferCloseAndDrain() {
854 check(runRingBufferCloseCase(RingBufferCloseContext::Read,
false),
"ringbuffer-close-drain",
855 "close did not wake and drain a blocked reader with Closed");
857 check(runRingBufferCloseCase(RingBufferCloseContext::Write,
true),
"ringbuffer-close-drain",
858 "close did not wake and drain a full-buffer writer with Closed");
864 passed &= check(monitor.tryAcquire(),
"ringbuffer-close-drain",
865 "close did not wake a registered readiness monitor");
868 NOTICE(
"HOSTED-WAIT-TEST: PASS ringbuffer-close-drain");
873bool runRingBufferTerminalCase(RingBufferCloseContext::Operation operation,
bool fill) {
880 RingBufferCloseContext context(buffer, operation);
882 runBlockingRingBufferOperation, &context,
nullptr,
false,
true);
883 waiter->setName(
"hosted RingBuffer terminal waiter");
884 const bool queued = waitUntilQueued(
waiter, Thread::CondWait);
887 const bool joined =
waiter->join();
890 return passed && context.entered == 1 && queued && joined && context.returned == 1 &&
894bool ringBufferTerminalDrain() {
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");
904 NOTICE(
"HOSTED-WAIT-TEST: PASS ringbuffer-terminal-drain");
909struct MailboxWaitContext {
910 enum Operation { Read, Write, Final };
912 MailboxWaitContext(
Mailbox<char, 2>* mailbox, Operation operation,
char value)
913 : mailbox(mailbox), operation(operation), value(value), returned(0), succeeded(0) {}
922int runMailboxWaiter(
void* parameter) {
923 MailboxWaitContext* context =
reinterpret_cast<MailboxWaitContext*
>(parameter);
924 Time::Timestamp timeout = Time::Infinity;
925 if (context->operation == MailboxWaitContext::Read) {
927 context->succeeded = context->mailbox->read(context->value, timeout, error) ? 1 : 0;
928 }
else if (context->operation == MailboxWaitContext::Write) {
932 context->succeeded = context->mailbox->closeWritesWithFinal(context->value) ? 1 : 0;
934 context->returned += 1;
938bool runMailboxBatchWakeCase(
bool writers) {
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'}};
948 for (
size_t i = 0; i < 2; ++i) {
950 &contexts[i],
nullptr,
false,
true);
951 waiters[i]->setName(
"hosted Mailbox batch waiter");
952 queued &= waitUntilQueued(waiters[i], Thread::CondWait);
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) {
962 const bool completed = contexts[0].returned == 1 && contexts[1].returned == 1;
970 for (
char& value : values) {
971 passed &= mailbox.takeAfterClose(value);
974 values[0] = contexts[0].value;
975 values[1] = contexts[1].value;
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));
985bool mailboxBatchWake() {
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");
992 NOTICE(
"HOSTED-WAIT-TEST: PASS mailbox-batch-wake");
997bool mailboxPendingFinal() {
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');
1004 nullptr,
false,
true);
1005 closer->setName(
"hosted Mailbox pending final");
1006 const bool queued = waitUntilQueued(closer, Thread::CondWait);
1008 char values[2] = {};
1009 timeout = 500 * Time::Multiplier::Millisecond;
1010 const size_t received = mailbox.read(values, 2, timeout);
1013 timeout = 500 * Time::Multiplier::Millisecond;
1014 const bool receivedFinal = mailbox.read(
final, timeout, error);
1016 Time::Timestamp zero = 0;
1019 const bool joined = closer->
join();
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");
1026 NOTICE(
"HOSTED-WAIT-TEST: PASS mailbox-pending-final");
1031struct BufferCloseContext {
1039 BufferCloseContext(
Buffer<char>* buffer, Operation operation)
1040 : buffer(buffer), operation(operation), entered(0), returned(0), result(1) {}
1043 Operation operation;
1049struct BufferTryWriteContext {
1051 : buffer(buffer), entered(0), returned(0), result(1) {}
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;
1068int runBlockingBufferOperation(
void* parameter) {
1069 BufferCloseContext* context =
reinterpret_cast<BufferCloseContext*
>(parameter);
1070 context->entered += 1;
1073 switch (context->operation) {
1074 case BufferCloseContext::Read:
1075 context->result = context->buffer->read(&value, 1,
true);
1077 case BufferCloseContext::Write:
1078 context->result = context->buffer->write(&value, 1,
true);
1080 case BufferCloseContext::CanRead:
1081 context->result = context->buffer->canRead(
true) ? 1 : 0;
1083 case BufferCloseContext::CanWrite:
1084 context->result = context->buffer->canWrite(
true) ? 1 : 0;
1087 context->returned += 1;
1091bool runBufferCloseCase(BufferCloseContext::Operation operation,
bool fill) {
1095 const char initial =
'a';
1096 passed &= buffer->
write(&initial, 1,
false) == 1;
1099 BufferCloseContext context(buffer, operation);
1101 &context,
nullptr,
false,
true);
1102 waiter->setName(
"hosted Buffer close waiter");
1103 const bool queued = waitUntilQueued(
waiter, Thread::CondWait);
1106 const bool joined =
waiter->join();
1107 return passed && context.entered == 1 && queued && joined && context.returned == 1 &&
1108 context.result == 0;
1111bool bufferCloseAndDrain() {
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");
1123 NOTICE(
"HOSTED-WAIT-TEST: PASS buffer-close-drain");
1128bool bufferTryWriteDoesNotWaitForLock() {
1130 BufferTryWriteContext context(&buffer);
1131 buffer.acquireHostedOperationLock();
1133 nullptr,
false,
true);
1134 writer->setName(
"hosted Buffer try-write contention");
1136 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
1137 while (!context.returned && Time::getTicks() < deadline) {
1140 const bool returnedWhileLocked = context.entered == 1 && context.returned == 1;
1141 buffer.releaseHostedOperationLock();
1142 const bool joined = writer->
join();
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");
1148 NOTICE(
"HOSTED-WAIT-TEST: PASS buffer-try-write");
1153bool runBufferTerminalCase(BufferCloseContext::Operation operation,
bool fill) {
1157 const char initial =
'a';
1158 passed &= buffer->
write(&initial, 1,
false) == 1;
1161 BufferCloseContext context(buffer, operation);
1163 &context,
nullptr,
false,
true);
1164 waiter->setName(
"hosted Buffer terminal waiter");
1165 const bool queued = waitUntilQueued(
waiter, Thread::CondWait);
1168 const bool joined =
waiter->join();
1171 return passed && context.entered == 1 && queued && joined && context.returned == 1 &&
1172 context.result == 0;
1175bool bufferTerminalDrain() {
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");
1187 NOTICE(
"HOSTED-WAIT-TEST: PASS buffer-terminal-drain");
1192bool terminalOperationAdmissionScope() {
1196 BufferCloseContext bufferContext(buffer, BufferCloseContext::Read);
1197 buffer->acquireHostedOperationLock();
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) {
1207 const bool bufferStayedBlocked = bufferContext.returned == 0;
1208 buffer->releaseHostedOperationLock();
1209 const bool bufferJoined = bufferWaiter->
join();
1210 const bool bufferRetired = buffer->getHostedActiveOperationCount() == 0;
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");
1218 constexpr size_t BufferSize = 512;
1222 return check(
false,
"terminal-operation-admission",
1223 "the MemoryPool admission fixture could not initialise");
1227 for (
size_t i = 0; i < bufferCount; ++i) {
1231 MemoryPoolContext poolContext(pool);
1232 pool->acquireHostedOperationLock();
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) {
1241 const bool poolStayedBlocked = poolContext.returned == 0;
1242 pool->releaseHostedOperationLock();
1243 const bool poolJoined = poolWaiter->
join();
1244 const bool poolRetired = pool->getHostedActiveOperationCount() == 0;
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");
1253 NOTICE(
"HOSTED-WAIT-TEST: PASS terminal-operation-admission");
1258struct TerminalTimeoutContext {
1260 : semaphore(semaphore),
1261 condition(condition),
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),
1274 delayInterrupted(0),
1275 delayDestructed(0) {}
1295class TerminalTimeoutStackCanary {
1297 explicit TerminalTimeoutStackCanary(
Atomic<size_t>* destructed) : m_Destructed(destructed) {}
1299 ~TerminalTimeoutStackCanary() {
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;
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();
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;
1346bool terminalTimeoutCleanup() {
1350 TerminalTimeoutContext context(&semaphore, &condition, &mutex);
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);
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);
1365 const bool semaphoreQueued = waitUntilQueued(semaphoreWaiter, Thread::SemWait);
1366 const bool conditionQueued = waitUntilQueued(conditionWaiter, Thread::CondWait);
1367 const bool delayQueued = waitUntilQueued(delayWaiter, Thread::EventWait);
1373 const bool semaphoreJoined = semaphoreWaiter->
join();
1374 const bool conditionJoined = conditionWaiter->
join();
1375 const bool delayJoined = delayWaiter->
join();
1378 passed &= check(semaphoreQueued && conditionQueued && delayQueued,
"terminal-timeout-cleanup",
1379 "not every timed waiter published its wait");
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");
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");
1399 while (Time::getTicks() < deadline) {
1404 NOTICE(
"HOSTED-WAIT-TEST: PASS terminal-timeout-cleanup");
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();
1420bool runHostedRingBufferRegressions() {
1421 return ringBufferCloseAndDrain() && ringBufferTerminalDrain() && mailboxBatchWake() &&
1422 mailboxPendingFinal();
Implements a Radix Tree, a kind of Trie with compressed keys.
size_t write(const T *buffer, size_t count, bool block=true)
MUST_USE_RESULT bool wait(WaitQueue::StackDiscardCleanup onStackDiscard=nullptr, void *stackDiscardContext=nullptr)
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
Error write(const T &obj, Time::Timestamp &timeout)
Writes one object, waiting for space up to the supplied timeout.
bool initialise(size_t poolSize, size_t bufferSize=1024)
virtual const char * getMemoryPressureDescription()=0
void registerHandler(size_t prio, MemoryPressureHandler *pHandler)
void removeHandler(MemoryPressureHandler *pHandler)
MUST_USE_RESULT bool tryEnter()
static constexpr size_t getPageSize() PURE
static bool getInterrupts()
static void setInterrupts(bool bEnable)
A key/value dictionary for string keys.
bool monitor(Thread *pThread, Event *pEvent)
monitor - add a new Event to be fired when something happens
static Scheduler & instance()
void setUnwindState(UnwindType ut)
@ TerminateThread
Exit only this thread during Process exit.
bool getWaitDebugInfo(WaitDebugInfo &info)
DebugState getDebugState(uintptr_t &address)
A vector / dynamic array.
void remove(const String &key)
MUST_USE_RESULT bool lookup(const String &key, T &value) const
void insert(const String &key, const T &value)
void pushBack(const T &value)