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
826bool timerWriterLockIndependentDispatch() {
827 constexpr const char* Test = "timer-dispatch-writer-lock-independent";
828 Timer* timer = Machine::instance().getTimer();
829 RegistryDispatchContext context(timer);
830 const bool registered = timer->registerHandler(&context.handler);
831
832 if (registered) {
833 g_TimerRegistryDispatchContext = &context;
834 context.mutationRequested = 1;
835 const uint64_t deadline = timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
836 while (context.mutationRequested != static_cast<size_t>(3) &&
837 timer->getTickCountNano() < deadline) {
839 }
840 g_TimerRegistryDispatchContext = nullptr;
841 }
842
843 const bool cleaned = registered && timer->unregisterHandler(&context.handler);
844 bool passed = true;
845 passed &= check(registered, Test, "the test handler could not be registered");
846 passed &= check(context.mutationRequested == 3 && context.hookCalls == 1 &&
847 context.admitted == 1 && context.calls >= 2,
848 Test, "dispatch or callback completion waited for the writer lock");
849 passed &= check(cleaned, Test, "the test handler could not be removed");
850 if (passed) {
851 NOTICE(
852 "HOSTED-WAIT-TEST: PASS "
853 "timer-dispatch-writer-lock-independent");
854 }
855 return passed;
856}
857
858void unregisterTimerBeforePin(TimerHandler* handler) {
859 RegistryDispatchContext* context = g_TimerRegistryDispatchContext;
860 if (!context || handler != &context->handler || !context->hookCalls.compareAndSwap(0, 1)) {
861 return;
862 }
863
864 if (context->timer->unregisterHandler(&context->handler)) {
865 context->unregisterSucceeded += 1;
866 }
867 context->hookCalls = 2;
868}
869
870bool timerPrePinUnregisterRevalidation() {
871 constexpr const char* Test = "timer-pre-pin-unregister-revalidation";
872 Timer* timer = Machine::instance().getTimer();
873 RegistryDispatchContext context(timer);
874
875 g_TimerRegistryDispatchContext = &context;
876 HostedTimer::setHandlerPrePinHook(unregisterTimerBeforePin);
877 const bool registered = timer->registerHandler(&context.handler);
878 if (registered) {
879 const uint64_t deadline = timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
880 while (context.hookCalls != static_cast<size_t>(2) && timer->getTickCountNano() < deadline) {
882 }
883 }
884 HostedTimer::setHandlerPrePinHook(nullptr);
885 g_TimerRegistryDispatchContext = nullptr;
886
887 const bool reused = context.unregisterSucceeded && timer->registerHandler(&context.handler);
888 bool cleaned = true;
889 if (reused) {
890 cleaned = timer->unregisterHandler(&context.handler);
891 } else if (registered && !context.unregisterSucceeded) {
892 cleaned = timer->unregisterHandler(&context.handler);
893 }
894
895 bool passed = true;
896 passed &= check(registered, Test, "the test handler could not be registered");
897 passed &= check(context.hookCalls == 2 && context.unregisterSucceeded == 1, Test,
898 "unregister did not retire the pre-pin publication");
899 passed &=
900 check(context.calls == 0, Test, "a stale pre-pin snapshot entered the retired callback");
901 passed &= check(reused && cleaned, Test, "the revalidated slot could not be reused and removed");
902 if (passed) {
903 NOTICE(
904 "HOSTED-WAIT-TEST: PASS "
905 "timer-pre-pin-unregister-revalidation");
906 }
907 return passed;
908}
909
910bool timerAtomicDrainSelfRevival() {
911 constexpr const char* Test = "timer-atomic-drain-self-revival";
912 Timer* timer = Machine::instance().getTimer();
913 AtomicDrainRaceContext context(timer);
914
915 g_AtomicDrainRaceContext = &context;
916 HostedTimer::setHandlerPinHook(atomicDrainPinHook);
917 HostedTimer::setHandlerAtomicDrainHook(atomicDrainTransitionHook);
918 Thread* remover = new Thread(Scheduler::instance().getKernelProcess(), removeTimerAtomically,
919 &context, nullptr, false, true);
920 remover->setName("hosted atomic timer-handler remover");
921
922 const bool removerWasReady = context.removerReady.acquireForCompletion(1, 2);
923 const bool registered = removerWasReady && timer->registerHandler(&context.handler);
924 if (!registered) {
925 context.phase = 1;
926 context.beginDrain.release();
927 }
928 const bool joined = remover->join();
929 HostedTimer::setHandlerAtomicDrainHook(nullptr);
930 HostedTimer::setHandlerPinHook(nullptr);
931 g_AtomicDrainRaceContext = nullptr;
932
933 const size_t callsAtAtomicReturn = context.handlerCalls;
934 const uint64_t liveDeadline = timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
935 while (context.handlerCalls == callsAtAtomicReturn && timer->getTickCountNano() < liveDeadline) {
937 }
938 const bool remainedLive = context.handlerCalls > callsAtAtomicReturn;
939 const bool cleaned = registered && timer->unregisterHandler(&context.handler);
940
941 bool passed = true;
942 passed &=
943 check(removerWasReady, Test, "the atomic remover did not become ready before registration");
944 passed &= check(registered, Test, "the test handler could not be registered");
945 passed &= check(context.beginDrainTimedOut == 0, Test,
946 "the atomic remover timed out waiting for the pin hook");
947 passed &= check(context.pinTransitionTimedOut == 0, Test,
948 "the pin hook timed out waiting for the atomic drain transition");
949 passed &= check(context.drainCompletionTimedOut == 0, Test,
950 "the atomic drain hook timed out waiting for callback revival");
951 passed &= check(
952 joined && context.failures == 0 && context.pinHookCalls == 1 && context.drainHookCalls == 1,
953 Test, "the atomic drainer interleaving did not complete cleanly");
954 passed &= check(context.selfRemovalRejected == 1 && context.atomicRemovalRejected == 1 &&
955 context.revivalSucceeded == 1,
956 Test, "the self-removal request was lost or could not be revived");
957 passed &= check(remainedLive && cleaned, Test,
958 "the revived handler was not live or could not be removed");
959 if (passed) {
960 NOTICE(
961 "HOSTED-WAIT-TEST: PASS "
962 "timer-atomic-drain-self-revival");
963 }
964 return passed;
965}
966
967class ThreadContextTimerHandler : public TimerHandler {
968 public:
969 explicit ThreadContextTimerHandler(Thread* registeringThread)
970 : registeringThread(registeringThread),
971 calls(0),
972 failures(0),
973 failureMask(0),
974 workerAddress(0) {}
975
976 void timer(uint64_t delta) override {
977 Thread* current = Processor::information().getCurrentThread();
978 const size_t currentAddress = reinterpret_cast<size_t>(current);
979 size_t expectedWorker = workerAddress.value();
980 if (!expectedWorker && current) {
981 workerAddress.compareAndSwap(0, currentAddress);
982 expectedWorker = workerAddress.value();
983 }
984
985 size_t failed = 0;
986 failed |= !current ? 1 : 0;
987 failed |= current == registeringThread ? 2 : 0;
988 failed |= expectedWorker != currentAddress ? 4 : 0;
989 failed |= !Processor::getInterrupts() ? 8 : 0;
990 failed |= current && current->getHostedSignalDepth() ? 16 : 0;
991 failed |= !delta || (delta % Time::Multiplier::Millisecond) ? 32 : 0;
992 if (failed) {
993 failures += 1;
994 failureMask |= failed;
995 }
996 calls += 1;
997 }
998
999 Thread* registeringThread;
1000 Atomic<size_t> calls;
1001 Atomic<size_t> failures;
1002 Atomic<size_t> failureMask;
1003 Atomic<size_t> workerAddress;
1004};
1005
1006bool hostedTimerThreadContext(Thread* registeringThread) {
1007 constexpr const char* Test = "hosted-timer-thread-context";
1008 Timer* timer = Machine::instance().getTimer();
1009 ThreadContextTimerHandler handler(registeringThread);
1010 const bool registered = timer->registerHandler(&handler);
1011
1012 const uint64_t deadline = timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
1013 while (registered && handler.calls < static_cast<size_t>(2) &&
1014 timer->getTickCountNano() < deadline) {
1016 }
1017
1018 const bool removed = registered && timer->unregisterHandler(&handler);
1019 const bool passed =
1020 check(registered && removed && handler.calls >= static_cast<size_t>(2) &&
1021 handler.failures == 0 && handler.workerAddress != static_cast<size_t>(0),
1022 Test, "timer callbacks did not remain on one ordinary worker context");
1023 if (!passed) {
1024 ERROR("HOSTED-WAIT-TEST: INFO "
1025 << Test << ": calls=" << handler.calls.value()
1026 << ", failures=" << handler.failures.value() << ", mask=" << Hex
1027 << handler.failureMask.value() << ", worker=" << handler.workerAddress.value()
1028 << ", registrar=" << reinterpret_cast<uintptr_t>(registeringThread));
1029 }
1030 if (passed) {
1031 NOTICE("HOSTED-WAIT-TEST: PASS hosted-timer-thread-context");
1032 }
1033 return passed;
1034}
1035
1036class HostedTimerOverrunHandler : public TimerHandler {
1037 public:
1038 HostedTimerOverrunHandler() : calls(0), maximumDelta(0) {}
1039
1040 void timer(uint64_t delta) override {
1041 uint64_t maximum = maximumDelta.value();
1042 while (delta > maximum && !maximumDelta.compareAndSwap(maximum, delta)) {
1043 maximum = maximumDelta.value();
1044 }
1045 calls += 1;
1046 }
1047
1048 Atomic<size_t> calls;
1049 Atomic<uint64_t> maximumDelta;
1050};
1051
1052bool hostedTimerOverrunAccounting() {
1053 constexpr const char* Test = "hosted-timer-overrun-accounting";
1054 Timer* timer = Machine::instance().getTimer();
1055 HostedTimerOverrunHandler handler;
1056 const bool registered = timer->registerHandler(&handler);
1057
1058 const uint64_t baselineDeadline =
1059 timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
1060 while (registered && !handler.calls && timer->getTickCountNano() < baselineDeadline) {
1062 }
1063
1064 const bool baselineObserved = handler.calls != static_cast<size_t>(0);
1065 handler.maximumDelta = 0;
1066 const bool interruptsWereEnabled = Processor::getInterrupts();
1067 if (baselineObserved && interruptsWereEnabled) {
1069 const uint64_t blockedUntil = timer->getTickCountNano() + (30 * Time::Multiplier::Millisecond);
1070 while (timer->getTickCountNano() < blockedUntil) {
1072 }
1074 }
1075
1076 const uint64_t overrunDeadline =
1077 timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
1078 while (handler.maximumDelta <= Time::Multiplier::Millisecond &&
1079 timer->getTickCountNano() < overrunDeadline) {
1081 }
1082
1083 const bool removed = registered && timer->unregisterHandler(&handler);
1084 const uint64_t maximumDelta = handler.maximumDelta.value();
1085 const bool passed =
1086 check(registered && baselineObserved && interruptsWereEnabled && removed &&
1087 maximumDelta > Time::Multiplier::Millisecond &&
1088 !(maximumDelta % Time::Multiplier::Millisecond),
1089 Test, "deferred POSIX expirations were not preserved in callback delta");
1090 if (passed) {
1091 NOTICE("HOSTED-WAIT-TEST: PASS hosted-timer-overrun-accounting");
1092 }
1093 return passed;
1094}
1095
1096bool timerClockAndDeadline(Thread* thread) {
1097 Timer* timer = Machine::instance().getTimer();
1098 bool passed = true;
1099
1100 const uint64_t nanosBefore = timer->getTickCountNano();
1101 const uint64_t millis = timer->getTickCount();
1102 const uint64_t nanosAfter = timer->getTickCountNano();
1103 passed &= check(millis >= nanosBefore / Time::Multiplier::Millisecond &&
1104 millis <= nanosAfter / Time::Multiplier::Millisecond,
1105 "timer-clock-deadline",
1106 "millisecond and nanosecond tick APIs use different clocks or units");
1107
1108 TimerTestEvent* remainingEvent = new TimerTestEvent(TimerTestEventNumber, false);
1109 timer->addAlarm(remainingEvent, 2);
1110 const size_t remaining = timer->removeAlarm(remainingEvent, false);
1111 delete remainingEvent;
1112 passed &= check(remaining == 2, "timer-clock-deadline",
1113 "alarm removal did not return whole remaining seconds");
1114
1115 thread->inhibitEvent(TimerTestEventNumber, true);
1116 TimerTestEvent* overflowEvent = new TimerTestEvent(TimerTestEventNumber, false);
1117 const uint64_t overflowStart = timer->getTickCountNano();
1118 timer->addAlarm(overflowEvent, ~static_cast<size_t>(0), ~static_cast<size_t>(0));
1119 while (timer->getTickCountNano() < overflowStart + (5 * Time::Multiplier::Millisecond)) {
1121 }
1122 const bool overflowFired = thread->hasEvent(overflowEvent);
1123 timer->removeAlarm(overflowEvent);
1124 thread->cullEvent(overflowEvent);
1125 delete overflowEvent;
1126 passed &= check(!overflowFired, "timer-clock-deadline",
1127 "an overflowing alarm duration wrapped into an immediate deadline");
1128
1129 TimerTestEvent* deadlineEvent = new TimerTestEvent(TimerTestEventNumber, false);
1130 const uint64_t start = timer->getTickCountNano();
1131 timer->addAlarm(deadlineEvent, 0, 20000);
1132
1133 const uint64_t earlyDeadline = start + (5 * Time::Multiplier::Millisecond);
1134 while (timer->getTickCountNano() < earlyDeadline) {
1136 }
1137 const bool firedEarly = thread->hasEvent(deadlineEvent);
1138
1139 const uint64_t lateDeadline = start + (250 * Time::Multiplier::Millisecond);
1140 while (!thread->hasEvent(deadlineEvent) && timer->getTickCountNano() < lateDeadline) {
1142 }
1143 const bool fired = thread->hasEvent(deadlineEvent);
1144
1145 timer->removeAlarm(deadlineEvent);
1146 thread->cullEvent(deadlineEvent);
1147 const bool removed = !thread->hasEvent(deadlineEvent);
1148 delete deadlineEvent;
1149 thread->inhibitEvent(TimerTestEventNumber, false);
1150
1151 passed &= check(!firedEarly, "timer-clock-deadline", "a 20ms alarm fired during its first 5ms");
1152 passed &= check(fired, "timer-clock-deadline", "a 20ms alarm did not publish within 250ms");
1153 passed &= check(removed, "timer-clock-deadline", "a cancelled alarm event remained queued");
1154
1155 if (passed) {
1156 NOTICE("HOSTED-WAIT-TEST: PASS timer-clock-deadline");
1157 }
1158 return passed;
1159}
1160
1161bool timerAlarmSendLinearization() {
1162 constexpr const char* Test = "timer-alarm-send-linearization";
1163 Timer* timer = Machine::instance().getTimer();
1164 Thread* owner = Processor::information().getCurrentThread();
1165 AlarmLinearizationContext context(timer, owner);
1166 context.event = new AlarmLinearizationEvent(context);
1167 owner->inhibitEvent(AlarmLinearizationEventNumber, true);
1168
1169 const bool interruptsWereEnabled = Processor::getInterrupts();
1171 g_AlarmLinearizationContext = &context;
1172 HostedTimer::setAlarmSendAdmissionHookForTest(observeAlarmSendAdmission);
1173 Processor::setInterrupts(interruptsWereEnabled);
1174
1175 Thread* remover = new Thread(Scheduler::instance().getKernelProcess(), removeLinearizingAlarm,
1176 &context, nullptr, false, true);
1177 remover->setName("hosted alarm linearization remover");
1178 const bool removerReady = context.removerReady.acquireForCompletion(1, 2);
1179 if (removerReady) {
1180 timer->addAlarm(context.event, 0, 1000);
1181 }
1182 const bool removerJoined = remover->join();
1183
1185 HostedTimer::setAlarmSendAdmissionHookForTest(nullptr);
1186 g_AlarmLinearizationContext = nullptr;
1187 Processor::setInterrupts(interruptsWereEnabled);
1188
1189 if (context.event) {
1190 timer->removeAlarm(context.event);
1191 owner->cullEvent(context.event);
1192 delete context.event;
1193 context.event = nullptr;
1194 }
1195 owner->inhibitEvent(AlarmLinearizationEventNumber, false);
1196
1197 const bool passed =
1198 check(interruptsWereEnabled && removerReady && removerJoined && context.hookCalls == 1 &&
1199 context.lockBoundaryObserved == 1 && context.removerReturned == 1 &&
1200 context.destructions == 1 && context.failures == 0,
1201 Test, "alarm cancellation crossed the Event send-admission boundary");
1202 if (passed) {
1203 NOTICE("HOSTED-WAIT-TEST: PASS timer-alarm-send-linearization");
1204 }
1205 return passed;
1206}
1207
1208bool timerAlarmRemovalLifetime() {
1209 constexpr size_t AlarmBatchSize = 17;
1210 constexpr size_t AlarmDelaySeconds = ~static_cast<size_t>(0);
1211 Timer* timer = Machine::instance().getTimer();
1212 TimerTestEvent* events[AlarmBatchSize] = {};
1213 bool passed = true;
1214
1215 // The alarm list retains sixteen erased nodes. The seventeenth removal
1216 // forces a node free, making an erase-then-dereference bug visible to ASan.
1217 for (size_t i = 0; i < AlarmBatchSize; ++i) {
1218 events[i] = new TimerTestEvent(TimerTestEventNumber, false);
1219 timer->addAlarm(events[i], AlarmDelaySeconds);
1220 }
1221 for (size_t i = 0; i < AlarmBatchSize; ++i) {
1222 timer->removeAlarm(events[i]);
1223 passed &= timer->removeAlarm(events[i], false) == 0;
1224 delete events[i];
1225 }
1226
1227 for (size_t i = 0; i < AlarmBatchSize; ++i) {
1228 events[i] = new TimerTestEvent(TimerTestEventNumber, false);
1229 timer->addAlarm(events[i], AlarmDelaySeconds);
1230 }
1231 for (size_t i = 0; i < AlarmBatchSize; ++i) {
1232 passed &= timer->removeAlarm(events[i], false) > 0;
1233 passed &= timer->removeAlarm(events[i], false) == 0;
1234 delete events[i];
1235 }
1236
1237 passed &= check(passed, "timer-alarm-removal-lifetime",
1238 "an alarm removal overload left its alarm registered");
1239 if (passed) {
1240 NOTICE("HOSTED-WAIT-TEST: PASS timer-alarm-removal-lifetime");
1241 }
1242 return passed;
1243}
1244
1245bool timerHandlerLifetimeBarrier() {
1246 Timer* timer = Machine::instance().getTimer();
1247 HandlerLifetimeContext context(timer);
1248 Thread* remover = new Thread(Scheduler::instance().getKernelProcess(), unregisterPinnedHandler,
1249 &context, nullptr, false, true);
1250 context.remover = remover;
1251 remover->setName("hosted timer-handler remover");
1252
1253 g_HandlerLifetimeContext = &context;
1254 HostedTimer::setHandlerPinHook(handlerPinHook);
1255 WaitQueue::setBeforeBlockHook(timerDrainBeforeBlockHook);
1256 const bool registered = timer->registerHandler(&context.handler);
1257 const bool joined = remover->join();
1258 WaitQueue::setBeforeBlockHook(nullptr);
1259 HostedTimer::setHandlerPinHook(nullptr);
1260 g_HandlerLifetimeContext = nullptr;
1261
1262 if (!context.unregisterSucceeded) {
1263 timer->unregisterHandler(&context.handler);
1264 }
1265
1266 const size_t callsAtUnregisterReturn = context.handlerCalls;
1267 const uint64_t postReturnDeadline =
1268 timer->getTickCountNano() + (10 * Time::Multiplier::Millisecond);
1269 while (timer->getTickCountNano() < postReturnDeadline) {
1271 }
1272
1273 SelfRemovingHandler selfRemoving(timer);
1274 const bool selfRegistered = timer->registerHandler(&selfRemoving);
1275 const uint64_t selfDeadline = timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
1276 while (!selfRemoving.calls && timer->getTickCountNano() < selfDeadline) {
1278 }
1279 const size_t selfCallsAfterRetirement = selfRemoving.calls;
1280 const uint64_t selfQuietDeadline =
1281 timer->getTickCountNano() + (10 * Time::Multiplier::Millisecond);
1282 while (timer->getTickCountNano() < selfQuietDeadline) {
1284 }
1285
1286 // A callback cannot synchronously wait for its own pin. The rejected
1287 // call is retired when the callback unwinds, making its slot reusable.
1288 const bool selfReregistered = timer->registerHandler(&selfRemoving);
1289 const bool selfCleanup = selfReregistered && timer->unregisterHandler(&selfRemoving);
1290
1291 bool passed = true;
1292 passed &= check(registered, "timer-handler-lifetime", "the test handler could not be registered");
1293 passed &= check(joined && context.failures == 0, "timer-handler-lifetime",
1294 "the concurrent unregister worker did not complete cleanly");
1295 passed &=
1296 check(context.hookCalls == 1 && context.hookObservedDrain == 1, "timer-handler-lifetime",
1297 "unregister returned instead of waiting for the pinned callback");
1298 const bool waitQueueDrainPassed =
1299 registered && joined && context.waitHookCalls == 1 && context.waitHookFailures == 0 &&
1300 context.drainWaitPublished == 1 && context.wakeBeforeBlock == 1 &&
1301 context.hookObservedDrain == 1 && context.unregisterSucceeded == 1 &&
1302 context.unregisterReturned == 1;
1303 passed &= check(waitQueueDrainPassed, "timer-handler-waitqueue-drain",
1304 "callback drain did not publish and consume a pre-block wake");
1305 if (waitQueueDrainPassed) {
1306 NOTICE("HOSTED-WAIT-TEST: PASS timer-handler-waitqueue-drain");
1307 }
1308 passed &= check(context.unregisterSucceeded == 1 && context.unregisterReturned == 1,
1309 "timer-handler-lifetime", "the pinned handler did not unregister successfully");
1310 const bool selfDrainRacePassed =
1311 registered && joined && context.failures == 0 && context.hookObservedDrain == 1 &&
1312 context.selfRemovalPending == 0 && context.selfRemovalRejected == 1 &&
1313 context.unregisterSucceeded == 1 && context.unregisterReturned == 1 &&
1314 context.revivalBlocked == 1;
1315 passed &= check(selfDrainRacePassed, "timer-self-unregister-vs-drainer",
1316 "self-removal escaped or revived a synchronous drain");
1317 if (selfDrainRacePassed) {
1318 NOTICE(
1319 "HOSTED-WAIT-TEST: PASS "
1320 "timer-self-unregister-vs-drainer");
1321 }
1322 passed &=
1323 check(context.handlerCalls >= 1 && context.handlerCalls == callsAtUnregisterReturn &&
1324 context.callbacksAfterReturn == 0,
1325 "timer-handler-lifetime", "a timer callback began after unregisterHandler returned");
1326 passed &=
1327 check(selfRegistered && selfRemoving.rejectionSeen == 1 && selfCallsAfterRetirement == 1 &&
1328 selfRemoving.calls == selfCallsAfterRetirement,
1329 "timer-handler-lifetime",
1330 "self-unregister was not rejected and retired after callback return");
1331 passed &= check(selfReregistered && selfCleanup, "timer-handler-lifetime",
1332 "deferred self-removal did not release its registry slot");
1333
1334 if (passed) {
1335 NOTICE("HOSTED-WAIT-TEST: PASS timer-handler-lifetime");
1336 }
1337 return passed;
1338}
1339
1340struct SemaphoreTimeoutContext {
1341 SemaphoreTimeoutContext(Semaphore* semaphore, Thread* waiter)
1342 : semaphore(semaphore), waiter(waiter), sawTimeoutEvent(0), failures(0) {}
1343
1344 Semaphore* semaphore;
1345 Thread* waiter;
1346 Atomic<size_t> sawTimeoutEvent;
1347 Atomic<size_t> failures;
1348};
1349
1350int releaseAfterTimeoutQueued(void* parameter) {
1351 SemaphoreTimeoutContext* context = reinterpret_cast<SemaphoreTimeoutContext*>(parameter);
1352 Timer* timer = Machine::instance().getTimer();
1353 const uint64_t deadline = timer->getTickCountNano() + (500 * Time::Multiplier::Millisecond);
1354
1355 while (!context->waiter->hasEvent(EventNumbers::Interrupt) &&
1356 timer->getTickCountNano() < deadline) {
1358 }
1359
1360 if (!context->waiter->hasEvent(EventNumbers::Interrupt)) {
1361 context->failures += 1;
1362 return 1;
1363 }
1364
1365 context->sawTimeoutEvent += 1;
1366 context->semaphore->release();
1367 return 0;
1368}
1369
1370bool semaphoreQueuedTimeoutCancellation(Thread* thread) {
1371 Semaphore semaphore(0);
1372 SemaphoreTimeoutContext context(&semaphore, thread);
1373 Thread* releaser = new Thread(Scheduler::instance().getKernelProcess(), releaseAfterTimeoutQueued,
1374 &context, nullptr, false, true);
1375 releaser->setName("hosted queued-timeout releaser");
1376
1377 thread->inhibitEvent(EventNumbers::Interrupt, true);
1378 Semaphore::SemaphoreError error = Semaphore::NoError;
1379 const bool acquired = semaphore.acquireWithError(1, 0, 20000, error);
1380 const bool joined = releaser->join();
1381 const bool eventRemoved = !thread->hasEvent(EventNumbers::Interrupt);
1382 thread->inhibitEvent(EventNumbers::Interrupt, false);
1383
1384 bool passed = true;
1385 passed &= check(acquired && error == Semaphore::NoError, "semaphore-timeout-cancel",
1386 "a signalled waiter reported the queued timeout instead");
1387 passed &=
1388 check(joined && context.failures == 0 && context.sawTimeoutEvent == 1,
1389 "semaphore-timeout-cancel", "the releaser did not observe exactly one queued timeout");
1390 passed &=
1391 check(eventRemoved, "semaphore-timeout-cancel", "the losing timeout event remained queued");
1392 passed &= check(semaphore.getValue() == 0, "semaphore-timeout-cancel",
1393 "the released semaphore unit was not consumed");
1394
1395 if (passed) {
1396 NOTICE("HOSTED-WAIT-TEST: PASS fresh-thread-timer-progress");
1397 NOTICE("HOSTED-WAIT-TEST: PASS semaphore-timeout-cancel");
1398 }
1399 return passed;
1400}
1401
1402bool relayUsesLatestDisposition(Thread* thread) {
1403 Timer* timer = Machine::instance().getTimer();
1404 TimerTestEvent dispositionA(DispositionAEventNumber, false,
1405 reinterpret_cast<uintptr_t>(&dispositionAHandler));
1406 TimerTestEvent dispositionB(DispositionBEventNumber, false,
1407 reinterpret_cast<uintptr_t>(&dispositionBHandler));
1408 RelayEvent relay(&relayDisposition, RelayTestEventNumber);
1409
1410 g_RelayCallbacks = 0;
1411 g_DispositionACalls = 0;
1412 g_DispositionBCalls = 0;
1413 g_DispositionAEvent = &dispositionA;
1414 g_DispositionBEvent = &dispositionB;
1415
1416 g_RelayDisposition = 1;
1417 timer->addAlarm(&relay, 0, 20000);
1418 g_RelayDisposition = 2;
1419
1420 const uint64_t deadline = timer->getTickCountNano() + (250 * Time::Multiplier::Millisecond);
1421 while (!g_DispositionBCalls && timer->getTickCountNano() < deadline) {
1423 }
1424
1425 timer->removeAlarm(&relay);
1426 thread->cullEvent(&relay);
1427 thread->cullEvent(&dispositionA);
1428 thread->cullEvent(&dispositionB);
1429 g_DispositionAEvent = nullptr;
1430 g_DispositionBEvent = nullptr;
1431
1432 const bool passed =
1433 check(g_RelayCallbacks == 1 && g_DispositionACalls == 0 && g_DispositionBCalls == 1,
1434 "relay-latest-disposition", "timer expiry retained the disposition from arm time");
1435 if (passed) {
1436 NOTICE("HOSTED-WAIT-TEST: PASS relay-latest-disposition");
1437 }
1438 return passed;
1439}
1440
1441bool exactCullRetainsOwnership(Thread* thread) {
1442 const size_t destructionsBefore = g_CullEventDestructions;
1443 TimerTestEvent* event = new TimerTestEvent(CullTestEventNumber, true);
1444
1445 thread->inhibitEvent(CullTestEventNumber, true);
1446 const bool queued = thread->sendEvent(event);
1447 thread->cullEvent(event);
1448 const bool removed = !thread->hasEvent(event);
1449 const bool retained = g_CullEventDestructions == destructionsBefore;
1450
1451 if (retained) {
1452 delete event;
1453 }
1454 thread->inhibitEvent(CullTestEventNumber, false);
1455
1456 bool passed = true;
1457 passed &= check(queued && removed, "timeoutguard-cancel-ownership",
1458 "the exact queued event was not culled");
1459 passed &= check(retained, "timeoutguard-cancel-ownership",
1460 "exact-event culling deleted caller-owned storage");
1461 passed &=
1462 check(g_CullEventDestructions == destructionsBefore + 1, "timeoutguard-cancel-ownership",
1463 "caller-owned event destruction did not occur exactly once");
1464
1465 if (passed) {
1466 NOTICE("HOSTED-WAIT-TEST: PASS timeoutguard-cancel-ownership");
1467 }
1468 return passed;
1469}
1470} // namespace
1471
1472bool runHostedTimerRegressions(Thread* thread) {
1473 return hostedTimerThreadContext(Processor::information().getCurrentThread()) &&
1474 hostedTimerOverrunAccounting() && timerWriterLockIndependentDispatch() &&
1475 timerPrePinUnregisterRevalidation() && timerAtomicDrainSelfRevival() &&
1476 timerPartialHazardAbandonment() && timerCommittedHazardAbandonment() &&
1477 timerDeferredSelfRemovalAbandonment() && timerClockAndDeadline(thread) &&
1478 timerAlarmSendLinearization() && timerAlarmRemovalLifetime() &&
1479 timerHandlerLifetimeBarrier() && semaphoreQueuedTimeoutCancellation(thread) &&
1480 relayUsesLatestDisposition(thread) && exactCullRetainsOwnership(thread);
1481}
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:226
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3184
bool hasEvent(Event *pEvent)
Definition Thread.cc:2639
bool join()
Definition Thread.cc:2767
void cullEvent(Event *pEvent)
Definition Thread.cc:2193
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:570
bool sendEvent(Event *pEvent)
Definition Thread.cc:1158
void inhibitEvent(size_t eventNumber, bool bInhibit)
Definition Thread.cc:1994
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:142
const size_t UserStart
Start of user-defined events.