8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/TargetInfo.h"
11#include "pedigree/kernel/process/PerProcessorScheduler.h"
12#include "pedigree/kernel/process/RoundRobin.h"
13#include "pedigree/kernel/process/Scheduler.h"
14#include "pedigree/kernel/process/Semaphore.h"
15#include "pedigree/kernel/process/SignalEvent.h"
16#include "pedigree/kernel/process/Thread.h"
17#include "pedigree/kernel/process/WaitQueue.h"
18#include "pedigree/kernel/processor/Processor.h"
19#include "pedigree/kernel/processor/ProcessorInformation.h"
20#include "pedigree/kernel/time/Time.h"
21#include "pedigree/kernel/utilities/RequestQueue.h"
23#include "modules/drivers/common/ata/AtaController.h"
26constexpr size_t RequestQueueSignalNumber = 11;
27constexpr size_t WaitAttempts = 10000;
31void hostedRequestQueueSignalHandler(
size_t) {
32 g_RequestQueueSignalCalls += 1;
35bool check(
bool condition,
const char* detail) {
40 ERROR(
"HOSTED-WAIT-TEST: FAIL requestqueue-lifecycle: " << detail);
44bool waitUntilQueued(
Thread* thread,
size_t debugState) {
45 for (
size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
47 uintptr_t debugAddress = 0;
75 :
RequestQueue(MakeConstantString(
"Hosted wait regression")),
78 queuedCancellations(0),
82 selfHaltRejections(0),
84 cancelHaltRejections(0),
85 cancelResumeRejections(0),
86 cancelPublicationRejections(0),
87 cancelPreallocatedRejections(0),
90 matchEqualPayload(false) {}
92 ~HostedRequestQueue()
override {
108 PreallocatedRequest cancelPreallocated;
109 uint64_t recorded[16] = {};
113 size_t requestWaiterCount() {
114 return m_RequestQueueWaiters.waiterCount();
118 auto guard = m_RequestQueueWaiters.acquire();
122 void setMaxAsyncRequests(
size_t maximum) {
123 m_nMaxAsyncRequests = maximum;
126 void setMatchEqualPayload(
bool match) {
127 matchEqualPayload = match;
131 uint64_t
executeRequest(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t, uint64_t, uint64_t,
132 uint64_t, uint64_t)
override {
137 case SelfSubmitInner:
140 return addRequest(0, SelfSubmitInner, p2, p3);
143 selfAsyncAccepted += 1;
146 selfAsyncAccepted += 1;
153 selfHaltRejections += 1;
158 case PreallocatedHold:
160 return releaseHold.
acquire() ? p2 : 0;
163 const size_t index = (recordedCount += 1) - 1;
164 if (index < (
sizeof(recorded) /
sizeof(recorded[0]))) {
165 recorded[index] = p2;
169 if (p1 == RecordHold) {
184 if (request.p1 == CancelQueued || request.p1 == CancelLifecycleProbe) {
185 queuedCancellations += 1;
187 if (request.p1 == CancelLifecycleProbe) {
189 cancelHaltRejections += 1;
192 cancelResumeRejections += 1;
194 if (
addRequest(0, CancelLifecycleProbe) == 0) {
195 cancelPublicationRejections += 1;
198 cancelPublicationRejections += 1;
201 cancelPublicationRejections += 1;
204 PreallocatedPublishResult::QueueStopped &&
205 cancelPreallocated.isAvailable()) {
206 cancelPreallocatedRejections += 1;
213 return matchEqualPayload && a.p1 == b.p1 && a.p2 == b.p2 && a.p3 == b.p3 && a.p4 == b.p4 &&
214 a.p5 == b.p5 && a.p6 == b.p6 && a.p7 == b.p7 && a.p8 == b.p8;
218 bool matchEqualPayload;
228 HostedAtaRequestQueue()
229 :
RequestQueue(MakeConstantString(
"Hosted ATA request identity")),
234 ~HostedAtaRequestQueue()
override {
243 uint64_t
executeRequest(uint64_t, uint64_t, uint64_t location, uint64_t, uint64_t, uint64_t,
244 uint64_t, uint64_t)
override {
245 const size_t execution = executions += 1;
246 if (execution == 1) {
256 return AtaRequestIdentityProbe::canCoalesceCanonicalRequests(a.p1, a.p2, a.p3, b.p1, b.p2,
261bool ataRequestIdentityRegression() {
262 constexpr uint64_t DiskA = 0xA7A;
263 constexpr uint64_t DiskB = 0xB7B;
264 constexpr uint64_t FirstPartitionPage = 0x600;
266 constexpr uint64_t SecondPartitionExtent = FirstPartitionPage + (128 * 1024);
268 HostedAtaRequestQueue writeQueue;
269 writeQueue.initialise();
270 bool passed = writeQueue.addAsyncRequest(0, SCSI_REQUEST_WRITE, DiskA, FirstPartitionPage) == 1 &&
271 writeQueue.firstStarted.acquire();
272 const bool repeatedWriteAccepted =
273 writeQueue.addAsyncRequest(0, SCSI_REQUEST_WRITE, DiskA, FirstPartitionPage) == 1;
274 const bool distinctWritePageAccepted =
275 writeQueue.addAsyncRequest(0, SCSI_REQUEST_WRITE, DiskA, SecondPartitionPage) == 1;
276 writeQueue.releaseFirst.release();
277 const bool writesDrained = writeQueue.drain();
278 writeQueue.destroy();
279 passed &= repeatedWriteAccepted && distinctWritePageAccepted && writesDrained &&
280 writeQueue.executions == 3;
282 HostedAtaRequestQueue syncQueue;
283 syncQueue.initialise();
284 passed &= syncQueue.addAsyncRequest(0, SCSI_REQUEST_SYNC, DiskA, FirstPartitionPage) == 1 &&
285 syncQueue.firstStarted.acquire();
286 const bool repeatedSyncAccepted =
287 syncQueue.addAsyncRequest(0, SCSI_REQUEST_SYNC, DiskA, FirstPartitionPage) == 1;
288 syncQueue.releaseFirst.release();
289 const bool syncsDrained = syncQueue.drain();
291 passed &= repeatedSyncAccepted && syncsDrained && syncQueue.executions == 2;
293 HostedAtaRequestQueue readQueue;
294 readQueue.initialise();
295 passed &= readQueue.addAsyncRequest(0, SCSI_REQUEST_READ, DiskA, FirstPartitionPage) == 1 &&
296 readQueue.firstStarted.acquire();
297 const bool repeatedReadCoalesced =
298 readQueue.addAsyncRequest(0, SCSI_REQUEST_READ, DiskA, FirstPartitionPage) == 0;
299 const bool distinctExtentAccepted =
300 readQueue.addAsyncRequest(0, SCSI_REQUEST_READ, DiskA, SecondPartitionExtent) == 1;
301 const bool differentTypeAccepted =
302 readQueue.addAsyncRequest(0, SCSI_REQUEST_WRITE, DiskA, FirstPartitionPage) == 1;
303 const bool differentDiskAccepted =
304 readQueue.addAsyncRequest(0, SCSI_REQUEST_READ, DiskB, FirstPartitionPage) == 1;
305 readQueue.releaseFirst.release();
306 const bool readsDrained = readQueue.drain();
308 passed &= repeatedReadCoalesced && distinctExtentAccepted && differentTypeAccepted &&
309 differentDiskAccepted && readsDrained && readQueue.executions == 4;
311 if (!check(passed,
"ATA request identity dropped write or sync work, or merged distinct reads")) {
315 NOTICE(
"HOSTED-WAIT-TEST: PASS ata-request-identity");
319bool watchdogProgressRegression() {
320 HostedRequestQueue queue;
323 bool passed = queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
324 queue.holdStarted.acquire();
325 passed &= queue.addAsyncRequest(0, HostedRequestQueue::Sum, 20, 22) == 1;
326 const RequestQueue::OverrunStatus transient = queue.sampleOverrunForTest();
328 queue.releaseHold.release();
329 const bool firstDrained = queue.drain();
330 const RequestQueue::OverrunStatus cleared = queue.sampleOverrunForTest();
332 passed &= queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
333 queue.holdStarted.acquire();
334 passed &= queue.addAsyncRequest(0, HostedRequestQueue::Sum, 19, 23) == 1;
335 const RequestQueue::OverrunStatus baseline = queue.sampleOverrunForTest();
336 const RequestQueue::OverrunStatus stalled = queue.sampleOverrunForTest();
338 queue.releaseHold.release();
339 const bool secondDrained = queue.drain();
342 passed &= transient == RequestQueue::OverrunStatus::Armed && firstDrained &&
343 cleared == RequestQueue::OverrunStatus::Clear &&
344 baseline == RequestQueue::OverrunStatus::Armed &&
345 stalled == RequestQueue::OverrunStatus::Stalled && secondDrained;
347 "the watchdog confused transient admission with a full "
348 "no-progress interval")) {
352 NOTICE(
"HOSTED-WAIT-TEST: PASS requestqueue-watchdog-progress");
356bool recursiveAsyncPublicationRegression() {
357 HostedRequestQueue queue;
358 queue.setMaxAsyncRequests(1);
359 queue.setMatchEqualPayload(
true);
362 const bool accepted = queue.publishAsyncRequest(0, HostedRequestQueue::SelfAsync, 20, 22);
363 const bool drained = queue.drain();
366 const bool passed = accepted && drained && queue.selfAsyncAccepted == 2 &&
367 queue.executions == 3 && queue.comparisons == 0 &&
368 !queue.publishAsyncRequest(0, HostedRequestQueue::Sum, 20, 22);
369 if (!check(passed,
"recursive async publication stalled, deduplicated, or escaped destroy")) {
373 NOTICE(
"HOSTED-WAIT-TEST: PASS requestqueue-recursive-async-publication");
377struct HeldRequestContext {
378 explicit HeldRequestContext(HostedRequestQueue* queue)
379 : queue(queue), finished(0), result(~0ULL) {}
381 HostedRequestQueue* queue;
386struct CompletionRequeueContext {
387 explicit CompletionRequeueContext(
Thread* target)
388 : target(target), requeues(0), hookFailures(0) {}
395CompletionRequeueContext* g_CompletionRequeueContext =
nullptr;
399 CompletionRequeueContext* context = g_CompletionRequeueContext;
400 if (!context || thread != context->target) {
404 if (!queue || channel.owner || channel.value || debugState != Thread::CondWait) {
405 context->hookFailures += 1;
407 context->requeues += 1;
410int submitHeldRequest(
void* parameter) {
411 HeldRequestContext* context =
reinterpret_cast<HeldRequestContext*
>(parameter);
412 context->result = context->queue->addRequest(0, HostedRequestQueue::HoldWorker, 42);
413 context->finished += 1;
417bool completionBarrierInterruption(HostedRequestQueue& queue,
bool terminal) {
418 HeldRequestContext request(&queue);
420 nullptr,
false,
true);
422 caller->setName(
"hosted RequestQueue terminal caller");
424 caller->setName(
"hosted RequestQueue signal caller");
427 const bool workerHeld = queue.holdStarted.acquire();
428 const bool initiallyQueued = waitUntilQueued(caller, Thread::CondWait);
430 CompletionRequeueContext requeue(caller);
431 g_CompletionRequeueContext = &requeue;
432 WaitQueue::setBeforeBlockHook(completionRequeueHook);
434 bool injected =
true;
438 g_RequestQueueSignalCalls = 0;
440 new SignalEvent(
reinterpret_cast<uintptr_t
>(&hostedRequestQueueSignalHandler),
441 RequestQueueSignalNumber, ~0UL, 0,
true,
true);
448 for (
size_t attempt = 0; attempt < WaitAttempts && !requeue.requeues && !request.finished;
453 WaitQueue::setBeforeBlockHook(
nullptr);
454 g_CompletionRequeueContext =
nullptr;
456 const bool deferred = requeue.requeues == 1 && requeue.hookFailures == 0 && request.finished == 0;
457 const bool signalDelivered = terminal || g_RequestQueueSignalCalls == 1;
459 queue.releaseHold.release();
460 const bool joined = caller->
join();
462 return check(workerHeld && initiallyQueued && injected && deferred && signalDelivered && joined &&
463 request.finished == 1 && request.result == 42,
464 terminal ?
"terminal teardown escaped a synchronous request completion"
465 :
"a signal returned a synchronous request before completion");
468struct RequestQueueDestroyContext {
469 explicit RequestQueueDestroyContext(HostedRequestQueue* queue) : queue(queue), finished(0) {}
471 HostedRequestQueue* queue;
475struct RequestQueueDrainContext {
476 explicit RequestQueueDrainContext(HostedRequestQueue* queue)
477 : queue(queue), finished(0), result(false) {}
479 HostedRequestQueue* queue;
484struct QueuedRequestContext {
485 explicit QueuedRequestContext(HostedRequestQueue* queue)
486 : queue(queue),
waiter(nullptr), published(0), hookFailures(0), finished(0), result(~0ULL) {}
488 HostedRequestQueue* queue;
496QueuedRequestContext* g_QueuedRequestContext =
nullptr;
500 QueuedRequestContext* context = g_QueuedRequestContext;
505 if (thread != context->waiter) {
509 if (!queue || channel.owner || channel.value || debugState != Thread::CondWait) {
510 context->hookFailures += 1;
512 context->published += 1;
515int submitQueuedRequest(
void* parameter) {
516 QueuedRequestContext* context =
reinterpret_cast<QueuedRequestContext*
>(parameter);
518 context->result = context->queue->addRequest(0, HostedRequestQueue::CancelLifecycleProbe);
519 context->finished += 1;
523int destroyRequestQueue(
void* parameter) {
524 RequestQueueDestroyContext* context =
reinterpret_cast<RequestQueueDestroyContext*
>(parameter);
525 context->queue->destroy();
526 context->finished += 1;
530int drainRequestQueue(
void* parameter) {
531 RequestQueueDrainContext* context =
reinterpret_cast<RequestQueueDrainContext*
>(parameter);
532 context->result = context->queue->drain();
533 context->finished += 1;
537struct RequestQueueHaltContext {
538 explicit RequestQueueHaltContext(HostedRequestQueue* queue)
539 : queue(queue), finished(0), result(false) {}
541 HostedRequestQueue* queue;
546int haltRequestQueue(
void* parameter) {
547 auto* context =
reinterpret_cast<RequestQueueHaltContext*
>(parameter);
548 context->result = context->queue->halt() ? 1 : 0;
549 context->finished += 1;
553struct PublicationPauseContext {
554 PublicationPauseContext() : calls(0), failures(0), entered(0), release(0) {}
562void pauseFirstPublication(
void* parameter) {
563 auto* context =
reinterpret_cast<PublicationPauseContext*
>(parameter);
564 if ((context->calls += 1) != 1) {
568 context->entered.release();
569 if (!context->release.acquireForCompletion()) {
570 context->failures += 1;
574struct PreallocatedPublicationContext {
575 PreallocatedPublicationContext(HostedRequestQueue* queue,
577 uint64_t operation, uint64_t value)
581 operation(operation),
583 result(
RequestQueue::PreallocatedPublishResult::QueueStopped),
586 HostedRequestQueue* queue;
591 RequestQueue::PreallocatedPublishResult result;
595int publishPreallocatedRequest(
void* parameter) {
596 auto* context =
reinterpret_cast<PreallocatedPublicationContext*
>(parameter);
597 context->result = context->queue->publishPreallocated(*context->request, context->priority,
598 context->operation, context->value);
599 context->finished += 1;
603struct AsyncPublicationContext {
604 AsyncPublicationContext(HostedRequestQueue* queue,
size_t priority, uint64_t operation,
608 operation(operation),
613 HostedRequestQueue* queue;
621int publishAsyncRequest(
void* parameter) {
622 auto* context =
reinterpret_cast<AsyncPublicationContext*
>(parameter);
624 context->queue->addAsyncRequest(context->priority, context->operation, context->value);
625 context->finished += 1;
629struct PreallocatedReleaseContext {
630 PreallocatedReleaseContext(HostedRequestQueue* queue,
bool requeueOnce,
bool holdFirst =
false,
631 bool probeOrdinaryPublication =
false)
634 requeueOnce(requeueOnce),
635 holdFirst(holdFirst),
636 probeOrdinaryPublication(probeOrdinaryPublication),
639 ordinaryPublicationRejections(0),
642 releaseCallback(0) {}
644 HostedRequestQueue* queue;
648 bool probeOrdinaryPublication;
657void preallocatedRequestReleased(
void* parameter) {
658 auto* context =
reinterpret_cast<PreallocatedReleaseContext*
>(parameter);
659 const size_t callback = (context->callbacks += 1);
660 if (!context->request || context->request->isAvailable()) {
661 context->failures += 1;
664 if (context->holdFirst && callback == 1) {
665 context->callbackEntered.release();
666 if (!context->releaseCallback.acquireForCompletion()) {
667 context->failures += 1;
672 if (context->probeOrdinaryPublication) {
674 if (context->queue->publishPreallocated(ordinary, 0, HostedRequestQueue::Sum, 20, 22) ==
675 RequestQueue::PreallocatedPublishResult::QueueStopped &&
676 ordinary.isAvailable()) {
677 context->ordinaryPublicationRejections += 1;
679 context->failures += 1;
683 if (context->request && context->requeueOnce && context->requeues.compareAndSwap(0, 1)) {
684 if (context->queue->republishPreallocatedWhileReleasing(*context->request, 0,
685 HostedRequestQueue::Sum, 20, 22) !=
686 RequestQueue::PreallocatedPublishResult::Accepted) {
687 context->failures += 1;
692struct ReleaseDestroyReentryContext {
693 explicit ReleaseDestroyReentryContext(HostedRequestQueue* queue)
699 allocationRejections(0),
700 preallocatedRejections(0),
704 HostedRequestQueue* queue;
716void releaseDuringDestroy(
void* parameter) {
717 auto* context =
reinterpret_cast<ReleaseDestroyReentryContext*
>(parameter);
718 context->callbacks += 1;
719 context->entered.release();
720 if (!context->release.acquireForCompletion()) {
721 context->failures += 1;
725 if (!context->queue->halt()) {
726 context->haltRejections += 1;
728 if (!context->queue->resume()) {
729 context->resumeRejections += 1;
731 if (context->queue->addRequest(0, HostedRequestQueue::Sum, 20, 22) == 0 &&
732 context->queue->addAsyncRequest(0, HostedRequestQueue::Sum, 20, 22) == 0) {
733 context->allocationRejections += 1;
735 if (context->queue->publishPreallocated(context->ordinary, 0, HostedRequestQueue::Sum, 20, 22) ==
736 RequestQueue::PreallocatedPublishResult::QueueStopped &&
737 context->ordinary.isAvailable()) {
738 context->preallocatedRejections += 1;
742bool predicateDoorbellRegression() {
743 using Result = RequestQueue::PreallocatedPublishResult;
745 HostedRequestQueue queue;
751 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
752 const size_t waitersBefore = queue.requestWaiterCount();
755 const Result accepted = queue.publishPreallocated(request, 0, HostedRequestQueue::Sum, 20, 22);
756 const Result busy = queue.publishPreallocated(request, 0, HostedRequestQueue::Sum, 19, 23);
757 const bool doorbellPending = PerProcessorScheduler::currentIrqWorkDoorbellPendingForTest();
758 const bool deferred = queue.executions == 0;
759 const size_t waitersAfterPublication = queue.requestWaiterCount();
762 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
763 const bool drained = queue.drain();
764 const size_t executions = queue.executions;
765 const bool available = request.isAvailable();
769 check(waitersBefore == 0 && waitersAfterPublication == 0 && accepted == Result::Accepted &&
770 busy == Result::TokenBusy && doorbellPending && deferred && drained &&
771 executions == 1 && available,
772 "IF=0 publication touched a WaitQueue or escaped the predicate "
775 NOTICE(
"HOSTED-WAIT-TEST: PASS requestqueue-predicate-doorbell");
780bool preallocatedDuplicateDomainRegression() {
781 using Result = RequestQueue::PreallocatedPublishResult;
783 HostedRequestQueue queue;
785 queue.setMatchEqualPayload(
true);
788 bool passed = queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
789 queue.holdStarted.acquire();
793 const Result preallocatedAccepted =
794 queue.publishPreallocated(preallocated, 1, HostedRequestQueue::Record, 77, 88);
797 const size_t comparisonsBefore = queue.comparisons;
798 const bool allocatedAccepted = queue.addAsyncRequest(1, HostedRequestQueue::Record, 77, 88) == 1;
799 const size_t comparisonsAfter = queue.comparisons;
801 queue.releaseHold.release();
802 const bool drained = queue.drain();
803 const bool executedBoth =
804 queue.recordedCount == 2 && queue.recorded[0] == 77 && queue.recorded[1] == 77;
807 passed &= preallocatedAccepted == Result::Accepted && allocatedAccepted &&
808 comparisonsBefore == 0 && comparisonsAfter == 0 && drained && executedBoth &&
809 preallocated.isAvailable() && queue.cancellations == 0 && queue.executions == 3;
810 passed = check(passed,
811 "allocation-backed duplicate comparison entered the preallocated "
813 "coalescing domain");
816 "HOSTED-WAIT-TEST: PASS "
817 "requestqueue-preallocated-duplicate-domain");
822bool intakeOrderingRegression() {
823 using Result = RequestQueue::PreallocatedPublishResult;
825 HostedRequestQueue queue;
830 bool passed = queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
831 queue.holdStarted.acquire();
832 passed &= queue.addAsyncRequest(3, HostedRequestQueue::Record, 30) == 1;
833 passed &= queue.addAsyncRequest(1, HostedRequestQueue::Record, 10) == 1;
837 const Result firstAccepted =
838 queue.publishPreallocated(firstPreallocated, 1, HostedRequestQueue::Record, 11);
841 passed &= queue.addAsyncRequest(1, HostedRequestQueue::Record, 12) == 1;
843 const Result secondAccepted =
844 queue.publishPreallocated(secondPreallocated, 1, HostedRequestQueue::Record, 13);
847 passed &= queue.addAsyncRequest(2, HostedRequestQueue::Record, 20) == 1;
848 passed &= queue.addAsyncRequest(0, HostedRequestQueue::Record, 1) == 1;
850 queue.releaseHold.release();
851 const bool drained = queue.drain();
852 const uint64_t expected[] = {1, 10, 11, 12, 13, 20, 30};
853 bool orderMatches = queue.recordedCount == (
sizeof(expected) /
sizeof(expected[0]));
855 for (
size_t i = 0; i < (
sizeof(expected) /
sizeof(expected[0])); ++i) {
856 orderMatches &= queue.recorded[i] == expected[i];
860 passed &= firstAccepted == Result::Accepted && secondAccepted == Result::Accepted && drained &&
861 orderMatches && queue.recordFailures == 0 && queue.executions == 8 &&
862 firstPreallocated.isAvailable() && secondPreallocated.isAvailable();
865 passed = check(passed,
"the shared intake lost same-priority FIFO or strict priority order");
867 NOTICE(
"HOSTED-WAIT-TEST: PASS requestqueue-intake-ordering");
872bool haltRetentionRegression() {
873 using Result = RequestQueue::PreallocatedPublishResult;
875 HostedRequestQueue queue;
880 bool passed = queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
881 queue.holdStarted.acquire();
882 passed &= queue.addAsyncRequest(1, HostedRequestQueue::Record, 40) == 1;
886 const Result retainedResult =
887 queue.publishPreallocated(retained, 1, HostedRequestQueue::Record, 41);
890 RequestQueueHaltContext haltContext(&queue);
892 &haltContext,
nullptr,
false,
true);
893 halter->setName(
"hosted RequestQueue halt retention regression");
895 bool stopping =
false;
896 for (
size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
897 stopping = queue.getLifecycleState() == RequestQueue::LifecycleState::Stopping;
904 Result rejectedResult = Result::Accepted;
907 rejectedResult = queue.publishPreallocated(rejected, 1, HostedRequestQueue::Record, 42);
910 const bool retainedBeforeRelease = queue.recordedCount == 0 && !retained.isAvailable();
911 queue.releaseHold.release();
913 const bool haltFinished = haltContext.finished == 1;
914 const bool haltSucceeded = haltContext.result == 1;
915 const bool stopped = queue.getLifecycleState() == RequestQueue::LifecycleState::Stopped;
916 const bool retainedWhileStopped = queue.recordedCount == 0 && !retained.isAvailable();
918 const bool resumed = queue.resume();
919 const bool drained = resumed && queue.drain();
920 const bool resumedInOrder =
921 queue.recordedCount == 2 && queue.recorded[0] == 40 && queue.recorded[1] == 41;
924 passed &= check(retainedResult == Result::Accepted,
925 "halt retention setup did not admit preallocated work");
926 passed &= check(stopping,
"halt did not publish the Stopping lifecycle state");
927 passed &= check(rejectedResult == Result::QueueStopped && rejected.isAvailable(),
928 "halt did not close preallocated admission");
929 passed &= check(retainedBeforeRelease,
930 "accepted work ran or was released before the active worker exited");
931 passed &= check(halterJoined,
"halt regression thread could not be joined");
932 passed &= check(haltFinished,
"halt regression thread did not return exactly once");
933 passed &= check(haltSucceeded,
"halt rejected its non-worker caller");
934 passed &= check(stopped,
"successful halt did not publish Stopped");
935 passed &= check(retainedWhileStopped,
936 "halt executed or released accepted work instead of retaining it");
937 passed &= check(resumed,
"halted queue rejected resume");
938 passed &= check(drained,
"resumed queue did not drain retained work");
940 check(resumedInOrder,
"resumed queue did not execute retained work in publication order");
941 passed &= check(retained.isAvailable(),
"resumed preallocated work did not release its token");
942 passed &= check(queue.recordFailures == 0 && queue.executions == 3,
943 "halt retention executed an unexpected request set");
945 NOTICE(
"HOSTED-WAIT-TEST: PASS requestqueue-halt-retention");
950bool rejectedPublisherDestroyWaitRegression() {
951 using Result = RequestQueue::PreallocatedPublishResult;
953 HostedRequestQueue queue;
955 PublicationPauseContext pause;
956 queue.setAfterPreallocatedAdmissionHookForTest(pauseFirstPublication, &pause);
958 PreallocatedPublicationContext publication(&queue, &request, 1, HostedRequestQueue::Record, 16);
960 publishPreallocatedRequest, &publication,
nullptr,
false,
true);
961 publisher->setName(
"hosted RequestQueue rejected publication pause");
962 const bool paused = pause.entered.acquire();
964 RequestQueueDestroyContext destroyContext(&queue);
966 &destroyContext,
nullptr,
false,
true);
967 destroyer->setName(
"hosted RequestQueue stopped publication drain regression");
969 bool drainWaitObserved =
false;
970 for (
size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
971 drainWaitObserved = queue.publisherDrainRetriesForTest() != 0;
972 if (drainWaitObserved) {
978 const bool destroyWaited = drainWaitObserved && !publication.finished &&
979 !destroyContext.finished && request.isAvailable();
980 pause.release.release();
981 const bool publisherJoined = publisher->
join();
982 const bool destroyerJoined = destroyer->
join();
983 queue.setAfterPreallocatedAdmissionHookForTest(
nullptr,
nullptr);
985 const bool passed = check(
986 paused && destroyWaited && publisherJoined && destroyerJoined && publication.finished == 1 &&
987 publication.result == Result::QueueStopped && destroyContext.finished == 1 &&
988 request.isAvailable() && pause.calls == 1 && pause.failures == 0 &&
989 queue.executions == 0 && queue.cancellations == 0 && queue.recordedCount == 0,
990 "a never-started queue outran a rejected publisher in its closed-gate "
994 "HOSTED-WAIT-TEST: PASS "
995 "requestqueue-rejected-publication-close-wait");
1000bool preallocatedAdmissionCloseWaitRegression() {
1001 using Result = RequestQueue::PreallocatedPublishResult;
1003 HostedRequestQueue queue;
1005 PublicationPauseContext pause;
1007 queue.setAfterPreallocatedAdmissionHookForTest(pauseFirstPublication, &pause);
1009 PreallocatedPublicationContext publication(&queue, &request, 1, HostedRequestQueue::Record, 17);
1011 publishPreallocatedRequest, &publication,
nullptr,
false,
true);
1012 publisher->setName(
"hosted RequestQueue admitted publication pause");
1013 const bool paused = pause.entered.acquire();
1015 RequestQueueDestroyContext destroyContext(&queue);
1017 &destroyContext,
nullptr,
false,
true);
1018 destroyer->setName(
"hosted RequestQueue publication drain regression");
1020 bool stopping =
false;
1021 for (
size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
1022 stopping = queue.getLifecycleState() == RequestQueue::LifecycleState::Stopping;
1029 const bool destroyWaited =
1030 stopping && !publication.finished && !destroyContext.finished && request.isAvailable();
1031 pause.release.release();
1032 const bool publisherJoined = publisher->
join();
1033 const bool destroyerJoined = destroyer->
join();
1034 queue.setAfterPreallocatedAdmissionHookForTest(
nullptr,
nullptr);
1036 const bool passed = check(
1037 paused && destroyWaited && publisherJoined && destroyerJoined && publication.finished == 1 &&
1038 publication.result == Result::Accepted && destroyContext.finished == 1 &&
1039 request.isAvailable() && pause.calls == 1 && pause.failures == 0 &&
1040 queue.executions == 0 && queue.cancellations == 1 && queue.recordedCount == 0,
1041 "destroy outran an admitted preallocated publisher paused before its "
1042 "closed-gate observation");
1045 "HOSTED-WAIT-TEST: PASS "
1046 "requestqueue-preallocated-admission-close-wait");
1051bool intakeTransientPublicationRegression() {
1052 using Result = RequestQueue::PreallocatedPublishResult;
1054 HostedRequestQueue queue;
1056 PublicationPauseContext pause;
1059 bool passed = queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
1060 queue.holdStarted.acquire();
1061 passed &= queue.addAsyncRequest(3, HostedRequestQueue::Record, 30) == 1;
1062 queue.setAfterIntakeExchangeHookForTest(pauseFirstPublication, &pause);
1064 PreallocatedPublicationContext first(&queue, &preallocated, 1, HostedRequestQueue::RecordHold,
1066 Thread* preallocatedPublisher =
1068 nullptr,
false,
true);
1069 preallocatedPublisher->setName(
"hosted RequestQueue incomplete MPSC publisher");
1070 const bool paused = pause.entered.acquire();
1072 AsyncPublicationContext second(&queue, 1, HostedRequestQueue::Record, 11);
1074 publishAsyncRequest, &second,
nullptr,
false,
true);
1075 ordinaryPublisher->setName(
"hosted RequestQueue transient duplicate scanner");
1077 queue.releaseHold.release();
1078 bool bothRetried =
false;
1079 for (
size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
1080 bothRetried = queue.workerTransientRetriesForTest() && queue.guardedTransientRetriesForTest();
1087 const bool noOvertake = queue.recordedCount == 0 && !first.finished && !second.finished;
1088 pause.release.release();
1089 const bool preallocatedJoined = preallocatedPublisher->
join();
1090 const bool firstHeld = queue.holdStarted.acquire();
1091 const bool ordinaryJoined = ordinaryPublisher->
join();
1092 queue.setAfterIntakeExchangeHookForTest(
nullptr,
nullptr);
1094 const bool firstStillLeads = firstHeld && queue.recordedCount == 1 && queue.recorded[0] == 10;
1095 queue.releaseHold.release();
1096 const bool drained = queue.drain();
1097 const uint64_t expected[] = {10, 11, 30};
1098 bool orderMatches = queue.recordedCount == (
sizeof(expected) /
sizeof(expected[0]));
1100 for (
size_t i = 0; i < (
sizeof(expected) /
sizeof(expected[0])); ++i) {
1101 orderMatches &= queue.recorded[i] == expected[i];
1106 passed &= paused && bothRetried && noOvertake && preallocatedJoined && ordinaryJoined &&
1107 first.result == Result::Accepted && first.finished == 1 && second.result == 1 &&
1108 second.finished == 1 && firstStillLeads && drained && orderMatches &&
1109 preallocated.isAvailable() && pause.calls == 2 && pause.failures == 0 &&
1110 queue.executions == 4 && queue.cancellations == 0 && queue.recordFailures == 0;
1111 passed = check(passed,
1112 "an incomplete MPSC link was treated as empty or lost priority/FIFO "
1116 "HOSTED-WAIT-TEST: PASS "
1117 "requestqueue-intake-transient-publication");
1122bool preallocatedRequestRegressions() {
1123 using Result = RequestQueue::PreallocatedPublishResult;
1127 HostedRequestQueue queue;
1128 PreallocatedReleaseContext releaseContext(&queue,
false,
true);
1130 releaseContext.request = &released;
1131 queue.setMaxAsyncRequests(0);
1134 passed &= check(queue.republishPreallocatedWhileReleasing(
1135 released, 0, HostedRequestQueue::Sum) == Result::TokenBusy &&
1136 released.isAvailable(),
1137 "an idle token accepted release-only republication");
1139 queue.publishPreallocated(released, 0, HostedRequestQueue::Sum, 19, 23) == Result::Accepted,
1140 "release-callback request was not admitted");
1141 passed &= check(releaseContext.callbackEntered.acquire(),
1142 "release callback did not enter its drain handoff");
1144 RequestQueueDrainContext drainContext(&queue);
1146 &drainContext,
nullptr,
false,
true);
1147 drainer->setName(
"hosted preallocated release drain regression");
1148 const bool drainWaitPublished = waitUntilQueued(drainer, Thread::CallbackDrain);
1149 passed &= check(drainWaitPublished && drainContext.finished == 0,
1150 "drain observed a transient empty queue during release");
1152 passed &= check(queue.republishPreallocatedWhileReleasing(released, 0, HostedRequestQueue::Sum,
1153 20, 22) == Result::Accepted &&
1154 !released.isAvailable(),
1155 "a producer could not win the final release handoff");
1156 releaseContext.releaseCallback.release();
1157 passed &= check(drainer->
join() && drainContext.result && released.isAvailable() &&
1158 releaseContext.callbacks == 2 && releaseContext.requeues == 0 &&
1159 releaseContext.failures == 0 && queue.executions == 2,
1160 "producer-assisted release work escaped drain");
1165 HostedRequestQueue queue;
1166 PreallocatedReleaseContext releaseContext(&queue,
true,
false,
true);
1168 releaseContext.request = &released;
1169 queue.setMaxAsyncRequests(0);
1172 passed &= check(queue.publishPreallocated(released, 0, HostedRequestQueue::Sum, 19, 23) ==
1174 queue.drain() && released.isAvailable() && releaseContext.callbacks == 2 &&
1175 releaseContext.requeues == 1 &&
1176 releaseContext.ordinaryPublicationRejections == 2 &&
1177 releaseContext.failures == 0 && queue.executions == 2,
1178 "release callback did not reject ordinary publication while "
1179 "preserving the explicit Releasing-token handoff");
1184 HostedRequestQueue queue;
1188 passed &= check(queue.publishPreallocated(request, 0, HostedRequestQueue::Sum, 20, 22) ==
1189 Result::QueueStopped &&
1190 request.isAvailable(),
1191 "a stopped queue claimed a preallocated token");
1192 passed &= check(queue.publishPreallocated(request, REQUEST_QUEUE_NUM_PRIORITIES,
1193 HostedRequestQueue::Sum) == Result::InvalidPriority &&
1194 request.isAvailable(),
1195 "an invalid preallocated priority claimed its token");
1199 const size_t comparisonsBefore = queue.comparisons;
1201 const Result accepted = queue.publishPreallocated(request, 0, HostedRequestQueue::Sum, 20, 22);
1202 const Result busy = queue.publishPreallocated(request, 0, HostedRequestQueue::Sum, 19, 23);
1203 const bool deferred = queue.executions == 0;
1205 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
1207 passed &= check(accepted == Result::Accepted && busy == Result::TokenBusy && deferred &&
1208 static_cast<size_t>(queue.comparisons) == comparisonsBefore,
1209 "preallocated IF=0 enqueue ran early or called virtual duplicate "
1211 passed &= check(queue.drain() && request.isAvailable() && queue.executions == 1,
1212 "executed preallocated work did not release its token");
1214 passed &= check(queue.publishPreallocated(request, 0, HostedRequestQueue::PreallocatedHold,
1215 42) == Result::Accepted &&
1216 queue.holdStarted.acquire(),
1217 "preallocated hold request was not admitted");
1218 passed &= check(queue.publishPreallocated(request, 0, HostedRequestQueue::PreallocatedHold,
1219 42) == Result::TokenBusy,
1220 "a published preallocated token was admitted twice");
1221 queue.setMaxAsyncRequests(1);
1222 const size_t cancellationsBeforeCapacity = queue.cancellations;
1224 queue.publishPreallocated(capacity, 0, HostedRequestQueue::Sum, 1, 2) == Result::Accepted &&
1225 !capacity.isAvailable() &&
1226 queue.addAsyncRequest(0, HostedRequestQueue::Sum, 3, 4) == 0 &&
1227 queue.cancellations == cancellationsBeforeCapacity + 1,
1228 "preallocated work depended on allocation backlog capacity");
1229 queue.setMaxAsyncRequests(256);
1230 queue.releaseHold.release();
1232 queue.drain() && request.isAvailable() && capacity.isAvailable() && queue.executions == 3,
1233 "over-capacity preallocated work did not drain exactly once");
1236 queue.publishPreallocated(capacity, 0, HostedRequestQueue::Sum, 19, 23) == Result::Accepted,
1237 "a drained preallocated token was not admitted again");
1238 passed &= check(queue.drain() && capacity.isAvailable() && queue.executions == 4,
1239 "a drained preallocated token could not be reused");
1244 HostedRequestQueue queue;
1245 PreallocatedReleaseContext releaseContext(&queue,
false);
1247 releaseContext.request = &cancelled;
1249 passed &= check(queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
1250 queue.holdStarted.acquire(),
1251 "teardown setup did not hold the worker");
1252 passed &= check(queue.publishPreallocated(cancelled, 0, HostedRequestQueue::CancelQueued) ==
1254 "teardown setup did not queue the preallocated token");
1256 RequestQueueDestroyContext destroyContext(&queue);
1258 &destroyContext,
nullptr,
false,
true);
1259 destroyer->setName(
"hosted preallocated request destroy regression");
1260 while (queue.getLifecycleState() != RequestQueue::LifecycleState::Stopping) {
1263 queue.releaseHold.release();
1265 passed &= check(destroyer->
join() && destroyContext.finished == 1 && cancelled.isAvailable() &&
1266 queue.cancellations == 1 && queue.queuedCancellations == 1 &&
1267 releaseContext.callbacks == 1 && releaseContext.failures == 0,
1268 "destroy did not cancel and release queued preallocated work");
1270 queue.publishPreallocated(cancelled, 0, HostedRequestQueue::Sum) == Result::QueueStopped &&
1271 cancelled.isAvailable(),
1272 "a stopped queue retained a reused preallocated token");
1276 NOTICE(
"HOSTED-WAIT-TEST: PASS requestqueue-preallocated-token");
1281bool releaseCallbackDestroyReentryRegression() {
1282 using Result = RequestQueue::PreallocatedPublishResult;
1284 HostedRequestQueue queue;
1285 ReleaseDestroyReentryContext releaseContext(&queue);
1289 const bool accepted =
1290 queue.publishPreallocated(released, 0, HostedRequestQueue::Sum, 20, 22) == Result::Accepted;
1291 const bool callbackEntered = accepted && releaseContext.entered.acquire();
1293 RequestQueueDestroyContext destroyContext(&queue);
1295 &destroyContext,
nullptr,
false,
true);
1296 destroyer->setName(
"hosted RequestQueue release callback destroy regression");
1298 bool stopping =
false;
1299 for (
size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
1300 stopping = queue.getLifecycleState() == RequestQueue::LifecycleState::Stopping;
1307 const bool destroyWaitedForRelease = stopping && !destroyContext.finished;
1308 releaseContext.release.release();
1312 check(callbackEntered && destroyWaitedForRelease && destroyerJoined &&
1313 destroyContext.finished == 1 && releaseContext.callbacks == 1 &&
1314 releaseContext.failures == 0 && releaseContext.haltRejections == 1 &&
1315 releaseContext.resumeRejections == 1 && releaseContext.allocationRejections == 1 &&
1316 releaseContext.preallocatedRejections == 1 && released.isAvailable() &&
1317 releaseContext.ordinary.isAvailable() && queue.executions == 1 &&
1318 queue.cancellations == 0 &&
1319 queue.getLifecycleState() == RequestQueue::LifecycleState::Destroyed,
1320 "a worker release callback re-entered queue lifecycle or publication "
1321 "while destroy waited for it");
1324 "HOSTED-WAIT-TEST: PASS "
1325 "requestqueue-release-callback-destroy-reentry");
1331bool runHostedRequestQueueRegressions() {
1333 if (!check(RoundRobin::runHostedIntrusiveQueueRegressions(current),
1334 "intrusive scheduler ready-queue invariants failed")) {
1337 NOTICE(
"HOSTED-WAIT-TEST: PASS scheduler-intrusive-ready-queue");
1339 if (!ataRequestIdentityRegression()) {
1343 if (!preallocatedRequestRegressions()) {
1347 if (!releaseCallbackDestroyReentryRegression()) {
1351 if (!predicateDoorbellRegression()) {
1355 if (!preallocatedDuplicateDomainRegression()) {
1359 if (!intakeOrderingRegression()) {
1363 if (!haltRetentionRegression()) {
1367 if (!rejectedPublisherDestroyWaitRegression()) {
1371 if (!preallocatedAdmissionCloseWaitRegression()) {
1375 if (!intakeTransientPublicationRegression()) {
1379 if (!watchdogProgressRegression()) {
1383 if (!recursiveAsyncPublicationRegression()) {
1388 HostedRequestQueue activeRequestQueue;
1389 activeRequestQueue.initialise();
1390 const bool accepted =
1391 activeRequestQueue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1;
1392 const bool active = accepted && activeRequestQueue.holdStarted.acquire();
1394 const Time::Timestamp deadline =
1395 Time::getTicks() + Time::Multiplier::Second + (100 * Time::Multiplier::Millisecond);
1396 while (Time::getTicks() < deadline) {
1400 activeRequestQueue.releaseHold.release();
1401 const bool drained = activeRequestQueue.drain();
1402 activeRequestQueue.destroy();
1403 if (!check(accepted && active && drained &&
1404 activeRequestQueue.executions ==
static_cast<size_t>(1),
1405 "an active request was misclassified as queued backlog")) {
1408 NOTICE(
"HOSTED-WAIT-TEST: PASS requestqueue-active-not-backlog");
1412 HostedRequestQueue ownedWorkerQueue;
1413 ownedWorkerQueue.initialise();
1414 Thread* idleWorker = ownedWorkerQueue.workerThread();
1418 const bool idleTerminalSafe =
1419 idleWorker && ownedWorkerQueue.addRequest(0, HostedRequestQueue::Sum, 20, 22) == 42;
1421 const bool halted = ownedWorkerQueue.halt();
1422 const bool resumed = ownedWorkerQueue.resume();
1423 Thread* activeWorker = ownedWorkerQueue.workerThread();
1425 ownedWorkerQueue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
1426 ownedWorkerQueue.holdStarted.acquire();
1430 ownedWorkerQueue.releaseHold.release();
1431 const bool activeTerminalSafe =
1432 held && activeWorker &&
1433 ownedWorkerQueue.addRequest(0, HostedRequestQueue::Sum, 19, 23) == 42;
1434 ownedWorkerQueue.destroy();
1436 if (!check(idleTerminalSafe && halted && resumed && activeTerminalSafe,
1437 "queue-owned worker termination orphaned work")) {
1440 NOTICE(
"HOSTED-WAIT-TEST: PASS requestqueue-worker-terminal-ownership");
1443 HostedRequestQueue queue;
1446 passed &= check(queue.addRequest(0, HostedRequestQueue::Sum, 1, 2) == 0,
1447 "a stopped queue accepted work before initialise");
1450 passed &= check(queue.getLifecycleState() == RequestQueue::LifecycleState::Accepting,
1451 "initialise did not start the worker");
1453 queue.addRequest(REQUEST_QUEUE_NUM_PRIORITIES - 1, HostedRequestQueue::Sum, 40, 2) == 42,
1454 "synchronous completion returned the wrong result");
1456 check(queue.addRequest(REQUEST_QUEUE_NUM_PRIORITIES, HostedRequestQueue::Sum, 1, 2) == 0,
1457 "an invalid priority was accepted");
1459 passed &= check(queue.halt(),
"halt rejected its non-worker caller");
1460 passed &= check(queue.getLifecycleState() == RequestQueue::LifecycleState::Stopped,
1461 "halt did not join the worker");
1462 passed &= check(queue.addRequest(0, HostedRequestQueue::Sum, 3, 4) == 0,
1463 "a halted queue accepted work");
1465 passed &= check(queue.resume(),
"resume rejected a halted queue");
1466 passed &= check(queue.addRequest(0, HostedRequestQueue::Sum, 20, 22) == 42,
1467 "the resumed worker did not complete synchronous work");
1468 passed &= check(queue.addRequest(0, HostedRequestQueue::SelfSubmit, 19, 23) == 42,
1469 "worker self-submission did not execute inline");
1470 passed &= check(queue.addRequest(0, HostedRequestQueue::SelfHalt, 77) == 77 &&
1471 queue.selfHaltRejections == 1 &&
1472 queue.getLifecycleState() == RequestQueue::LifecycleState::Accepting &&
1473 queue.addRequest(0, HostedRequestQueue::Sum, 20, 22) == 42,
1474 "worker self-halt did not reject without stopping the queue");
1476 passed &= completionBarrierInterruption(queue,
false);
1477 passed &= completionBarrierInterruption(queue,
true);
1479 passed &= check(queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1,
1480 "the drain test request was not accepted");
1481 passed &= check(queue.holdStarted.acquire(),
"the drain test worker did not enter its request");
1482 RequestQueueDrainContext drainContext(&queue);
1484 &drainContext,
nullptr,
false,
true);
1485 drainer->setName(
"hosted RequestQueue drain regression");
1486 const bool drainWaitPublished = waitUntilQueued(drainer, Thread::CallbackDrain);
1487 const bool drainBlocked = drainContext.finished == 0;
1488 queue.releaseHold.release();
1489 passed &= check(drainer->
join() && drainWaitPublished && drainBlocked &&
1490 drainContext.finished == 1 && drainContext.result,
1491 "drain returned before all published work completed");
1493 passed &= check(queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1,
1494 "the blocking worker request was not accepted");
1495 passed &= check(queue.holdStarted.acquire(),
"the worker did not enter its held request");
1497 QueuedRequestContext queuedContext(&queue);
1498 g_QueuedRequestContext = &queuedContext;
1499 WaitQueue::setBeforeBlockHook(queuedRequestWaitHook);
1501 &queuedContext,
nullptr,
false,
true);
1502 submitter->setName(
"hosted RequestQueue queued caller");
1504 while (queuedContext.published < 1) {
1507 WaitQueue::setBeforeBlockHook(
nullptr);
1508 g_QueuedRequestContext =
nullptr;
1510 RequestQueueDestroyContext destroyContext(&queue);
1512 &destroyContext,
nullptr,
false,
true);
1513 destroyer->setName(
"hosted RequestQueue destroy regression");
1515 while (queue.getLifecycleState() != RequestQueue::LifecycleState::Stopping) {
1518 queue.releaseHold.release();
1520 passed &= check(destroyer->
join(),
"the concurrent destroy worker could not be joined");
1521 passed &= check(submitter->
join(),
"the cancelled synchronous caller could not be joined");
1522 passed &= check(destroyContext.finished == 1,
"destroy did not complete exactly once");
1523 passed &= check(queuedContext.published == 1 && queuedContext.hookFailures == 0,
1524 "the queued synchronous caller did not publish one completion wait");
1525 passed &= check(queuedContext.finished == 1 && queuedContext.result == 0,
1526 "destroy did not wake and reject the queued synchronous caller");
1528 check(queue.cancelHaltRejections == 1 && queue.cancelResumeRejections == 1 &&
1529 queue.cancelPublicationRejections == 3 && queue.cancelPreallocatedRejections == 1 &&
1530 queue.cancelPreallocated.isAvailable(),
1531 "destroy cancellation did not reject recursive lifecycle entry");
1534 passed &= check(queue.getLifecycleState() == RequestQueue::LifecycleState::Destroyed,
1535 "destroy was not idempotent");
1537 const bool resumeRejected = !queue.resume();
1540 const RequestQueue::PreallocatedPublishResult postDestroyResult =
1541 queue.publishPreallocated(postDestroy, 0, HostedRequestQueue::Sum, 20, 22);
1543 passed &= check(resumeRejected &&
1544 queue.getLifecycleState() == RequestQueue::LifecycleState::Destroyed &&
1545 queue.addRequest(0, HostedRequestQueue::Sum, 20, 22) == 0 &&
1546 postDestroyResult == RequestQueue::PreallocatedPublishResult::QueueStopped &&
1547 postDestroy.isAvailable(),
1548 "destroyed queue reopened publication");
1550 passed &= check(queue.getLifecycleState() == RequestQueue::LifecycleState::Destroyed,
1551 "repeated destroy changed the terminal lifecycle state");
1552 passed &= check(queue.executions == 10,
"the worker executed an unexpected number of requests");
1553 passed &= check(queue.cancellations == 5,
"rejected requests did not release their payloads");
1554 passed &= check(queue.queuedCancellations == 1,
1555 "destroy did not cancel the queued synchronous request");
1558 NOTICE(
"HOSTED-WAIT-TEST: PASS requestqueue-lifecycle");
static bool canCoalesceCanonicalRequests(uint64_t aType, uint64_t aDisk, uint64_t aLocation, uint64_t bType, uint64_t bDisk, uint64_t bLocation)
static bool getInterrupts()
static ProcessorInformation & information()
static void setInterrupts(bool bEnable)
virtual bool compareRequests(const Request &a, const Request &b)
MUST_USE_RESULT bool publishAsyncRequest(size_t priority, uint64_t p1=0, uint64_t p2=0, uint64_t p3=0, uint64_t p4=0, uint64_t p5=0, uint64_t p6=0, uint64_t p7=0, uint64_t p8=0)
MUST_USE_RESULT bool halt()
MUST_USE_RESULT bool resume()
MUST_USE_RESULT PreallocatedPublishResult publishPreallocated(PreallocatedRequest &request, size_t priority, uint64_t p1=0, uint64_t p2=0, uint64_t p3=0, uint64_t p4=0, uint64_t p5=0, uint64_t p6=0, uint64_t p7=0, uint64_t p8=0)
virtual void cancelRequest(const Request &request)
virtual uint64_t executeRequest(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4, uint64_t p5, uint64_t p6, uint64_t p7, uint64_t p8)=0
MUST_USE_RESULT uint64_t addRequest(size_t priority, uint64_t p1=0, uint64_t p2=0, uint64_t p3=0, uint64_t p4=0, uint64_t p5=0, uint64_t p6=0, uint64_t p7=0, uint64_t p8=0)
uint64_t addAsyncRequest(size_t priority, uint64_t p1=0, uint64_t p2=0, uint64_t p3=0, uint64_t p4=0, uint64_t p5=0, uint64_t p6=0, uint64_t p7=0, uint64_t p8=0)
static Scheduler & instance()
MUST_USE_RESULT bool acquireForCompletion(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
bool acquire(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
static constexpr size_t getPageSize() noexcept
void setUnwindState(UnwindType ut)
@ TerminateThread
Exit only this thread during Process exit.
bool getWaitDebugInfo(WaitDebugInfo &info)
DebugState getDebugState(uintptr_t &address)
bool sendEvent(Event *pEvent)