The Pedigree Project 0.1
TimerHandlerRegistry.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/LockGuard.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/machine/TimerHandler.h"
11#include "pedigree/kernel/machine/TimerHandlerRegistry.h"
12#include "pedigree/kernel/process/TerminationDeferral.h"
13#include "pedigree/kernel/process/Thread.h"
14#include "pedigree/kernel/processor/Processor.h"
15#include "pedigree/kernel/processor/ProcessorInformation.h"
16#include "pedigree/kernel/utilities/assert.h"
17
18TimerHandlerRegistry::TimerHandlerRegistry()
19 : m_Handlers(),
20 m_ActiveDispatches(),
21 m_DispatchWaiters(),
22 m_HandlerLock(false)
23#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
24 ,
25 m_HandlerPinHook(nullptr),
26 m_HandlerPrePinHook(nullptr),
27 m_HandlerHazardClaimHook(nullptr),
28 m_HandlerAtomicDrainHook(nullptr)
29#endif
30{
31}
32
33size_t TimerHandlerRegistry::makePublication(size_t generation, SlotMode mode, bool selfRemoval,
34 bool synchronousDrain) {
35 return (generation << GenerationShift) | (selfRemoval ? SelfRemovalBit : 0) |
36 (synchronousDrain ? SynchronousDrainBit : 0) | static_cast<size_t>(mode);
37}
38
39size_t TimerHandlerRegistry::generationOf(size_t publication) {
40 return publication >> GenerationShift;
41}
42
43TimerHandlerRegistry::SlotMode TimerHandlerRegistry::modeOf(size_t publication) {
44 return static_cast<SlotMode>(publication & ModeMask);
45}
46
47bool TimerHandlerRegistry::selfRemovalOf(size_t publication) {
48 return publication & SelfRemovalBit;
49}
50
51bool TimerHandlerRegistry::synchronousDrainOf(size_t publication) {
52 return publication & SynchronousDrainBit;
53}
54
55bool TimerHandlerRegistry::retireSlot(HandlerSlot& slot, size_t expectedPublication,
56 TimerHandler* expectedHandler) {
57 const size_t retiringPublication =
58 makePublication(generationOf(expectedPublication), SlotMode::Retiring);
59 if (!__atomic_compare_exchange_n(&slot.publication, &expectedPublication, retiringPublication,
60 false, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
61 return false;
62 }
63
64 assert(__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) == expectedHandler);
65 __atomic_store_n(&slot.deadline, NoDeadline, __ATOMIC_RELEASE);
66 __atomic_store_n(&slot.lastDispatch, NoDeadline, __ATOMIC_RELEASE);
67 __atomic_store_n(&slot.handler, nullptr, __ATOMIC_RELEASE);
68 __atomic_store_n(&slot.publication,
69 makePublication(generationOf(retiringPublication), SlotMode::Empty),
70 __ATOMIC_RELEASE);
71 return true;
72}
73
74bool TimerHandlerRegistry::publishDispatch(DispatchCleanup& cleanup) {
75 HandlerSlot& slot = *cleanup.slot;
76 void* token = &cleanup;
77 assert(cleanup.owner);
78 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
79 ActiveDispatch& dispatch = m_ActiveDispatches[i];
80 // Select before claiming so cleanup can release even a partial publication.
81 __atomic_store_n(&cleanup.activeDispatch, &dispatch, __ATOMIC_RELAXED);
82 void* expectedToken = nullptr;
83 if (__atomic_compare_exchange_n(&dispatch.token, &expectedToken, token, false, __ATOMIC_SEQ_CST,
84 __ATOMIC_SEQ_CST)) {
85 __atomic_add_fetch(&dispatch.generation, static_cast<size_t>(1), __ATOMIC_ACQ_REL);
86 __atomic_store_n(&dispatch.owner, cleanup.owner, __ATOMIC_RELAXED);
87
88#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
89 HandlerHazardClaimHook hazardClaimHook =
90 __atomic_load_n(&m_HandlerHazardClaimHook, __ATOMIC_ACQUIRE);
91 if (hazardClaimHook) {
92 hazardClaimHook(__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE));
93 }
94#endif
95
96 // This store is the callback pin. Cleanup is armed before the
97 // token claim, so abandoning either side of this publication is
98 // recoverable without a separate in-flight counter.
99 __atomic_store_n(&dispatch.slot, &slot, __ATOMIC_SEQ_CST);
100 return true;
101 }
102 }
103
104 return false;
105}
106
107bool TimerHandlerRegistry::unpublishDispatch(DispatchCleanup& cleanup, bool required) {
108 ActiveDispatch* dispatch = __atomic_load_n(&cleanup.activeDispatch, __ATOMIC_RELAXED);
109 void* token = &cleanup;
110 if (!dispatch || __atomic_load_n(&dispatch->token, __ATOMIC_ACQUIRE) != token) {
111 if (required) {
112 FATAL_NOLOCK("Timer callback hazard was released more than once.");
113 }
114 return false;
115 }
116
117 const size_t generation = __atomic_load_n(&dispatch->generation, __ATOMIC_ACQUIRE);
118 HandlerSlot* publishedSlot = __atomic_load_n(&dispatch->slot, __ATOMIC_SEQ_CST);
119 if (__atomic_load_n(&dispatch->token, __ATOMIC_ACQUIRE) != token ||
120 __atomic_load_n(&dispatch->generation, __ATOMIC_ACQUIRE) != generation) {
121 if (required) {
122 FATAL_NOLOCK("Timer callback hazard changed during release.");
123 }
124 return false;
125 }
126
127 HandlerSlot& slot = *cleanup.slot;
128 const size_t admittedPublication = cleanup.publication;
129 if (publishedSlot && publishedSlot != &slot) {
130 FATAL_NOLOCK("Timer callback hazard changed slots during release.");
131 return false;
132 }
133
134 const bool committed = publishedSlot == &slot;
135 __atomic_store_n(&dispatch->slot, nullptr, __ATOMIC_SEQ_CST);
136 __atomic_store_n(&dispatch->owner, nullptr, __ATOMIC_RELAXED);
137 __atomic_store_n(&dispatch->token, nullptr, __ATOMIC_RELEASE);
138
139 if (!committed) {
140 return true;
141 }
142
143 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
144 if (generationOf(publication) != generationOf(admittedPublication)) {
145 // A remover won before a partial hazard publication committed and the
146 // slot has since been reused. This stale dispatch must not retire the
147 // new generation.
148 return true;
149 }
150
151 const SlotMode mode = modeOf(publication);
152 if (mode != SlotMode::Draining && mode != SlotMode::Deferred && !selfRemovalOf(publication)) {
153 // The pin was cleared before this publication sample. A later remover
154 // will see that release in its own hazard scan; only an existing drain
155 // needs callback-return help, so ordinary ticks need no table scan.
156 return true;
157 }
158 if (hasActiveDispatch(slot)) {
159 return true;
160 }
161
162 const size_t drainGeneration = generationOf(publication);
163 auto guard = m_DispatchWaiters.acquire();
164 const size_t finalPublication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
165 if (generationOf(finalPublication) != drainGeneration || hasActiveDispatch(slot)) {
166 return true;
167 }
168
169 const bool synchronousDrain = synchronousDrainOf(finalPublication);
170 if (modeOf(finalPublication) == SlotMode::Deferred ||
171 (selfRemovalOf(finalPublication) && !synchronousDrain)) {
172 TimerHandler* handler = __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE);
173 if (handler) {
174 retireSlot(slot, finalPublication, handler);
175 }
176 }
177
178 if (synchronousDrain) {
179 guard.wakeAll(WaitQueue::WakeReason::Signalled, WaitQueue::Channel(&slot, drainGeneration));
180 }
181 return true;
182}
183
184void TimerHandlerRegistry::abandonDispatch(void* context) {
185 DispatchCleanup* dispatch = reinterpret_cast<DispatchCleanup*>(context);
186 dispatch->registry->unpublishDispatch(*dispatch, false);
187}
188
189bool TimerHandlerRegistry::hasActiveDispatch(HandlerSlot& target) const {
190 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
191 const ActiveDispatch& dispatch = m_ActiveDispatches[i];
192 void* token = __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE);
193 if (!token) {
194 continue;
195 }
196
197 const size_t generation = __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE);
198 HandlerSlot* slot = __atomic_load_n(&dispatch.slot, __ATOMIC_SEQ_CST);
199 if (slot == &target && __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE) == token &&
200 __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE) == generation) {
201 return true;
202 }
203 }
204 return false;
205}
206
207bool TimerHandlerRegistry::findCurrentDispatch(void* owner, HandlerSlot* target,
208 bool& callbackContext) const {
209 callbackContext = false;
210 bool foundTarget = false;
211 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
212 const ActiveDispatch& dispatch = m_ActiveDispatches[i];
213 void* token = __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE);
214 if (!token) {
215 continue;
216 }
217
218 const size_t generation = __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE);
219 void* dispatchOwner = __atomic_load_n(&dispatch.owner, __ATOMIC_RELAXED);
220 HandlerSlot* slot = __atomic_load_n(&dispatch.slot, __ATOMIC_SEQ_CST);
221 if (__atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE) != token ||
222 __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE) != generation) {
223 continue;
224 }
225
226 if (dispatchOwner == owner && slot) {
227 callbackContext = true;
228 foundTarget |= slot == target;
229 }
230 }
231 return foundTarget;
232}
233
234void* TimerHandlerRegistry::currentDispatchOwner() {
235 ProcessorInformation& information = Processor::information();
236 Thread* thread = information.getCurrentThread();
237 return thread ? static_cast<void*>(thread) : static_cast<void*>(&information);
238}
239
240bool TimerHandlerRegistry::registerHandler(TimerHandler* handler) {
241 if (!handler) {
242 return false;
243 }
244
245 LockGuard<Spinlock> guard(m_HandlerLock);
246 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
247 HandlerSlot& slot = m_Handlers[i];
248 while (true) {
249 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
250 if (__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) != handler) {
251 break;
252 }
253
254 const SlotMode mode = modeOf(publication);
255 if (mode == SlotMode::Retiring) {
257 continue;
258 }
259 if (mode == SlotMode::Empty) {
260 break;
261 }
262 const bool abandonedAtomicDrain = mode == SlotMode::Draining && selfRemovalOf(publication) &&
263 !synchronousDrainOf(publication);
264 if (mode != SlotMode::Deferred &&
265 !(mode == SlotMode::Enabled && selfRemovalOf(publication)) && !abandonedAtomicDrain) {
266 return false;
267 }
268
269 size_t expectedPublication = publication;
270 __atomic_store_n(&slot.deadline, NoDeadline, __ATOMIC_RELEASE);
271 __atomic_store_n(&slot.lastDispatch, NoDeadline, __ATOMIC_RELEASE);
272 if (__atomic_compare_exchange_n(&slot.publication, &expectedPublication,
273 makePublication(generationOf(publication), SlotMode::Enabled),
274 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
275 return true;
276 }
277 }
278 }
279
280 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
281 HandlerSlot& slot = m_Handlers[i];
282 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
283 if (modeOf(publication) == SlotMode::Empty &&
284 !__atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE)) {
285 const size_t generation = generationOf(publication) + 1;
286 __atomic_store_n(&slot.deadline, NoDeadline, __ATOMIC_RELEASE);
287 __atomic_store_n(&slot.lastDispatch, NoDeadline, __ATOMIC_RELEASE);
288 __atomic_store_n(&slot.handler, handler, __ATOMIC_RELEASE);
289 __atomic_store_n(&slot.publication, makePublication(generation, SlotMode::Enabled),
290 __ATOMIC_SEQ_CST);
291 return true;
292 }
293 }
294
295 return false;
296}
297
298bool TimerHandlerRegistry::armHandler(TimerHandler* handler, uint64_t deadline, uint64_t now) {
299 if (!handler) {
300 return false;
301 }
302
303 LockGuard<Spinlock> guard(m_HandlerLock);
304 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
305 HandlerSlot& slot = m_Handlers[i];
306 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST);
307 if (modeOf(publication) != SlotMode::Enabled ||
308 __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) != handler) {
309 continue;
310 }
311
312 if (!deadline) {
313 __atomic_store_n(&slot.deadline, NoDeadline, __ATOMIC_RELEASE);
314 __atomic_store_n(&slot.lastDispatch, NoDeadline, __ATOMIC_RELEASE);
315 return __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST) == publication &&
316 __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) == handler;
317 }
318
319 const uint64_t armed = __atomic_load_n(&slot.deadline, __ATOMIC_ACQUIRE);
320 if (__atomic_load_n(&slot.lastDispatch, __ATOMIC_ACQUIRE) == NoDeadline ||
321 (armed == NoDeadline && !hasActiveDispatch(slot))) {
322 __atomic_store_n(&slot.lastDispatch, now, __ATOMIC_RELEASE);
323 }
324 __atomic_store_n(&slot.deadline, deadline, __ATOMIC_RELEASE);
325 return __atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST) == publication &&
326 __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) == handler;
327 }
328 return false;
329}
330
332 uint64_t earliest = NoDeadline;
333 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
334 const HandlerSlot& slot = m_Handlers[i];
335 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_ACQUIRE);
336 if (modeOf(publication) != SlotMode::Enabled) {
337 continue;
338 }
339 const uint64_t deadline = __atomic_load_n(&slot.deadline, __ATOMIC_ACQUIRE);
340 if (__atomic_load_n(&slot.publication, __ATOMIC_ACQUIRE) == publication &&
341 deadline < earliest) {
342 earliest = deadline;
343 }
344 }
345 return earliest;
346}
347
349 if (!handler) {
350 return false;
351 }
352
353 void* owner = currentDispatchOwner();
354 Thread* current = Processor::information().getCurrentThread();
355 const bool canYield = current && Processor::getInterrupts();
356 bool callbackContext = false;
357 findCurrentDispatch(owner, nullptr, callbackContext);
358
359 // A callback must not arm TerminationDeferral even when a hosted test
360 // invokes dispatch with interrupts enabled.
361 if (!canYield || callbackContext) {
362 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
363 HandlerSlot& candidate = m_Handlers[i];
364 while (true) {
365 size_t publication = __atomic_load_n(&candidate.publication, __ATOMIC_SEQ_CST);
366 if (__atomic_load_n(&candidate.handler, __ATOMIC_ACQUIRE) != handler) {
367 break;
368 }
369
370 const SlotMode mode = modeOf(publication);
371 bool currentTargetDispatch = false;
372 const bool selfUnregister = findCurrentDispatch(owner, &candidate, currentTargetDispatch);
373 if (selfUnregister) {
374 if (mode == SlotMode::Empty || mode == SlotMode::Retiring || selfRemovalOf(publication)) {
375 return false;
376 }
377
378 const SlotMode deferredMode = mode == SlotMode::Enabled ? SlotMode::Deferred : mode;
379 size_t expectedPublication = publication;
380 if (__atomic_compare_exchange_n(&candidate.publication, &expectedPublication,
381 makePublication(generationOf(publication), deferredMode,
382 true, synchronousDrainOf(publication)),
383 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
384 return false;
385 }
386 continue;
387 }
388
389 if (mode != SlotMode::Enabled || selfRemovalOf(publication)) {
390 return false;
391 }
392
393 const size_t drainingPublication =
394 makePublication(generationOf(publication), SlotMode::Draining);
395 if (!__atomic_compare_exchange_n(&candidate.publication, &publication, drainingPublication,
396 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
397 continue;
398 }
399
400#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
401 HandlerAtomicDrainHook atomicDrainHook =
402 __atomic_load_n(&m_HandlerAtomicDrainHook, __ATOMIC_ACQUIRE);
403 if (atomicDrainHook) {
404 atomicDrainHook(handler);
405 }
406#endif
407
408 if (hasActiveDispatch(candidate)) {
409 size_t expectedPublication = drainingPublication;
410 const bool reopened = __atomic_compare_exchange_n(
411 &candidate.publication, &expectedPublication,
412 makePublication(generationOf(drainingPublication), SlotMode::Enabled,
413 selfRemovalOf(drainingPublication)),
414 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
415 if (!reopened && modeOf(expectedPublication) == SlotMode::Draining &&
416 selfRemovalOf(expectedPublication) && !synchronousDrainOf(expectedPublication)) {
417 // This atomic drainer has abandoned removal. Leave the
418 // callback revivable without discarding the concurrent
419 // self-removal request.
420 size_t markedDraining = expectedPublication;
421 __atomic_compare_exchange_n(
422 &candidate.publication, &markedDraining,
423 makePublication(generationOf(expectedPublication), SlotMode::Deferred, true), false,
424 __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
425 }
426 return false;
427 }
428
429 return retireSlot(candidate, drainingPublication, handler);
430 }
431 }
432
433 return false;
434 }
435
436 TerminationDeferral terminationDeferral;
437
438 m_HandlerLock.acquire();
439
440 HandlerSlot* slot = nullptr;
441 size_t publication = 0;
442 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
443 const size_t candidatePublication =
444 __atomic_load_n(&m_Handlers[i].publication, __ATOMIC_SEQ_CST);
445 if (modeOf(candidatePublication) != SlotMode::Empty &&
446 __atomic_load_n(&m_Handlers[i].handler, __ATOMIC_ACQUIRE) == handler) {
447 slot = &m_Handlers[i];
448 publication = candidatePublication;
449 break;
450 }
451 }
452
453 if (!slot) {
454 m_HandlerLock.release();
455 return false;
456 }
457
458 size_t drainingPublication = 0;
459 while (true) {
460 const SlotMode mode = modeOf(publication);
461 if (mode == SlotMode::Retiring) {
463 publication = __atomic_load_n(&slot->publication, __ATOMIC_SEQ_CST);
464 continue;
465 }
466 if (mode == SlotMode::Empty) {
467 const bool retired = __atomic_load_n(&slot->handler, __ATOMIC_ACQUIRE) != handler;
468 m_HandlerLock.release();
469 return retired;
470 }
471 if (mode != SlotMode::Enabled && mode != SlotMode::Deferred) {
472 m_HandlerLock.release();
473 return false;
474 }
475
476 drainingPublication = makePublication(generationOf(publication), SlotMode::Draining,
477 selfRemovalOf(publication), true);
478 size_t expectedPublication = publication;
479 if (__atomic_compare_exchange_n(&slot->publication, &expectedPublication, drainingPublication,
480 false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) {
481 break;
482 }
483 publication = expectedPublication;
484 }
485
486 m_HandlerLock.release();
487
488 const size_t drainGeneration = generationOf(drainingPublication);
489 while (true) {
490 auto guard = m_DispatchWaiters.acquire();
491 const size_t finalPublication = __atomic_load_n(&slot->publication, __ATOMIC_SEQ_CST);
492 TimerHandler* finalHandler = __atomic_load_n(&slot->handler, __ATOMIC_ACQUIRE);
493 if (generationOf(finalPublication) != drainGeneration) {
494 return true;
495 }
496
497 const SlotMode finalMode = modeOf(finalPublication);
498 if (finalMode == SlotMode::Empty) {
499 return finalHandler != handler;
500 }
501 if (finalHandler != handler || !synchronousDrainOf(finalPublication) ||
502 (finalMode != SlotMode::Draining && finalMode != SlotMode::Deferred)) {
503 return false;
504 }
505 if (!hasActiveDispatch(*slot)) {
506 if (retireSlot(*slot, finalPublication, handler)) {
507 return true;
508 }
509 continue;
510 }
511
512 const WaitQueue::WakeReason reason =
513 guard.waitForCompletion(WaitQueue::Channel(slot, drainGeneration), Thread::CallbackDrain,
514 reinterpret_cast<uintptr_t>(handler));
515 (void)reason;
516 }
517}
518
519bool TimerHandlerRegistry::dispatch(uint64_t delta, TimerHandler* onlyHandler) {
520 return dispatchSelected(delta, onlyHandler, false, 0);
521}
522
524 return dispatchSelected(0, nullptr, true, now);
525}
526
527bool TimerHandlerRegistry::dispatchSelected(uint64_t delta, TimerHandler* onlyHandler, bool dueOnly,
528 uint64_t now) {
529 bool admitted = false;
530
531 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
532 HandlerSlot& slot = m_Handlers[i];
533 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_ACQUIRE);
534 if (modeOf(publication) != SlotMode::Enabled) {
535 continue;
536 }
537
538 TimerHandler* handler = __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE);
539 if (!handler || (onlyHandler && handler != onlyHandler)) {
540 continue;
541 }
542 if (dueOnly) {
543 const uint64_t deadline = __atomic_load_n(&slot.deadline, __ATOMIC_ACQUIRE);
544 if (deadline == NoDeadline || deadline > now) {
545 continue;
546 }
547 }
548
549#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
550 HandlerPrePinHook prePinHook = __atomic_load_n(&m_HandlerPrePinHook, __ATOMIC_ACQUIRE);
551 if (prePinHook) {
552 prePinHook(handler);
553 }
554#endif
555
556 void* owner = currentDispatchOwner();
557 Thread* thread = Processor::information().getCurrentThread();
558 DispatchCleanup dispatchCleanup(this, &slot, owner, publication);
559 if (thread) {
560 // Cleanup is visible before even the hazard-table token is
561 // claimed. A no-return callback, nested exception, or timeout
562 // restore can therefore abandon every later publication point.
563 thread->armAtomicStateCleanup(dispatchCleanup.cleanup, abandonDispatch, &dispatchCleanup);
564 }
565
566 if (!publishDispatch(dispatchCleanup)) {
567 if (thread) {
568 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
569 }
570 FATAL_NOLOCK("Timer callback hazard table exhausted.");
571 return admitted;
572 }
573
574 if (__atomic_load_n(&slot.publication, __ATOMIC_SEQ_CST) != publication ||
575 __atomic_load_n(&slot.handler, __ATOMIC_ACQUIRE) != handler) {
576 unpublishDispatch(dispatchCleanup, true);
577 if (thread) {
578 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
579 }
580 continue;
581 }
582
583 uint64_t callbackDelta = delta;
584 if (dueOnly) {
585 uint64_t deadline = __atomic_load_n(&slot.deadline, __ATOMIC_ACQUIRE);
586 while (deadline != NoDeadline && deadline <= now) {
587 if (__atomic_compare_exchange_n(&slot.deadline, &deadline, NoDeadline, false,
588 __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) {
589 const uint64_t previous = __atomic_exchange_n(&slot.lastDispatch, now, __ATOMIC_ACQ_REL);
590 callbackDelta = previous == NoDeadline || now < previous ? 0 : now - previous;
591 break;
592 }
593 }
594 if (deadline == NoDeadline || deadline > now) {
595 unpublishDispatch(dispatchCleanup, true);
596 if (thread) {
597 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
598 }
599 continue;
600 }
601 }
602
603 admitted = true;
604
605#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
606 HandlerPinHook hook = __atomic_load_n(&m_HandlerPinHook, __ATOMIC_ACQUIRE);
607 if (hook) {
608 hook(handler);
609 }
610#endif
611
612 // Keep callback entry and return independent of ordinary deferred-scope
613 // state: a timer interrupt can arrive while that state is being
614 // mutated.
615 handler->timer(callbackDelta);
616 unpublishDispatch(dispatchCleanup, true);
617 if (thread) {
618 thread->disarmAtomicStateCleanup(dispatchCleanup.cleanup);
619 }
620 }
621
622 return admitted;
623}
624
626 LockGuard<Spinlock> guard(m_HandlerLock);
627 for (size_t i = 0; i < MaxHandlerSlots; ++i) {
628 HandlerSlot& slot = m_Handlers[i];
629 assert(!hasActiveDispatch(slot));
630 const size_t publication = __atomic_load_n(&slot.publication, __ATOMIC_ACQUIRE);
631 __atomic_store_n(&slot.deadline, NoDeadline, __ATOMIC_RELEASE);
632 __atomic_store_n(&slot.lastDispatch, NoDeadline, __ATOMIC_RELEASE);
633 __atomic_store_n(&slot.handler, nullptr, __ATOMIC_RELEASE);
634 __atomic_store_n(&slot.publication,
635 makePublication(generationOf(publication) + 1, SlotMode::Empty),
636 __ATOMIC_RELEASE);
637 }
638
639 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
640 assert(!__atomic_load_n(&m_ActiveDispatches[i].token, __ATOMIC_ACQUIRE));
641 assert(!__atomic_load_n(&m_ActiveDispatches[i].owner, __ATOMIC_ACQUIRE));
642 assert(!__atomic_load_n(&m_ActiveDispatches[i].slot, __ATOMIC_ACQUIRE));
643 }
644}
645
646#if HOSTED && PEDIGREE_HOSTED_SMOKE_TESTS
647void TimerHandlerRegistry::setHandlerPinHook(HandlerPinHook hook) {
648 __atomic_store_n(&m_HandlerPinHook, hook, __ATOMIC_RELEASE);
649}
650
651void TimerHandlerRegistry::setHandlerPrePinHook(HandlerPrePinHook hook) {
652 __atomic_store_n(&m_HandlerPrePinHook, hook, __ATOMIC_RELEASE);
653}
654
655void TimerHandlerRegistry::setHandlerHazardClaimHook(HandlerHazardClaimHook hook) {
656 __atomic_store_n(&m_HandlerHazardClaimHook, hook, __ATOMIC_RELEASE);
657}
658
659void TimerHandlerRegistry::setHandlerAtomicDrainHook(HandlerAtomicDrainHook hook) {
660 __atomic_store_n(&m_HandlerAtomicDrainHook, hook, __ATOMIC_RELEASE);
661}
662
663void TimerHandlerRegistry::withMutationLockForTest(MutationLockHook hook) {
664 m_HandlerLock.acquire();
665 if (hook) {
666 hook();
667 }
668 m_HandlerLock.release();
669}
670
671size_t TimerHandlerRegistry::activeDispatchCountForTest(TimerHandler* handler) {
672 size_t count = 0;
673 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
674 ActiveDispatch& dispatch = m_ActiveDispatches[i];
675 void* token = __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE);
676 if (!token) {
677 continue;
678 }
679 const size_t generation = __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE);
680 HandlerSlot* slot = __atomic_load_n(&dispatch.slot, __ATOMIC_SEQ_CST);
681 TimerHandler* activeHandler =
682 slot ? __atomic_load_n(&slot->handler, __ATOMIC_ACQUIRE) : nullptr;
683 if (activeHandler == handler && __atomic_load_n(&dispatch.token, __ATOMIC_ACQUIRE) == token &&
684 __atomic_load_n(&dispatch.generation, __ATOMIC_ACQUIRE) == generation) {
685 ++count;
686 }
687 }
688 return count;
689}
690
691size_t TimerHandlerRegistry::claimedDispatchCountForTest() {
692 size_t count = 0;
693 for (size_t i = 0; i < MaxActiveDispatches; ++i) {
694 if (__atomic_load_n(&m_ActiveDispatches[i].token, __ATOMIC_ACQUIRE)) {
695 ++count;
696 }
697 }
698 return count;
699}
700#endif
static bool getInterrupts()
static ProcessorInformation & information()
static void pause()
void release()
Definition Spinlock.cc:168
bool acquire(bool recurse=false, bool safe=true)
Definition Spinlock.cc:36
bool armHandler(TimerHandler *handler, uint64_t deadline, uint64_t now)
bool dispatchDue(uint64_t now)
bool dispatch(uint64_t delta, TimerHandler *onlyHandler=nullptr)
bool unregisterHandler(TimerHandler *handler)
virtual void timer(uint64_t delta)=0