The Pedigree Project 0.1
requestqueue-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/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"
22
23#include "modules/drivers/common/ata/AtaController.h"
24
25namespace {
26constexpr size_t RequestQueueSignalNumber = 11;
27constexpr size_t WaitAttempts = 10000;
28
29Atomic<size_t> g_RequestQueueSignalCalls(0);
30
31void hostedRequestQueueSignalHandler(size_t) {
32 g_RequestQueueSignalCalls += 1;
33}
34
35bool check(bool condition, const char* detail) {
36 if (condition) {
37 return true;
38 }
39
40 ERROR("HOSTED-WAIT-TEST: FAIL requestqueue-lifecycle: " << detail);
41 return false;
42}
43
44bool waitUntilQueued(Thread* thread, size_t debugState) {
45 for (size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
46 Thread::WaitDebugInfo info = {};
47 uintptr_t debugAddress = 0;
48 if (thread->getWaitDebugInfo(info) && info.queue && info.queued &&
49 thread->getDebugState(debugAddress) == debugState) {
50 return true;
51 }
53 }
54 return false;
55}
56
57class HostedRequestQueue : public RequestQueue {
58 public:
59 enum Operation {
60 Sum = 1,
61 SelfSubmit,
62 SelfSubmitInner,
63 SelfAsync,
64 SelfAsyncInner,
65 SelfHalt,
66 HoldWorker,
67 CancelQueued,
68 CancelLifecycleProbe,
69 PreallocatedHold,
70 Record,
71 RecordHold,
72 };
73
74 HostedRequestQueue()
75 : RequestQueue(MakeConstantString("Hosted wait regression")),
76 executions(0),
77 cancellations(0),
78 queuedCancellations(0),
79 comparisons(0),
80 recordedCount(0),
81 recordFailures(0),
82 selfHaltRejections(0),
83 selfAsyncAccepted(0),
84 cancelHaltRejections(0),
85 cancelResumeRejections(0),
86 cancelPublicationRejections(0),
87 cancelPreallocatedRejections(0),
88 holdStarted(0),
89 releaseHold(0),
90 matchEqualPayload(false) {}
91
92 ~HostedRequestQueue() override {
93 destroy();
94 }
95
96 Atomic<size_t> executions;
97 Atomic<size_t> cancellations;
98 Atomic<size_t> queuedCancellations;
99 Atomic<size_t> comparisons;
100 Atomic<size_t> recordedCount;
101 Atomic<size_t> recordFailures;
102 Atomic<size_t> selfHaltRejections;
103 Atomic<size_t> selfAsyncAccepted;
104 Atomic<size_t> cancelHaltRejections;
105 Atomic<size_t> cancelResumeRejections;
106 Atomic<size_t> cancelPublicationRejections;
107 Atomic<size_t> cancelPreallocatedRejections;
108 PreallocatedRequest cancelPreallocated;
109 uint64_t recorded[16] = {};
110 Semaphore holdStarted;
111 Semaphore releaseHold;
112
113 size_t requestWaiterCount() {
114 return m_RequestQueueWaiters.waiterCount();
115 }
116
117 Thread* workerThread() {
118 auto guard = m_RequestQueueWaiters.acquire();
119 return m_pThread;
120 }
121
122 void setMaxAsyncRequests(size_t maximum) {
123 m_nMaxAsyncRequests = maximum;
124 }
125
126 void setMatchEqualPayload(bool match) {
127 matchEqualPayload = match;
128 }
129
130 protected:
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 {
133 executions += 1;
134
135 switch (p1) {
136 case Sum:
137 case SelfSubmitInner:
138 return p2 + p3;
139 case SelfSubmit:
140 return addRequest(0, SelfSubmitInner, p2, p3);
141 case SelfAsync:
142 if (publishAsyncRequest(0, SelfAsyncInner, p2, p3)) {
143 selfAsyncAccepted += 1;
144 }
145 if (publishAsyncRequest(0, SelfAsyncInner, p2, p3)) {
146 selfAsyncAccepted += 1;
147 }
148 return 0;
149 case SelfAsyncInner:
150 return p2 + p3;
151 case SelfHalt:
152 if (!halt()) {
153 selfHaltRejections += 1;
154 return p2;
155 }
156 return 0;
157 case HoldWorker:
158 case PreallocatedHold:
159 holdStarted.release();
160 return releaseHold.acquire() ? p2 : 0;
161 case Record:
162 case RecordHold: {
163 const size_t index = (recordedCount += 1) - 1;
164 if (index < (sizeof(recorded) / sizeof(recorded[0]))) {
165 recorded[index] = p2;
166 } else {
167 recordFailures += 1;
168 }
169 if (p1 == RecordHold) {
170 holdStarted.release();
171 if (!releaseHold.acquireForCompletion()) {
172 return 0;
173 }
174 }
175 return p2;
176 }
177 default:
178 return 0;
179 }
180 }
181
182 void cancelRequest(const Request& request) override {
183 cancellations += 1;
184 if (request.p1 == CancelQueued || request.p1 == CancelLifecycleProbe) {
185 queuedCancellations += 1;
186 }
187 if (request.p1 == CancelLifecycleProbe) {
188 if (!halt()) {
189 cancelHaltRejections += 1;
190 }
191 if (!resume()) {
192 cancelResumeRejections += 1;
193 }
194 if (addRequest(0, CancelLifecycleProbe) == 0) {
195 cancelPublicationRejections += 1;
196 }
197 if (addAsyncRequest(0, CancelLifecycleProbe) == 0) {
198 cancelPublicationRejections += 1;
199 }
200 if (!publishAsyncRequest(0, CancelLifecycleProbe)) {
201 cancelPublicationRejections += 1;
202 }
203 if (publishPreallocated(cancelPreallocated, 0, CancelLifecycleProbe) ==
204 PreallocatedPublishResult::QueueStopped &&
205 cancelPreallocated.isAvailable()) {
206 cancelPreallocatedRejections += 1;
207 }
208 }
209 }
210
211 bool compareRequests(const Request& a, const Request& b) override {
212 comparisons += 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;
215 }
216
217 private:
218 bool matchEqualPayload;
219};
220
221class AtaRequestIdentityProbe : public AtaController {
222 public:
224};
225
226class HostedAtaRequestQueue : public RequestQueue {
227 public:
228 HostedAtaRequestQueue()
229 : RequestQueue(MakeConstantString("Hosted ATA request identity")),
230 executions(0),
231 firstStarted(0),
232 releaseFirst(0) {}
233
234 ~HostedAtaRequestQueue() override {
235 destroy();
236 }
237
238 Atomic<size_t> executions;
239 Semaphore firstStarted;
240 Semaphore releaseFirst;
241
242 protected:
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) {
247 firstStarted.release();
248 if (!releaseFirst.acquireForCompletion()) {
249 return 0;
250 }
251 }
252 return location;
253 }
254
255 bool compareRequests(const Request& a, const Request& b) override {
256 return AtaRequestIdentityProbe::canCoalesceCanonicalRequests(a.p1, a.p2, a.p3, b.p1, b.p2,
257 b.p3);
258 }
259};
260
261bool ataRequestIdentityRegression() {
262 constexpr uint64_t DiskA = 0xA7A;
263 constexpr uint64_t DiskB = 0xB7B;
264 constexpr uint64_t FirstPartitionPage = 0x600;
265 constexpr uint64_t SecondPartitionPage = FirstPartitionPage + TargetInfo::getPageSize();
266 constexpr uint64_t SecondPartitionExtent = FirstPartitionPage + (128 * 1024);
267
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;
281
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();
290 syncQueue.destroy();
291 passed &= repeatedSyncAccepted && syncsDrained && syncQueue.executions == 2;
292
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();
307 readQueue.destroy();
308 passed &= repeatedReadCoalesced && distinctExtentAccepted && differentTypeAccepted &&
309 differentDiskAccepted && readsDrained && readQueue.executions == 4;
310
311 if (!check(passed, "ATA request identity dropped write or sync work, or merged distinct reads")) {
312 return false;
313 }
314
315 NOTICE("HOSTED-WAIT-TEST: PASS ata-request-identity");
316 return true;
317}
318
319bool watchdogProgressRegression() {
320 HostedRequestQueue queue;
321 queue.initialise();
322
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();
327
328 queue.releaseHold.release();
329 const bool firstDrained = queue.drain();
330 const RequestQueue::OverrunStatus cleared = queue.sampleOverrunForTest();
331
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();
337
338 queue.releaseHold.release();
339 const bool secondDrained = queue.drain();
340 queue.destroy();
341
342 passed &= transient == RequestQueue::OverrunStatus::Armed && firstDrained &&
343 cleared == RequestQueue::OverrunStatus::Clear &&
344 baseline == RequestQueue::OverrunStatus::Armed &&
345 stalled == RequestQueue::OverrunStatus::Stalled && secondDrained;
346 if (!check(passed,
347 "the watchdog confused transient admission with a full "
348 "no-progress interval")) {
349 return false;
350 }
351
352 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-watchdog-progress");
353 return true;
354}
355
356bool recursiveAsyncPublicationRegression() {
357 HostedRequestQueue queue;
358 queue.setMaxAsyncRequests(1);
359 queue.setMatchEqualPayload(true);
360 queue.initialise();
361
362 const bool accepted = queue.publishAsyncRequest(0, HostedRequestQueue::SelfAsync, 20, 22);
363 const bool drained = queue.drain();
364 queue.destroy();
365
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")) {
370 return false;
371 }
372
373 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-recursive-async-publication");
374 return true;
375}
376
377struct HeldRequestContext {
378 explicit HeldRequestContext(HostedRequestQueue* queue)
379 : queue(queue), finished(0), result(~0ULL) {}
380
381 HostedRequestQueue* queue;
382 Atomic<size_t> finished;
383 uint64_t result;
384};
385
386struct CompletionRequeueContext {
387 explicit CompletionRequeueContext(Thread* target)
388 : target(target), requeues(0), hookFailures(0) {}
389
390 Thread* target;
391 Atomic<size_t> requeues;
392 Atomic<size_t> hookFailures;
393};
394
395CompletionRequeueContext* g_CompletionRequeueContext = nullptr;
396
397void completionRequeueHook(WaitQueue* queue, Thread* thread, const WaitQueue::Channel& channel,
398 size_t debugState) {
399 CompletionRequeueContext* context = g_CompletionRequeueContext;
400 if (!context || thread != context->target) {
401 return;
402 }
403
404 if (!queue || channel.owner || channel.value || debugState != Thread::CondWait) {
405 context->hookFailures += 1;
406 }
407 context->requeues += 1;
408}
409
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;
414 return 0;
415}
416
417bool completionBarrierInterruption(HostedRequestQueue& queue, bool terminal) {
418 HeldRequestContext request(&queue);
419 Thread* caller = new Thread(Scheduler::instance().getKernelProcess(), submitHeldRequest, &request,
420 nullptr, false, true);
421 if (terminal) {
422 caller->setName("hosted RequestQueue terminal caller");
423 } else {
424 caller->setName("hosted RequestQueue signal caller");
425 }
426
427 const bool workerHeld = queue.holdStarted.acquire();
428 const bool initiallyQueued = waitUntilQueued(caller, Thread::CondWait);
429
430 CompletionRequeueContext requeue(caller);
431 g_CompletionRequeueContext = &requeue;
432 WaitQueue::setBeforeBlockHook(completionRequeueHook);
433
434 bool injected = true;
435 if (terminal) {
437 } else {
438 g_RequestQueueSignalCalls = 0;
439 SignalEvent* event =
440 new SignalEvent(reinterpret_cast<uintptr_t>(&hostedRequestQueueSignalHandler),
441 RequestQueueSignalNumber, ~0UL, 0, true, true);
442 injected = caller->sendEvent(event);
443 if (!injected) {
444 delete event;
445 }
446 }
447
448 for (size_t attempt = 0; attempt < WaitAttempts && !requeue.requeues && !request.finished;
449 ++attempt) {
451 }
452
453 WaitQueue::setBeforeBlockHook(nullptr);
454 g_CompletionRequeueContext = nullptr;
455
456 const bool deferred = requeue.requeues == 1 && requeue.hookFailures == 0 && request.finished == 0;
457 const bool signalDelivered = terminal || g_RequestQueueSignalCalls == 1;
458
459 queue.releaseHold.release();
460 const bool joined = caller->join();
461
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");
466}
467
468struct RequestQueueDestroyContext {
469 explicit RequestQueueDestroyContext(HostedRequestQueue* queue) : queue(queue), finished(0) {}
470
471 HostedRequestQueue* queue;
472 Atomic<size_t> finished;
473};
474
475struct RequestQueueDrainContext {
476 explicit RequestQueueDrainContext(HostedRequestQueue* queue)
477 : queue(queue), finished(0), result(false) {}
478
479 HostedRequestQueue* queue;
480 Atomic<size_t> finished;
481 Atomic<size_t> result;
482};
483
484struct QueuedRequestContext {
485 explicit QueuedRequestContext(HostedRequestQueue* queue)
486 : queue(queue), waiter(nullptr), published(0), hookFailures(0), finished(0), result(~0ULL) {}
487
488 HostedRequestQueue* queue;
489 Thread* waiter;
490 Atomic<size_t> published;
491 Atomic<size_t> hookFailures;
492 Atomic<size_t> finished;
493 uint64_t result;
494};
495
496QueuedRequestContext* g_QueuedRequestContext = nullptr;
497
498void queuedRequestWaitHook(WaitQueue* queue, Thread* thread, const WaitQueue::Channel& channel,
499 size_t debugState) {
500 QueuedRequestContext* context = g_QueuedRequestContext;
501 if (!context) {
502 return;
503 }
504
505 if (thread != context->waiter) {
506 return;
507 }
508
509 if (!queue || channel.owner || channel.value || debugState != Thread::CondWait) {
510 context->hookFailures += 1;
511 }
512 context->published += 1;
513}
514
515int submitQueuedRequest(void* parameter) {
516 QueuedRequestContext* context = reinterpret_cast<QueuedRequestContext*>(parameter);
517 context->waiter = Processor::information().getCurrentThread();
518 context->result = context->queue->addRequest(0, HostedRequestQueue::CancelLifecycleProbe);
519 context->finished += 1;
520 return 0;
521}
522
523int destroyRequestQueue(void* parameter) {
524 RequestQueueDestroyContext* context = reinterpret_cast<RequestQueueDestroyContext*>(parameter);
525 context->queue->destroy();
526 context->finished += 1;
527 return 0;
528}
529
530int drainRequestQueue(void* parameter) {
531 RequestQueueDrainContext* context = reinterpret_cast<RequestQueueDrainContext*>(parameter);
532 context->result = context->queue->drain();
533 context->finished += 1;
534 return 0;
535}
536
537struct RequestQueueHaltContext {
538 explicit RequestQueueHaltContext(HostedRequestQueue* queue)
539 : queue(queue), finished(0), result(false) {}
540
541 HostedRequestQueue* queue;
542 Atomic<size_t> finished;
543 Atomic<size_t> result;
544};
545
546int haltRequestQueue(void* parameter) {
547 auto* context = reinterpret_cast<RequestQueueHaltContext*>(parameter);
548 context->result = context->queue->halt() ? 1 : 0;
549 context->finished += 1;
550 return 0;
551}
552
553struct PublicationPauseContext {
554 PublicationPauseContext() : calls(0), failures(0), entered(0), release(0) {}
555
556 Atomic<size_t> calls;
557 Atomic<size_t> failures;
558 Semaphore entered;
559 Semaphore release;
560};
561
562void pauseFirstPublication(void* parameter) {
563 auto* context = reinterpret_cast<PublicationPauseContext*>(parameter);
564 if ((context->calls += 1) != 1) {
565 return;
566 }
567
568 context->entered.release();
569 if (!context->release.acquireForCompletion()) {
570 context->failures += 1;
571 }
572}
573
574struct PreallocatedPublicationContext {
575 PreallocatedPublicationContext(HostedRequestQueue* queue,
576 RequestQueue::PreallocatedRequest* request, size_t priority,
577 uint64_t operation, uint64_t value)
578 : queue(queue),
579 request(request),
580 priority(priority),
581 operation(operation),
582 value(value),
583 result(RequestQueue::PreallocatedPublishResult::QueueStopped),
584 finished(0) {}
585
586 HostedRequestQueue* queue;
588 size_t priority;
589 uint64_t operation;
590 uint64_t value;
591 RequestQueue::PreallocatedPublishResult result;
592 Atomic<size_t> finished;
593};
594
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;
600 return 0;
601}
602
603struct AsyncPublicationContext {
604 AsyncPublicationContext(HostedRequestQueue* queue, size_t priority, uint64_t operation,
605 uint64_t value)
606 : queue(queue),
607 priority(priority),
608 operation(operation),
609 value(value),
610 result(0),
611 finished(0) {}
612
613 HostedRequestQueue* queue;
614 size_t priority;
615 uint64_t operation;
616 uint64_t value;
617 uint64_t result;
618 Atomic<size_t> finished;
619};
620
621int publishAsyncRequest(void* parameter) {
622 auto* context = reinterpret_cast<AsyncPublicationContext*>(parameter);
623 context->result =
624 context->queue->addAsyncRequest(context->priority, context->operation, context->value);
625 context->finished += 1;
626 return 0;
627}
628
629struct PreallocatedReleaseContext {
630 PreallocatedReleaseContext(HostedRequestQueue* queue, bool requeueOnce, bool holdFirst = false,
631 bool probeOrdinaryPublication = false)
632 : queue(queue),
633 request(nullptr),
634 requeueOnce(requeueOnce),
635 holdFirst(holdFirst),
636 probeOrdinaryPublication(probeOrdinaryPublication),
637 callbacks(0),
638 requeues(0),
639 ordinaryPublicationRejections(0),
640 failures(0),
641 callbackEntered(0),
642 releaseCallback(0) {}
643
644 HostedRequestQueue* queue;
646 bool requeueOnce;
647 bool holdFirst;
648 bool probeOrdinaryPublication;
649 Atomic<size_t> callbacks;
650 Atomic<size_t> requeues;
651 Atomic<size_t> ordinaryPublicationRejections;
652 Atomic<size_t> failures;
653 Semaphore callbackEntered;
654 Semaphore releaseCallback;
655};
656
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;
662 }
663
664 if (context->holdFirst && callback == 1) {
665 context->callbackEntered.release();
666 if (!context->releaseCallback.acquireForCompletion()) {
667 context->failures += 1;
668 return;
669 }
670 }
671
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;
678 } else {
679 context->failures += 1;
680 }
681 }
682
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;
688 }
689 }
690}
691
692struct ReleaseDestroyReentryContext {
693 explicit ReleaseDestroyReentryContext(HostedRequestQueue* queue)
694 : queue(queue),
695 callbacks(0),
696 failures(0),
697 haltRejections(0),
698 resumeRejections(0),
699 allocationRejections(0),
700 preallocatedRejections(0),
701 entered(0),
702 release(0) {}
703
704 HostedRequestQueue* queue;
706 Atomic<size_t> callbacks;
707 Atomic<size_t> failures;
708 Atomic<size_t> haltRejections;
709 Atomic<size_t> resumeRejections;
710 Atomic<size_t> allocationRejections;
711 Atomic<size_t> preallocatedRejections;
712 Semaphore entered;
713 Semaphore release;
714};
715
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;
722 return;
723 }
724
725 if (!context->queue->halt()) {
726 context->haltRejections += 1;
727 }
728 if (!context->queue->resume()) {
729 context->resumeRejections += 1;
730 }
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;
734 }
735 if (context->queue->publishPreallocated(context->ordinary, 0, HostedRequestQueue::Sum, 20, 22) ==
736 RequestQueue::PreallocatedPublishResult::QueueStopped &&
737 context->ordinary.isAvailable()) {
738 context->preallocatedRejections += 1;
739 }
740}
741
742bool predicateDoorbellRegression() {
743 using Result = RequestQueue::PreallocatedPublishResult;
744
745 HostedRequestQueue queue;
747 queue.initialise();
748
749 // Consume initialise()'s readiness notification so the publication below
750 // is the only possible source of the observed doorbell.
751 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
752 const size_t waitersBefore = queue.requestWaiterCount();
753 const bool interrupts = Processor::getInterrupts();
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();
760 Processor::setInterrupts(interrupts);
761
762 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
763 const bool drained = queue.drain();
764 const size_t executions = queue.executions;
765 const bool available = request.isAvailable();
766 queue.destroy();
767
768 const bool passed =
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 "
773 "doorbell");
774 if (passed) {
775 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-predicate-doorbell");
776 }
777 return passed;
778}
779
780bool preallocatedDuplicateDomainRegression() {
781 using Result = RequestQueue::PreallocatedPublishResult;
782
783 HostedRequestQueue queue;
785 queue.setMatchEqualPayload(true);
786 queue.initialise();
787
788 bool passed = queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
789 queue.holdStarted.acquire();
790
791 const bool interrupts = Processor::getInterrupts();
793 const Result preallocatedAccepted =
794 queue.publishPreallocated(preallocated, 1, HostedRequestQueue::Record, 77, 88);
795 Processor::setInterrupts(interrupts);
796
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;
800
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;
805 queue.destroy();
806
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 "
812 "token "
813 "coalescing domain");
814 if (passed) {
815 NOTICE(
816 "HOSTED-WAIT-TEST: PASS "
817 "requestqueue-preallocated-duplicate-domain");
818 }
819 return passed;
820}
821
822bool intakeOrderingRegression() {
823 using Result = RequestQueue::PreallocatedPublishResult;
824
825 HostedRequestQueue queue;
826 RequestQueue::PreallocatedRequest firstPreallocated;
827 RequestQueue::PreallocatedRequest secondPreallocated;
828 queue.initialise();
829
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;
834
835 const bool interrupts = Processor::getInterrupts();
837 const Result firstAccepted =
838 queue.publishPreallocated(firstPreallocated, 1, HostedRequestQueue::Record, 11);
839 Processor::setInterrupts(interrupts);
840
841 passed &= queue.addAsyncRequest(1, HostedRequestQueue::Record, 12) == 1;
843 const Result secondAccepted =
844 queue.publishPreallocated(secondPreallocated, 1, HostedRequestQueue::Record, 13);
845 Processor::setInterrupts(interrupts);
846
847 passed &= queue.addAsyncRequest(2, HostedRequestQueue::Record, 20) == 1;
848 passed &= queue.addAsyncRequest(0, HostedRequestQueue::Record, 1) == 1;
849
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]));
854 if (orderMatches) {
855 for (size_t i = 0; i < (sizeof(expected) / sizeof(expected[0])); ++i) {
856 orderMatches &= queue.recorded[i] == expected[i];
857 }
858 }
859
860 passed &= firstAccepted == Result::Accepted && secondAccepted == Result::Accepted && drained &&
861 orderMatches && queue.recordFailures == 0 && queue.executions == 8 &&
862 firstPreallocated.isAvailable() && secondPreallocated.isAvailable();
863 queue.destroy();
864
865 passed = check(passed, "the shared intake lost same-priority FIFO or strict priority order");
866 if (passed) {
867 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-intake-ordering");
868 }
869 return passed;
870}
871
872bool haltRetentionRegression() {
873 using Result = RequestQueue::PreallocatedPublishResult;
874
875 HostedRequestQueue queue;
878 queue.initialise();
879
880 bool passed = queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
881 queue.holdStarted.acquire();
882 passed &= queue.addAsyncRequest(1, HostedRequestQueue::Record, 40) == 1;
883
884 const bool interrupts = Processor::getInterrupts();
886 const Result retainedResult =
887 queue.publishPreallocated(retained, 1, HostedRequestQueue::Record, 41);
888 Processor::setInterrupts(interrupts);
889
890 RequestQueueHaltContext haltContext(&queue);
891 Thread* halter = new Thread(Scheduler::instance().getKernelProcess(), haltRequestQueue,
892 &haltContext, nullptr, false, true);
893 halter->setName("hosted RequestQueue halt retention regression");
894
895 bool stopping = false;
896 for (size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
897 stopping = queue.getLifecycleState() == RequestQueue::LifecycleState::Stopping;
898 if (stopping) {
899 break;
900 }
902 }
903
904 Result rejectedResult = Result::Accepted;
905 if (stopping) {
907 rejectedResult = queue.publishPreallocated(rejected, 1, HostedRequestQueue::Record, 42);
908 Processor::setInterrupts(interrupts);
909 }
910 const bool retainedBeforeRelease = queue.recordedCount == 0 && !retained.isAvailable();
911 queue.releaseHold.release();
912 const bool halterJoined = halter->joinForCompletion();
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();
917
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;
922 queue.destroy();
923
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");
939 passed &=
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");
944 if (passed) {
945 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-halt-retention");
946 }
947 return passed;
948}
949
950bool rejectedPublisherDestroyWaitRegression() {
951 using Result = RequestQueue::PreallocatedPublishResult;
952
953 HostedRequestQueue queue;
955 PublicationPauseContext pause;
956 queue.setAfterPreallocatedAdmissionHookForTest(pauseFirstPublication, &pause);
957
958 PreallocatedPublicationContext publication(&queue, &request, 1, HostedRequestQueue::Record, 16);
959 Thread* publisher = new Thread(Scheduler::instance().getKernelProcess(),
960 publishPreallocatedRequest, &publication, nullptr, false, true);
961 publisher->setName("hosted RequestQueue rejected publication pause");
962 const bool paused = pause.entered.acquire();
963
964 RequestQueueDestroyContext destroyContext(&queue);
965 Thread* destroyer = new Thread(Scheduler::instance().getKernelProcess(), destroyRequestQueue,
966 &destroyContext, nullptr, false, true);
967 destroyer->setName("hosted RequestQueue stopped publication drain regression");
968
969 bool drainWaitObserved = false;
970 for (size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
971 drainWaitObserved = queue.publisherDrainRetriesForTest() != 0;
972 if (drainWaitObserved) {
973 break;
974 }
976 }
977
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);
984
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 "
991 "lifetime count");
992 if (passed) {
993 NOTICE(
994 "HOSTED-WAIT-TEST: PASS "
995 "requestqueue-rejected-publication-close-wait");
996 }
997 return passed;
998}
999
1000bool preallocatedAdmissionCloseWaitRegression() {
1001 using Result = RequestQueue::PreallocatedPublishResult;
1002
1003 HostedRequestQueue queue;
1005 PublicationPauseContext pause;
1006 queue.initialise();
1007 queue.setAfterPreallocatedAdmissionHookForTest(pauseFirstPublication, &pause);
1008
1009 PreallocatedPublicationContext publication(&queue, &request, 1, HostedRequestQueue::Record, 17);
1010 Thread* publisher = new Thread(Scheduler::instance().getKernelProcess(),
1011 publishPreallocatedRequest, &publication, nullptr, false, true);
1012 publisher->setName("hosted RequestQueue admitted publication pause");
1013 const bool paused = pause.entered.acquire();
1014
1015 RequestQueueDestroyContext destroyContext(&queue);
1016 Thread* destroyer = new Thread(Scheduler::instance().getKernelProcess(), destroyRequestQueue,
1017 &destroyContext, nullptr, false, true);
1018 destroyer->setName("hosted RequestQueue publication drain regression");
1019
1020 bool stopping = false;
1021 for (size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
1022 stopping = queue.getLifecycleState() == RequestQueue::LifecycleState::Stopping;
1023 if (stopping) {
1024 break;
1025 }
1027 }
1028
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);
1035
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");
1043 if (passed) {
1044 NOTICE(
1045 "HOSTED-WAIT-TEST: PASS "
1046 "requestqueue-preallocated-admission-close-wait");
1047 }
1048 return passed;
1049}
1050
1051bool intakeTransientPublicationRegression() {
1052 using Result = RequestQueue::PreallocatedPublishResult;
1053
1054 HostedRequestQueue queue;
1056 PublicationPauseContext pause;
1057 queue.initialise();
1058
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);
1063
1064 PreallocatedPublicationContext first(&queue, &preallocated, 1, HostedRequestQueue::RecordHold,
1065 10);
1066 Thread* preallocatedPublisher =
1067 new Thread(Scheduler::instance().getKernelProcess(), publishPreallocatedRequest, &first,
1068 nullptr, false, true);
1069 preallocatedPublisher->setName("hosted RequestQueue incomplete MPSC publisher");
1070 const bool paused = pause.entered.acquire();
1071
1072 AsyncPublicationContext second(&queue, 1, HostedRequestQueue::Record, 11);
1073 Thread* ordinaryPublisher = new Thread(Scheduler::instance().getKernelProcess(),
1074 publishAsyncRequest, &second, nullptr, false, true);
1075 ordinaryPublisher->setName("hosted RequestQueue transient duplicate scanner");
1076
1077 queue.releaseHold.release();
1078 bool bothRetried = false;
1079 for (size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
1080 bothRetried = queue.workerTransientRetriesForTest() && queue.guardedTransientRetriesForTest();
1081 if (bothRetried) {
1082 break;
1083 }
1085 }
1086
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);
1093
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]));
1099 if (orderMatches) {
1100 for (size_t i = 0; i < (sizeof(expected) / sizeof(expected[0])); ++i) {
1101 orderMatches &= queue.recorded[i] == expected[i];
1102 }
1103 }
1104 queue.destroy();
1105
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 "
1113 "ordering");
1114 if (passed) {
1115 NOTICE(
1116 "HOSTED-WAIT-TEST: PASS "
1117 "requestqueue-intake-transient-publication");
1118 }
1119 return passed;
1120}
1121
1122bool preallocatedRequestRegressions() {
1123 using Result = RequestQueue::PreallocatedPublishResult;
1124
1125 bool passed = true;
1126 {
1127 HostedRequestQueue queue;
1128 PreallocatedReleaseContext releaseContext(&queue, false, true);
1129 RequestQueue::PreallocatedRequest released(preallocatedRequestReleased, &releaseContext);
1130 releaseContext.request = &released;
1131 queue.setMaxAsyncRequests(0);
1132 queue.initialise();
1133
1134 passed &= check(queue.republishPreallocatedWhileReleasing(
1135 released, 0, HostedRequestQueue::Sum) == Result::TokenBusy &&
1136 released.isAvailable(),
1137 "an idle token accepted release-only republication");
1138 passed &= check(
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");
1143
1144 RequestQueueDrainContext drainContext(&queue);
1145 Thread* drainer = new Thread(Scheduler::instance().getKernelProcess(), drainRequestQueue,
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");
1151
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");
1161 queue.destroy();
1162 }
1163
1164 {
1165 HostedRequestQueue queue;
1166 PreallocatedReleaseContext releaseContext(&queue, true, false, true);
1167 RequestQueue::PreallocatedRequest released(preallocatedRequestReleased, &releaseContext);
1168 releaseContext.request = &released;
1169 queue.setMaxAsyncRequests(0);
1170 queue.initialise();
1171
1172 passed &= check(queue.publishPreallocated(released, 0, HostedRequestQueue::Sum, 19, 23) ==
1173 Result::Accepted &&
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");
1180 queue.destroy();
1181 }
1182
1183 {
1184 HostedRequestQueue queue;
1187
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");
1196
1197 queue.initialise();
1198 const bool interrupts = Processor::getInterrupts();
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;
1204 Processor::setInterrupts(interrupts);
1205 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
1206
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 "
1210 "detection");
1211 passed &= check(queue.drain() && request.isAvailable() && queue.executions == 1,
1212 "executed preallocated work did not release its token");
1213
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;
1223 passed &= check(
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();
1231 passed &= check(
1232 queue.drain() && request.isAvailable() && capacity.isAvailable() && queue.executions == 3,
1233 "over-capacity preallocated work did not drain exactly once");
1234
1235 passed &= check(
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");
1240 queue.destroy();
1241 }
1242
1243 {
1244 HostedRequestQueue queue;
1245 PreallocatedReleaseContext releaseContext(&queue, false);
1246 RequestQueue::PreallocatedRequest cancelled(preallocatedRequestReleased, &releaseContext);
1247 releaseContext.request = &cancelled;
1248 queue.initialise();
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) ==
1253 Result::Accepted,
1254 "teardown setup did not queue the preallocated token");
1255
1256 RequestQueueDestroyContext destroyContext(&queue);
1257 Thread* destroyer = new Thread(Scheduler::instance().getKernelProcess(), destroyRequestQueue,
1258 &destroyContext, nullptr, false, true);
1259 destroyer->setName("hosted preallocated request destroy regression");
1260 while (queue.getLifecycleState() != RequestQueue::LifecycleState::Stopping) {
1262 }
1263 queue.releaseHold.release();
1264
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");
1269 passed &= check(
1270 queue.publishPreallocated(cancelled, 0, HostedRequestQueue::Sum) == Result::QueueStopped &&
1271 cancelled.isAvailable(),
1272 "a stopped queue retained a reused preallocated token");
1273 }
1274
1275 if (passed) {
1276 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-preallocated-token");
1277 }
1278 return passed;
1279}
1280
1281bool releaseCallbackDestroyReentryRegression() {
1282 using Result = RequestQueue::PreallocatedPublishResult;
1283
1284 HostedRequestQueue queue;
1285 ReleaseDestroyReentryContext releaseContext(&queue);
1286 RequestQueue::PreallocatedRequest released(releaseDuringDestroy, &releaseContext);
1287 queue.initialise();
1288
1289 const bool accepted =
1290 queue.publishPreallocated(released, 0, HostedRequestQueue::Sum, 20, 22) == Result::Accepted;
1291 const bool callbackEntered = accepted && releaseContext.entered.acquire();
1292
1293 RequestQueueDestroyContext destroyContext(&queue);
1294 Thread* destroyer = new Thread(Scheduler::instance().getKernelProcess(), destroyRequestQueue,
1295 &destroyContext, nullptr, false, true);
1296 destroyer->setName("hosted RequestQueue release callback destroy regression");
1297
1298 bool stopping = false;
1299 for (size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
1300 stopping = queue.getLifecycleState() == RequestQueue::LifecycleState::Stopping;
1301 if (stopping) {
1302 break;
1303 }
1305 }
1306
1307 const bool destroyWaitedForRelease = stopping && !destroyContext.finished;
1308 releaseContext.release.release();
1309 const bool destroyerJoined = destroyer->joinForCompletion();
1310
1311 const bool passed =
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");
1322 if (passed) {
1323 NOTICE(
1324 "HOSTED-WAIT-TEST: PASS "
1325 "requestqueue-release-callback-destroy-reentry");
1326 }
1327 return passed;
1328}
1329} // namespace
1330
1331bool runHostedRequestQueueRegressions() {
1332 Thread* current = Processor::information().getCurrentThread();
1333 if (!check(RoundRobin::runHostedIntrusiveQueueRegressions(current),
1334 "intrusive scheduler ready-queue invariants failed")) {
1335 return false;
1336 }
1337 NOTICE("HOSTED-WAIT-TEST: PASS scheduler-intrusive-ready-queue");
1338
1339 if (!ataRequestIdentityRegression()) {
1340 return false;
1341 }
1342
1343 if (!preallocatedRequestRegressions()) {
1344 return false;
1345 }
1346
1347 if (!releaseCallbackDestroyReentryRegression()) {
1348 return false;
1349 }
1350
1351 if (!predicateDoorbellRegression()) {
1352 return false;
1353 }
1354
1355 if (!preallocatedDuplicateDomainRegression()) {
1356 return false;
1357 }
1358
1359 if (!intakeOrderingRegression()) {
1360 return false;
1361 }
1362
1363 if (!haltRetentionRegression()) {
1364 return false;
1365 }
1366
1367 if (!rejectedPublisherDestroyWaitRegression()) {
1368 return false;
1369 }
1370
1371 if (!preallocatedAdmissionCloseWaitRegression()) {
1372 return false;
1373 }
1374
1375 if (!intakeTransientPublicationRegression()) {
1376 return false;
1377 }
1378
1379 if (!watchdogProgressRegression()) {
1380 return false;
1381 }
1382
1383 if (!recursiveAsyncPublicationRegression()) {
1384 return false;
1385 }
1386
1387 {
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();
1393
1394 const Time::Timestamp deadline =
1395 Time::getTicks() + Time::Multiplier::Second + (100 * Time::Multiplier::Millisecond);
1396 while (Time::getTicks() < deadline) {
1398 }
1399
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")) {
1406 return false;
1407 }
1408 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-active-not-backlog");
1409 }
1410
1411 {
1412 HostedRequestQueue ownedWorkerQueue;
1413 ownedWorkerQueue.initialise();
1414 Thread* idleWorker = ownedWorkerQueue.workerThread();
1415 if (idleWorker) {
1417 }
1418 const bool idleTerminalSafe =
1419 idleWorker && ownedWorkerQueue.addRequest(0, HostedRequestQueue::Sum, 20, 22) == 42;
1420
1421 const bool halted = ownedWorkerQueue.halt();
1422 const bool resumed = ownedWorkerQueue.resume();
1423 Thread* activeWorker = ownedWorkerQueue.workerThread();
1424 const bool held =
1425 ownedWorkerQueue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
1426 ownedWorkerQueue.holdStarted.acquire();
1427 if (activeWorker) {
1429 }
1430 ownedWorkerQueue.releaseHold.release();
1431 const bool activeTerminalSafe =
1432 held && activeWorker &&
1433 ownedWorkerQueue.addRequest(0, HostedRequestQueue::Sum, 19, 23) == 42;
1434 ownedWorkerQueue.destroy();
1435
1436 if (!check(idleTerminalSafe && halted && resumed && activeTerminalSafe,
1437 "queue-owned worker termination orphaned work")) {
1438 return false;
1439 }
1440 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-worker-terminal-ownership");
1441 }
1442
1443 HostedRequestQueue queue;
1444 bool passed = true;
1445
1446 passed &= check(queue.addRequest(0, HostedRequestQueue::Sum, 1, 2) == 0,
1447 "a stopped queue accepted work before initialise");
1448
1449 queue.initialise();
1450 passed &= check(queue.getLifecycleState() == RequestQueue::LifecycleState::Accepting,
1451 "initialise did not start the worker");
1452 passed &= check(
1453 queue.addRequest(REQUEST_QUEUE_NUM_PRIORITIES - 1, HostedRequestQueue::Sum, 40, 2) == 42,
1454 "synchronous completion returned the wrong result");
1455 passed &=
1456 check(queue.addRequest(REQUEST_QUEUE_NUM_PRIORITIES, HostedRequestQueue::Sum, 1, 2) == 0,
1457 "an invalid priority was accepted");
1458
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");
1464
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");
1475
1476 passed &= completionBarrierInterruption(queue, false);
1477 passed &= completionBarrierInterruption(queue, true);
1478
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);
1483 Thread* drainer = new Thread(Scheduler::instance().getKernelProcess(), drainRequestQueue,
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");
1492
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");
1496
1497 QueuedRequestContext queuedContext(&queue);
1498 g_QueuedRequestContext = &queuedContext;
1499 WaitQueue::setBeforeBlockHook(queuedRequestWaitHook);
1500 Thread* submitter = new Thread(Scheduler::instance().getKernelProcess(), submitQueuedRequest,
1501 &queuedContext, nullptr, false, true);
1502 submitter->setName("hosted RequestQueue queued caller");
1503
1504 while (queuedContext.published < 1) {
1506 }
1507 WaitQueue::setBeforeBlockHook(nullptr);
1508 g_QueuedRequestContext = nullptr;
1509
1510 RequestQueueDestroyContext destroyContext(&queue);
1511 Thread* destroyer = new Thread(Scheduler::instance().getKernelProcess(), destroyRequestQueue,
1512 &destroyContext, nullptr, false, true);
1513 destroyer->setName("hosted RequestQueue destroy regression");
1514
1515 while (queue.getLifecycleState() != RequestQueue::LifecycleState::Stopping) {
1517 }
1518 queue.releaseHold.release();
1519
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");
1527 passed &=
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");
1532
1533 queue.destroy();
1534 passed &= check(queue.getLifecycleState() == RequestQueue::LifecycleState::Destroyed,
1535 "destroy was not idempotent");
1537 const bool resumeRejected = !queue.resume();
1538 const bool interrupts = Processor::getInterrupts();
1540 const RequestQueue::PreallocatedPublishResult postDestroyResult =
1541 queue.publishPreallocated(postDestroy, 0, HostedRequestQueue::Sum, 20, 22);
1542 Processor::setInterrupts(interrupts);
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");
1549 queue.destroy();
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");
1556
1557 if (passed) {
1558 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-lifecycle");
1559 }
1560 return passed;
1561}
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)
virtual void destroy()
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()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:236
void release(size_t n=1)
Definition Semaphore.cc:549
MUST_USE_RESULT bool acquireForCompletion(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
Definition Semaphore.cc:372
bool acquire(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
Definition Semaphore.cc:355
static constexpr size_t getPageSize() noexcept
Definition TargetInfo.h:40
void setUnwindState(UnwindType ut)
Definition Thread.cc:3580
@ TerminateThread
Exit only this thread during Process exit.
Definition Thread.h:519
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3163
bool joinForCompletion()
Definition Thread.cc:2750
bool join()
Definition Thread.cc:2746
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:574
bool sendEvent(Event *pEvent)
Definition Thread.cc:1115