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/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"
27
28namespace {
29bool check(bool condition, const char* test, const char* detail) {
30 if (condition) {
31 return true;
32 }
33
34 ERROR("HOSTED-WAIT-TEST: FAIL " << test << ": " << detail);
35 return false;
36}
37
38bool waitUntilQueued(Thread* thread, size_t debugState) {
39 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
40 while (Time::getTicks() < deadline) {
41 Thread::WaitDebugInfo info = {};
42 uintptr_t debugAddress = 0;
43 if (thread->getWaitDebugInfo(info) && info.queue && info.queued &&
44 thread->getDebugState(debugAddress) == debugState) {
45 return true;
46 }
48 }
49 return false;
50}
51
52bool radixTreeExportedAbi() {
54 const String key("hosted-radix-tree-abi");
55 void* const expected = &tree;
56
57 void* value = expected;
58 const bool missing = !tree.lookup(key, value) && value == nullptr;
59
60 tree.insert(key, expected);
61 value = nullptr;
62 const bool found = tree.lookup(key, value) && value == expected;
63
64 tree.remove(key);
65 value = expected;
66 const bool removed = !tree.lookup(key, value) && value == nullptr;
67
68 const bool passed = check(missing && found && removed, "radix-tree-exported-abi",
69 "module-to-kernel bool-and-output lookup contract failed");
70 if (passed) {
71 NOTICE("HOSTED-WAIT-TEST: PASS radix-tree-exported-abi");
72 }
73 return passed;
74}
75
76bool semaphoreDrainAvailable() {
77 Semaphore semaphore(3);
78 const size_t firstDrain = semaphore.drainAvailable();
79 const bool emptyAfterFirstDrain = !semaphore.tryAcquire();
80 semaphore.release(2);
81 const size_t secondDrain = semaphore.drainAvailable();
82 const size_t emptyDrain = semaphore.drainAvailable();
83
84 const bool passed =
85 check(firstDrain == 3 && emptyAfterFirstDrain && secondDrain == 2 && emptyDrain == 0 &&
86 !semaphore.tryAcquire(),
87 "semaphore-drain-available", "available items were not removed exactly once");
88 if (passed) {
89 NOTICE("HOSTED-WAIT-TEST: PASS semaphore-drain-available");
90 }
91 return passed;
92}
93
94struct CompletionContext {
95 explicit CompletionContext(Completion* completion)
96 : completion(completion), entered(0), completed(0) {}
97
98 Completion* completion;
99 Atomic<size_t> entered;
100 Atomic<size_t> completed;
101};
102
103struct TerminalCompletionContext {
104 explicit TerminalCompletionContext(Semaphore* completion)
105 : completion(completion), entered(0), acquired(0), returned(0) {}
106
107 Semaphore* completion;
108 Atomic<size_t> entered;
109 Atomic<size_t> acquired;
110 Atomic<size_t> returned;
111};
112
113struct OperationBarrierContext {
114 explicit OperationBarrierContext(OperationBarrier* barrier)
115 : barrier(barrier), workEntered(0), workFinished(0), closeFinished(0), releaseWork(0) {}
116
117 OperationBarrier* barrier;
118 Atomic<size_t> workEntered;
119 Atomic<size_t> workFinished;
120 Atomic<size_t> closeFinished;
121 Semaphore releaseWork;
122};
123
124int waitForCompletion(void* parameter) {
125 CompletionContext* context = reinterpret_cast<CompletionContext*>(parameter);
126 context->entered += 1;
127 if (context->completion->wait()) {
128 context->completed += 1;
129 }
130 return 0;
131}
132
133int waitForTerminalCompletion(void* parameter) {
134 TerminalCompletionContext* context = reinterpret_cast<TerminalCompletionContext*>(parameter);
135 context->entered += 1;
136 if (context->completion->acquireForCompletion()) {
137 context->acquired += 1;
138 }
139 context->returned += 1;
140 return 0;
141}
142
143int runAdmittedOperation(void* parameter) {
144 OperationBarrierContext* context = reinterpret_cast<OperationBarrierContext*>(parameter);
145 context->workEntered += 1;
146 const bool released = context->releaseWork.acquireForCompletion();
147 (void)released;
148 context->workFinished += 1;
149 context->barrier->leave();
150 return 0;
151}
152
153int closeOperationBarrier(void* parameter) {
154 OperationBarrierContext* context = reinterpret_cast<OperationBarrierContext*>(parameter);
155 context->barrier->closeAndWait();
156 context->closeFinished += 1;
157 return 0;
158}
159
160bool completionLifecycle() {
161 bool passed = true;
162
163 Completion latched;
164 passed &=
165 check(latched.complete(), "completion-lifecycle", "the first early completion was rejected");
166 passed &=
167 check(!latched.complete(), "completion-lifecycle", "duplicate early completion was accepted");
168 passed &=
169 check(latched.wait(), "completion-lifecycle", "complete-before-wait did not stay latched");
170
171 Completion delayed;
172 CompletionContext context(&delayed);
173 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), waitForCompletion, &context,
174 nullptr, false, true);
175 waiter->setName("hosted Completion waiter");
176
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();
181
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");
188
189 if (passed) {
190 NOTICE("HOSTED-WAIT-TEST: PASS completion-lifecycle");
191 }
192 return passed;
193}
194
195bool terminalCompletionBarrier() {
196 Semaphore completion(0);
197 TerminalCompletionContext context(&completion);
198 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), waitForTerminalCompletion,
199 &context, nullptr, false, true);
200 waiter->setName("hosted terminal completion waiter");
201
202 const bool queued = waitUntilQueued(waiter, Thread::SemWait);
203 waiter->setUnwindState(Thread::TerminateThread);
204
205 // Let the terminal wake reach acquireForCompletion(). It must re-enrol
206 // rather than abandon storage still owned by the producer.
207 for (size_t i = 0; i < 8; ++i) {
209 }
210 const bool deferred = context.entered == 1 && context.returned == 0;
211
212 completion.release();
213 const bool joined = waiter->join();
214
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");
218 if (passed) {
219 NOTICE("HOSTED-WAIT-TEST: PASS terminal-completion-barrier");
220 }
221 return passed;
222}
223
224bool operationBarrierLifecycle() {
225 OperationBarrier barrier;
226 OperationBarrierContext context(&barrier);
227 Process* process = Scheduler::instance().getKernelProcess();
228
229 const bool admitted = barrier.tryEnter();
230 Thread* worker = new Thread(process, runAdmittedOperation, &context, nullptr, false, true);
231 worker->setName("hosted admitted operation");
232
233 while (!context.workEntered) {
235 }
236
237 Thread* closer = new Thread(process, closeOperationBarrier, &context, nullptr, false, true);
238 closer->setName("hosted operation barrier closer");
239
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();
243
244 context.releaseWork.release();
245 const bool workerJoined = worker->join();
246 const bool closerJoined = closer->join();
247 const bool drained = barrier.isClosedAndDrained();
248
249 const bool passed =
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");
254 if (passed) {
255 NOTICE("HOSTED-WAIT-TEST: PASS operation-barrier-lifecycle");
256 }
257 return passed;
258}
259
260struct ConditionTimeoutContext {
261 ConditionTimeoutContext(ConditionVariable* condition, Thread* waiter, Time::Timestamp signalAt)
262 : condition(condition), waiter(waiter), signalAt(signalAt), published(0), signals(0) {}
263
264 ConditionVariable* condition;
265 Thread* waiter;
266 Time::Timestamp signalAt;
267 Atomic<size_t> published;
268 Atomic<size_t> signals;
269};
270
271Atomic<size_t> g_ZeroTimeoutPublications(0);
272Thread* g_ZeroTimeoutThread = nullptr;
273
274void observeZeroTimeoutPublication(WaitQueue*, Thread* thread, const WaitQueue::Channel&,
275 size_t debugState) {
276 if (thread == g_ZeroTimeoutThread && debugState == Thread::CondWait) {
277 g_ZeroTimeoutPublications += 1;
278 }
279}
280
281int delayedConditionSignal(void* parameter) {
282 ConditionTimeoutContext* context = reinterpret_cast<ConditionTimeoutContext*>(parameter);
283 if (waitUntilQueued(context->waiter, Thread::CondWait)) {
284 context->published += 1;
285 }
286
287 while (Time::getTicks() < context->signalAt) {
289 }
290 context->condition->signal();
291 context->signals += 1;
292 return 0;
293}
294
295bool conditionVariableTimeoutAccounting(Thread* thread) {
296 ConditionVariable condition;
297 Mutex mutex;
298 bool passed = true;
299
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));
304 Thread* signaler = new Thread(Scheduler::instance().getKernelProcess(), delayedConditionSignal,
305 &context, nullptr, false, true);
306 signaler->setName("hosted timed ConditionVariable signaler");
307
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();
313 mutex.release();
314 const bool signalerJoined = signaler->join();
315
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");
325
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();
332 mutex.release();
333
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");
340
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();
353 mutex.release();
354
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");
363
364 if (passed) {
365 NOTICE("HOSTED-WAIT-TEST: PASS condition-variable-timeout");
366 }
367 return passed;
368}
369
370struct MemoryPoolContext {
371 explicit MemoryPoolContext(MemoryPool* pool) : pool(pool), entered(0), returned(0), result(0) {}
372
373 MemoryPool* pool;
374 Atomic<size_t> entered;
375 Atomic<size_t> returned;
376 Atomic<uintptr_t> result;
377};
378
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;
384 return 0;
385}
386
387bool memoryPoolBlockingAndStride() {
388 constexpr size_t BufferSize = 512;
389 MemoryPool pool("hosted-memory-pool-regression");
390 bool passed = true;
391 if (!pool.initialise(1, BufferSize)) {
392 return check(false, "memory-pool-lifecycle", "a one-page hosted pool could not be initialised");
393 }
394
395 const size_t bufferCount = PhysicalMemoryManager::getPageSize() / BufferSize;
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");
401
402 Vector<uintptr_t> buffers;
403 for (size_t i = 0; i < bufferCount; ++i) {
404 const uintptr_t buffer = pool.allocateNow();
405 buffers.pushBack(buffer);
406 passed &= check(buffer != 0, "memory-pool-lifecycle",
407 "the pool exhausted before its advertised capacity");
408 if (i) {
409 passed &= check(buffer == buffers[0] + (i * BufferSize), "memory-pool-lifecycle",
410 "buffers did not use the configured fixed stride");
411 }
412 passed &= check((buffer % BufferSize) == 0, "memory-pool-lifecycle",
413 "a buffer did not retain its configured alignment");
414 }
415 passed &= check(pool.allocateNow() == 0, "memory-pool-lifecycle",
416 "nonblocking allocation succeeded after exhaustion");
417
418 MemoryPoolContext context(&pool);
419 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), allocateFromExhaustedPool,
420 &context, nullptr, false, true);
421 waiter->setName("hosted MemoryPool waiter");
422 const bool queued = waitUntilQueued(waiter, Thread::CondWait);
423
424 const size_t freedIndex = bufferCount / 2;
425 const uintptr_t freed = buffers[freedIndex];
426 pool.free(freed);
427 const bool joined = waiter->join();
428 const uintptr_t reused = context.result;
429
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");
434
435 for (size_t i = 0; i < buffers.count(); ++i) {
436 if (i != freedIndex) {
437 pool.free(buffers[i]);
438 }
439 }
440 if (reused) {
441 pool.free(reused);
442 }
443
444 if (passed) {
445 NOTICE("HOSTED-WAIT-TEST: PASS memory-pool-lifecycle");
446 }
447 return passed;
448}
449
450bool memoryPoolCloseAndDrain() {
451 constexpr size_t BufferSize = 512;
452 MemoryPool* pool = new MemoryPool("hosted-memory-pool-close-regression");
453 bool passed = true;
454 if (!pool->initialise(1, BufferSize)) {
455 delete pool;
456 return check(false, "memory-pool-close-drain",
457 "a one-page hosted pool could not be initialised");
458 }
459
460 const size_t bufferCount = PhysicalMemoryManager::getPageSize() / BufferSize;
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");
464 }
465
466 MemoryPoolContext context(pool);
467 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), allocateFromExhaustedPool,
468 &context, nullptr, false, true);
469 waiter->setName("hosted MemoryPool close waiter");
470 const bool queued = waitUntilQueued(waiter, Thread::CondWait);
471
472 // Destruction must wake this already-entered allocation and wait until it
473 // has stopped touching the pool's mutex and condition variable.
474 delete pool;
475 const bool joined = waiter->join();
476
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");
481
482 if (passed) {
483 NOTICE("HOSTED-WAIT-TEST: PASS memory-pool-close-drain");
484 }
485 return passed;
486}
487
488bool memoryPoolTerminalDrain() {
489 constexpr size_t BufferSize = 512;
490 MemoryPool* pool = new MemoryPool("hosted-memory-pool-terminal-regression");
491 bool passed = true;
492 if (!pool->initialise(1, BufferSize)) {
493 delete pool;
494 return check(false, "memory-pool-terminal-drain",
495 "a one-page hosted pool could not be initialised");
496 }
497
498 const size_t bufferCount = PhysicalMemoryManager::getPageSize() / BufferSize;
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");
502 }
503
504 MemoryPoolContext context(pool);
505 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), allocateFromExhaustedPool,
506 &context, nullptr, false, true);
507 waiter->setName("hosted MemoryPool terminal waiter");
508 const bool queued = waitUntilQueued(waiter, Thread::CondWait);
509
510 waiter->setUnwindState(Thread::TerminateThread);
511 const bool joined = waiter->join();
512
513 // The terminal request returns through the protected operation scope so
514 // its ordinary RAII cleanup retires the pool reference before teardown.
515 delete pool;
516
517 passed &= check(
518 context.entered == 1 && queued && joined && context.returned == 1 && context.result == 0,
519 "memory-pool-terminal-drain", "terminal cancellation skipped or corrupted operation cleanup");
520
521 if (passed) {
522 NOTICE("HOSTED-WAIT-TEST: PASS memory-pool-terminal-drain");
523 }
524 return passed;
525}
526
527class CountingPressureHandler : public MemoryPressureHandler {
528 public:
529 CountingPressureHandler() : calls(0) {}
530
531 const char* getMemoryPressureDescription() override {
532 return "hosted registry-reentry peer";
533 }
534
535 bool compact() override {
536 calls += 1;
537 return false;
538 }
539
540 Atomic<size_t> calls;
541};
542
543class BlockingPressureHandler : public MemoryPressureHandler {
544 public:
545 BlockingPressureHandler(MemoryPressureManager* manager, MemoryPressureHandler* reentryPeer)
546 : entered(0),
547 releaseCallback(0),
548 calls(0),
549 reentries(0),
550 manager(manager),
551 reentryPeer(reentryPeer) {}
552
553 const char* getMemoryPressureDescription() override {
554 return "hosted callback-lifetime regression";
555 }
556
557 bool compact() override {
558 calls += 1;
559
560 // Registry mutation from inside a callback must not recurse on a lock.
561 manager->removeHandler(reentryPeer);
562 manager->registerHandler(MemoryPressureManager::LowPriority, reentryPeer);
563 reentries += 1;
564
565 entered.release();
566 const bool released = releaseCallback.acquireForCompletion();
567 assert(released);
568 return false;
569 }
570
571 Semaphore entered;
572 Semaphore releaseCallback;
573 Atomic<size_t> calls;
574 Atomic<size_t> reentries;
575 MemoryPressureManager* manager;
576 MemoryPressureHandler* reentryPeer;
577};
578
579struct PressureManagerContext {
580 PressureManagerContext(MemoryPressureManager* manager, MemoryPressureHandler* handler)
581 : manager(manager),
582 handler(handler),
583 compactEntered(0),
584 compactReturned(0),
585 removeEntered(0),
586 removeReturned(0) {}
587
588 MemoryPressureManager* manager;
589 MemoryPressureHandler* handler;
590 Atomic<size_t> compactEntered;
591 Atomic<size_t> compactReturned;
592 Atomic<size_t> removeEntered;
593 Atomic<size_t> removeReturned;
594};
595
596int compactPressureManager(void* parameter) {
597 PressureManagerContext* context = reinterpret_cast<PressureManagerContext*>(parameter);
598 context->compactEntered += 1;
599 context->manager->compact();
600 context->compactReturned += 1;
601 return 0;
602}
603
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;
609 return 0;
610}
611
612bool memoryPressureCallbackBarrier() {
613 MemoryPressureManager manager;
614 CountingPressureHandler reentryPeer;
615 BlockingPressureHandler handler(&manager, &reentryPeer);
616 PressureManagerContext context(&manager, &handler);
617 bool passed = true;
618
619 manager.registerHandler(MemoryPressureManager::LowPriority, &reentryPeer);
620 manager.registerHandler(MemoryPressureManager::HighestPriority, &handler);
621
622 Thread* compactor = new Thread(Scheduler::instance().getKernelProcess(), compactPressureManager,
623 &context, nullptr, false, true);
624 compactor->setName("hosted pressure compactor");
625
626 const bool callbackEntered = handler.entered.acquire(1, 0, 500000);
627 Thread* remover = new Thread(Scheduler::instance().getKernelProcess(), removePressureHandler,
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) {
633 }
634
635 Thread* followingCompactor = new Thread(Scheduler::instance().getKernelProcess(),
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) {
641 }
642
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) {
649 Thread::WaitDebugInfo waitInfo;
650 removerWaitPublished = remover->getStatus() == Thread::Sleeping &&
651 remover->getWaitDebugInfo(waitInfo) && waitInfo.channelOwner == &handler;
652 compactorWaitPublished = followingCompactor->getStatus() == Thread::Sleeping &&
653 followingCompactor->getWaitDebugInfo(waitInfo) &&
654 waitInfo.channelOwner == &manager;
655 if (!(removerWaitPublished && compactorWaitPublished)) {
657 }
658 }
659
660 passed &=
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");
666
667 // Always release the callback before joining, including failure paths.
668 handler.releaseCallback.release();
669 const bool compactJoined = compactor->join();
670 const bool followingCompactJoined = followingCompactor->join();
671 const bool removeJoined = remover->join();
672
673 passed &= check(
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");
677
678 const size_t peerCallsBefore = reentryPeer.calls;
679 passed &=
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");
683 manager.removeHandler(&reentryPeer);
684
685 manager.registerHandler(MemoryPressureManager::LowPriority, &reentryPeer);
686 const size_t callsBeforeAtomicAttempt = reentryPeer.calls;
687 const bool interruptsWereEnabled = Processor::getInterrupts();
689 const bool atomicCompactResult = manager.compact();
690 Processor::setInterrupts(interruptsWereEnabled);
691 manager.removeHandler(&reentryPeer);
692 passed &= check(!atomicCompactResult && reentryPeer.calls == callsBeforeAtomicAttempt,
693 "memory-pressure-callback-barrier",
694 "an atomic-context pressure pass entered a blocking callback");
695
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();
700 manager.removeHandler(&reentryPeer);
701 const size_t callsAfterRemoval = reentryPeer.calls;
702 const bool emptyCompactResult = manager.compact();
703
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");
708 }
709
710 if (passed) {
711 NOTICE(
712 "HOSTED-WAIT-TEST: PASS "
713 "memory-pressure-callback-barrier");
714 }
715 return passed;
716}
717
718struct RingBufferCloseContext {
719 enum Operation {
720 Read,
721 Write,
722 };
723
724 RingBufferCloseContext(RingBuffer<char>* buffer, Operation operation)
725 : buffer(buffer),
726 operation(operation),
727 entered(0),
728 returned(0),
729 succeeded(1),
730 error(RingBuffer<char>::NoError) {}
731
732 RingBuffer<char>* buffer;
733 Operation operation;
734 Atomic<size_t> entered;
735 Atomic<size_t> returned;
736 Atomic<size_t> succeeded;
737 Atomic<size_t> error;
738};
739
740int runBlockingRingBufferOperation(void* parameter) {
741 RingBufferCloseContext* context = reinterpret_cast<RingBufferCloseContext*>(parameter);
742 context->entered += 1;
743
744 if (context->operation == RingBufferCloseContext::Read) {
745 char value = 0;
746 Time::Timestamp timeout = Time::Infinity;
747 RingBuffer<char>::Error error = RingBuffer<char>::NoError;
748 context->succeeded = context->buffer->read(value, timeout, error) ? 1 : 0;
749 context->error = error;
750 } else {
751 Time::Timestamp timeout = Time::Infinity;
752 context->error = context->buffer->write('b', timeout);
753 context->succeeded = context->error == RingBuffer<char>::NoError ? 1 : 0;
754 }
755
756 context->returned += 1;
757 return 0;
758}
759
760bool runRingBufferCloseCase(RingBufferCloseContext::Operation operation, bool fill) {
761 RingBuffer<char>* buffer = new RingBuffer<char>(1);
762 bool passed = true;
763 if (fill) {
764 passed &= buffer->write('a') == RingBuffer<char>::NoError;
765 }
766
767 RingBufferCloseContext context(buffer, operation);
768 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(),
769 runBlockingRingBufferOperation, &context, nullptr, false, true);
770 waiter->setName("hosted RingBuffer close waiter");
771 const bool queued = waitUntilQueued(waiter, Thread::CondWait);
772
773 delete buffer;
774 const bool joined = waiter->join();
775 return passed && context.entered == 1 && queued && joined && context.returned == 1 &&
776 context.succeeded == 0 && context.error == RingBuffer<char>::Closed;
777}
778
779bool ringBufferCloseAndDrain() {
780 bool passed = true;
781 passed &=
782 check(runRingBufferCloseCase(RingBufferCloseContext::Read, false), "ringbuffer-close-drain",
783 "close did not wake and drain a blocked reader with Closed");
784 passed &=
785 check(runRingBufferCloseCase(RingBufferCloseContext::Write, true), "ringbuffer-close-drain",
786 "close did not wake and drain a full-buffer writer with Closed");
787
788 RingBuffer<char>* buffer = new RingBuffer<char>(1);
789 Semaphore monitor(0, false);
790 buffer->monitor(&monitor);
791 delete buffer;
792 passed &= check(monitor.tryAcquire(), "ringbuffer-close-drain",
793 "close did not wake a registered readiness monitor");
794
795 if (passed) {
796 NOTICE("HOSTED-WAIT-TEST: PASS ringbuffer-close-drain");
797 }
798 return passed;
799}
800
801struct BufferCloseContext {
802 enum Operation {
803 Read,
804 Write,
805 CanRead,
806 CanWrite,
807 };
808
809 BufferCloseContext(Buffer<char>* buffer, Operation operation)
810 : buffer(buffer), operation(operation), entered(0), returned(0), result(1) {}
811
812 Buffer<char>* buffer;
813 Operation operation;
814 Atomic<size_t> entered;
815 Atomic<size_t> returned;
816 Atomic<size_t> result;
817};
818
819struct BufferTryWriteContext {
820 explicit BufferTryWriteContext(Buffer<char>* buffer)
821 : buffer(buffer), entered(0), returned(0), result(1) {}
822
823 Buffer<char>* buffer;
824 Atomic<size_t> entered;
825 Atomic<size_t> returned;
826 Atomic<size_t> result;
827};
828
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;
835 return 0;
836}
837
838int runBlockingBufferOperation(void* parameter) {
839 BufferCloseContext* context = reinterpret_cast<BufferCloseContext*>(parameter);
840 context->entered += 1;
841
842 char value = 'x';
843 switch (context->operation) {
844 case BufferCloseContext::Read:
845 context->result = context->buffer->read(&value, 1, true);
846 break;
847 case BufferCloseContext::Write:
848 context->result = context->buffer->write(&value, 1, true);
849 break;
850 case BufferCloseContext::CanRead:
851 context->result = context->buffer->canRead(true) ? 1 : 0;
852 break;
853 case BufferCloseContext::CanWrite:
854 context->result = context->buffer->canWrite(true) ? 1 : 0;
855 break;
856 }
857 context->returned += 1;
858 return 0;
859}
860
861bool runBufferCloseCase(BufferCloseContext::Operation operation, bool fill) {
862 Buffer<char>* buffer = new Buffer<char>(1);
863 bool passed = true;
864 if (fill) {
865 const char initial = 'a';
866 passed &= buffer->write(&initial, 1, false) == 1;
867 }
868
869 BufferCloseContext context(buffer, operation);
870 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), runBlockingBufferOperation,
871 &context, nullptr, false, true);
872 waiter->setName("hosted Buffer close waiter");
873 const bool queued = waitUntilQueued(waiter, Thread::CondWait);
874
875 delete buffer;
876 const bool joined = waiter->join();
877 return passed && context.entered == 1 && queued && joined && context.returned == 1 &&
878 context.result == 0;
879}
880
881bool bufferCloseAndDrain() {
882 bool passed = true;
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");
891
892 if (passed) {
893 NOTICE("HOSTED-WAIT-TEST: PASS buffer-close-drain");
894 }
895 return passed;
896}
897
898bool bufferTryWriteDoesNotWaitForLock() {
899 Buffer<char> buffer(2);
900 BufferTryWriteContext context(&buffer);
901 buffer.acquireHostedOperationLock();
902 Thread* writer = new Thread(Scheduler::instance().getKernelProcess(), runBufferTryWrite, &context,
903 nullptr, false, true);
904 writer->setName("hosted Buffer try-write contention");
905
906 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
907 while (!context.returned && Time::getTicks() < deadline) {
909 }
910 const bool returnedWhileLocked = context.entered == 1 && context.returned == 1;
911 buffer.releaseHostedOperationLock();
912 const bool joined = writer->join();
913
914 const bool passed =
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");
917 if (passed) {
918 NOTICE("HOSTED-WAIT-TEST: PASS buffer-try-write");
919 }
920 return passed;
921}
922
923bool runBufferTerminalCase(BufferCloseContext::Operation operation, bool fill) {
924 Buffer<char>* buffer = new Buffer<char>(1);
925 bool passed = true;
926 if (fill) {
927 const char initial = 'a';
928 passed &= buffer->write(&initial, 1, false) == 1;
929 }
930
931 BufferCloseContext context(buffer, operation);
932 Thread* waiter = new Thread(Scheduler::instance().getKernelProcess(), runBlockingBufferOperation,
933 &context, nullptr, false, true);
934 waiter->setName("hosted Buffer terminal waiter");
935 const bool queued = waitUntilQueued(waiter, Thread::CondWait);
936
937 waiter->setUnwindState(Thread::TerminateThread);
938 const bool joined = waiter->join();
939 delete buffer;
940
941 return passed && context.entered == 1 && queued && joined && context.returned == 1 &&
942 context.result == 0;
943}
944
945bool bufferTerminalDrain() {
946 bool passed = true;
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");
955
956 if (passed) {
957 NOTICE("HOSTED-WAIT-TEST: PASS buffer-terminal-drain");
958 }
959 return passed;
960}
961
962bool terminalOperationAdmissionScope() {
963 bool passed = true;
964
965 Buffer<char>* buffer = new Buffer<char>(1);
966 BufferCloseContext bufferContext(buffer, BufferCloseContext::Read);
967 buffer->acquireHostedOperationLock();
968 Thread* bufferWaiter =
969 new Thread(Scheduler::instance().getKernelProcess(), runBlockingBufferOperation,
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) {
976 }
977 const bool bufferStayedBlocked = bufferContext.returned == 0;
978 buffer->releaseHostedOperationLock();
979 const bool bufferJoined = bufferWaiter->join();
980 const bool bufferRetired = buffer->getHostedActiveOperationCount() == 0;
981 delete buffer;
982
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");
987
988 constexpr size_t BufferSize = 512;
989 MemoryPool* pool = new MemoryPool("hosted-terminal-operation-admission");
990 if (!pool->initialise(1, BufferSize)) {
991 delete pool;
992 return check(false, "terminal-operation-admission",
993 "the MemoryPool admission fixture could not initialise");
994 }
995
996 const size_t bufferCount = PhysicalMemoryManager::getPageSize() / BufferSize;
997 for (size_t i = 0; i < bufferCount; ++i) {
998 passed &= pool->allocateNow() != 0;
999 }
1000
1001 MemoryPoolContext poolContext(pool);
1002 pool->acquireHostedOperationLock();
1003 Thread* poolWaiter = new Thread(Scheduler::instance().getKernelProcess(),
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) {
1010 }
1011 const bool poolStayedBlocked = poolContext.returned == 0;
1012 pool->releaseHostedOperationLock();
1013 const bool poolJoined = poolWaiter->join();
1014 const bool poolRetired = pool->getHostedActiveOperationCount() == 0;
1015 delete pool;
1016
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");
1021
1022 if (passed) {
1023 NOTICE("HOSTED-WAIT-TEST: PASS terminal-operation-admission");
1024 }
1025 return passed;
1026}
1027
1028struct TerminalTimeoutContext {
1029 TerminalTimeoutContext(Semaphore* semaphore, ConditionVariable* condition, Mutex* mutex)
1030 : semaphore(semaphore),
1031 condition(condition),
1032 mutex(mutex),
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),
1042 delayEntered(0),
1043 delayReturned(0),
1044 delayInterrupted(0),
1045 delayDestructed(0) {}
1046
1047 Semaphore* semaphore;
1048 ConditionVariable* condition;
1049 Mutex* mutex;
1050 Atomic<size_t> semaphoreEntered;
1051 Atomic<size_t> semaphoreReturned;
1052 Atomic<size_t> semaphoreInterrupted;
1053 Atomic<size_t> semaphoreDestructed;
1054 Atomic<size_t> conditionEntered;
1055 Atomic<size_t> conditionReturned;
1056 Atomic<size_t> conditionTerminal;
1057 Atomic<size_t> conditionMutexHeld;
1058 Atomic<size_t> conditionDestructed;
1059 Atomic<size_t> delayEntered;
1060 Atomic<size_t> delayReturned;
1061 Atomic<size_t> delayInterrupted;
1062 Atomic<size_t> delayDestructed;
1063};
1064
1065class TerminalTimeoutStackCanary {
1066 public:
1067 explicit TerminalTimeoutStackCanary(Atomic<size_t>* destructed) : m_Destructed(destructed) {}
1068
1069 ~TerminalTimeoutStackCanary() {
1070 *m_Destructed += 1;
1071 }
1072
1073 private:
1074 Atomic<size_t>* m_Destructed;
1075};
1076
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;
1086 return 0;
1087}
1088
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();
1103 }
1104 return 0;
1105}
1106
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;
1113 return 0;
1114}
1115
1116bool terminalTimeoutCleanup() {
1117 Semaphore semaphore(0);
1118 ConditionVariable condition;
1119 Mutex mutex;
1120 TerminalTimeoutContext context(&semaphore, &condition, &mutex);
1121 Process* process = Scheduler::instance().getKernelProcess();
1122
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);
1128
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);
1134
1135 const bool semaphoreQueued = waitUntilQueued(semaphoreWaiter, Thread::SemWait);
1136 const bool conditionQueued = waitUntilQueued(conditionWaiter, Thread::CondWait);
1137 const bool delayQueued = waitUntilQueued(delayWaiter, Thread::EventWait);
1138
1139 semaphoreWaiter->setUnwindState(Thread::TerminateThread);
1140 conditionWaiter->setUnwindState(Thread::TerminateThread);
1142
1143 const bool semaphoreJoined = semaphoreWaiter->join();
1144 const bool conditionJoined = conditionWaiter->join();
1145 const bool delayJoined = delayWaiter->join();
1146
1147 bool passed = true;
1148 passed &= check(semaphoreQueued && conditionQueued && delayQueued, "terminal-timeout-cleanup",
1149 "not every timed waiter published its wait");
1150 passed &=
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");
1160 passed &=
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");
1166
1167 // Advance beyond every original deadline. A stale timer target would now
1168 // deliver into a joined and freed Thread; hosted ASan makes that fatal.
1169 while (Time::getTicks() < deadline) {
1171 }
1172
1173 if (passed) {
1174 NOTICE("HOSTED-WAIT-TEST: PASS terminal-timeout-cleanup");
1175 }
1176 return passed;
1177}
1178} // namespace
1179
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();
1188}
1189
1190bool runHostedRingBufferRegressions() {
1191 return ringBufferCloseAndDrain();
1192}
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:111
size_t getDataSize()
Definition Buffer.cc:484
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)
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:49
Utility class to provide a ring buffer.
Definition RingBuffer.h:62
bool monitor(Thread *pThread, Event *pEvent)
monitor - add a new Event to be fired when something happens
Definition RingBuffer.h:529
Error write(const T &obj, Time::Timestamp &timeout)
write - write a byte to the ring buffer.
Definition RingBuffer.h:173
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:226
void setUnwindState(UnwindType ut)
Definition Thread.cc:3628
@ TerminateThread
Exit only this thread during Process exit.
Definition Thread.h:515
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3184
bool join()
Definition Thread.cc:2767
Status getStatus() const
Definition Thread.h:431
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:570
A vector / dynamic array.
Definition Vector.h:33
void remove(const String &key)
Definition RadixTree.h:514
MUST_USE_RESULT bool lookup(const String &key, T &value) const
Definition RadixTree.h:463
void insert(const String &key, const T &value)
Definition RadixTree.h:362
void pushBack(const T &value)
Definition Vector.h:275
size_t count() const
Definition Vector.h:270