8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/process/Completion.h"
11#include "pedigree/kernel/process/ConditionVariable.h"
12#include "pedigree/kernel/process/MemoryPressureManager.h"
13#include "pedigree/kernel/process/Mutex.h"
14#include "pedigree/kernel/process/OperationBarrier.h"
15#include "pedigree/kernel/process/Scheduler.h"
16#include "pedigree/kernel/process/Semaphore.h"
17#include "pedigree/kernel/process/Thread.h"
18#include "pedigree/kernel/process/WaitQueue.h"
19#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
20#include "pedigree/kernel/processor/Processor.h"
21#include "pedigree/kernel/time/Time.h"
22#include "pedigree/kernel/utilities/Buffer.h"
23#include "pedigree/kernel/utilities/MemoryPool.h"
25#include "pedigree/kernel/utilities/RingBuffer.h"
26#include "pedigree/kernel/utilities/Vector.h"
29bool check(
bool condition,
const char* test,
const char* detail) {
34 ERROR(
"HOSTED-WAIT-TEST: FAIL " << test <<
": " << detail);
38bool waitUntilQueued(
Thread* thread,
size_t debugState) {
39 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
40 while (Time::getTicks() < deadline) {
42 uintptr_t debugAddress = 0;
52bool radixTreeExportedAbi() {
54 const String key(
"hosted-radix-tree-abi");
55 void*
const expected = &tree;
57 void* value = expected;
58 const bool missing = !tree.
lookup(key, value) && value ==
nullptr;
60 tree.
insert(key, expected);
62 const bool found = tree.
lookup(key, value) && value == expected;
66 const bool removed = !tree.
lookup(key, value) && value ==
nullptr;
68 const bool passed = check(missing && found && removed,
"radix-tree-exported-abi",
69 "module-to-kernel bool-and-output lookup contract failed");
71 NOTICE(
"HOSTED-WAIT-TEST: PASS radix-tree-exported-abi");
76bool semaphoreDrainAvailable() {
78 const size_t firstDrain = semaphore.drainAvailable();
79 const bool emptyAfterFirstDrain = !semaphore.tryAcquire();
81 const size_t secondDrain = semaphore.drainAvailable();
82 const size_t emptyDrain = semaphore.drainAvailable();
85 check(firstDrain == 3 && emptyAfterFirstDrain && secondDrain == 2 && emptyDrain == 0 &&
86 !semaphore.tryAcquire(),
87 "semaphore-drain-available",
"available items were not removed exactly once");
89 NOTICE(
"HOSTED-WAIT-TEST: PASS semaphore-drain-available");
94struct CompletionContext {
95 explicit CompletionContext(
Completion* completion)
96 : completion(completion), entered(0), completed(0) {}
103struct TerminalCompletionContext {
104 explicit TerminalCompletionContext(
Semaphore* completion)
105 : completion(completion), entered(0), acquired(0), returned(0) {}
113struct OperationBarrierContext {
115 : barrier(barrier), workEntered(0), workFinished(0), closeFinished(0), releaseWork(0) {}
124int waitForCompletion(
void* parameter) {
125 CompletionContext* context =
reinterpret_cast<CompletionContext*
>(parameter);
126 context->entered += 1;
127 if (context->completion->wait()) {
128 context->completed += 1;
133int waitForTerminalCompletion(
void* parameter) {
134 TerminalCompletionContext* context =
reinterpret_cast<TerminalCompletionContext*
>(parameter);
135 context->entered += 1;
136 if (context->completion->acquireForCompletion()) {
137 context->acquired += 1;
139 context->returned += 1;
143int runAdmittedOperation(
void* parameter) {
144 OperationBarrierContext* context =
reinterpret_cast<OperationBarrierContext*
>(parameter);
145 context->workEntered += 1;
146 const bool released = context->releaseWork.acquireForCompletion();
148 context->workFinished += 1;
149 context->barrier->leave();
153int closeOperationBarrier(
void* parameter) {
154 OperationBarrierContext* context =
reinterpret_cast<OperationBarrierContext*
>(parameter);
155 context->barrier->closeAndWait();
156 context->closeFinished += 1;
160bool completionLifecycle() {
165 check(latched.
complete(),
"completion-lifecycle",
"the first early completion was rejected");
167 check(!latched.
complete(),
"completion-lifecycle",
"duplicate early completion was accepted");
169 check(latched.
wait(),
"completion-lifecycle",
"complete-before-wait did not stay latched");
172 CompletionContext context(&delayed);
174 nullptr,
false,
true);
175 waiter->setName(
"hosted Completion waiter");
177 const bool queued = waitUntilQueued(
waiter, Thread::SemWait);
178 const bool completed = delayed.
complete();
179 const bool duplicateRejected = !delayed.
complete();
180 const bool joined =
waiter->join();
182 passed &= check(context.entered == 1 && queued,
"completion-lifecycle",
183 "waiter-before-complete did not publish its wait");
184 passed &= check(completed && duplicateRejected,
"completion-lifecycle",
185 "delayed completion was not exactly-once");
186 passed &= check(joined && context.completed == 1,
"completion-lifecycle",
187 "the delayed waiter did not wake exactly once");
190 NOTICE(
"HOSTED-WAIT-TEST: PASS completion-lifecycle");
195bool terminalCompletionBarrier() {
197 TerminalCompletionContext context(&completion);
199 &context,
nullptr,
false,
true);
200 waiter->setName(
"hosted terminal completion waiter");
202 const bool queued = waitUntilQueued(
waiter, Thread::SemWait);
207 for (
size_t i = 0; i < 8; ++i) {
210 const bool deferred = context.entered == 1 && context.returned == 0;
212 completion.release();
213 const bool joined =
waiter->join();
215 const bool passed = check(
216 queued && deferred && joined && context.acquired == 1 && context.returned == 1,
217 "terminal-completion-barrier",
"terminal teardown escaped an unfinished completion barrier");
219 NOTICE(
"HOSTED-WAIT-TEST: PASS terminal-completion-barrier");
224bool operationBarrierLifecycle() {
226 OperationBarrierContext context(&barrier);
229 const bool admitted = barrier.
tryEnter();
230 Thread*
worker =
new Thread(process, runAdmittedOperation, &context,
nullptr,
false,
true);
231 worker->setName(
"hosted admitted operation");
233 while (!context.workEntered) {
237 Thread* closer =
new Thread(process, closeOperationBarrier, &context,
nullptr,
false,
true);
238 closer->setName(
"hosted operation barrier closer");
240 const bool closeQueued = waitUntilQueued(closer, Thread::CallbackDrain);
241 const bool closeBlocked = closeQueued && context.closeFinished == 0 && context.workFinished == 0;
242 const bool lateRejected = !barrier.
tryEnter();
244 context.releaseWork.release();
245 const bool workerJoined =
worker->join();
246 const bool closerJoined = closer->
join();
247 const bool drained = barrier.isClosedAndDrained();
250 check(admitted && closeBlocked && lateRejected && workerJoined && closerJoined &&
251 context.workFinished == 1 && context.closeFinished == 1 && drained,
252 "operation-barrier-lifecycle",
253 "close did not reject late work and drain the admitted operation");
255 NOTICE(
"HOSTED-WAIT-TEST: PASS operation-barrier-lifecycle");
260struct ConditionTimeoutContext {
262 : condition(condition),
waiter(
waiter), signalAt(signalAt), published(0), signals(0) {}
266 Time::Timestamp signalAt;
272Thread* g_ZeroTimeoutThread =
nullptr;
276 if (thread == g_ZeroTimeoutThread && debugState == Thread::CondWait) {
277 g_ZeroTimeoutPublications += 1;
281int delayedConditionSignal(
void* parameter) {
282 ConditionTimeoutContext* context =
reinterpret_cast<ConditionTimeoutContext*
>(parameter);
283 if (waitUntilQueued(context->waiter, Thread::CondWait)) {
284 context->published += 1;
287 while (Time::getTicks() < context->signalAt) {
290 context->condition->signal();
291 context->signals += 1;
295bool conditionVariableTimeoutAccounting(
Thread* thread) {
300 constexpr Time::Timestamp InitialTimeout = 250 * Time::Multiplier::Millisecond;
301 Time::Timestamp remaining = InitialTimeout;
302 ConditionTimeoutContext context(&condition, thread,
303 Time::getTicks() + (20 * Time::Multiplier::Millisecond));
305 &context,
nullptr,
false,
true);
306 signaler->setName(
"hosted timed ConditionVariable signaler");
308 passed &= check(mutex.acquire(),
"condition-variable-timeout",
309 "the signalled wait mutex could not be acquired");
310 ConditionVariable::Error error = ConditionVariable::NoError;
311 const bool signalled = condition.
wait(mutex, remaining, error);
312 const bool mutexHeldAfterSignal = mutex.isOwnedByCurrentThread();
314 const bool signalerJoined = signaler->
join();
316 passed &= check(signalled && error == ConditionVariable::NoError,
"condition-variable-timeout",
317 "the delayed signal was reported as an error");
318 passed &= check(context.published == 1 && context.signals == 1 && signalerJoined,
319 "condition-variable-timeout",
320 "the delayed signal did not observe one published waiter");
321 passed &= check(mutexHeldAfterSignal,
"condition-variable-timeout",
322 "the signalled wait did not reacquire its mutex");
323 passed &= check(remaining > 0 && remaining < InitialTimeout,
"condition-variable-timeout",
324 "remaining timeout underflowed or used incompatible clock units");
326 Time::Timestamp expiring = 20 * Time::Multiplier::Millisecond;
327 passed &= check(mutex.acquire(),
"condition-variable-timeout",
328 "the expiring wait mutex could not be acquired");
329 error = ConditionVariable::NoError;
330 const bool timedWait = condition.
wait(mutex, expiring, error);
331 const bool mutexHeldAfterTimeout = mutex.isOwnedByCurrentThread();
334 passed &= check(!timedWait && error == ConditionVariable::TimedOut,
"condition-variable-timeout",
335 "an expired wait did not report TimedOut");
336 passed &= check(expiring == 0,
"condition-variable-timeout",
337 "an expired wait retained a nonzero timeout");
338 passed &= check(mutexHeldAfterTimeout,
"condition-variable-timeout",
339 "the expired wait did not reacquire its mutex");
341 Time::Timestamp immediate = 0;
342 passed &= check(mutex.acquire(),
"condition-variable-timeout",
343 "the immediate wait mutex could not be acquired");
344 const size_t alarmCreatesBeforeImmediate = Time::getHostedAlarmCreateCount();
345 g_ZeroTimeoutPublications = 0;
346 g_ZeroTimeoutThread = thread;
347 WaitQueue::setBeforeBlockHook(observeZeroTimeoutPublication);
348 error = ConditionVariable::NoError;
349 const bool immediateWait = condition.
wait(mutex, immediate, error);
350 WaitQueue::setBeforeBlockHook(
nullptr);
351 g_ZeroTimeoutThread =
nullptr;
352 const bool mutexHeldAfterImmediate = mutex.isOwnedByCurrentThread();
355 passed &= check(!immediateWait && error == ConditionVariable::TimedOut && immediate == 0,
356 "condition-variable-timeout",
"zero did not request an immediate timeout");
357 passed &= check(mutexHeldAfterImmediate,
"condition-variable-timeout",
358 "the immediate timeout released its mutex");
359 passed &= check(g_ZeroTimeoutPublications == 0 &&
360 Time::getHostedAlarmCreateCount() == alarmCreatesBeforeImmediate,
361 "condition-variable-timeout",
362 "the immediate timeout published a waiter or allocated an alarm");
365 NOTICE(
"HOSTED-WAIT-TEST: PASS condition-variable-timeout");
370struct MemoryPoolContext {
371 explicit MemoryPoolContext(
MemoryPool* pool) : pool(pool), entered(0), returned(0), result(0) {}
379int allocateFromExhaustedPool(
void* parameter) {
380 MemoryPoolContext* context =
reinterpret_cast<MemoryPoolContext*
>(parameter);
381 context->entered += 1;
382 context->result = context->pool->allocate();
383 context->returned += 1;
387bool memoryPoolBlockingAndStride() {
388 constexpr size_t BufferSize = 512;
389 MemoryPool pool(
"hosted-memory-pool-regression");
391 if (!pool.initialise(1, BufferSize)) {
392 return check(
false,
"memory-pool-lifecycle",
"a one-page hosted pool could not be initialised");
396 pool.acquireHostedMappingLock();
397 const bool compactedWhileMapping = pool.trim();
398 pool.releaseHostedMappingLock();
399 passed &= check(!compactedWhileMapping,
"memory-pool-lifecycle",
400 "memory-pressure compaction waited on its own mapping lock");
403 for (
size_t i = 0; i < bufferCount; ++i) {
404 const uintptr_t buffer = pool.allocateNow();
406 passed &= check(buffer != 0,
"memory-pool-lifecycle",
407 "the pool exhausted before its advertised capacity");
409 passed &= check(buffer == buffers[0] + (i * BufferSize),
"memory-pool-lifecycle",
410 "buffers did not use the configured fixed stride");
412 passed &= check((buffer % BufferSize) == 0,
"memory-pool-lifecycle",
413 "a buffer did not retain its configured alignment");
415 passed &= check(pool.allocateNow() == 0,
"memory-pool-lifecycle",
416 "nonblocking allocation succeeded after exhaustion");
418 MemoryPoolContext context(&pool);
420 &context,
nullptr,
false,
true);
421 waiter->setName(
"hosted MemoryPool waiter");
422 const bool queued = waitUntilQueued(
waiter, Thread::CondWait);
424 const size_t freedIndex = bufferCount / 2;
425 const uintptr_t freed = buffers[freedIndex];
427 const bool joined =
waiter->join();
428 const uintptr_t reused = context.result;
430 passed &= check(context.entered == 1 && queued,
"memory-pool-lifecycle",
431 "the exhausted-pool allocation did not block");
432 passed &= check(joined && reused == freed,
"memory-pool-lifecycle",
433 "free did not wake the waiter with the released buffer");
435 for (
size_t i = 0; i < buffers.
count(); ++i) {
436 if (i != freedIndex) {
437 pool.free(buffers[i]);
445 NOTICE(
"HOSTED-WAIT-TEST: PASS memory-pool-lifecycle");
450bool memoryPoolCloseAndDrain() {
451 constexpr size_t BufferSize = 512;
456 return check(
false,
"memory-pool-close-drain",
457 "a one-page hosted pool could not be initialised");
461 for (
size_t i = 0; i < bufferCount; ++i) {
462 passed &= check(pool->
allocateNow() != 0,
"memory-pool-close-drain",
463 "the close regression could not exhaust the pool");
466 MemoryPoolContext context(pool);
468 &context,
nullptr,
false,
true);
469 waiter->setName(
"hosted MemoryPool close waiter");
470 const bool queued = waitUntilQueued(
waiter, Thread::CondWait);
475 const bool joined =
waiter->join();
477 passed &= check(context.entered == 1 && queued,
"memory-pool-close-drain",
478 "the exhausted allocation was not active before close");
479 passed &= check(joined && context.result == 0,
"memory-pool-close-drain",
480 "close did not drain the blocked allocation with a null result");
483 NOTICE(
"HOSTED-WAIT-TEST: PASS memory-pool-close-drain");
488bool memoryPoolTerminalDrain() {
489 constexpr size_t BufferSize = 512;
494 return check(
false,
"memory-pool-terminal-drain",
495 "a one-page hosted pool could not be initialised");
499 for (
size_t i = 0; i < bufferCount; ++i) {
500 passed &= check(pool->
allocateNow() != 0,
"memory-pool-terminal-drain",
501 "the terminal regression could not exhaust the pool");
504 MemoryPoolContext context(pool);
506 &context,
nullptr,
false,
true);
507 waiter->setName(
"hosted MemoryPool terminal waiter");
508 const bool queued = waitUntilQueued(
waiter, Thread::CondWait);
511 const bool joined =
waiter->join();
518 context.entered == 1 && queued && joined && context.returned == 1 && context.result == 0,
519 "memory-pool-terminal-drain",
"terminal cancellation skipped or corrupted operation cleanup");
522 NOTICE(
"HOSTED-WAIT-TEST: PASS memory-pool-terminal-drain");
529 CountingPressureHandler() : calls(0) {}
532 return "hosted registry-reentry peer";
551 reentryPeer(reentryPeer) {}
554 return "hosted callback-lifetime regression";
561 manager->removeHandler(reentryPeer);
562 manager->registerHandler(MemoryPressureManager::LowPriority, reentryPeer);
566 const bool released = releaseCallback.acquireForCompletion();
579struct PressureManagerContext {
596int compactPressureManager(
void* parameter) {
597 PressureManagerContext* context =
reinterpret_cast<PressureManagerContext*
>(parameter);
598 context->compactEntered += 1;
599 context->manager->compact();
600 context->compactReturned += 1;
604int removePressureHandler(
void* parameter) {
605 PressureManagerContext* context =
reinterpret_cast<PressureManagerContext*
>(parameter);
606 context->removeEntered += 1;
607 context->manager->removeHandler(context->handler);
608 context->removeReturned += 1;
612bool memoryPressureCallbackBarrier() {
614 CountingPressureHandler reentryPeer;
615 BlockingPressureHandler handler(&manager, &reentryPeer);
616 PressureManagerContext context(&manager, &handler);
619 manager.
registerHandler(MemoryPressureManager::LowPriority, &reentryPeer);
620 manager.
registerHandler(MemoryPressureManager::HighestPriority, &handler);
623 &context,
nullptr,
false,
true);
624 compactor->setName(
"hosted pressure compactor");
626 const bool callbackEntered = handler.entered.acquire(1, 0, 500000);
628 &context,
nullptr,
false,
true);
629 remover->setName(
"hosted pressure remover");
630 const Time::Timestamp removerDeadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
631 while (context.removeEntered != 1 && Time::getTicks() < removerDeadline) {
636 compactPressureManager, &context,
nullptr,
false,
true);
637 followingCompactor->setName(
"hosted pressure following compactor");
638 const Time::Timestamp followerDeadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
639 while (context.compactEntered < 2 && Time::getTicks() < followerDeadline) {
643 bool removerWaitPublished =
false;
644 bool compactorWaitPublished =
false;
645 const Time::Timestamp publicationDeadline =
646 Time::getTicks() + (500 * Time::Multiplier::Millisecond);
647 while (!(removerWaitPublished && compactorWaitPublished) &&
648 Time::getTicks() < publicationDeadline) {
650 removerWaitPublished = remover->
getStatus() == Thread::Sleeping &&
652 compactorWaitPublished = followingCompactor->
getStatus() == Thread::Sleeping &&
654 waitInfo.channelOwner == &manager;
655 if (!(removerWaitPublished && compactorWaitPublished)) {
661 check(callbackEntered && context.removeEntered == 1 && context.removeReturned == 0 &&
662 handler.reentries == 1 && context.compactEntered == 2 &&
663 context.compactReturned == 0 && removerWaitPublished && compactorWaitPublished,
664 "memory-pressure-callback-barrier",
665 "callback reentry failed or a lifetime wait was not published");
668 handler.releaseCallback.release();
669 const bool compactJoined = compactor->
join();
670 const bool followingCompactJoined = followingCompactor->
join();
671 const bool removeJoined = remover->
join();
674 compactJoined && followingCompactJoined && removeJoined && context.compactReturned == 2 &&
675 context.removeReturned == 1 && handler.calls == 1,
676 "memory-pressure-callback-barrier",
"the callback and removal did not complete exactly once");
678 const size_t peerCallsBefore = reentryPeer.calls;
680 check(!manager.
compact() && handler.calls == 1 && reentryPeer.calls == peerCallsBefore + 1,
681 "memory-pressure-callback-barrier",
682 "a removed handler ran again or the reentrant registration was lost");
685 manager.
registerHandler(MemoryPressureManager::LowPriority, &reentryPeer);
686 const size_t callsBeforeAtomicAttempt = reentryPeer.calls;
689 const bool atomicCompactResult = manager.
compact();
692 passed &= check(!atomicCompactResult && reentryPeer.calls == callsBeforeAtomicAttempt,
693 "memory-pressure-callback-barrier",
694 "an atomic-context pressure pass entered a blocking callback");
696 for (
size_t i = 0; i < 3; ++i) {
697 const size_t callsBefore = reentryPeer.calls;
698 manager.
registerHandler(MemoryPressureManager::LowPriority, &reentryPeer);
699 const bool compactResult = manager.
compact();
701 const size_t callsAfterRemoval = reentryPeer.calls;
702 const bool emptyCompactResult = manager.
compact();
704 passed &= check(!compactResult && !emptyCompactResult && callsAfterRemoval == callsBefore + 1 &&
705 reentryPeer.calls == callsAfterRemoval,
706 "memory-pressure-callback-barrier",
707 "repeated register/compact/remove was not stable");
712 "HOSTED-WAIT-TEST: PASS "
713 "memory-pressure-callback-barrier");
718struct RingBufferCloseContext {
726 operation(operation),
740int runBlockingRingBufferOperation(
void* parameter) {
741 RingBufferCloseContext* context =
reinterpret_cast<RingBufferCloseContext*
>(parameter);
742 context->entered += 1;
744 if (context->operation == RingBufferCloseContext::Read) {
746 Time::Timestamp timeout = Time::Infinity;
748 context->succeeded = context->buffer->read(value, timeout, error) ? 1 : 0;
749 context->error = error;
751 Time::Timestamp timeout = Time::Infinity;
752 context->error = context->buffer->write(
'b', timeout);
756 context->returned += 1;
760bool runRingBufferCloseCase(RingBufferCloseContext::Operation operation,
bool fill) {
767 RingBufferCloseContext context(buffer, operation);
769 runBlockingRingBufferOperation, &context,
nullptr,
false,
true);
770 waiter->setName(
"hosted RingBuffer close waiter");
771 const bool queued = waitUntilQueued(
waiter, Thread::CondWait);
774 const bool joined =
waiter->join();
775 return passed && context.entered == 1 && queued && joined && context.returned == 1 &&
779bool ringBufferCloseAndDrain() {
782 check(runRingBufferCloseCase(RingBufferCloseContext::Read,
false),
"ringbuffer-close-drain",
783 "close did not wake and drain a blocked reader with Closed");
785 check(runRingBufferCloseCase(RingBufferCloseContext::Write,
true),
"ringbuffer-close-drain",
786 "close did not wake and drain a full-buffer writer with Closed");
792 passed &= check(monitor.tryAcquire(),
"ringbuffer-close-drain",
793 "close did not wake a registered readiness monitor");
796 NOTICE(
"HOSTED-WAIT-TEST: PASS ringbuffer-close-drain");
801struct BufferCloseContext {
809 BufferCloseContext(
Buffer<char>* buffer, Operation operation)
810 : buffer(buffer), operation(operation), entered(0), returned(0), result(1) {}
819struct BufferTryWriteContext {
821 : buffer(buffer), entered(0), returned(0), result(1) {}
829int runBufferTryWrite(
void* parameter) {
830 BufferTryWriteContext* context =
reinterpret_cast<BufferTryWriteContext*
>(parameter);
831 const char input[] = {
'a',
'b'};
832 context->entered += 1;
833 context->result = context->buffer->tryWrite(input,
sizeof(input)) ? 1 : 0;
834 context->returned += 1;
838int runBlockingBufferOperation(
void* parameter) {
839 BufferCloseContext* context =
reinterpret_cast<BufferCloseContext*
>(parameter);
840 context->entered += 1;
843 switch (context->operation) {
844 case BufferCloseContext::Read:
845 context->result = context->buffer->read(&value, 1,
true);
847 case BufferCloseContext::Write:
848 context->result = context->buffer->write(&value, 1,
true);
850 case BufferCloseContext::CanRead:
851 context->result = context->buffer->canRead(
true) ? 1 : 0;
853 case BufferCloseContext::CanWrite:
854 context->result = context->buffer->canWrite(
true) ? 1 : 0;
857 context->returned += 1;
861bool runBufferCloseCase(BufferCloseContext::Operation operation,
bool fill) {
865 const char initial =
'a';
866 passed &= buffer->
write(&initial, 1,
false) == 1;
869 BufferCloseContext context(buffer, operation);
871 &context,
nullptr,
false,
true);
872 waiter->setName(
"hosted Buffer close waiter");
873 const bool queued = waitUntilQueued(
waiter, Thread::CondWait);
876 const bool joined =
waiter->join();
877 return passed && context.entered == 1 && queued && joined && context.returned == 1 &&
881bool bufferCloseAndDrain() {
883 passed &= check(runBufferCloseCase(BufferCloseContext::Read,
false),
"buffer-close-drain",
884 "close did not drain an already-entered blocking read");
885 passed &= check(runBufferCloseCase(BufferCloseContext::Write,
true),
"buffer-close-drain",
886 "close did not drain an already-entered blocking write");
887 passed &= check(runBufferCloseCase(BufferCloseContext::CanRead,
false),
"buffer-close-drain",
888 "close did not drain an already-entered canRead");
889 passed &= check(runBufferCloseCase(BufferCloseContext::CanWrite,
true),
"buffer-close-drain",
890 "close did not drain an already-entered canWrite");
893 NOTICE(
"HOSTED-WAIT-TEST: PASS buffer-close-drain");
898bool bufferTryWriteDoesNotWaitForLock() {
900 BufferTryWriteContext context(&buffer);
901 buffer.acquireHostedOperationLock();
903 nullptr,
false,
true);
904 writer->setName(
"hosted Buffer try-write contention");
906 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
907 while (!context.returned && Time::getTicks() < deadline) {
910 const bool returnedWhileLocked = context.entered == 1 && context.returned == 1;
911 buffer.releaseHostedOperationLock();
912 const bool joined = writer->
join();
915 check(returnedWhileLocked && joined && context.result == 0 && buffer.
getDataSize() == 0,
916 "buffer-try-write",
"tryWrite waited for the lock or changed the buffer on failure");
918 NOTICE(
"HOSTED-WAIT-TEST: PASS buffer-try-write");
923bool runBufferTerminalCase(BufferCloseContext::Operation operation,
bool fill) {
927 const char initial =
'a';
928 passed &= buffer->
write(&initial, 1,
false) == 1;
931 BufferCloseContext context(buffer, operation);
933 &context,
nullptr,
false,
true);
934 waiter->setName(
"hosted Buffer terminal waiter");
935 const bool queued = waitUntilQueued(
waiter, Thread::CondWait);
938 const bool joined =
waiter->join();
941 return passed && context.entered == 1 && queued && joined && context.returned == 1 &&
945bool bufferTerminalDrain() {
947 passed &= check(runBufferTerminalCase(BufferCloseContext::Read,
false),
"buffer-terminal-drain",
948 "a terminated blocking read retained an active operation");
949 passed &= check(runBufferTerminalCase(BufferCloseContext::Write,
true),
"buffer-terminal-drain",
950 "a terminated blocking write retained an active operation");
951 passed &= check(runBufferTerminalCase(BufferCloseContext::CanRead,
false),
952 "buffer-terminal-drain",
"a terminated canRead retained an active operation");
953 passed &= check(runBufferTerminalCase(BufferCloseContext::CanWrite,
true),
954 "buffer-terminal-drain",
"a terminated canWrite retained an active operation");
957 NOTICE(
"HOSTED-WAIT-TEST: PASS buffer-terminal-drain");
962bool terminalOperationAdmissionScope() {
966 BufferCloseContext bufferContext(buffer, BufferCloseContext::Read);
967 buffer->acquireHostedOperationLock();
970 &bufferContext,
nullptr,
false,
true);
971 bufferWaiter->setName(
"hosted Buffer operation-admission waiter");
972 const bool bufferQueued = waitUntilQueued(bufferWaiter, Thread::SemWait);
974 for (
size_t i = 0; i < 8; ++i) {
977 const bool bufferStayedBlocked = bufferContext.returned == 0;
978 buffer->releaseHostedOperationLock();
979 const bool bufferJoined = bufferWaiter->
join();
980 const bool bufferRetired = buffer->getHostedActiveOperationCount() == 0;
983 passed &= check(bufferQueued && bufferStayedBlocked && bufferJoined &&
984 bufferContext.returned == 1 && bufferContext.result == 0 && bufferRetired,
985 "terminal-operation-admission",
986 "Buffer termination escaped the admission mutex or leaked its pin");
988 constexpr size_t BufferSize = 512;
992 return check(
false,
"terminal-operation-admission",
993 "the MemoryPool admission fixture could not initialise");
997 for (
size_t i = 0; i < bufferCount; ++i) {
1001 MemoryPoolContext poolContext(pool);
1002 pool->acquireHostedOperationLock();
1004 allocateFromExhaustedPool, &poolContext,
nullptr,
false,
true);
1005 poolWaiter->setName(
"hosted MemoryPool operation-admission waiter");
1006 const bool poolQueued = waitUntilQueued(poolWaiter, Thread::SemWait);
1008 for (
size_t i = 0; i < 8; ++i) {
1011 const bool poolStayedBlocked = poolContext.returned == 0;
1012 pool->releaseHostedOperationLock();
1013 const bool poolJoined = poolWaiter->
join();
1014 const bool poolRetired = pool->getHostedActiveOperationCount() == 0;
1017 passed &= check(poolQueued && poolStayedBlocked && poolJoined && poolContext.returned == 1 &&
1018 poolContext.result == 0 && poolRetired,
1019 "terminal-operation-admission",
1020 "MemoryPool termination escaped the admission mutex or leaked its pin");
1023 NOTICE(
"HOSTED-WAIT-TEST: PASS terminal-operation-admission");
1028struct TerminalTimeoutContext {
1030 : semaphore(semaphore),
1031 condition(condition),
1033 semaphoreEntered(0),
1034 semaphoreReturned(0),
1035 semaphoreInterrupted(0),
1036 semaphoreDestructed(0),
1037 conditionEntered(0),
1038 conditionReturned(0),
1039 conditionTerminal(0),
1040 conditionMutexHeld(0),
1041 conditionDestructed(0),
1044 delayInterrupted(0),
1045 delayDestructed(0) {}
1065class TerminalTimeoutStackCanary {
1067 explicit TerminalTimeoutStackCanary(
Atomic<size_t>* destructed) : m_Destructed(destructed) {}
1069 ~TerminalTimeoutStackCanary() {
1077int terminalTimedSemaphoreWait(
void* parameter) {
1078 TerminalTimeoutContext* context =
reinterpret_cast<TerminalTimeoutContext*
>(parameter);
1079 TerminalTimeoutStackCanary stackCanary(&context->semaphoreDestructed);
1080 context->semaphoreEntered += 1;
1081 Semaphore::SemaphoreError error = Semaphore::NoError;
1082 const bool acquired = context->semaphore->acquireWithError(1, 0, 500000, error);
1083 context->semaphoreInterrupted =
1084 !acquired && error == Semaphore::Interrupted ?
static_cast<size_t>(1) : 0;
1085 context->semaphoreReturned += 1;
1089int terminalTimedConditionWait(
void* parameter) {
1090 TerminalTimeoutContext* context =
reinterpret_cast<TerminalTimeoutContext*
>(parameter);
1091 TerminalTimeoutStackCanary stackCanary(&context->conditionDestructed);
1092 context->mutex->acquire();
1093 context->conditionEntered += 1;
1094 Time::Timestamp timeout = 500 * Time::Multiplier::Millisecond;
1095 ConditionVariable::Error error = ConditionVariable::NoError;
1096 const bool signalled = context->condition->wait(*context->mutex, timeout, error);
1097 context->conditionTerminal =
1098 !signalled && error == ConditionVariable::TerminationDeferred ?
static_cast<size_t>(1) : 0;
1099 context->conditionMutexHeld = context->mutex->isOwnedByCurrentThread() ? 1 : 0;
1100 context->conditionReturned += 1;
1101 if (context->conditionMutexHeld) {
1102 context->mutex->release();
1107int terminalDelay(
void* parameter) {
1108 TerminalTimeoutContext* context =
reinterpret_cast<TerminalTimeoutContext*
>(parameter);
1109 TerminalTimeoutStackCanary stackCanary(&context->delayDestructed);
1110 context->delayEntered += 1;
1111 context->delayInterrupted = Time::delay(500 * Time::Multiplier::Millisecond) ? 0 : 1;
1112 context->delayReturned += 1;
1116bool terminalTimeoutCleanup() {
1120 TerminalTimeoutContext context(&semaphore, &condition, &mutex);
1123 const size_t semaphoreCreates = Semaphore::getHostedTimeoutCreateCount();
1124 const size_t semaphoreDestroys = Semaphore::getHostedTimeoutDestroyCount();
1125 const size_t alarmCreates = Time::getHostedAlarmCreateCount();
1126 const size_t alarmDestroys = Time::getHostedAlarmDestroyCount();
1127 const Time::Timestamp deadline = Time::getTicks() + (550 * Time::Multiplier::Millisecond);
1129 Thread* semaphoreWaiter =
1130 new Thread(process, terminalTimedSemaphoreWait, &context,
nullptr,
false,
true);
1131 Thread* conditionWaiter =
1132 new Thread(process, terminalTimedConditionWait, &context,
nullptr,
false,
true);
1133 Thread* delayWaiter =
new Thread(process, terminalDelay, &context,
nullptr,
false,
true);
1135 const bool semaphoreQueued = waitUntilQueued(semaphoreWaiter, Thread::SemWait);
1136 const bool conditionQueued = waitUntilQueued(conditionWaiter, Thread::CondWait);
1137 const bool delayQueued = waitUntilQueued(delayWaiter, Thread::EventWait);
1143 const bool semaphoreJoined = semaphoreWaiter->
join();
1144 const bool conditionJoined = conditionWaiter->
join();
1145 const bool delayJoined = delayWaiter->
join();
1148 passed &= check(semaphoreQueued && conditionQueued && delayQueued,
"terminal-timeout-cleanup",
1149 "not every timed waiter published its wait");
1151 check(semaphoreJoined && conditionJoined && delayJoined && context.semaphoreEntered == 1 &&
1152 context.conditionEntered == 1 && context.delayEntered == 1 &&
1153 context.semaphoreReturned == 1 && context.semaphoreInterrupted == 1 &&
1154 context.semaphoreDestructed == 1 && context.conditionReturned == 1 &&
1155 context.conditionTerminal == 1 && context.conditionMutexHeld == 1 &&
1156 context.conditionDestructed == 1 && context.delayReturned == 1 &&
1157 context.delayInterrupted == 1 && context.delayDestructed == 1,
1158 "terminal-timeout-cleanup",
1159 "a terminal timed wait did not return through ordinary stack cleanup");
1161 check(Semaphore::getHostedTimeoutCreateCount() == semaphoreCreates + 1 &&
1162 Semaphore::getHostedTimeoutDestroyCount() == semaphoreDestroys + 1 &&
1163 Time::getHostedAlarmCreateCount() == alarmCreates + 2 &&
1164 Time::getHostedAlarmDestroyCount() == alarmDestroys + 2,
1165 "terminal-timeout-cleanup",
"a cancelled timeout event was not destroyed exactly once");
1169 while (Time::getTicks() < deadline) {
1174 NOTICE(
"HOSTED-WAIT-TEST: PASS terminal-timeout-cleanup");
1180bool runHostedPrimitiveRegressions(
Thread* thread) {
1181 return radixTreeExportedAbi() && semaphoreDrainAvailable() && completionLifecycle() &&
1182 terminalCompletionBarrier() && operationBarrierLifecycle() &&
1183 conditionVariableTimeoutAccounting(thread) && memoryPoolBlockingAndStride() &&
1184 memoryPoolCloseAndDrain() && memoryPoolTerminalDrain() &&
1185 memoryPressureCallbackBarrier() && bufferCloseAndDrain() &&
1186 bufferTryWriteDoesNotWaitForLock() && bufferTerminalDrain() &&
1187 terminalOperationAdmissionScope() && terminalTimeoutCleanup();
1190bool runHostedRingBufferRegressions() {
1191 return ringBufferCloseAndDrain();
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 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.
Utility class to provide a ring buffer.
bool monitor(Thread *pThread, Event *pEvent)
monitor - add a new Event to be fired when something happens
Error write(const T &obj, Time::Timestamp &timeout)
write - write a byte to the ring buffer.
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)