The Pedigree Project 0.1
timer-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/machine/Machine.h"
11#include "pedigree/kernel/machine/Timer.h"
12#include "pedigree/kernel/machine/TimerHandler.h"
13#include "pedigree/kernel/machine/TimerHandlerRegistry.h"
14#include "pedigree/kernel/process/Event.h"
15#include "pedigree/kernel/process/PerProcessorScheduler.h"
16#include "pedigree/kernel/process/RelayEvent.h"
17#include "pedigree/kernel/process/Scheduler.h"
18#include "pedigree/kernel/process/Semaphore.h"
19#include "pedigree/kernel/process/Thread.h"
20#include "pedigree/kernel/process/eventNumbers.h"
21#include "pedigree/kernel/processor/Processor.h"
22#include "pedigree/kernel/time/Time.h"
23
24#include "system/kernel/machine/hosted/Timer.h"
25
26namespace {
27constexpr size_t TimerTestEventNumber = EventNumbers::UserStart + 1;
28constexpr size_t CullTestEventNumber = EventNumbers::UserStart + 2;
29constexpr size_t RelayTestEventNumber = EventNumbers::UserStart + 3;
30constexpr size_t DispositionAEventNumber = EventNumbers::UserStart + 4;
31constexpr size_t DispositionBEventNumber = EventNumbers::UserStart + 5;
32constexpr size_t AlarmLinearizationEventNumber = EventNumbers::UserStart + 6;
33
34Atomic<size_t> g_CullEventDestructions(0);
35Atomic<size_t> g_RelayDisposition(0);
36Atomic<size_t> g_RelayCallbacks(0);
37Atomic<size_t> g_DispositionACalls(0);
38Atomic<size_t> g_DispositionBCalls(0);
39Event* g_DispositionAEvent = nullptr;
40Event* g_DispositionBEvent = nullptr;
41
42bool check(bool condition, const char* test, const char* detail);
43
44bool hasTimerCallbackDrainWait(Thread* thread, TimerHandler* handler) {
45 Thread::WaitDebugInfo wait = {};
46 uintptr_t debugAddress = 0;
47 return thread && thread->getWaitDebugInfo(wait) && wait.queue && wait.channelOwner &&
48 wait.channelValue && wait.queued && wait.reason == WaitQueue::WakeReason::Waiting &&
49 thread->getDebugState(debugAddress) == Thread::CallbackDrain &&
50 debugAddress == reinterpret_cast<uintptr_t>(handler);
51}
52
53struct RegistryDispatchContext;
54RegistryDispatchContext* g_TimerRegistryDispatchContext = nullptr;
55void dispatchTimerWhileWriterLocked();
56
57class RegistryDispatchTimerHandler : public TimerHandler {
58 public:
59 explicit RegistryDispatchTimerHandler(RegistryDispatchContext& context) : m_Context(context) {}
60
61 void timer(uint64_t delta) override;
62
63 private:
64 RegistryDispatchContext& m_Context;
65};
66
67struct RegistryDispatchContext {
68 explicit RegistryDispatchContext(Timer* timer)
69 : timer(timer),
70 handler(*this),
71 calls(0),
72 hookCalls(0),
73 admitted(0),
74 unregisterSucceeded(0),
75 mutationRequested(0),
76 delta(0) {}
77
78 Timer* timer;
79 RegistryDispatchTimerHandler handler;
80 Atomic<size_t> calls;
81 Atomic<size_t> hookCalls;
82 Atomic<size_t> admitted;
83 Atomic<size_t> unregisterSucceeded;
84 Atomic<size_t> mutationRequested;
85 uint64_t delta;
86};
87
88void RegistryDispatchTimerHandler::timer(uint64_t delta) {
89 m_Context.calls += 1;
90 if (m_Context.mutationRequested.compareAndSwap(1, 2)) {
91 m_Context.delta = delta;
92 HostedTimer::withHandlerMutationLockForTest(dispatchTimerWhileWriterLocked);
93 m_Context.mutationRequested = 3;
94 }
95}
96
97void dispatchTimerWhileWriterLocked() {
98 RegistryDispatchContext* context = g_TimerRegistryDispatchContext;
99 if (!context) {
100 return;
101 }
102
103 context->hookCalls += 1;
104 if (HostedTimer::dispatchHandlerForTest(&context->handler, context->delta)) {
105 context->admitted += 1;
106 }
107}
108
109struct AtomicDrainRaceContext;
110AtomicDrainRaceContext* g_AtomicDrainRaceContext = nullptr;
111
112class AtomicDrainRaceHandler : public TimerHandler {
113 public:
114 explicit AtomicDrainRaceHandler(AtomicDrainRaceContext& context) : m_Context(context) {}
115
116 void timer(uint64_t) override;
117
118 private:
119 AtomicDrainRaceContext& m_Context;
120};
121
122struct AtomicDrainRaceContext {
123 explicit AtomicDrainRaceContext(Timer* timer)
124 : timer(timer),
125 handler(*this),
126 removerReady(0, false),
127 beginDrain(0, false),
128 phase(0),
129 pinHookCalls(0),
130 drainHookCalls(0),
131 handlerCalls(0),
132 selfRemovalRejected(0),
133 revivalSucceeded(0),
134 atomicRemovalRejected(0),
135 beginDrainTimedOut(0),
136 pinTransitionTimedOut(0),
137 drainCompletionTimedOut(0),
138 failures(0) {}
139
140 Timer* timer;
141 AtomicDrainRaceHandler handler;
142 Semaphore removerReady;
143 Semaphore beginDrain;
144 Atomic<size_t> phase;
145 Atomic<size_t> pinHookCalls;
146 Atomic<size_t> drainHookCalls;
147 Atomic<size_t> handlerCalls;
148 Atomic<size_t> selfRemovalRejected;
149 Atomic<size_t> revivalSucceeded;
150 Atomic<size_t> atomicRemovalRejected;
151 Atomic<size_t> beginDrainTimedOut;
152 Atomic<size_t> pinTransitionTimedOut;
153 Atomic<size_t> drainCompletionTimedOut;
154 Atomic<size_t> failures;
155};
156
157void AtomicDrainRaceHandler::timer(uint64_t) {
158 m_Context.handlerCalls += 1;
159 if (m_Context.phase != static_cast<size_t>(2)) {
160 return;
161 }
162
163 if (!m_Context.timer->unregisterHandler(this)) {
164 m_Context.selfRemovalRejected += 1;
165 }
166 if (m_Context.timer->registerHandler(this)) {
167 m_Context.revivalSucceeded += 1;
168 }
169 m_Context.phase = 3;
170}
171
172void atomicDrainPinHook(TimerHandler* handler) {
173 constexpr size_t YieldLimit = 10000;
174 AtomicDrainRaceContext* context = g_AtomicDrainRaceContext;
175 if (!context || handler != &context->handler || !context->phase.compareAndSwap(0, 1)) {
176 return;
177 }
178
179 context->pinHookCalls += 1;
180 context->beginDrain.release();
181 for (size_t i = 0; context->phase != static_cast<size_t>(2) && i < YieldLimit; ++i) {
183 }
184 if (context->phase != static_cast<size_t>(2)) {
185 context->pinTransitionTimedOut += 1;
186 context->failures += 1;
187 context->phase = 3;
188 }
189}
190
191void atomicDrainTransitionHook(TimerHandler* handler) {
192 constexpr size_t YieldLimit = 10000;
193 AtomicDrainRaceContext* context = g_AtomicDrainRaceContext;
194 if (!context || handler != &context->handler || !context->phase.compareAndSwap(1, 2)) {
195 if (context && handler == &context->handler) {
196 context->failures += 1;
197 }
198 return;
199 }
200
201 context->drainHookCalls += 1;
202 for (size_t i = 0; context->phase != static_cast<size_t>(3) && i < YieldLimit; ++i) {
204 }
205 if (context->phase != static_cast<size_t>(3)) {
206 context->drainCompletionTimedOut += 1;
207 context->failures += 1;
208 context->phase = 3;
209 }
210}
211
212int removeTimerAtomically(void* parameter) {
213 AtomicDrainRaceContext* context = reinterpret_cast<AtomicDrainRaceContext*>(parameter);
214 context->removerReady.release();
215 if (!context->beginDrain.acquireForCompletion(1, 2)) {
216 context->beginDrainTimedOut += 1;
217 context->failures += 1;
218 return 1;
219 }
220 if (context->phase != static_cast<size_t>(1)) {
221 context->failures += 1;
222 return 1;
223 }
224
225 const bool interruptsWereEnabled = Processor::getInterrupts();
227 const bool removed = context->timer->unregisterHandler(&context->handler);
228 Processor::setInterrupts(interruptsWereEnabled);
229 if (!removed) {
230 context->atomicRemovalRejected += 1;
231 } else {
232 context->failures += 1;
233 }
234 return 0;
235}
236
237enum class TimerAbandonPoint {
238 PartialHazard,
239 CommittedHazard,
240 DeferredSelfRemoval,
241};
242
243struct TimerAbandonContext;
244TimerAbandonContext* g_TimerAbandonContext = nullptr;
245
246class AbandoningTimerHandler : public TimerHandler {
247 public:
248 explicit AbandoningTimerHandler(TimerAbandonContext& context) : m_Context(context) {}
249
250 void timer(uint64_t) override;
251
252 private:
253 TimerAbandonContext& m_Context;
254};
255
256struct TimerAbandonContext {
257 TimerAbandonContext(Timer* timer, TimerHandlerRegistry* registry, TimerAbandonPoint abandonPoint)
258 : timer(timer),
259 registry(registry),
260 handler(*this),
261 dispatcher(nullptr),
262 remover(nullptr),
263 point(abandonPoint),
264 phase(0),
265 hookCalls(0),
266 hookObservedDrain(0),
267 handlerCalls(0),
268 dispatchReturned(0),
269 unregisterReturned(0),
270 unregisterSucceeded(0),
271 selfRemovalRejected(0),
272 failures(0) {}
273
274 Timer* timer;
275 TimerHandlerRegistry* registry;
276 AbandoningTimerHandler handler;
277 Thread* dispatcher;
278 Thread* remover;
279 TimerAbandonPoint point;
280 Atomic<size_t> phase;
281 Atomic<size_t> hookCalls;
282 Atomic<size_t> hookObservedDrain;
283 Atomic<size_t> handlerCalls;
284 Atomic<size_t> dispatchReturned;
285 Atomic<size_t> unregisterReturned;
286 Atomic<size_t> unregisterSucceeded;
287 Atomic<size_t> selfRemovalRejected;
288 Atomic<size_t> failures;
289};
290
291void abandonCurrentTimerDispatcher() {
292 Processor::information().getCurrentThread()->getScheduler()->abandonCurrentThreadStack(
293 PerProcessorScheduler::StackDiscardReason::HostedRegression);
294}
295
296void AbandoningTimerHandler::timer(uint64_t) {
297 if (Processor::information().getCurrentThread() != m_Context.dispatcher) {
298 return;
299 }
300
301 m_Context.handlerCalls += 1;
302 if (m_Context.point != TimerAbandonPoint::DeferredSelfRemoval) {
303 m_Context.failures += 1;
304 return;
305 }
306
307 if (!m_Context.registry->unregisterHandler(this)) {
308 m_Context.selfRemovalRejected += 1;
309 } else {
310 m_Context.failures += 1;
311 }
312 abandonCurrentTimerDispatcher();
313}
314
315void abandonPartialTimerHazard(TimerHandler* handler) {
316 TimerAbandonContext* context = g_TimerAbandonContext;
317 if (!context || handler != &context->handler ||
318 context->point != TimerAbandonPoint::PartialHazard ||
319 Processor::information().getCurrentThread() != context->dispatcher) {
320 return;
321 }
322
323 context->hookCalls += 1;
324 abandonCurrentTimerDispatcher();
325}
326
327void abandonCommittedTimerHazard(TimerHandler* handler) {
328 TimerAbandonContext* context = g_TimerAbandonContext;
329 if (!context || handler != &context->handler ||
330 context->point != TimerAbandonPoint::CommittedHazard ||
331 Processor::information().getCurrentThread() != context->dispatcher ||
332 !context->phase.compareAndSwap(0, 1)) {
333 return;
334 }
335
336 context->hookCalls += 1;
337 const uint64_t deadline =
338 context->timer->getTickCountNano() + (500 * Time::Multiplier::Millisecond);
339 while (context->timer->getTickCountNano() < deadline) {
340 if (context->phase == static_cast<size_t>(2) && context->remover &&
341 hasTimerCallbackDrainWait(context->remover, &context->handler)) {
342 context->hookObservedDrain += 1;
343 abandonCurrentTimerDispatcher();
344 }
346 }
347
348 context->failures += 1;
349 abandonCurrentTimerDispatcher();
350}
351
352int awaitAbandoningTimer(void* parameter) {
353 TimerAbandonContext* context = reinterpret_cast<TimerAbandonContext*>(parameter);
354
355 // These tests deliberately abandon this exact dispatcher stack from a
356 // registry hook or callback. Drive the registry directly so the lifetime
357 // test does not depend on which thread services the hardware timer.
358 context->registry->dispatch(Time::Multiplier::Millisecond, &context->handler);
359 context->failures += 1;
360 context->dispatchReturned += 1;
361 return 1;
362}
363
364int unregisterAbandoningTimer(void* parameter) {
365 TimerAbandonContext* context = reinterpret_cast<TimerAbandonContext*>(parameter);
366 const uint64_t deadline =
367 context->timer->getTickCountNano() + (500 * Time::Multiplier::Millisecond);
368 while (context->phase != static_cast<size_t>(1) &&
369 context->timer->getTickCountNano() < deadline) {
371 }
372 if (context->phase != static_cast<size_t>(1)) {
373 context->failures += 1;
374 return 1;
375 }
376
377 context->phase = 2;
378 if (context->registry->unregisterHandler(&context->handler)) {
379 context->unregisterSucceeded += 1;
380 }
381 context->unregisterReturned += 1;
382 return 0;
383}
384
385bool timerPartialHazardAbandonment() {
386 constexpr const char* Test = "timer-partial-hazard-abandonment";
387 Timer* timer = Machine::instance().getTimer();
388 TimerHandlerRegistry registry;
389 TimerAbandonContext context(timer, &registry, TimerAbandonPoint::PartialHazard);
390 context.dispatcher = new Thread(Scheduler::instance().getKernelProcess(), awaitAbandoningTimer,
391 &context, nullptr, false, true, true);
392 context.dispatcher->setName("hosted partial timer hazard");
393
394 g_TimerAbandonContext = &context;
395 registry.setHandlerHazardClaimHook(abandonPartialTimerHazard);
396 const bool registered = registry.registerHandler(&context.handler);
397 const bool started = registered && context.dispatcher->start();
398 const bool joined = started && context.dispatcher->join();
399 registry.setHandlerHazardClaimHook(nullptr);
400 g_TimerAbandonContext = nullptr;
401
402 const size_t claimed = registry.claimedDispatchCountForTest();
403 const size_t active = registry.activeDispatchCountForTest(&context.handler);
404 const bool removed = registered && registry.unregisterHandler(&context.handler);
405 const bool reused = removed && registry.registerHandler(&context.handler);
406 const bool cleaned = reused && registry.unregisterHandler(&context.handler);
407
408 bool passed = true;
409 passed &= check(registered && started && joined, Test,
410 "the partial-publication worker did not complete");
411 passed &= check(context.hookCalls == 1 && context.handlerCalls == 0 &&
412 context.dispatchReturned == 0 && context.failures == 0,
413 Test, "dispatch escaped the forced partial-publication window");
414 passed &= check(claimed == 0 && active == 0, Test,
415 "stack abandonment leaked a partial callback hazard");
416 passed &= check(removed && reused && cleaned, Test,
417 "the handler slot could not be removed and reused after cleanup");
418 if (passed) {
419 NOTICE("HOSTED-WAIT-TEST: PASS timer-partial-hazard-abandonment");
420 }
421 return passed;
422}
423
424bool timerCommittedHazardAbandonment() {
425 constexpr const char* Test = "timer-abandoned-dispatch-cleanup";
426 Timer* timer = Machine::instance().getTimer();
427 TimerHandlerRegistry registry;
428 TimerAbandonContext context(timer, &registry, TimerAbandonPoint::CommittedHazard);
429 context.remover = new Thread(Scheduler::instance().getKernelProcess(), unregisterAbandoningTimer,
430 &context, nullptr, false, true);
431 context.remover->setName("hosted abandoned timer remover");
432 context.dispatcher = new Thread(Scheduler::instance().getKernelProcess(), awaitAbandoningTimer,
433 &context, nullptr, false, true, true);
434 context.dispatcher->setName("hosted abandoned timer dispatch");
435
436 g_TimerAbandonContext = &context;
437 registry.setHandlerPinHook(abandonCommittedTimerHazard);
438 const bool registered = registry.registerHandler(&context.handler);
439 if (!registered) {
440 context.phase = 1;
441 }
442 const bool started = registered && context.dispatcher->start();
443 const bool dispatcherJoined = started && context.dispatcher->join();
444 const bool removerJoined = context.remover->join();
445 registry.setHandlerPinHook(nullptr);
446 g_TimerAbandonContext = nullptr;
447
448 if (!context.unregisterSucceeded && registered) {
449 registry.unregisterHandler(&context.handler);
450 }
451 const size_t claimed = registry.claimedDispatchCountForTest();
452 const size_t active = registry.activeDispatchCountForTest(&context.handler);
453 const bool reused = context.unregisterSucceeded && registry.registerHandler(&context.handler);
454 const bool cleaned = reused && registry.unregisterHandler(&context.handler);
455
456 bool passed = true;
457 passed &= check(registered && started && dispatcherJoined && removerJoined, Test,
458 "the abandoned-dispatch workers did not complete");
459 passed &= check(context.failures == 0 && context.hookCalls == 1 && context.hookObservedDrain == 1,
460 Test, "the committed drain-and-abandon window was not reached");
461 passed &= check(context.handlerCalls == 0 && context.dispatchReturned == 0, Test,
462 "the abandoned timer stack returned through normal dispatch");
463 passed &= check(claimed == 0 && active == 0, Test,
464 "stack abandonment leaked a committed callback hazard");
465 passed &= check(
466 context.unregisterSucceeded == 1 && context.unregisterReturned == 1 && reused && cleaned,
467 Test, "cleanup did not release the synchronous unregister barrier");
468 if (passed) {
469 NOTICE("HOSTED-WAIT-TEST: PASS timer-abandoned-dispatch-cleanup");
470 }
471 return passed;
472}
473
474bool timerDeferredSelfRemovalAbandonment() {
475 constexpr const char* Test = "timer-abandoned-self-removal";
476 Timer* timer = Machine::instance().getTimer();
477 TimerHandlerRegistry registry;
478 TimerAbandonContext context(timer, &registry, TimerAbandonPoint::DeferredSelfRemoval);
479 context.dispatcher = new Thread(Scheduler::instance().getKernelProcess(), awaitAbandoningTimer,
480 &context, nullptr, false, true, true);
481 context.dispatcher->setName("hosted abandoned timer self-removal");
482
483 const bool registered = registry.registerHandler(&context.handler);
484 const bool started = registered && context.dispatcher->start();
485 const bool joined = started && context.dispatcher->join();
486 const size_t claimed = registry.claimedDispatchCountForTest();
487 const size_t active = registry.activeDispatchCountForTest(&context.handler);
488 const bool reused = registered && registry.registerHandler(&context.handler);
489 const bool cleaned = reused && registry.unregisterHandler(&context.handler);
490
491 bool passed = true;
492 passed &=
493 check(registered && started && joined, Test, "the self-removing worker did not complete");
494 passed &= check(context.handlerCalls == 1 && context.selfRemovalRejected == 1 &&
495 context.dispatchReturned == 0 && context.failures == 0,
496 Test, "the callback did not defer removal before abandoning its stack");
497 passed &=
498 check(claimed == 0 && active == 0, Test, "deferred self-removal leaked a callback hazard");
499 passed &= check(reused && cleaned, Test,
500 "final abandoned-stack cleanup did not retire and reuse the slot");
501 if (passed) {
502 NOTICE("HOSTED-WAIT-TEST: PASS timer-abandoned-self-removal");
503 }
504 return passed;
505}
506
507struct HandlerLifetimeContext;
508HandlerLifetimeContext* g_HandlerLifetimeContext = nullptr;
509
510class LifetimeHandler : public TimerHandler {
511 public:
512 explicit LifetimeHandler(HandlerLifetimeContext& context) : m_Context(context) {}
513
514 void timer(uint64_t) override;
515
516 private:
517 HandlerLifetimeContext& m_Context;
518};
519
520struct HandlerLifetimeContext {
521 explicit HandlerLifetimeContext(Timer* timer)
522 : timer(timer),
523 handler(*this),
524 remover(nullptr),
525 phase(0),
526 hookCalls(0),
527 hookObservedDrain(0),
528 handlerCalls(0),
529 callbacksAfterReturn(0),
530 unregisterReturned(0),
531 unregisterSucceeded(0),
532 selfRemovalPending(1),
533 selfRemovalRejected(0),
534 revivalBlocked(0),
535 waitHookCalls(0),
536 waitHookFailures(0),
537 drainWaitPublished(0),
538 wakeBeforeBlock(0),
539 failures(0) {}
540
541 Timer* timer;
542 LifetimeHandler handler;
543 Thread* remover;
544 Atomic<size_t> phase;
545 Atomic<size_t> hookCalls;
546 Atomic<size_t> hookObservedDrain;
547 Atomic<size_t> handlerCalls;
548 Atomic<size_t> callbacksAfterReturn;
549 Atomic<size_t> unregisterReturned;
550 Atomic<size_t> unregisterSucceeded;
551 Atomic<size_t> selfRemovalPending;
552 Atomic<size_t> selfRemovalRejected;
553 Atomic<size_t> revivalBlocked;
554 Atomic<size_t> waitHookCalls;
555 Atomic<size_t> waitHookFailures;
556 Atomic<size_t> drainWaitPublished;
557 Atomic<size_t> wakeBeforeBlock;
558 Atomic<size_t> failures;
559};
560
561void LifetimeHandler::timer(uint64_t) {
562 m_Context.handlerCalls += 1;
563 if (m_Context.unregisterReturned) {
564 m_Context.callbacksAfterReturn += 1;
565 }
566 if (m_Context.selfRemovalPending.compareAndSwap(1, 0) &&
567 !m_Context.timer->unregisterHandler(this)) {
568 m_Context.selfRemovalRejected += 1;
569 if (!m_Context.timer->registerHandler(this)) {
570 m_Context.revivalBlocked += 1;
571 }
572 }
573}
574
575class SelfRemovingHandler : public TimerHandler {
576 public:
577 explicit SelfRemovingHandler(Timer* timer) : m_Timer(timer), calls(0), rejectionSeen(0) {}
578
579 void timer(uint64_t) override {
580 calls += 1;
581 if (!m_Timer->unregisterHandler(this)) {
582 rejectionSeen += 1;
583 }
584 }
585
586 Timer* m_Timer;
587 Atomic<size_t> calls;
588 Atomic<size_t> rejectionSeen;
589};
590
591void timerDrainBeforeBlockHook(WaitQueue* queue, Thread* thread, const WaitQueue::Channel& channel,
592 size_t debugState) {
593 constexpr size_t YieldLimit = 10000;
594 HandlerLifetimeContext* context = g_HandlerLifetimeContext;
595 if (!context || thread != context->remover) {
596 return;
597 }
598
599 context->waitHookCalls += 1;
600 Thread::WaitDebugInfo wait = {};
601 if (!queue || !channel.owner || !channel.value || debugState != Thread::CallbackDrain ||
602 !thread->getWaitDebugInfo(wait) || wait.queue != queue ||
603 wait.channelOwner != channel.owner || wait.channelValue != channel.value || !wait.queued ||
604 wait.reason != WaitQueue::WakeReason::Waiting ||
605 !hasTimerCallbackDrainWait(thread, &context->handler)) {
606 context->waitHookFailures += 1;
607 return;
608 }
609
610 context->drainWaitPublished += 1;
611 const uint64_t deadline =
612 context->timer->getTickCountNano() + (500 * Time::Multiplier::Millisecond);
613 for (size_t i = 0; context->timer->getTickCountNano() < deadline && i < YieldLimit; ++i) {
614 Thread::WaitDebugInfo currentWait = {};
615 if (thread->getWaitDebugInfo(currentWait) && currentWait.queue == queue &&
616 currentWait.channelOwner == channel.owner && currentWait.channelValue == channel.value &&
617 currentWait.reason == WaitQueue::WakeReason::Signalled) {
618 context->wakeBeforeBlock += 1;
619 return;
620 }
622 }
623
624 context->waitHookFailures += 1;
625}
626
627void handlerPinHook(TimerHandler* handler) {
628 constexpr size_t YieldLimit = 10000;
629 HandlerLifetimeContext* context = g_HandlerLifetimeContext;
630 if (!context || handler != &context->handler || !context->phase.compareAndSwap(0, 1)) {
631 return;
632 }
633
634 context->hookCalls += 1;
635 const uint64_t deadline =
636 context->timer->getTickCountNano() + (500 * Time::Multiplier::Millisecond);
637 bool observedDrain = false;
638 for (size_t i = 0; context->timer->getTickCountNano() < deadline && i < YieldLimit; ++i) {
639 if (context->phase == static_cast<size_t>(2) && context->drainWaitPublished &&
640 !context->unregisterReturned &&
641 hasTimerCallbackDrainWait(context->remover, &context->handler)) {
642 observedDrain = true;
643 break;
644 }
646 }
647
648 if (observedDrain) {
649 // This is the debugger-visible ownership barrier, not merely evidence
650 // that the remover happened to lose a timeslice while polling.
651 context->hookObservedDrain += 1;
652 } else {
653 context->failures += 1;
654 }
655 context->phase = 3;
656}
657
658int unregisterPinnedHandler(void* parameter) {
659 HandlerLifetimeContext* context = reinterpret_cast<HandlerLifetimeContext*>(parameter);
660 const uint64_t deadline =
661 context->timer->getTickCountNano() + (500 * Time::Multiplier::Millisecond);
662 while (context->phase != static_cast<size_t>(1) &&
663 context->timer->getTickCountNano() < deadline) {
665 }
666
667 if (context->phase != static_cast<size_t>(1)) {
668 context->failures += 1;
669 return 1;
670 }
671
672 context->phase = 2;
673 if (context->timer->unregisterHandler(&context->handler)) {
674 context->unregisterSucceeded += 1;
675 }
676 context->unregisterReturned += 1;
677 context->phase = 4;
678 return 0;
679}
680
681void ignoredTimerEvent(size_t) {}
682
683void dispositionAHandler(size_t) {
684 g_DispositionACalls += 1;
685}
686
687void dispositionBHandler(size_t) {
688 g_DispositionBCalls += 1;
689}
690
691void relayDisposition(Thread* thread) {
692 g_RelayCallbacks += 1;
693 Event* event = g_RelayDisposition == 1 ? g_DispositionAEvent : g_DispositionBEvent;
694 if (event) {
695 thread->sendEvent(event);
696 }
697}
698
699class TimerTestEvent : public Event {
700 public:
701 TimerTestEvent(size_t number, bool deletable,
702 uintptr_t handler = reinterpret_cast<uintptr_t>(&ignoredTimerEvent))
703 : Event(handler, deletable), m_Number(number) {}
704
705 ~TimerTestEvent() override {
706 if (m_Number == CullTestEventNumber) {
707 g_CullEventDestructions += 1;
708 }
709 }
710
711 size_t serialize(uint8_t*) override {
712 return 0;
713 }
714
715 size_t getNumber() override {
716 return m_Number;
717 }
718
719 private:
720 size_t m_Number;
721};
722
723struct AlarmLinearizationContext;
724
725class AlarmLinearizationEvent : public TimerTestEvent {
726 public:
727 explicit AlarmLinearizationEvent(AlarmLinearizationContext& context);
728 ~AlarmLinearizationEvent() override;
729
730 private:
731 AlarmLinearizationContext& m_Context;
732};
733
734struct AlarmLinearizationContext {
735 AlarmLinearizationContext(Timer* timer, Thread* owner)
736 : timer(timer),
737 owner(owner),
738 event(nullptr),
739 removerReady(0, false),
740 phase(0),
741 hookCalls(0),
742 lockBoundaryObserved(0),
743 removerReturned(0),
744 destructions(0),
745 failures(0) {}
746
747 Timer* timer;
748 Thread* owner;
749 AlarmLinearizationEvent* event;
750 Semaphore removerReady;
751 Atomic<size_t> phase;
752 Atomic<size_t> hookCalls;
753 Atomic<size_t> lockBoundaryObserved;
754 Atomic<size_t> removerReturned;
755 Atomic<size_t> destructions;
756 Atomic<size_t> failures;
757};
758
759AlarmLinearizationContext* g_AlarmLinearizationContext = nullptr;
760
761AlarmLinearizationEvent::AlarmLinearizationEvent(AlarmLinearizationContext& context)
762 : TimerTestEvent(AlarmLinearizationEventNumber, false), m_Context(context) {}
763
764AlarmLinearizationEvent::~AlarmLinearizationEvent() {
765 m_Context.destructions += 1;
766}
767
768void observeAlarmSendAdmission(Event* event) {
769 AlarmLinearizationContext* context = g_AlarmLinearizationContext;
770 if (!context || event != context->event) {
771 return;
772 }
773
774 context->hookCalls += 1;
775 const bool lockHeld = HostedTimer::alarmLockHeldForTest();
776 const bool interruptsDisabled = !Processor::getInterrupts();
777 if (lockHeld && interruptsDisabled && !context->removerReturned && !context->destructions) {
778 context->lockBoundaryObserved += 1;
779 } else {
780 context->failures += 1;
781 }
782 context->phase = 1;
783
784 // If a future change reopens the claim-to-send gap, let the remover win
785 // that gap so ASan observes the stale Event use deterministically.
786 if (!lockHeld && Processor::getInterrupts()) {
787 const uint64_t deadline =
788 context->timer->getTickCountNano() + (500 * Time::Multiplier::Millisecond);
789 while (!context->destructions && context->timer->getTickCountNano() < deadline) {
791 }
792 }
793}
794
795int removeLinearizingAlarm(void* parameter) {
796 AlarmLinearizationContext* context = reinterpret_cast<AlarmLinearizationContext*>(parameter);
797 context->removerReady.release();
798
799 const uint64_t deadline =
800 context->timer->getTickCountNano() + (500 * Time::Multiplier::Millisecond);
801 while (!context->phase && context->timer->getTickCountNano() < deadline) {
803 }
804 if (!context->phase) {
805 context->failures += 1;
806 return 1;
807 }
808
809 context->timer->removeAlarm(context->event);
810 context->owner->cullEvent(context->event);
811 delete context->event;
812 context->event = nullptr;
813 context->removerReturned = 1;
814 return 0;
815}
816
817bool check(bool condition, const char* test, const char* detail) {
818 if (condition) {
819 return true;
820 }
821
822 ERROR("HOSTED-WAIT-TEST: FAIL " << test << ": " << detail);
823 return false;
824}
825
826class DeadlineRegistryHandler : public TimerHandler {
827 public:
828 explicit DeadlineRegistryHandler(TimerHandlerRegistry& registry)
829 : calls(0), deltas(), rearmDeadline(0), rearmTime(0), m_Registry(registry) {}
830
831 void timer(uint64_t delta) override {
832 if (calls < 3) {
833 deltas[calls] = delta;
834 }
835 ++calls;
836 if (rearmDeadline) {
837 m_Registry.armHandler(this, rearmDeadline, rearmTime);
838 rearmDeadline = 0;
839 }
840 }
841
842 size_t calls;
843 uint64_t deltas[3];
844 uint64_t rearmDeadline;
845 uint64_t rearmTime;
846
847 private:
848 TimerHandlerRegistry& m_Registry;
849};
850
851bool timerRegistryAbsoluteDeadlines() {
852 constexpr const char* Test = "timer-registry-absolute-deadlines";
853 TimerHandlerRegistry registry;
854 DeadlineRegistryHandler first(registry);
855 DeadlineRegistryHandler second(registry);
856 DeadlineRegistryHandler absent(registry);
857
858 bool passed = true;
859 passed &= check(!registry.armHandler(&absent, 20, 10), Test, "an unregistered handler was armed");
860 const bool firstRegistered = registry.registerHandler(&first);
861 const bool secondRegistered = registry.registerHandler(&second);
862 first.rearmDeadline = 40;
863 first.rearmTime = 20;
864 const bool firstArmed = registry.armHandler(&first, 20, 10);
865 const bool secondArmed = registry.armHandler(&second, 30, 10);
866 passed &= check(firstRegistered && secondRegistered && firstArmed && secondArmed, Test,
867 "handlers could not be registered and armed");
868 passed &= check(registry.nextDeadline() == 20 && !registry.dispatchDue(19), Test,
869 "a deadline fired early");
870 passed &= check(registry.dispatchDue(20) && first.calls == 1 && first.deltas[0] == 10 &&
871 second.calls == 0 && registry.nextDeadline() == 30,
872 Test, "the first callback did not rearm safely");
873 passed &= check(registry.dispatchDue(35) && second.calls == 1 && second.deltas[0] == 25 &&
874 registry.nextDeadline() == 40,
875 Test, "the second callback received the wrong elapsed time");
876 passed &= check(registry.dispatchDue(40) && first.calls == 2 && first.deltas[1] == 20 &&
877 registry.nextDeadline() == Time::Infinity,
878 Test, "the rearmed callback fired more than once or had the wrong delta");
879
880 const bool armedAgain = registry.armHandler(&second, 50, 35);
881 const bool disarmed = registry.armHandler(&second, 0, 35);
882 passed &= check(armedAgain && disarmed && registry.nextDeadline() == Time::Infinity &&
883 !registry.dispatchDue(60) && second.calls == 1,
884 Test, "disarming did not cancel a pending deadline");
885 const bool armedAfterIdle = registry.armHandler(&second, 90, 80);
886 passed &= check(
887 armedAfterIdle && registry.dispatchDue(90) && second.calls == 2 && second.deltas[1] == 10,
888 Test, "a new arm included time spent disarmed");
889 const bool armedAfterOneShot = registry.armHandler(&second, 120, 110);
890 passed &= check(
891 armedAfterOneShot && registry.dispatchDue(120) && second.calls == 3 && second.deltas[2] == 10,
892 Test, "a new one-shot inherited an old callback baseline");
893
894 const bool firstRemoved = registry.unregisterHandler(&first);
895 const bool secondRemoved = registry.unregisterHandler(&second);
896 const bool firstReused = firstRemoved && registry.registerHandler(&first);
897 passed &= check(firstRemoved && secondRemoved && firstReused &&
898 registry.nextDeadline() == Time::Infinity && !registry.dispatchDue(100) &&
899 first.calls == 2,
900 Test, "a retired deadline survived slot reuse");
901 const bool rearmedAfterReuse = firstReused && registry.armHandler(&first, 110, 100);
902 passed &= check(
903 rearmedAfterReuse && registry.dispatchDue(110) && first.calls == 3 && first.deltas[2] == 10,
904 Test, "slot reuse retained a prior callback baseline");
905 passed &= check(firstReused && registry.unregisterHandler(&first), Test,
906 "a reused handler could not be removed");
907
908 if (passed) {
909 NOTICE("HOSTED-WAIT-TEST: PASS timer-registry-absolute-deadlines");
910 }
911 return passed;
912}
913
914bool timerWriterLockIndependentDispatch() {
915 constexpr const char* Test = "timer-dispatch-writer-lock-independent";
916 Timer* timer = Machine::instance().getTimer();
917 RegistryDispatchContext context(timer);
918 const bool registered = timer->registerHandler(&context.handler);
919
920 if (registered) {
921 g_TimerRegistryDispatchContext = &context;
922 context.mutationRequested = 1;
923 const uint64_t deadline = timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
924 while (context.mutationRequested != static_cast<size_t>(3) &&
925 timer->getTickCountNano() < deadline) {
927 }
928 g_TimerRegistryDispatchContext = nullptr;
929 }
930
931 const bool cleaned = registered && timer->unregisterHandler(&context.handler);
932 bool passed = true;
933 passed &= check(registered, Test, "the test handler could not be registered");
934 passed &= check(context.mutationRequested == 3 && context.hookCalls == 1 &&
935 context.admitted == 1 && context.calls >= 2,
936 Test, "dispatch or callback completion waited for the writer lock");
937 passed &= check(cleaned, Test, "the test handler could not be removed");
938 if (passed) {
939 NOTICE(
940 "HOSTED-WAIT-TEST: PASS "
941 "timer-dispatch-writer-lock-independent");
942 }
943 return passed;
944}
945
946void unregisterTimerBeforePin(TimerHandler* handler) {
947 RegistryDispatchContext* context = g_TimerRegistryDispatchContext;
948 if (!context || handler != &context->handler || !context->hookCalls.compareAndSwap(0, 1)) {
949 return;
950 }
951
952 if (context->timer->unregisterHandler(&context->handler)) {
953 context->unregisterSucceeded += 1;
954 }
955 context->hookCalls = 2;
956}
957
958bool timerPrePinUnregisterRevalidation() {
959 constexpr const char* Test = "timer-pre-pin-unregister-revalidation";
960 Timer* timer = Machine::instance().getTimer();
961 RegistryDispatchContext context(timer);
962
963 g_TimerRegistryDispatchContext = &context;
964 HostedTimer::setHandlerPrePinHook(unregisterTimerBeforePin);
965 const bool registered = timer->registerHandler(&context.handler);
966 if (registered) {
967 const uint64_t deadline = timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
968 while (context.hookCalls != static_cast<size_t>(2) && timer->getTickCountNano() < deadline) {
970 }
971 }
972 HostedTimer::setHandlerPrePinHook(nullptr);
973 g_TimerRegistryDispatchContext = nullptr;
974
975 const bool reused = context.unregisterSucceeded && timer->registerHandler(&context.handler);
976 bool cleaned = true;
977 if (reused) {
978 cleaned = timer->unregisterHandler(&context.handler);
979 } else if (registered && !context.unregisterSucceeded) {
980 cleaned = timer->unregisterHandler(&context.handler);
981 }
982
983 bool passed = true;
984 passed &= check(registered, Test, "the test handler could not be registered");
985 passed &= check(context.hookCalls == 2 && context.unregisterSucceeded == 1, Test,
986 "unregister did not retire the pre-pin publication");
987 passed &=
988 check(context.calls == 0, Test, "a stale pre-pin snapshot entered the retired callback");
989 passed &= check(reused && cleaned, Test, "the revalidated slot could not be reused and removed");
990 if (passed) {
991 NOTICE(
992 "HOSTED-WAIT-TEST: PASS "
993 "timer-pre-pin-unregister-revalidation");
994 }
995 return passed;
996}
997
998bool timerAtomicDrainSelfRevival() {
999 constexpr const char* Test = "timer-atomic-drain-self-revival";
1000 Timer* timer = Machine::instance().getTimer();
1001 AtomicDrainRaceContext context(timer);
1002
1003 g_AtomicDrainRaceContext = &context;
1004 HostedTimer::setHandlerPinHook(atomicDrainPinHook);
1005 HostedTimer::setHandlerAtomicDrainHook(atomicDrainTransitionHook);
1006 Thread* remover = new Thread(Scheduler::instance().getKernelProcess(), removeTimerAtomically,
1007 &context, nullptr, false, true);
1008 remover->setName("hosted atomic timer-handler remover");
1009
1010 const bool removerWasReady = context.removerReady.acquireForCompletion(1, 2);
1011 const bool registered = removerWasReady && timer->registerHandler(&context.handler);
1012 if (!registered) {
1013 context.phase = 1;
1014 context.beginDrain.release();
1015 }
1016 const bool joined = remover->join();
1017 HostedTimer::setHandlerAtomicDrainHook(nullptr);
1018 HostedTimer::setHandlerPinHook(nullptr);
1019 g_AtomicDrainRaceContext = nullptr;
1020
1021 const size_t callsAtAtomicReturn = context.handlerCalls;
1022 const uint64_t liveDeadline = timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
1023 while (context.handlerCalls == callsAtAtomicReturn && timer->getTickCountNano() < liveDeadline) {
1025 }
1026 const bool remainedLive = context.handlerCalls > callsAtAtomicReturn;
1027 const bool cleaned = registered && timer->unregisterHandler(&context.handler);
1028
1029 bool passed = true;
1030 passed &=
1031 check(removerWasReady, Test, "the atomic remover did not become ready before registration");
1032 passed &= check(registered, Test, "the test handler could not be registered");
1033 passed &= check(context.beginDrainTimedOut == 0, Test,
1034 "the atomic remover timed out waiting for the pin hook");
1035 passed &= check(context.pinTransitionTimedOut == 0, Test,
1036 "the pin hook timed out waiting for the atomic drain transition");
1037 passed &= check(context.drainCompletionTimedOut == 0, Test,
1038 "the atomic drain hook timed out waiting for callback revival");
1039 passed &= check(
1040 joined && context.failures == 0 && context.pinHookCalls == 1 && context.drainHookCalls == 1,
1041 Test, "the atomic drainer interleaving did not complete cleanly");
1042 passed &= check(context.selfRemovalRejected == 1 && context.atomicRemovalRejected == 1 &&
1043 context.revivalSucceeded == 1,
1044 Test, "the self-removal request was lost or could not be revived");
1045 passed &= check(remainedLive && cleaned, Test,
1046 "the revived handler was not live or could not be removed");
1047 if (passed) {
1048 NOTICE(
1049 "HOSTED-WAIT-TEST: PASS "
1050 "timer-atomic-drain-self-revival");
1051 }
1052 return passed;
1053}
1054
1055class ThreadContextTimerHandler : public TimerHandler {
1056 public:
1057 explicit ThreadContextTimerHandler(Thread* registeringThread)
1058 : registeringThread(registeringThread),
1059 calls(0),
1060 failures(0),
1061 failureMask(0),
1062 workerAddress(0) {}
1063
1064 void timer(uint64_t delta) override {
1065 Thread* current = Processor::information().getCurrentThread();
1066 const size_t currentAddress = reinterpret_cast<size_t>(current);
1067 size_t expectedWorker = workerAddress.value();
1068 if (!expectedWorker && current) {
1069 workerAddress.compareAndSwap(0, currentAddress);
1070 expectedWorker = workerAddress.value();
1071 }
1072
1073 size_t failed = 0;
1074 failed |= !current ? 1 : 0;
1075 failed |= current == registeringThread ? 2 : 0;
1076 failed |= expectedWorker != currentAddress ? 4 : 0;
1077 failed |= !Processor::getInterrupts() ? 8 : 0;
1078 failed |= current && current->getHostedSignalDepth() ? 16 : 0;
1079 failed |= !delta || (delta % Time::Multiplier::Millisecond) ? 32 : 0;
1080 if (failed) {
1081 failures += 1;
1082 failureMask |= failed;
1083 }
1084 calls += 1;
1085 }
1086
1087 Thread* registeringThread;
1088 Atomic<size_t> calls;
1089 Atomic<size_t> failures;
1090 Atomic<size_t> failureMask;
1091 Atomic<size_t> workerAddress;
1092};
1093
1094bool hostedTimerThreadContext(Thread* registeringThread) {
1095 constexpr const char* Test = "hosted-timer-thread-context";
1096 Timer* timer = Machine::instance().getTimer();
1097 ThreadContextTimerHandler handler(registeringThread);
1098 const bool registered = timer->registerHandler(&handler);
1099
1100 const uint64_t deadline = timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
1101 while (registered && handler.calls < static_cast<size_t>(2) &&
1102 timer->getTickCountNano() < deadline) {
1104 }
1105
1106 const bool removed = registered && timer->unregisterHandler(&handler);
1107 const bool passed =
1108 check(registered && removed && handler.calls >= static_cast<size_t>(2) &&
1109 handler.failures == 0 && handler.workerAddress != static_cast<size_t>(0),
1110 Test, "timer callbacks did not remain on one ordinary worker context");
1111 if (!passed) {
1112 ERROR("HOSTED-WAIT-TEST: INFO "
1113 << Test << ": calls=" << handler.calls.value()
1114 << ", failures=" << handler.failures.value() << ", mask=" << Hex
1115 << handler.failureMask.value() << ", worker=" << handler.workerAddress.value()
1116 << ", registrar=" << reinterpret_cast<uintptr_t>(registeringThread));
1117 }
1118 if (passed) {
1119 NOTICE("HOSTED-WAIT-TEST: PASS hosted-timer-thread-context");
1120 }
1121 return passed;
1122}
1123
1124class HostedTimerOverrunHandler : public TimerHandler {
1125 public:
1126 HostedTimerOverrunHandler() : calls(0), maximumDelta(0) {}
1127
1128 void timer(uint64_t delta) override {
1129 uint64_t maximum = maximumDelta.value();
1130 while (delta > maximum && !maximumDelta.compareAndSwap(maximum, delta)) {
1131 maximum = maximumDelta.value();
1132 }
1133 calls += 1;
1134 }
1135
1136 Atomic<size_t> calls;
1137 Atomic<uint64_t> maximumDelta;
1138};
1139
1140bool hostedTimerOverrunAccounting() {
1141 constexpr const char* Test = "hosted-timer-overrun-accounting";
1142 Timer* timer = Machine::instance().getTimer();
1143 HostedTimerOverrunHandler handler;
1144 const bool registered = timer->registerHandler(&handler);
1145
1146 const uint64_t baselineDeadline =
1147 timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
1148 while (registered && !handler.calls && timer->getTickCountNano() < baselineDeadline) {
1150 }
1151
1152 const bool baselineObserved = handler.calls != static_cast<size_t>(0);
1153 handler.maximumDelta = 0;
1154 const bool interruptsWereEnabled = Processor::getInterrupts();
1155 if (baselineObserved && interruptsWereEnabled) {
1157 const uint64_t blockedUntil = timer->getTickCountNano() + (30 * Time::Multiplier::Millisecond);
1158 while (timer->getTickCountNano() < blockedUntil) {
1160 }
1162 }
1163
1164 const uint64_t overrunDeadline =
1165 timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
1166 while (handler.maximumDelta <= Time::Multiplier::Millisecond &&
1167 timer->getTickCountNano() < overrunDeadline) {
1169 }
1170
1171 const bool removed = registered && timer->unregisterHandler(&handler);
1172 const uint64_t maximumDelta = handler.maximumDelta.value();
1173 const bool passed =
1174 check(registered && baselineObserved && interruptsWereEnabled && removed &&
1175 maximumDelta > Time::Multiplier::Millisecond &&
1176 !(maximumDelta % Time::Multiplier::Millisecond),
1177 Test, "deferred POSIX expirations were not preserved in callback delta");
1178 if (passed) {
1179 NOTICE("HOSTED-WAIT-TEST: PASS hosted-timer-overrun-accounting");
1180 }
1181 return passed;
1182}
1183
1184bool timerClockAndDeadline(Thread* thread) {
1185 Timer* timer = Machine::instance().getTimer();
1186 bool passed = true;
1187
1188 const uint64_t nanosBefore = timer->getTickCountNano();
1189 const uint64_t millis = timer->getTickCount();
1190 const uint64_t nanosAfter = timer->getTickCountNano();
1191 passed &= check(millis >= nanosBefore / Time::Multiplier::Millisecond &&
1192 millis <= nanosAfter / Time::Multiplier::Millisecond,
1193 "timer-clock-deadline",
1194 "millisecond and nanosecond tick APIs use different clocks or units");
1195
1196 TimerTestEvent* remainingEvent = new TimerTestEvent(TimerTestEventNumber, false);
1197 timer->addAlarm(remainingEvent, 2);
1198 const size_t remaining = timer->removeAlarm(remainingEvent, false);
1199 delete remainingEvent;
1200 passed &= check(remaining == 2, "timer-clock-deadline",
1201 "alarm removal did not return whole remaining seconds");
1202
1203 thread->inhibitEvent(TimerTestEventNumber, true);
1204 TimerTestEvent* overflowEvent = new TimerTestEvent(TimerTestEventNumber, false);
1205 const uint64_t overflowStart = timer->getTickCountNano();
1206 timer->addAlarm(overflowEvent, ~static_cast<size_t>(0), ~static_cast<size_t>(0));
1207 while (timer->getTickCountNano() < overflowStart + (5 * Time::Multiplier::Millisecond)) {
1209 }
1210 const bool overflowFired = thread->hasEvent(overflowEvent);
1211 timer->removeAlarm(overflowEvent);
1212 thread->cullEvent(overflowEvent);
1213 delete overflowEvent;
1214 passed &= check(!overflowFired, "timer-clock-deadline",
1215 "an overflowing alarm duration wrapped into an immediate deadline");
1216
1217 TimerTestEvent* deadlineEvent = new TimerTestEvent(TimerTestEventNumber, false);
1218 const uint64_t start = timer->getTickCountNano();
1219 timer->addAlarm(deadlineEvent, 0, 20000);
1220
1221 const uint64_t earlyDeadline = start + (5 * Time::Multiplier::Millisecond);
1222 while (timer->getTickCountNano() < earlyDeadline) {
1224 }
1225 const bool firedEarly = thread->hasEvent(deadlineEvent);
1226
1227 const uint64_t lateDeadline = start + (250 * Time::Multiplier::Millisecond);
1228 while (!thread->hasEvent(deadlineEvent) && timer->getTickCountNano() < lateDeadline) {
1230 }
1231 const bool fired = thread->hasEvent(deadlineEvent);
1232
1233 timer->removeAlarm(deadlineEvent);
1234 thread->cullEvent(deadlineEvent);
1235 const bool removed = !thread->hasEvent(deadlineEvent);
1236 delete deadlineEvent;
1237 thread->inhibitEvent(TimerTestEventNumber, false);
1238
1239 passed &= check(!firedEarly, "timer-clock-deadline", "a 20ms alarm fired during its first 5ms");
1240 passed &= check(fired, "timer-clock-deadline", "a 20ms alarm did not publish within 250ms");
1241 passed &= check(removed, "timer-clock-deadline", "a cancelled alarm event remained queued");
1242
1243 if (passed) {
1244 NOTICE("HOSTED-WAIT-TEST: PASS timer-clock-deadline");
1245 }
1246 return passed;
1247}
1248
1249bool timerAlarmSendLinearization() {
1250 constexpr const char* Test = "timer-alarm-send-linearization";
1251 Timer* timer = Machine::instance().getTimer();
1252 Thread* owner = Processor::information().getCurrentThread();
1253 AlarmLinearizationContext context(timer, owner);
1254 context.event = new AlarmLinearizationEvent(context);
1255 owner->inhibitEvent(AlarmLinearizationEventNumber, true);
1256
1257 const bool interruptsWereEnabled = Processor::getInterrupts();
1259 g_AlarmLinearizationContext = &context;
1260 HostedTimer::setAlarmSendAdmissionHookForTest(observeAlarmSendAdmission);
1261 Processor::setInterrupts(interruptsWereEnabled);
1262
1263 Thread* remover = new Thread(Scheduler::instance().getKernelProcess(), removeLinearizingAlarm,
1264 &context, nullptr, false, true);
1265 remover->setName("hosted alarm linearization remover");
1266 const bool removerReady = context.removerReady.acquireForCompletion(1, 2);
1267 if (removerReady) {
1268 timer->addAlarm(context.event, 0, 1000);
1269 }
1270 const bool removerJoined = remover->join();
1271
1273 HostedTimer::setAlarmSendAdmissionHookForTest(nullptr);
1274 g_AlarmLinearizationContext = nullptr;
1275 Processor::setInterrupts(interruptsWereEnabled);
1276
1277 if (context.event) {
1278 timer->removeAlarm(context.event);
1279 owner->cullEvent(context.event);
1280 delete context.event;
1281 context.event = nullptr;
1282 }
1283 owner->inhibitEvent(AlarmLinearizationEventNumber, false);
1284
1285 const bool passed =
1286 check(interruptsWereEnabled && removerReady && removerJoined && context.hookCalls == 1 &&
1287 context.lockBoundaryObserved == 1 && context.removerReturned == 1 &&
1288 context.destructions == 1 && context.failures == 0,
1289 Test, "alarm cancellation crossed the Event send-admission boundary");
1290 if (passed) {
1291 NOTICE("HOSTED-WAIT-TEST: PASS timer-alarm-send-linearization");
1292 }
1293 return passed;
1294}
1295
1296bool timerAlarmRemovalLifetime() {
1297 constexpr size_t AlarmBatchSize = 17;
1298 constexpr size_t AlarmDelaySeconds = ~static_cast<size_t>(0);
1299 Timer* timer = Machine::instance().getTimer();
1300 TimerTestEvent* events[AlarmBatchSize] = {};
1301 bool passed = true;
1302
1303 // The alarm list retains sixteen erased nodes. The seventeenth removal
1304 // forces a node free, making an erase-then-dereference bug visible to ASan.
1305 for (size_t i = 0; i < AlarmBatchSize; ++i) {
1306 events[i] = new TimerTestEvent(TimerTestEventNumber, false);
1307 timer->addAlarm(events[i], AlarmDelaySeconds);
1308 }
1309 for (size_t i = 0; i < AlarmBatchSize; ++i) {
1310 timer->removeAlarm(events[i]);
1311 passed &= timer->removeAlarm(events[i], false) == 0;
1312 delete events[i];
1313 }
1314
1315 for (size_t i = 0; i < AlarmBatchSize; ++i) {
1316 events[i] = new TimerTestEvent(TimerTestEventNumber, false);
1317 timer->addAlarm(events[i], AlarmDelaySeconds);
1318 }
1319 for (size_t i = 0; i < AlarmBatchSize; ++i) {
1320 passed &= timer->removeAlarm(events[i], false) > 0;
1321 passed &= timer->removeAlarm(events[i], false) == 0;
1322 delete events[i];
1323 }
1324
1325 passed &= check(passed, "timer-alarm-removal-lifetime",
1326 "an alarm removal overload left its alarm registered");
1327 if (passed) {
1328 NOTICE("HOSTED-WAIT-TEST: PASS timer-alarm-removal-lifetime");
1329 }
1330 return passed;
1331}
1332
1333bool timerHandlerLifetimeBarrier() {
1334 Timer* timer = Machine::instance().getTimer();
1335 HandlerLifetimeContext context(timer);
1336 Thread* remover = new Thread(Scheduler::instance().getKernelProcess(), unregisterPinnedHandler,
1337 &context, nullptr, false, true);
1338 context.remover = remover;
1339 remover->setName("hosted timer-handler remover");
1340
1341 g_HandlerLifetimeContext = &context;
1342 HostedTimer::setHandlerPinHook(handlerPinHook);
1343 WaitQueue::setBeforeBlockHook(timerDrainBeforeBlockHook);
1344 const bool registered = timer->registerHandler(&context.handler);
1345 const bool joined = remover->join();
1346 WaitQueue::setBeforeBlockHook(nullptr);
1347 HostedTimer::setHandlerPinHook(nullptr);
1348 g_HandlerLifetimeContext = nullptr;
1349
1350 if (!context.unregisterSucceeded) {
1351 timer->unregisterHandler(&context.handler);
1352 }
1353
1354 const size_t callsAtUnregisterReturn = context.handlerCalls;
1355 const uint64_t postReturnDeadline =
1356 timer->getTickCountNano() + (10 * Time::Multiplier::Millisecond);
1357 while (timer->getTickCountNano() < postReturnDeadline) {
1359 }
1360
1361 SelfRemovingHandler selfRemoving(timer);
1362 const bool selfRegistered = timer->registerHandler(&selfRemoving);
1363 const uint64_t selfDeadline = timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
1364 while (!selfRemoving.calls && timer->getTickCountNano() < selfDeadline) {
1366 }
1367 const size_t selfCallsAfterRetirement = selfRemoving.calls;
1368 const uint64_t selfQuietDeadline =
1369 timer->getTickCountNano() + (10 * Time::Multiplier::Millisecond);
1370 while (timer->getTickCountNano() < selfQuietDeadline) {
1372 }
1373
1374 // A callback cannot synchronously wait for its own pin. The rejected
1375 // call is retired when the callback unwinds, making its slot reusable.
1376 const bool selfReregistered = timer->registerHandler(&selfRemoving);
1377 const bool selfCleanup = selfReregistered && timer->unregisterHandler(&selfRemoving);
1378
1379 bool passed = true;
1380 passed &= check(registered, "timer-handler-lifetime", "the test handler could not be registered");
1381 passed &= check(joined && context.failures == 0, "timer-handler-lifetime",
1382 "the concurrent unregister worker did not complete cleanly");
1383 passed &=
1384 check(context.hookCalls == 1 && context.hookObservedDrain == 1, "timer-handler-lifetime",
1385 "unregister returned instead of waiting for the pinned callback");
1386 const bool waitQueueDrainPassed =
1387 registered && joined && context.waitHookCalls == 1 && context.waitHookFailures == 0 &&
1388 context.drainWaitPublished == 1 && context.wakeBeforeBlock == 1 &&
1389 context.hookObservedDrain == 1 && context.unregisterSucceeded == 1 &&
1390 context.unregisterReturned == 1;
1391 passed &= check(waitQueueDrainPassed, "timer-handler-waitqueue-drain",
1392 "callback drain did not publish and consume a pre-block wake");
1393 if (waitQueueDrainPassed) {
1394 NOTICE("HOSTED-WAIT-TEST: PASS timer-handler-waitqueue-drain");
1395 }
1396 passed &= check(context.unregisterSucceeded == 1 && context.unregisterReturned == 1,
1397 "timer-handler-lifetime", "the pinned handler did not unregister successfully");
1398 const bool selfDrainRacePassed =
1399 registered && joined && context.failures == 0 && context.hookObservedDrain == 1 &&
1400 context.selfRemovalPending == 0 && context.selfRemovalRejected == 1 &&
1401 context.unregisterSucceeded == 1 && context.unregisterReturned == 1 &&
1402 context.revivalBlocked == 1;
1403 passed &= check(selfDrainRacePassed, "timer-self-unregister-vs-drainer",
1404 "self-removal escaped or revived a synchronous drain");
1405 if (selfDrainRacePassed) {
1406 NOTICE(
1407 "HOSTED-WAIT-TEST: PASS "
1408 "timer-self-unregister-vs-drainer");
1409 }
1410 passed &=
1411 check(context.handlerCalls >= 1 && context.handlerCalls == callsAtUnregisterReturn &&
1412 context.callbacksAfterReturn == 0,
1413 "timer-handler-lifetime", "a timer callback began after unregisterHandler returned");
1414 passed &=
1415 check(selfRegistered && selfRemoving.rejectionSeen == 1 && selfCallsAfterRetirement == 1 &&
1416 selfRemoving.calls == selfCallsAfterRetirement,
1417 "timer-handler-lifetime",
1418 "self-unregister was not rejected and retired after callback return");
1419 passed &= check(selfReregistered && selfCleanup, "timer-handler-lifetime",
1420 "deferred self-removal did not release its registry slot");
1421
1422 if (passed) {
1423 NOTICE("HOSTED-WAIT-TEST: PASS timer-handler-lifetime");
1424 }
1425 return passed;
1426}
1427
1428struct SemaphoreTimeoutContext {
1429 SemaphoreTimeoutContext(Semaphore* semaphore, Thread* waiter)
1430 : semaphore(semaphore), waiter(waiter), sawTimeoutEvent(0), failures(0) {}
1431
1432 Semaphore* semaphore;
1433 Thread* waiter;
1434 Atomic<size_t> sawTimeoutEvent;
1435 Atomic<size_t> failures;
1436};
1437
1438int releaseAfterTimeoutQueued(void* parameter) {
1439 SemaphoreTimeoutContext* context = reinterpret_cast<SemaphoreTimeoutContext*>(parameter);
1440 Timer* timer = Machine::instance().getTimer();
1441 const uint64_t deadline = timer->getTickCountNano() + (500 * Time::Multiplier::Millisecond);
1442
1443 while (!context->waiter->hasEvent(EventNumbers::Interrupt) &&
1444 timer->getTickCountNano() < deadline) {
1446 }
1447
1448 if (!context->waiter->hasEvent(EventNumbers::Interrupt)) {
1449 context->failures += 1;
1450 return 1;
1451 }
1452
1453 context->sawTimeoutEvent += 1;
1454 context->semaphore->release();
1455 return 0;
1456}
1457
1458bool semaphoreQueuedTimeoutCancellation(Thread* thread) {
1459 Semaphore semaphore(0);
1460 SemaphoreTimeoutContext context(&semaphore, thread);
1461 Thread* releaser = new Thread(Scheduler::instance().getKernelProcess(), releaseAfterTimeoutQueued,
1462 &context, nullptr, false, true);
1463 releaser->setName("hosted queued-timeout releaser");
1464
1465 thread->inhibitEvent(EventNumbers::Interrupt, true);
1466 Semaphore::SemaphoreError error = Semaphore::NoError;
1467 const bool acquired = semaphore.acquireWithError(1, 0, 20000, error);
1468 const bool joined = releaser->join();
1469 const bool eventRemoved = !thread->hasEvent(EventNumbers::Interrupt);
1470 thread->inhibitEvent(EventNumbers::Interrupt, false);
1471
1472 bool passed = true;
1473 passed &= check(acquired && error == Semaphore::NoError, "semaphore-timeout-cancel",
1474 "a signalled waiter reported the queued timeout instead");
1475 passed &=
1476 check(joined && context.failures == 0 && context.sawTimeoutEvent == 1,
1477 "semaphore-timeout-cancel", "the releaser did not observe exactly one queued timeout");
1478 passed &=
1479 check(eventRemoved, "semaphore-timeout-cancel", "the losing timeout event remained queued");
1480 passed &= check(semaphore.getValue() == 0, "semaphore-timeout-cancel",
1481 "the released semaphore unit was not consumed");
1482
1483 if (passed) {
1484 NOTICE("HOSTED-WAIT-TEST: PASS fresh-thread-timer-progress");
1485 NOTICE("HOSTED-WAIT-TEST: PASS semaphore-timeout-cancel");
1486 }
1487 return passed;
1488}
1489
1490bool relayUsesLatestDisposition(Thread* thread) {
1491 Timer* timer = Machine::instance().getTimer();
1492 TimerTestEvent dispositionA(DispositionAEventNumber, false,
1493 reinterpret_cast<uintptr_t>(&dispositionAHandler));
1494 TimerTestEvent dispositionB(DispositionBEventNumber, false,
1495 reinterpret_cast<uintptr_t>(&dispositionBHandler));
1496 RelayEvent relay(&relayDisposition, RelayTestEventNumber);
1497
1498 g_RelayCallbacks = 0;
1499 g_DispositionACalls = 0;
1500 g_DispositionBCalls = 0;
1501 g_DispositionAEvent = &dispositionA;
1502 g_DispositionBEvent = &dispositionB;
1503
1504 g_RelayDisposition = 1;
1505 timer->addAlarm(&relay, 0, 20000);
1506 g_RelayDisposition = 2;
1507
1508 const uint64_t deadline = timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
1509 while (!g_DispositionBCalls && timer->getTickCountNano() < deadline) {
1511 }
1512
1513 timer->removeAlarm(&relay);
1514 thread->cullEvent(&relay);
1515 thread->cullEvent(&dispositionA);
1516 thread->cullEvent(&dispositionB);
1517 g_DispositionAEvent = nullptr;
1518 g_DispositionBEvent = nullptr;
1519
1520 const bool passed =
1521 check(g_RelayCallbacks == 1 && g_DispositionACalls == 0 && g_DispositionBCalls == 1,
1522 "relay-latest-disposition", "timer expiry retained the disposition from arm time");
1523 if (passed) {
1524 NOTICE("HOSTED-WAIT-TEST: PASS relay-latest-disposition");
1525 }
1526 return passed;
1527}
1528
1529bool exactCullRetainsOwnership(Thread* thread) {
1530 const size_t destructionsBefore = g_CullEventDestructions;
1531 TimerTestEvent* event = new TimerTestEvent(CullTestEventNumber, true);
1532
1533 thread->inhibitEvent(CullTestEventNumber, true);
1534 const bool queued = thread->sendEvent(event);
1535 thread->cullEvent(event);
1536 const bool removed = !thread->hasEvent(event);
1537 const bool retained = g_CullEventDestructions == destructionsBefore;
1538
1539 if (retained) {
1540 delete event;
1541 }
1542 thread->inhibitEvent(CullTestEventNumber, false);
1543
1544 bool passed = true;
1545 passed &= check(queued && removed, "timeoutguard-cancel-ownership",
1546 "the exact queued event was not culled");
1547 passed &= check(retained, "timeoutguard-cancel-ownership",
1548 "exact-event culling deleted caller-owned storage");
1549 passed &=
1550 check(g_CullEventDestructions == destructionsBefore + 1, "timeoutguard-cancel-ownership",
1551 "caller-owned event destruction did not occur exactly once");
1552
1553 if (passed) {
1554 NOTICE("HOSTED-WAIT-TEST: PASS timeoutguard-cancel-ownership");
1555 }
1556 return passed;
1557}
1558} // namespace
1559
1560bool runHostedTimerRegressions(Thread* thread) {
1561 return hostedTimerThreadContext(Processor::information().getCurrentThread()) &&
1562 hostedTimerOverrunAccounting() && timerWriterLockIndependentDispatch() &&
1563 timerRegistryAbsoluteDeadlines() && timerPrePinUnregisterRevalidation() &&
1564 timerAtomicDrainSelfRevival() && timerPartialHazardAbandonment() &&
1565 timerCommittedHazardAbandonment() && timerDeferredSelfRemovalAbandonment() &&
1566 timerClockAndDeadline(thread) && timerAlarmSendLinearization() &&
1567 timerAlarmRemovalLifetime() && timerHandlerLifetimeBarrier() &&
1568 semaphoreQueuedTimeoutCancellation(thread) && relayUsesLatestDisposition(thread) &&
1569 exactCullRetainsOwnership(thread);
1570}
Definition Event.h:49
virtual size_t getNumber()=0
virtual size_t serialize(uint8_t *pBuffer)=0
virtual Timer * getTimer()=0
static bool getInterrupts()
static ProcessorInformation & information()
static void pause()
static void setInterrupts(bool bEnable)
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:236
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3163
bool hasEvent(Event *pEvent)
Definition Thread.cc:2618
bool join()
Definition Thread.cc:2746
void cullEvent(Event *pEvent)
Definition Thread.cc:2172
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:574
bool sendEvent(Event *pEvent)
Definition Thread.cc:1115
void inhibitEvent(size_t eventNumber, bool bInhibit)
Definition Thread.cc:1973
bool armHandler(TimerHandler *handler, uint64_t deadline, uint64_t now)
bool dispatchDue(uint64_t now)
bool unregisterHandler(TimerHandler *handler)
virtual void timer(uint64_t delta)=0
virtual uint64_t getTickCount()=0
virtual void removeAlarm(class Event *pEvent)=0
virtual uint64_t getTickCountNano()
virtual void addAlarm(class Event *pEvent, size_t alarmSecs, size_t alarmUsecs=0)=0
@ Hex
Definition Log.h:124
const size_t UserStart
Start of user-defined events.