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 SelfHalt,
64 HoldWorker,
65 CancelQueued,
66 CancelLifecycleProbe,
67 PreallocatedHold,
68 Record,
69 RecordHold,
70 };
71
72 HostedRequestQueue()
73 : RequestQueue(MakeConstantString("Hosted wait regression")),
74 executions(0),
75 cancellations(0),
76 queuedCancellations(0),
77 comparisons(0),
78 recordedCount(0),
79 recordFailures(0),
80 selfHaltRejections(0),
81 cancelHaltRejections(0),
82 cancelResumeRejections(0),
83 cancelPublicationRejections(0),
84 cancelPreallocatedRejections(0),
85 holdStarted(0),
86 releaseHold(0),
87 matchEqualPayload(false) {}
88
89 ~HostedRequestQueue() override {
90 destroy();
91 }
92
93 Atomic<size_t> executions;
94 Atomic<size_t> cancellations;
95 Atomic<size_t> queuedCancellations;
96 Atomic<size_t> comparisons;
97 Atomic<size_t> recordedCount;
98 Atomic<size_t> recordFailures;
99 Atomic<size_t> selfHaltRejections;
100 Atomic<size_t> cancelHaltRejections;
101 Atomic<size_t> cancelResumeRejections;
102 Atomic<size_t> cancelPublicationRejections;
103 Atomic<size_t> cancelPreallocatedRejections;
104 PreallocatedRequest cancelPreallocated;
105 uint64_t recorded[16] = {};
106 Semaphore holdStarted;
107 Semaphore releaseHold;
108
109 size_t requestWaiterCount() {
110 return m_RequestQueueWaiters.waiterCount();
111 }
112
113 Thread* workerThread() {
114 auto guard = m_RequestQueueWaiters.acquire();
115 return m_pThread;
116 }
117
118 void setMaxAsyncRequests(size_t maximum) {
119 m_nMaxAsyncRequests = maximum;
120 }
121
122 void setMatchEqualPayload(bool match) {
123 matchEqualPayload = match;
124 }
125
126 protected:
127 uint64_t executeRequest(uint64_t p1, uint64_t p2, uint64_t p3, uint64_t, uint64_t, uint64_t,
128 uint64_t, uint64_t) override {
129 executions += 1;
130
131 switch (p1) {
132 case Sum:
133 case SelfSubmitInner:
134 return p2 + p3;
135 case SelfSubmit:
136 return addRequest(0, SelfSubmitInner, p2, p3);
137 case SelfHalt:
138 if (!halt()) {
139 selfHaltRejections += 1;
140 return p2;
141 }
142 return 0;
143 case HoldWorker:
144 case PreallocatedHold:
145 holdStarted.release();
146 return releaseHold.acquire() ? p2 : 0;
147 case Record:
148 case RecordHold: {
149 const size_t index = (recordedCount += 1) - 1;
150 if (index < (sizeof(recorded) / sizeof(recorded[0]))) {
151 recorded[index] = p2;
152 } else {
153 recordFailures += 1;
154 }
155 if (p1 == RecordHold) {
156 holdStarted.release();
157 if (!releaseHold.acquireForCompletion()) {
158 return 0;
159 }
160 }
161 return p2;
162 }
163 default:
164 return 0;
165 }
166 }
167
168 void cancelRequest(const Request& request) override {
169 cancellations += 1;
170 if (request.p1 == CancelQueued || request.p1 == CancelLifecycleProbe) {
171 queuedCancellations += 1;
172 }
173 if (request.p1 == CancelLifecycleProbe) {
174 if (!halt()) {
175 cancelHaltRejections += 1;
176 }
177 if (!resume()) {
178 cancelResumeRejections += 1;
179 }
180 if (addRequest(0, CancelLifecycleProbe) == 0) {
181 cancelPublicationRejections += 1;
182 }
183 if (addAsyncRequest(0, CancelLifecycleProbe) == 0) {
184 cancelPublicationRejections += 1;
185 }
186 if (publishPreallocated(cancelPreallocated, 0, CancelLifecycleProbe) ==
187 PreallocatedPublishResult::QueueStopped &&
188 cancelPreallocated.isAvailable()) {
189 cancelPreallocatedRejections += 1;
190 }
191 }
192 }
193
194 bool compareRequests(const Request& a, const Request& b) override {
195 comparisons += 1;
196 return matchEqualPayload && a.p1 == b.p1 && a.p2 == b.p2 && a.p3 == b.p3 && a.p4 == b.p4 &&
197 a.p5 == b.p5 && a.p6 == b.p6 && a.p7 == b.p7 && a.p8 == b.p8;
198 }
199
200 private:
201 bool matchEqualPayload;
202};
203
204class AtaRequestIdentityProbe : public AtaController {
205 public:
207};
208
209class HostedAtaRequestQueue : public RequestQueue {
210 public:
211 HostedAtaRequestQueue()
212 : RequestQueue(MakeConstantString("Hosted ATA request identity")),
213 executions(0),
214 firstStarted(0),
215 releaseFirst(0) {}
216
217 ~HostedAtaRequestQueue() override {
218 destroy();
219 }
220
221 Atomic<size_t> executions;
222 Semaphore firstStarted;
223 Semaphore releaseFirst;
224
225 protected:
226 uint64_t executeRequest(uint64_t, uint64_t, uint64_t location, uint64_t, uint64_t, uint64_t,
227 uint64_t, uint64_t) override {
228 const size_t execution = executions += 1;
229 if (execution == 1) {
230 firstStarted.release();
231 if (!releaseFirst.acquireForCompletion()) {
232 return 0;
233 }
234 }
235 return location;
236 }
237
238 bool compareRequests(const Request& a, const Request& b) override {
239 return AtaRequestIdentityProbe::canCoalesceCanonicalRequests(a.p1, a.p2, a.p3, b.p1, b.p2,
240 b.p3);
241 }
242};
243
244bool ataRequestIdentityRegression() {
245 constexpr uint64_t DiskA = 0xA7A;
246 constexpr uint64_t DiskB = 0xB7B;
247 constexpr uint64_t FirstPartitionPage = 0x600;
248 constexpr uint64_t SecondPartitionPage = FirstPartitionPage + TargetInfo::getPageSize();
249 constexpr uint64_t SecondPartitionExtent = FirstPartitionPage + (128 * 1024);
250
251 HostedAtaRequestQueue writeQueue;
252 writeQueue.initialise();
253 bool passed = writeQueue.addAsyncRequest(0, SCSI_REQUEST_WRITE, DiskA, FirstPartitionPage) == 1 &&
254 writeQueue.firstStarted.acquire();
255 const bool repeatedWriteAccepted =
256 writeQueue.addAsyncRequest(0, SCSI_REQUEST_WRITE, DiskA, FirstPartitionPage) == 1;
257 const bool distinctWritePageAccepted =
258 writeQueue.addAsyncRequest(0, SCSI_REQUEST_WRITE, DiskA, SecondPartitionPage) == 1;
259 writeQueue.releaseFirst.release();
260 const bool writesDrained = writeQueue.drain();
261 writeQueue.destroy();
262 passed &= repeatedWriteAccepted && distinctWritePageAccepted && writesDrained &&
263 writeQueue.executions == 3;
264
265 HostedAtaRequestQueue syncQueue;
266 syncQueue.initialise();
267 passed &= syncQueue.addAsyncRequest(0, SCSI_REQUEST_SYNC, DiskA, FirstPartitionPage) == 1 &&
268 syncQueue.firstStarted.acquire();
269 const bool repeatedSyncAccepted =
270 syncQueue.addAsyncRequest(0, SCSI_REQUEST_SYNC, DiskA, FirstPartitionPage) == 1;
271 syncQueue.releaseFirst.release();
272 const bool syncsDrained = syncQueue.drain();
273 syncQueue.destroy();
274 passed &= repeatedSyncAccepted && syncsDrained && syncQueue.executions == 2;
275
276 HostedAtaRequestQueue readQueue;
277 readQueue.initialise();
278 passed &= readQueue.addAsyncRequest(0, SCSI_REQUEST_READ, DiskA, FirstPartitionPage) == 1 &&
279 readQueue.firstStarted.acquire();
280 const bool repeatedReadCoalesced =
281 readQueue.addAsyncRequest(0, SCSI_REQUEST_READ, DiskA, FirstPartitionPage) == 0;
282 const bool distinctExtentAccepted =
283 readQueue.addAsyncRequest(0, SCSI_REQUEST_READ, DiskA, SecondPartitionExtent) == 1;
284 const bool differentTypeAccepted =
285 readQueue.addAsyncRequest(0, SCSI_REQUEST_WRITE, DiskA, FirstPartitionPage) == 1;
286 const bool differentDiskAccepted =
287 readQueue.addAsyncRequest(0, SCSI_REQUEST_READ, DiskB, FirstPartitionPage) == 1;
288 readQueue.releaseFirst.release();
289 const bool readsDrained = readQueue.drain();
290 readQueue.destroy();
291 passed &= repeatedReadCoalesced && distinctExtentAccepted && differentTypeAccepted &&
292 differentDiskAccepted && readsDrained && readQueue.executions == 4;
293
294 if (!check(passed, "ATA request identity dropped write or sync work, or merged distinct reads")) {
295 return false;
296 }
297
298 NOTICE("HOSTED-WAIT-TEST: PASS ata-request-identity");
299 return true;
300}
301
302bool watchdogProgressRegression() {
303 HostedRequestQueue queue;
304 queue.initialise();
305
306 bool passed = queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
307 queue.holdStarted.acquire();
308 passed &= queue.addAsyncRequest(0, HostedRequestQueue::Sum, 20, 22) == 1;
309 const RequestQueue::OverrunStatus transient = queue.sampleOverrunForTest();
310
311 queue.releaseHold.release();
312 const bool firstDrained = queue.drain();
313 const RequestQueue::OverrunStatus cleared = queue.sampleOverrunForTest();
314
315 passed &= queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
316 queue.holdStarted.acquire();
317 passed &= queue.addAsyncRequest(0, HostedRequestQueue::Sum, 19, 23) == 1;
318 const RequestQueue::OverrunStatus baseline = queue.sampleOverrunForTest();
319 const RequestQueue::OverrunStatus stalled = queue.sampleOverrunForTest();
320
321 queue.releaseHold.release();
322 const bool secondDrained = queue.drain();
323 queue.destroy();
324
325 passed &= transient == RequestQueue::OverrunStatus::Armed && firstDrained &&
326 cleared == RequestQueue::OverrunStatus::Clear &&
327 baseline == RequestQueue::OverrunStatus::Armed &&
328 stalled == RequestQueue::OverrunStatus::Stalled && secondDrained;
329 if (!check(passed,
330 "the watchdog confused transient admission with a full "
331 "no-progress interval")) {
332 return false;
333 }
334
335 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-watchdog-progress");
336 return true;
337}
338
339struct HeldRequestContext {
340 explicit HeldRequestContext(HostedRequestQueue* queue)
341 : queue(queue), finished(0), result(~0ULL) {}
342
343 HostedRequestQueue* queue;
344 Atomic<size_t> finished;
345 uint64_t result;
346};
347
348struct CompletionRequeueContext {
349 explicit CompletionRequeueContext(Thread* target)
350 : target(target), requeues(0), hookFailures(0) {}
351
352 Thread* target;
353 Atomic<size_t> requeues;
354 Atomic<size_t> hookFailures;
355};
356
357CompletionRequeueContext* g_CompletionRequeueContext = nullptr;
358
359void completionRequeueHook(WaitQueue* queue, Thread* thread, const WaitQueue::Channel& channel,
360 size_t debugState) {
361 CompletionRequeueContext* context = g_CompletionRequeueContext;
362 if (!context || thread != context->target) {
363 return;
364 }
365
366 if (!queue || channel.owner || channel.value || debugState != Thread::CondWait) {
367 context->hookFailures += 1;
368 }
369 context->requeues += 1;
370}
371
372int submitHeldRequest(void* parameter) {
373 HeldRequestContext* context = reinterpret_cast<HeldRequestContext*>(parameter);
374 context->result = context->queue->addRequest(0, HostedRequestQueue::HoldWorker, 42);
375 context->finished += 1;
376 return 0;
377}
378
379bool completionBarrierInterruption(HostedRequestQueue& queue, bool terminal) {
380 HeldRequestContext request(&queue);
381 Thread* caller = new Thread(Scheduler::instance().getKernelProcess(), submitHeldRequest, &request,
382 nullptr, false, true);
383 if (terminal) {
384 caller->setName("hosted RequestQueue terminal caller");
385 } else {
386 caller->setName("hosted RequestQueue signal caller");
387 }
388
389 const bool workerHeld = queue.holdStarted.acquire();
390 const bool initiallyQueued = waitUntilQueued(caller, Thread::CondWait);
391
392 CompletionRequeueContext requeue(caller);
393 g_CompletionRequeueContext = &requeue;
394 WaitQueue::setBeforeBlockHook(completionRequeueHook);
395
396 bool injected = true;
397 if (terminal) {
399 } else {
400 g_RequestQueueSignalCalls = 0;
401 SignalEvent* event =
402 new SignalEvent(reinterpret_cast<uintptr_t>(&hostedRequestQueueSignalHandler),
403 RequestQueueSignalNumber, ~0UL, 0, true, true);
404 injected = caller->sendEvent(event);
405 if (!injected) {
406 delete event;
407 }
408 }
409
410 for (size_t attempt = 0; attempt < WaitAttempts && !requeue.requeues && !request.finished;
411 ++attempt) {
413 }
414
415 WaitQueue::setBeforeBlockHook(nullptr);
416 g_CompletionRequeueContext = nullptr;
417
418 const bool deferred = requeue.requeues == 1 && requeue.hookFailures == 0 && request.finished == 0;
419 const bool signalDelivered = terminal || g_RequestQueueSignalCalls == 1;
420
421 queue.releaseHold.release();
422 const bool joined = caller->join();
423
424 return check(workerHeld && initiallyQueued && injected && deferred && signalDelivered && joined &&
425 request.finished == 1 && request.result == 42,
426 terminal ? "terminal teardown escaped a synchronous request completion"
427 : "a signal returned a synchronous request before completion");
428}
429
430struct RequestQueueDestroyContext {
431 explicit RequestQueueDestroyContext(HostedRequestQueue* queue) : queue(queue), finished(0) {}
432
433 HostedRequestQueue* queue;
434 Atomic<size_t> finished;
435};
436
437struct RequestQueueDrainContext {
438 explicit RequestQueueDrainContext(HostedRequestQueue* queue)
439 : queue(queue), finished(0), result(false) {}
440
441 HostedRequestQueue* queue;
442 Atomic<size_t> finished;
443 Atomic<size_t> result;
444};
445
446struct QueuedRequestContext {
447 explicit QueuedRequestContext(HostedRequestQueue* queue)
448 : queue(queue), waiter(nullptr), published(0), hookFailures(0), finished(0), result(~0ULL) {}
449
450 HostedRequestQueue* queue;
451 Thread* waiter;
452 Atomic<size_t> published;
453 Atomic<size_t> hookFailures;
454 Atomic<size_t> finished;
455 uint64_t result;
456};
457
458QueuedRequestContext* g_QueuedRequestContext = nullptr;
459
460void queuedRequestWaitHook(WaitQueue* queue, Thread* thread, const WaitQueue::Channel& channel,
461 size_t debugState) {
462 QueuedRequestContext* context = g_QueuedRequestContext;
463 if (!context) {
464 return;
465 }
466
467 if (thread != context->waiter) {
468 return;
469 }
470
471 if (!queue || channel.owner || channel.value || debugState != Thread::CondWait) {
472 context->hookFailures += 1;
473 }
474 context->published += 1;
475}
476
477int submitQueuedRequest(void* parameter) {
478 QueuedRequestContext* context = reinterpret_cast<QueuedRequestContext*>(parameter);
479 context->waiter = Processor::information().getCurrentThread();
480 context->result = context->queue->addRequest(0, HostedRequestQueue::CancelLifecycleProbe);
481 context->finished += 1;
482 return 0;
483}
484
485int destroyRequestQueue(void* parameter) {
486 RequestQueueDestroyContext* context = reinterpret_cast<RequestQueueDestroyContext*>(parameter);
487 context->queue->destroy();
488 context->finished += 1;
489 return 0;
490}
491
492int drainRequestQueue(void* parameter) {
493 RequestQueueDrainContext* context = reinterpret_cast<RequestQueueDrainContext*>(parameter);
494 context->result = context->queue->drain();
495 context->finished += 1;
496 return 0;
497}
498
499struct RequestQueueHaltContext {
500 explicit RequestQueueHaltContext(HostedRequestQueue* queue)
501 : queue(queue), finished(0), result(false) {}
502
503 HostedRequestQueue* queue;
504 Atomic<size_t> finished;
505 Atomic<size_t> result;
506};
507
508int haltRequestQueue(void* parameter) {
509 auto* context = reinterpret_cast<RequestQueueHaltContext*>(parameter);
510 context->result = context->queue->halt() ? 1 : 0;
511 context->finished += 1;
512 return 0;
513}
514
515struct PublicationPauseContext {
516 PublicationPauseContext() : calls(0), failures(0), entered(0), release(0) {}
517
518 Atomic<size_t> calls;
519 Atomic<size_t> failures;
520 Semaphore entered;
521 Semaphore release;
522};
523
524void pauseFirstPublication(void* parameter) {
525 auto* context = reinterpret_cast<PublicationPauseContext*>(parameter);
526 if ((context->calls += 1) != 1) {
527 return;
528 }
529
530 context->entered.release();
531 if (!context->release.acquireForCompletion()) {
532 context->failures += 1;
533 }
534}
535
536struct PreallocatedPublicationContext {
537 PreallocatedPublicationContext(HostedRequestQueue* queue,
538 RequestQueue::PreallocatedRequest* request, size_t priority,
539 uint64_t operation, uint64_t value)
540 : queue(queue),
541 request(request),
542 priority(priority),
543 operation(operation),
544 value(value),
545 result(RequestQueue::PreallocatedPublishResult::QueueStopped),
546 finished(0) {}
547
548 HostedRequestQueue* queue;
550 size_t priority;
551 uint64_t operation;
552 uint64_t value;
553 RequestQueue::PreallocatedPublishResult result;
554 Atomic<size_t> finished;
555};
556
557int publishPreallocatedRequest(void* parameter) {
558 auto* context = reinterpret_cast<PreallocatedPublicationContext*>(parameter);
559 context->result = context->queue->publishPreallocated(*context->request, context->priority,
560 context->operation, context->value);
561 context->finished += 1;
562 return 0;
563}
564
565struct AsyncPublicationContext {
566 AsyncPublicationContext(HostedRequestQueue* queue, size_t priority, uint64_t operation,
567 uint64_t value)
568 : queue(queue),
569 priority(priority),
570 operation(operation),
571 value(value),
572 result(0),
573 finished(0) {}
574
575 HostedRequestQueue* queue;
576 size_t priority;
577 uint64_t operation;
578 uint64_t value;
579 uint64_t result;
580 Atomic<size_t> finished;
581};
582
583int publishAsyncRequest(void* parameter) {
584 auto* context = reinterpret_cast<AsyncPublicationContext*>(parameter);
585 context->result =
586 context->queue->addAsyncRequest(context->priority, context->operation, context->value);
587 context->finished += 1;
588 return 0;
589}
590
591struct PreallocatedReleaseContext {
592 PreallocatedReleaseContext(HostedRequestQueue* queue, bool requeueOnce, bool holdFirst = false,
593 bool probeOrdinaryPublication = false)
594 : queue(queue),
595 request(nullptr),
596 requeueOnce(requeueOnce),
597 holdFirst(holdFirst),
598 probeOrdinaryPublication(probeOrdinaryPublication),
599 callbacks(0),
600 requeues(0),
601 ordinaryPublicationRejections(0),
602 failures(0),
603 callbackEntered(0),
604 releaseCallback(0) {}
605
606 HostedRequestQueue* queue;
608 bool requeueOnce;
609 bool holdFirst;
610 bool probeOrdinaryPublication;
611 Atomic<size_t> callbacks;
612 Atomic<size_t> requeues;
613 Atomic<size_t> ordinaryPublicationRejections;
614 Atomic<size_t> failures;
615 Semaphore callbackEntered;
616 Semaphore releaseCallback;
617};
618
619void preallocatedRequestReleased(void* parameter) {
620 auto* context = reinterpret_cast<PreallocatedReleaseContext*>(parameter);
621 const size_t callback = (context->callbacks += 1);
622 if (!context->request || context->request->isAvailable()) {
623 context->failures += 1;
624 }
625
626 if (context->holdFirst && callback == 1) {
627 context->callbackEntered.release();
628 if (!context->releaseCallback.acquireForCompletion()) {
629 context->failures += 1;
630 return;
631 }
632 }
633
634 if (context->probeOrdinaryPublication) {
636 if (context->queue->publishPreallocated(ordinary, 0, HostedRequestQueue::Sum, 20, 22) ==
637 RequestQueue::PreallocatedPublishResult::QueueStopped &&
638 ordinary.isAvailable()) {
639 context->ordinaryPublicationRejections += 1;
640 } else {
641 context->failures += 1;
642 }
643 }
644
645 if (context->request && context->requeueOnce && context->requeues.compareAndSwap(0, 1)) {
646 if (context->queue->republishPreallocatedWhileReleasing(*context->request, 0,
647 HostedRequestQueue::Sum, 20, 22) !=
648 RequestQueue::PreallocatedPublishResult::Accepted) {
649 context->failures += 1;
650 }
651 }
652}
653
654struct ReleaseDestroyReentryContext {
655 explicit ReleaseDestroyReentryContext(HostedRequestQueue* queue)
656 : queue(queue),
657 callbacks(0),
658 failures(0),
659 haltRejections(0),
660 resumeRejections(0),
661 allocationRejections(0),
662 preallocatedRejections(0),
663 entered(0),
664 release(0) {}
665
666 HostedRequestQueue* queue;
668 Atomic<size_t> callbacks;
669 Atomic<size_t> failures;
670 Atomic<size_t> haltRejections;
671 Atomic<size_t> resumeRejections;
672 Atomic<size_t> allocationRejections;
673 Atomic<size_t> preallocatedRejections;
674 Semaphore entered;
675 Semaphore release;
676};
677
678void releaseDuringDestroy(void* parameter) {
679 auto* context = reinterpret_cast<ReleaseDestroyReentryContext*>(parameter);
680 context->callbacks += 1;
681 context->entered.release();
682 if (!context->release.acquireForCompletion()) {
683 context->failures += 1;
684 return;
685 }
686
687 if (!context->queue->halt()) {
688 context->haltRejections += 1;
689 }
690 if (!context->queue->resume()) {
691 context->resumeRejections += 1;
692 }
693 if (context->queue->addRequest(0, HostedRequestQueue::Sum, 20, 22) == 0 &&
694 context->queue->addAsyncRequest(0, HostedRequestQueue::Sum, 20, 22) == 0) {
695 context->allocationRejections += 1;
696 }
697 if (context->queue->publishPreallocated(context->ordinary, 0, HostedRequestQueue::Sum, 20, 22) ==
698 RequestQueue::PreallocatedPublishResult::QueueStopped &&
699 context->ordinary.isAvailable()) {
700 context->preallocatedRejections += 1;
701 }
702}
703
704bool predicateDoorbellRegression() {
705 using Result = RequestQueue::PreallocatedPublishResult;
706
707 HostedRequestQueue queue;
709 queue.initialise();
710
711 // Consume initialise()'s readiness notification so the publication below
712 // is the only possible source of the observed doorbell.
713 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
714 const size_t waitersBefore = queue.requestWaiterCount();
715 const bool interrupts = Processor::getInterrupts();
717 const Result accepted = queue.publishPreallocated(request, 0, HostedRequestQueue::Sum, 20, 22);
718 const Result busy = queue.publishPreallocated(request, 0, HostedRequestQueue::Sum, 19, 23);
719 const bool doorbellPending = PerProcessorScheduler::currentIrqWorkDoorbellPendingForTest();
720 const bool deferred = queue.executions == 0;
721 const size_t waitersAfterPublication = queue.requestWaiterCount();
722 Processor::setInterrupts(interrupts);
723
724 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
725 const bool drained = queue.drain();
726 const size_t executions = queue.executions;
727 const bool available = request.isAvailable();
728 queue.destroy();
729
730 const bool passed =
731 check(waitersBefore == 0 && waitersAfterPublication == 0 && accepted == Result::Accepted &&
732 busy == Result::TokenBusy && doorbellPending && deferred && drained &&
733 executions == 1 && available,
734 "IF=0 publication touched a WaitQueue or escaped the predicate "
735 "doorbell");
736 if (passed) {
737 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-predicate-doorbell");
738 }
739 return passed;
740}
741
742bool preallocatedDuplicateDomainRegression() {
743 using Result = RequestQueue::PreallocatedPublishResult;
744
745 HostedRequestQueue queue;
747 queue.setMatchEqualPayload(true);
748 queue.initialise();
749
750 bool passed = queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
751 queue.holdStarted.acquire();
752
753 const bool interrupts = Processor::getInterrupts();
755 const Result preallocatedAccepted =
756 queue.publishPreallocated(preallocated, 1, HostedRequestQueue::Record, 77, 88);
757 Processor::setInterrupts(interrupts);
758
759 const size_t comparisonsBefore = queue.comparisons;
760 const bool allocatedAccepted = queue.addAsyncRequest(1, HostedRequestQueue::Record, 77, 88) == 1;
761 const size_t comparisonsAfter = queue.comparisons;
762
763 queue.releaseHold.release();
764 const bool drained = queue.drain();
765 const bool executedBoth =
766 queue.recordedCount == 2 && queue.recorded[0] == 77 && queue.recorded[1] == 77;
767 queue.destroy();
768
769 passed &= preallocatedAccepted == Result::Accepted && allocatedAccepted &&
770 comparisonsBefore == 0 && comparisonsAfter == 0 && drained && executedBoth &&
771 preallocated.isAvailable() && queue.cancellations == 0 && queue.executions == 3;
772 passed = check(passed,
773 "allocation-backed duplicate comparison entered the preallocated "
774 "token "
775 "coalescing domain");
776 if (passed) {
777 NOTICE(
778 "HOSTED-WAIT-TEST: PASS "
779 "requestqueue-preallocated-duplicate-domain");
780 }
781 return passed;
782}
783
784bool intakeOrderingRegression() {
785 using Result = RequestQueue::PreallocatedPublishResult;
786
787 HostedRequestQueue queue;
788 RequestQueue::PreallocatedRequest firstPreallocated;
789 RequestQueue::PreallocatedRequest secondPreallocated;
790 queue.initialise();
791
792 bool passed = queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
793 queue.holdStarted.acquire();
794 passed &= queue.addAsyncRequest(3, HostedRequestQueue::Record, 30) == 1;
795 passed &= queue.addAsyncRequest(1, HostedRequestQueue::Record, 10) == 1;
796
797 const bool interrupts = Processor::getInterrupts();
799 const Result firstAccepted =
800 queue.publishPreallocated(firstPreallocated, 1, HostedRequestQueue::Record, 11);
801 Processor::setInterrupts(interrupts);
802
803 passed &= queue.addAsyncRequest(1, HostedRequestQueue::Record, 12) == 1;
805 const Result secondAccepted =
806 queue.publishPreallocated(secondPreallocated, 1, HostedRequestQueue::Record, 13);
807 Processor::setInterrupts(interrupts);
808
809 passed &= queue.addAsyncRequest(2, HostedRequestQueue::Record, 20) == 1;
810 passed &= queue.addAsyncRequest(0, HostedRequestQueue::Record, 1) == 1;
811
812 queue.releaseHold.release();
813 const bool drained = queue.drain();
814 const uint64_t expected[] = {1, 10, 11, 12, 13, 20, 30};
815 bool orderMatches = queue.recordedCount == (sizeof(expected) / sizeof(expected[0]));
816 if (orderMatches) {
817 for (size_t i = 0; i < (sizeof(expected) / sizeof(expected[0])); ++i) {
818 orderMatches &= queue.recorded[i] == expected[i];
819 }
820 }
821
822 passed &= firstAccepted == Result::Accepted && secondAccepted == Result::Accepted && drained &&
823 orderMatches && queue.recordFailures == 0 && queue.executions == 8 &&
824 firstPreallocated.isAvailable() && secondPreallocated.isAvailable();
825 queue.destroy();
826
827 passed = check(passed, "the shared intake lost same-priority FIFO or strict priority order");
828 if (passed) {
829 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-intake-ordering");
830 }
831 return passed;
832}
833
834bool haltRetentionRegression() {
835 using Result = RequestQueue::PreallocatedPublishResult;
836
837 HostedRequestQueue queue;
840 queue.initialise();
841
842 bool passed = queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
843 queue.holdStarted.acquire();
844 passed &= queue.addAsyncRequest(1, HostedRequestQueue::Record, 40) == 1;
845
846 const bool interrupts = Processor::getInterrupts();
848 const Result retainedResult =
849 queue.publishPreallocated(retained, 1, HostedRequestQueue::Record, 41);
850 Processor::setInterrupts(interrupts);
851
852 RequestQueueHaltContext haltContext(&queue);
853 Thread* halter = new Thread(Scheduler::instance().getKernelProcess(), haltRequestQueue,
854 &haltContext, nullptr, false, true);
855 halter->setName("hosted RequestQueue halt retention regression");
856
857 bool stopping = false;
858 for (size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
859 stopping = queue.getLifecycleState() == RequestQueue::LifecycleState::Stopping;
860 if (stopping) {
861 break;
862 }
864 }
865
866 Result rejectedResult = Result::Accepted;
867 if (stopping) {
869 rejectedResult = queue.publishPreallocated(rejected, 1, HostedRequestQueue::Record, 42);
870 Processor::setInterrupts(interrupts);
871 }
872 const bool retainedBeforeRelease = queue.recordedCount == 0 && !retained.isAvailable();
873 queue.releaseHold.release();
874 const bool halterJoined = halter->joinForCompletion();
875 const bool haltFinished = haltContext.finished == 1;
876 const bool haltSucceeded = haltContext.result == 1;
877 const bool stopped = queue.getLifecycleState() == RequestQueue::LifecycleState::Stopped;
878 const bool retainedWhileStopped = queue.recordedCount == 0 && !retained.isAvailable();
879
880 const bool resumed = queue.resume();
881 const bool drained = resumed && queue.drain();
882 const bool resumedInOrder =
883 queue.recordedCount == 2 && queue.recorded[0] == 40 && queue.recorded[1] == 41;
884 queue.destroy();
885
886 passed &= check(retainedResult == Result::Accepted,
887 "halt retention setup did not admit preallocated work");
888 passed &= check(stopping, "halt did not publish the Stopping lifecycle state");
889 passed &= check(rejectedResult == Result::QueueStopped && rejected.isAvailable(),
890 "halt did not close preallocated admission");
891 passed &= check(retainedBeforeRelease,
892 "accepted work ran or was released before the active worker exited");
893 passed &= check(halterJoined, "halt regression thread could not be joined");
894 passed &= check(haltFinished, "halt regression thread did not return exactly once");
895 passed &= check(haltSucceeded, "halt rejected its non-worker caller");
896 passed &= check(stopped, "successful halt did not publish Stopped");
897 passed &= check(retainedWhileStopped,
898 "halt executed or released accepted work instead of retaining it");
899 passed &= check(resumed, "halted queue rejected resume");
900 passed &= check(drained, "resumed queue did not drain retained work");
901 passed &=
902 check(resumedInOrder, "resumed queue did not execute retained work in publication order");
903 passed &= check(retained.isAvailable(), "resumed preallocated work did not release its token");
904 passed &= check(queue.recordFailures == 0 && queue.executions == 3,
905 "halt retention executed an unexpected request set");
906 if (passed) {
907 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-halt-retention");
908 }
909 return passed;
910}
911
912bool rejectedPublisherDestroyWaitRegression() {
913 using Result = RequestQueue::PreallocatedPublishResult;
914
915 HostedRequestQueue queue;
917 PublicationPauseContext pause;
918 queue.setAfterPreallocatedAdmissionHookForTest(pauseFirstPublication, &pause);
919
920 PreallocatedPublicationContext publication(&queue, &request, 1, HostedRequestQueue::Record, 16);
921 Thread* publisher = new Thread(Scheduler::instance().getKernelProcess(),
922 publishPreallocatedRequest, &publication, nullptr, false, true);
923 publisher->setName("hosted RequestQueue rejected publication pause");
924 const bool paused = pause.entered.acquire();
925
926 RequestQueueDestroyContext destroyContext(&queue);
927 Thread* destroyer = new Thread(Scheduler::instance().getKernelProcess(), destroyRequestQueue,
928 &destroyContext, nullptr, false, true);
929 destroyer->setName("hosted RequestQueue stopped publication drain regression");
930
931 bool drainWaitObserved = false;
932 for (size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
933 drainWaitObserved = queue.publisherDrainRetriesForTest() != 0;
934 if (drainWaitObserved) {
935 break;
936 }
938 }
939
940 const bool destroyWaited = drainWaitObserved && !publication.finished &&
941 !destroyContext.finished && request.isAvailable();
942 pause.release.release();
943 const bool publisherJoined = publisher->join();
944 const bool destroyerJoined = destroyer->join();
945 queue.setAfterPreallocatedAdmissionHookForTest(nullptr, nullptr);
946
947 const bool passed = check(
948 paused && destroyWaited && publisherJoined && destroyerJoined && publication.finished == 1 &&
949 publication.result == Result::QueueStopped && destroyContext.finished == 1 &&
950 request.isAvailable() && pause.calls == 1 && pause.failures == 0 &&
951 queue.executions == 0 && queue.cancellations == 0 && queue.recordedCount == 0,
952 "a never-started queue outran a rejected publisher in its closed-gate "
953 "lifetime count");
954 if (passed) {
955 NOTICE(
956 "HOSTED-WAIT-TEST: PASS "
957 "requestqueue-rejected-publication-close-wait");
958 }
959 return passed;
960}
961
962bool preallocatedAdmissionCloseWaitRegression() {
963 using Result = RequestQueue::PreallocatedPublishResult;
964
965 HostedRequestQueue queue;
967 PublicationPauseContext pause;
968 queue.initialise();
969 queue.setAfterPreallocatedAdmissionHookForTest(pauseFirstPublication, &pause);
970
971 PreallocatedPublicationContext publication(&queue, &request, 1, HostedRequestQueue::Record, 17);
972 Thread* publisher = new Thread(Scheduler::instance().getKernelProcess(),
973 publishPreallocatedRequest, &publication, nullptr, false, true);
974 publisher->setName("hosted RequestQueue admitted publication pause");
975 const bool paused = pause.entered.acquire();
976
977 RequestQueueDestroyContext destroyContext(&queue);
978 Thread* destroyer = new Thread(Scheduler::instance().getKernelProcess(), destroyRequestQueue,
979 &destroyContext, nullptr, false, true);
980 destroyer->setName("hosted RequestQueue publication drain regression");
981
982 bool stopping = false;
983 for (size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
984 stopping = queue.getLifecycleState() == RequestQueue::LifecycleState::Stopping;
985 if (stopping) {
986 break;
987 }
989 }
990
991 const bool destroyWaited =
992 stopping && !publication.finished && !destroyContext.finished && request.isAvailable();
993 pause.release.release();
994 const bool publisherJoined = publisher->join();
995 const bool destroyerJoined = destroyer->join();
996 queue.setAfterPreallocatedAdmissionHookForTest(nullptr, nullptr);
997
998 const bool passed = check(
999 paused && destroyWaited && publisherJoined && destroyerJoined && publication.finished == 1 &&
1000 publication.result == Result::Accepted && destroyContext.finished == 1 &&
1001 request.isAvailable() && pause.calls == 1 && pause.failures == 0 &&
1002 queue.executions == 0 && queue.cancellations == 1 && queue.recordedCount == 0,
1003 "destroy outran an admitted preallocated publisher paused before its "
1004 "closed-gate observation");
1005 if (passed) {
1006 NOTICE(
1007 "HOSTED-WAIT-TEST: PASS "
1008 "requestqueue-preallocated-admission-close-wait");
1009 }
1010 return passed;
1011}
1012
1013bool intakeTransientPublicationRegression() {
1014 using Result = RequestQueue::PreallocatedPublishResult;
1015
1016 HostedRequestQueue queue;
1018 PublicationPauseContext pause;
1019 queue.initialise();
1020
1021 bool passed = queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
1022 queue.holdStarted.acquire();
1023 passed &= queue.addAsyncRequest(3, HostedRequestQueue::Record, 30) == 1;
1024 queue.setAfterIntakeExchangeHookForTest(pauseFirstPublication, &pause);
1025
1026 PreallocatedPublicationContext first(&queue, &preallocated, 1, HostedRequestQueue::RecordHold,
1027 10);
1028 Thread* preallocatedPublisher =
1029 new Thread(Scheduler::instance().getKernelProcess(), publishPreallocatedRequest, &first,
1030 nullptr, false, true);
1031 preallocatedPublisher->setName("hosted RequestQueue incomplete MPSC publisher");
1032 const bool paused = pause.entered.acquire();
1033
1034 AsyncPublicationContext second(&queue, 1, HostedRequestQueue::Record, 11);
1035 Thread* ordinaryPublisher = new Thread(Scheduler::instance().getKernelProcess(),
1036 publishAsyncRequest, &second, nullptr, false, true);
1037 ordinaryPublisher->setName("hosted RequestQueue transient duplicate scanner");
1038
1039 queue.releaseHold.release();
1040 bool bothRetried = false;
1041 for (size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
1042 bothRetried = queue.workerTransientRetriesForTest() && queue.guardedTransientRetriesForTest();
1043 if (bothRetried) {
1044 break;
1045 }
1047 }
1048
1049 const bool noOvertake = queue.recordedCount == 0 && !first.finished && !second.finished;
1050 pause.release.release();
1051 const bool preallocatedJoined = preallocatedPublisher->join();
1052 const bool firstHeld = queue.holdStarted.acquire();
1053 const bool ordinaryJoined = ordinaryPublisher->join();
1054 queue.setAfterIntakeExchangeHookForTest(nullptr, nullptr);
1055
1056 const bool firstStillLeads = firstHeld && queue.recordedCount == 1 && queue.recorded[0] == 10;
1057 queue.releaseHold.release();
1058 const bool drained = queue.drain();
1059 const uint64_t expected[] = {10, 11, 30};
1060 bool orderMatches = queue.recordedCount == (sizeof(expected) / sizeof(expected[0]));
1061 if (orderMatches) {
1062 for (size_t i = 0; i < (sizeof(expected) / sizeof(expected[0])); ++i) {
1063 orderMatches &= queue.recorded[i] == expected[i];
1064 }
1065 }
1066 queue.destroy();
1067
1068 passed &= paused && bothRetried && noOvertake && preallocatedJoined && ordinaryJoined &&
1069 first.result == Result::Accepted && first.finished == 1 && second.result == 1 &&
1070 second.finished == 1 && firstStillLeads && drained && orderMatches &&
1071 preallocated.isAvailable() && pause.calls == 2 && pause.failures == 0 &&
1072 queue.executions == 4 && queue.cancellations == 0 && queue.recordFailures == 0;
1073 passed = check(passed,
1074 "an incomplete MPSC link was treated as empty or lost priority/FIFO "
1075 "ordering");
1076 if (passed) {
1077 NOTICE(
1078 "HOSTED-WAIT-TEST: PASS "
1079 "requestqueue-intake-transient-publication");
1080 }
1081 return passed;
1082}
1083
1084bool preallocatedRequestRegressions() {
1085 using Result = RequestQueue::PreallocatedPublishResult;
1086
1087 bool passed = true;
1088 {
1089 HostedRequestQueue queue;
1090 PreallocatedReleaseContext releaseContext(&queue, false, true);
1091 RequestQueue::PreallocatedRequest released(preallocatedRequestReleased, &releaseContext);
1092 releaseContext.request = &released;
1093 queue.setMaxAsyncRequests(0);
1094 queue.initialise();
1095
1096 passed &= check(queue.republishPreallocatedWhileReleasing(
1097 released, 0, HostedRequestQueue::Sum) == Result::TokenBusy &&
1098 released.isAvailable(),
1099 "an idle token accepted release-only republication");
1100 passed &= check(
1101 queue.publishPreallocated(released, 0, HostedRequestQueue::Sum, 19, 23) == Result::Accepted,
1102 "release-callback request was not admitted");
1103 passed &= check(releaseContext.callbackEntered.acquire(),
1104 "release callback did not enter its drain handoff");
1105
1106 RequestQueueDrainContext drainContext(&queue);
1107 Thread* drainer = new Thread(Scheduler::instance().getKernelProcess(), drainRequestQueue,
1108 &drainContext, nullptr, false, true);
1109 drainer->setName("hosted preallocated release drain regression");
1110 const bool drainWaitPublished = waitUntilQueued(drainer, Thread::CallbackDrain);
1111 passed &= check(drainWaitPublished && drainContext.finished == 0,
1112 "drain observed a transient empty queue during release");
1113
1114 passed &= check(queue.republishPreallocatedWhileReleasing(released, 0, HostedRequestQueue::Sum,
1115 20, 22) == Result::Accepted &&
1116 !released.isAvailable(),
1117 "a producer could not win the final release handoff");
1118 releaseContext.releaseCallback.release();
1119 passed &= check(drainer->join() && drainContext.result && released.isAvailable() &&
1120 releaseContext.callbacks == 2 && releaseContext.requeues == 0 &&
1121 releaseContext.failures == 0 && queue.executions == 2,
1122 "producer-assisted release work escaped drain");
1123 queue.destroy();
1124 }
1125
1126 {
1127 HostedRequestQueue queue;
1128 PreallocatedReleaseContext releaseContext(&queue, true, false, true);
1129 RequestQueue::PreallocatedRequest released(preallocatedRequestReleased, &releaseContext);
1130 releaseContext.request = &released;
1131 queue.setMaxAsyncRequests(0);
1132 queue.initialise();
1133
1134 passed &= check(queue.publishPreallocated(released, 0, HostedRequestQueue::Sum, 19, 23) ==
1135 Result::Accepted &&
1136 queue.drain() && released.isAvailable() && releaseContext.callbacks == 2 &&
1137 releaseContext.requeues == 1 &&
1138 releaseContext.ordinaryPublicationRejections == 2 &&
1139 releaseContext.failures == 0 && queue.executions == 2,
1140 "release callback did not reject ordinary publication while "
1141 "preserving the explicit Releasing-token handoff");
1142 queue.destroy();
1143 }
1144
1145 {
1146 HostedRequestQueue queue;
1149
1150 passed &= check(queue.publishPreallocated(request, 0, HostedRequestQueue::Sum, 20, 22) ==
1151 Result::QueueStopped &&
1152 request.isAvailable(),
1153 "a stopped queue claimed a preallocated token");
1154 passed &= check(queue.publishPreallocated(request, REQUEST_QUEUE_NUM_PRIORITIES,
1155 HostedRequestQueue::Sum) == Result::InvalidPriority &&
1156 request.isAvailable(),
1157 "an invalid preallocated priority claimed its token");
1158
1159 queue.initialise();
1160 const bool interrupts = Processor::getInterrupts();
1161 const size_t comparisonsBefore = queue.comparisons;
1163 const Result accepted = queue.publishPreallocated(request, 0, HostedRequestQueue::Sum, 20, 22);
1164 const Result busy = queue.publishPreallocated(request, 0, HostedRequestQueue::Sum, 19, 23);
1165 const bool deferred = queue.executions == 0;
1166 Processor::setInterrupts(interrupts);
1167 PerProcessorScheduler::serviceCurrentIrqWorkDoorbellForTest();
1168
1169 passed &= check(accepted == Result::Accepted && busy == Result::TokenBusy && deferred &&
1170 static_cast<size_t>(queue.comparisons) == comparisonsBefore,
1171 "preallocated IF=0 enqueue ran early or called virtual duplicate "
1172 "detection");
1173 passed &= check(queue.drain() && request.isAvailable() && queue.executions == 1,
1174 "executed preallocated work did not release its token");
1175
1176 passed &= check(queue.publishPreallocated(request, 0, HostedRequestQueue::PreallocatedHold,
1177 42) == Result::Accepted &&
1178 queue.holdStarted.acquire(),
1179 "preallocated hold request was not admitted");
1180 passed &= check(queue.publishPreallocated(request, 0, HostedRequestQueue::PreallocatedHold,
1181 42) == Result::TokenBusy,
1182 "a published preallocated token was admitted twice");
1183 queue.setMaxAsyncRequests(1);
1184 const size_t cancellationsBeforeCapacity = queue.cancellations;
1185 passed &= check(
1186 queue.publishPreallocated(capacity, 0, HostedRequestQueue::Sum, 1, 2) == Result::Accepted &&
1187 !capacity.isAvailable() &&
1188 queue.addAsyncRequest(0, HostedRequestQueue::Sum, 3, 4) == 0 &&
1189 queue.cancellations == cancellationsBeforeCapacity + 1,
1190 "preallocated work depended on allocation backlog capacity");
1191 queue.setMaxAsyncRequests(256);
1192 queue.releaseHold.release();
1193 passed &= check(
1194 queue.drain() && request.isAvailable() && capacity.isAvailable() && queue.executions == 3,
1195 "over-capacity preallocated work did not drain exactly once");
1196
1197 passed &= check(
1198 queue.publishPreallocated(capacity, 0, HostedRequestQueue::Sum, 19, 23) == Result::Accepted,
1199 "a drained preallocated token was not admitted again");
1200 passed &= check(queue.drain() && capacity.isAvailable() && queue.executions == 4,
1201 "a drained preallocated token could not be reused");
1202 queue.destroy();
1203 }
1204
1205 {
1206 HostedRequestQueue queue;
1207 PreallocatedReleaseContext releaseContext(&queue, false);
1208 RequestQueue::PreallocatedRequest cancelled(preallocatedRequestReleased, &releaseContext);
1209 releaseContext.request = &cancelled;
1210 queue.initialise();
1211 passed &= check(queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
1212 queue.holdStarted.acquire(),
1213 "teardown setup did not hold the worker");
1214 passed &= check(queue.publishPreallocated(cancelled, 0, HostedRequestQueue::CancelQueued) ==
1215 Result::Accepted,
1216 "teardown setup did not queue the preallocated token");
1217
1218 RequestQueueDestroyContext destroyContext(&queue);
1219 Thread* destroyer = new Thread(Scheduler::instance().getKernelProcess(), destroyRequestQueue,
1220 &destroyContext, nullptr, false, true);
1221 destroyer->setName("hosted preallocated request destroy regression");
1222 while (queue.getLifecycleState() != RequestQueue::LifecycleState::Stopping) {
1224 }
1225 queue.releaseHold.release();
1226
1227 passed &= check(destroyer->join() && destroyContext.finished == 1 && cancelled.isAvailable() &&
1228 queue.cancellations == 1 && queue.queuedCancellations == 1 &&
1229 releaseContext.callbacks == 1 && releaseContext.failures == 0,
1230 "destroy did not cancel and release queued preallocated work");
1231 passed &= check(
1232 queue.publishPreallocated(cancelled, 0, HostedRequestQueue::Sum) == Result::QueueStopped &&
1233 cancelled.isAvailable(),
1234 "a stopped queue retained a reused preallocated token");
1235 }
1236
1237 if (passed) {
1238 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-preallocated-token");
1239 }
1240 return passed;
1241}
1242
1243bool releaseCallbackDestroyReentryRegression() {
1244 using Result = RequestQueue::PreallocatedPublishResult;
1245
1246 HostedRequestQueue queue;
1247 ReleaseDestroyReentryContext releaseContext(&queue);
1248 RequestQueue::PreallocatedRequest released(releaseDuringDestroy, &releaseContext);
1249 queue.initialise();
1250
1251 const bool accepted =
1252 queue.publishPreallocated(released, 0, HostedRequestQueue::Sum, 20, 22) == Result::Accepted;
1253 const bool callbackEntered = accepted && releaseContext.entered.acquire();
1254
1255 RequestQueueDestroyContext destroyContext(&queue);
1256 Thread* destroyer = new Thread(Scheduler::instance().getKernelProcess(), destroyRequestQueue,
1257 &destroyContext, nullptr, false, true);
1258 destroyer->setName("hosted RequestQueue release callback destroy regression");
1259
1260 bool stopping = false;
1261 for (size_t attempt = 0; attempt < WaitAttempts; ++attempt) {
1262 stopping = queue.getLifecycleState() == RequestQueue::LifecycleState::Stopping;
1263 if (stopping) {
1264 break;
1265 }
1267 }
1268
1269 const bool destroyWaitedForRelease = stopping && !destroyContext.finished;
1270 releaseContext.release.release();
1271 const bool destroyerJoined = destroyer->joinForCompletion();
1272
1273 const bool passed =
1274 check(callbackEntered && destroyWaitedForRelease && destroyerJoined &&
1275 destroyContext.finished == 1 && releaseContext.callbacks == 1 &&
1276 releaseContext.failures == 0 && releaseContext.haltRejections == 1 &&
1277 releaseContext.resumeRejections == 1 && releaseContext.allocationRejections == 1 &&
1278 releaseContext.preallocatedRejections == 1 && released.isAvailable() &&
1279 releaseContext.ordinary.isAvailable() && queue.executions == 1 &&
1280 queue.cancellations == 0 &&
1281 queue.getLifecycleState() == RequestQueue::LifecycleState::Destroyed,
1282 "a worker release callback re-entered queue lifecycle or publication "
1283 "while destroy waited for it");
1284 if (passed) {
1285 NOTICE(
1286 "HOSTED-WAIT-TEST: PASS "
1287 "requestqueue-release-callback-destroy-reentry");
1288 }
1289 return passed;
1290}
1291} // namespace
1292
1293bool runHostedRequestQueueRegressions() {
1294 Thread* current = Processor::information().getCurrentThread();
1295 if (!check(RoundRobin::runHostedIntrusiveQueueRegressions(current),
1296 "intrusive scheduler ready-queue invariants failed")) {
1297 return false;
1298 }
1299 NOTICE("HOSTED-WAIT-TEST: PASS scheduler-intrusive-ready-queue");
1300
1301 if (!ataRequestIdentityRegression()) {
1302 return false;
1303 }
1304
1305 if (!preallocatedRequestRegressions()) {
1306 return false;
1307 }
1308
1309 if (!releaseCallbackDestroyReentryRegression()) {
1310 return false;
1311 }
1312
1313 if (!predicateDoorbellRegression()) {
1314 return false;
1315 }
1316
1317 if (!preallocatedDuplicateDomainRegression()) {
1318 return false;
1319 }
1320
1321 if (!intakeOrderingRegression()) {
1322 return false;
1323 }
1324
1325 if (!haltRetentionRegression()) {
1326 return false;
1327 }
1328
1329 if (!rejectedPublisherDestroyWaitRegression()) {
1330 return false;
1331 }
1332
1333 if (!preallocatedAdmissionCloseWaitRegression()) {
1334 return false;
1335 }
1336
1337 if (!intakeTransientPublicationRegression()) {
1338 return false;
1339 }
1340
1341 if (!watchdogProgressRegression()) {
1342 return false;
1343 }
1344
1345 {
1346 HostedRequestQueue activeRequestQueue;
1347 activeRequestQueue.initialise();
1348 const bool accepted =
1349 activeRequestQueue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1;
1350 const bool active = accepted && activeRequestQueue.holdStarted.acquire();
1351
1352 const Time::Timestamp deadline =
1353 Time::getTicks() + Time::Multiplier::Second + (100 * Time::Multiplier::Millisecond);
1354 while (Time::getTicks() < deadline) {
1356 }
1357
1358 activeRequestQueue.releaseHold.release();
1359 const bool drained = activeRequestQueue.drain();
1360 activeRequestQueue.destroy();
1361 if (!check(accepted && active && drained &&
1362 activeRequestQueue.executions == static_cast<size_t>(1),
1363 "an active request was misclassified as queued backlog")) {
1364 return false;
1365 }
1366 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-active-not-backlog");
1367 }
1368
1369 {
1370 HostedRequestQueue ownedWorkerQueue;
1371 ownedWorkerQueue.initialise();
1372 Thread* idleWorker = ownedWorkerQueue.workerThread();
1373 if (idleWorker) {
1375 }
1376 const bool idleTerminalSafe =
1377 idleWorker && ownedWorkerQueue.addRequest(0, HostedRequestQueue::Sum, 20, 22) == 42;
1378
1379 const bool halted = ownedWorkerQueue.halt();
1380 const bool resumed = ownedWorkerQueue.resume();
1381 Thread* activeWorker = ownedWorkerQueue.workerThread();
1382 const bool held =
1383 ownedWorkerQueue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1 &&
1384 ownedWorkerQueue.holdStarted.acquire();
1385 if (activeWorker) {
1387 }
1388 ownedWorkerQueue.releaseHold.release();
1389 const bool activeTerminalSafe =
1390 held && activeWorker &&
1391 ownedWorkerQueue.addRequest(0, HostedRequestQueue::Sum, 19, 23) == 42;
1392 ownedWorkerQueue.destroy();
1393
1394 if (!check(idleTerminalSafe && halted && resumed && activeTerminalSafe,
1395 "queue-owned worker termination orphaned work")) {
1396 return false;
1397 }
1398 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-worker-terminal-ownership");
1399 }
1400
1401 HostedRequestQueue queue;
1402 bool passed = true;
1403
1404 passed &= check(queue.addRequest(0, HostedRequestQueue::Sum, 1, 2) == 0,
1405 "a stopped queue accepted work before initialise");
1406
1407 queue.initialise();
1408 passed &= check(queue.getLifecycleState() == RequestQueue::LifecycleState::Accepting,
1409 "initialise did not start the worker");
1410 passed &= check(
1411 queue.addRequest(REQUEST_QUEUE_NUM_PRIORITIES - 1, HostedRequestQueue::Sum, 40, 2) == 42,
1412 "synchronous completion returned the wrong result");
1413 passed &=
1414 check(queue.addRequest(REQUEST_QUEUE_NUM_PRIORITIES, HostedRequestQueue::Sum, 1, 2) == 0,
1415 "an invalid priority was accepted");
1416
1417 passed &= check(queue.halt(), "halt rejected its non-worker caller");
1418 passed &= check(queue.getLifecycleState() == RequestQueue::LifecycleState::Stopped,
1419 "halt did not join the worker");
1420 passed &= check(queue.addRequest(0, HostedRequestQueue::Sum, 3, 4) == 0,
1421 "a halted queue accepted work");
1422
1423 passed &= check(queue.resume(), "resume rejected a halted queue");
1424 passed &= check(queue.addRequest(0, HostedRequestQueue::Sum, 20, 22) == 42,
1425 "the resumed worker did not complete synchronous work");
1426 passed &= check(queue.addRequest(0, HostedRequestQueue::SelfSubmit, 19, 23) == 42,
1427 "worker self-submission did not execute inline");
1428 passed &= check(queue.addRequest(0, HostedRequestQueue::SelfHalt, 77) == 77 &&
1429 queue.selfHaltRejections == 1 &&
1430 queue.getLifecycleState() == RequestQueue::LifecycleState::Accepting &&
1431 queue.addRequest(0, HostedRequestQueue::Sum, 20, 22) == 42,
1432 "worker self-halt did not reject without stopping the queue");
1433
1434 passed &= completionBarrierInterruption(queue, false);
1435 passed &= completionBarrierInterruption(queue, true);
1436
1437 passed &= check(queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1,
1438 "the drain test request was not accepted");
1439 passed &= check(queue.holdStarted.acquire(), "the drain test worker did not enter its request");
1440 RequestQueueDrainContext drainContext(&queue);
1441 Thread* drainer = new Thread(Scheduler::instance().getKernelProcess(), drainRequestQueue,
1442 &drainContext, nullptr, false, true);
1443 drainer->setName("hosted RequestQueue drain regression");
1444 const bool drainWaitPublished = waitUntilQueued(drainer, Thread::CallbackDrain);
1445 const bool drainBlocked = drainContext.finished == 0;
1446 queue.releaseHold.release();
1447 passed &= check(drainer->join() && drainWaitPublished && drainBlocked &&
1448 drainContext.finished == 1 && drainContext.result,
1449 "drain returned before all published work completed");
1450
1451 passed &= check(queue.addAsyncRequest(0, HostedRequestQueue::HoldWorker, 42) == 1,
1452 "the blocking worker request was not accepted");
1453 passed &= check(queue.holdStarted.acquire(), "the worker did not enter its held request");
1454
1455 QueuedRequestContext queuedContext(&queue);
1456 g_QueuedRequestContext = &queuedContext;
1457 WaitQueue::setBeforeBlockHook(queuedRequestWaitHook);
1458 Thread* submitter = new Thread(Scheduler::instance().getKernelProcess(), submitQueuedRequest,
1459 &queuedContext, nullptr, false, true);
1460 submitter->setName("hosted RequestQueue queued caller");
1461
1462 while (queuedContext.published < 1) {
1464 }
1465 WaitQueue::setBeforeBlockHook(nullptr);
1466 g_QueuedRequestContext = nullptr;
1467
1468 RequestQueueDestroyContext destroyContext(&queue);
1469 Thread* destroyer = new Thread(Scheduler::instance().getKernelProcess(), destroyRequestQueue,
1470 &destroyContext, nullptr, false, true);
1471 destroyer->setName("hosted RequestQueue destroy regression");
1472
1473 while (queue.getLifecycleState() != RequestQueue::LifecycleState::Stopping) {
1475 }
1476 queue.releaseHold.release();
1477
1478 passed &= check(destroyer->join(), "the concurrent destroy worker could not be joined");
1479 passed &= check(submitter->join(), "the cancelled synchronous caller could not be joined");
1480 passed &= check(destroyContext.finished == 1, "destroy did not complete exactly once");
1481 passed &= check(queuedContext.published == 1 && queuedContext.hookFailures == 0,
1482 "the queued synchronous caller did not publish one completion wait");
1483 passed &= check(queuedContext.finished == 1 && queuedContext.result == 0,
1484 "destroy did not wake and reject the queued synchronous caller");
1485 passed &=
1486 check(queue.cancelHaltRejections == 1 && queue.cancelResumeRejections == 1 &&
1487 queue.cancelPublicationRejections == 2 && queue.cancelPreallocatedRejections == 1 &&
1488 queue.cancelPreallocated.isAvailable(),
1489 "destroy cancellation did not reject recursive lifecycle entry");
1490
1491 queue.destroy();
1492 passed &= check(queue.getLifecycleState() == RequestQueue::LifecycleState::Destroyed,
1493 "destroy was not idempotent");
1495 const bool resumeRejected = !queue.resume();
1496 const bool interrupts = Processor::getInterrupts();
1498 const RequestQueue::PreallocatedPublishResult postDestroyResult =
1499 queue.publishPreallocated(postDestroy, 0, HostedRequestQueue::Sum, 20, 22);
1500 Processor::setInterrupts(interrupts);
1501 passed &= check(resumeRejected &&
1502 queue.getLifecycleState() == RequestQueue::LifecycleState::Destroyed &&
1503 queue.addRequest(0, HostedRequestQueue::Sum, 20, 22) == 0 &&
1504 postDestroyResult == RequestQueue::PreallocatedPublishResult::QueueStopped &&
1505 postDestroy.isAvailable(),
1506 "destroyed queue reopened publication");
1507 queue.destroy();
1508 passed &= check(queue.getLifecycleState() == RequestQueue::LifecycleState::Destroyed,
1509 "repeated destroy changed the terminal lifecycle state");
1510 passed &= check(queue.executions == 10, "the worker executed an unexpected number of requests");
1511 passed &= check(queue.cancellations == 5, "rejected requests did not release their payloads");
1512 passed &= check(queue.queuedCancellations == 1,
1513 "destroy did not cancel the queued synchronous request");
1514
1515 if (passed) {
1516 NOTICE("HOSTED-WAIT-TEST: PASS requestqueue-lifecycle");
1517 }
1518 return passed;
1519}
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 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:226
void release(size_t n=1)
Definition Semaphore.cc:546
MUST_USE_RESULT bool acquireForCompletion(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
Definition Semaphore.cc:369
bool acquire(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
Definition Semaphore.cc:352
static constexpr size_t getPageSize() noexcept
Definition TargetInfo.h:40
void setUnwindState(UnwindType ut)
Definition Thread.cc:3628
@ TerminateThread
Exit only this thread during Process exit.
Definition Thread.h:515
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3184
bool joinForCompletion()
Definition Thread.cc:2771
bool join()
Definition Thread.cc:2767
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:570
bool sendEvent(Event *pEvent)
Definition Thread.cc:1158