8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/machine/InputManager.h"
11#include "pedigree/kernel/process/Scheduler.h"
12#include "pedigree/kernel/process/Thread.h"
15constexpr size_t Attempts = 10000;
17bool check(
bool condition,
const char* detail) {
22 ERROR(
"HOSTED-WAIT-TEST: FAIL input-callback-lifetime: " << detail);
26struct CallbackLifetimeContext {
27 CallbackLifetimeContext()
28 : removers{nullptr, nullptr},
34 callbackAfterRemoval(0),
49CallbackLifetimeContext* g_CallbackLifetimeContext =
nullptr;
52 CallbackLifetimeContext* context =
reinterpret_cast<CallbackLifetimeContext*
>(notification.meta);
53 context->callbackCalls += 1;
54 if (context->removalReturned) {
55 context->callbackAfterRemoval += 1;
60 CallbackLifetimeContext* context = g_CallbackLifetimeContext;
61 if (!context || callback != lifetimeCallback || meta != context ||
62 !context->phase.compareAndSwap(0, 1)) {
66 context->hookCalls += 1;
67 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
68 bool allDraining = context->removersStarted == 2;
69 for (
size_t i = 0; allDraining && i < 2; ++i) {
71 uintptr_t debugAddress = 0;
72 allDraining &= context->removers[i]->getWaitDebugInfo(info) && info.queue &&
73 info.channelOwner && info.queued &&
74 context->removers[i]->getDebugState(debugAddress) == Thread::CallbackDrain &&
75 debugAddress ==
reinterpret_cast<uintptr_t
>(lifetimeCallback);
78 context->hookObservedDrain += 1;
84 if (!context->hookObservedDrain) {
85 context->failures += 1;
90int removePinnedCallback(
void* parameter) {
91 CallbackLifetimeContext* context =
reinterpret_cast<CallbackLifetimeContext*
>(parameter);
92 for (
size_t attempt = 0; attempt < Attempts && context->phase !=
static_cast<size_t>(1);
97 if (context->phase !=
static_cast<size_t>(1)) {
98 context->failures += 1;
102 context->removersStarted += 1;
104 context->removalReturned += 1;
108struct SelfRemovalContext {
109 SelfRemovalContext() : calls(0) {}
115 SelfRemovalContext* context =
reinterpret_cast<SelfRemovalContext*
>(notification.meta);
120bool callbackLifetimeBarrier() {
122 CallbackLifetimeContext context;
123 for (
size_t i = 0; i < 2; ++i) {
125 &context,
nullptr,
false,
true);
126 context.removers[i]->setName(
"hosted input-callback remover");
130 g_CallbackLifetimeContext = &context;
131 InputManager::setCallbackPinHook(callbackPinHook);
135 for (
auto remover : context.removers) {
136 joined &= remover->join();
138 InputManager::setCallbackPinHook(
nullptr);
139 g_CallbackLifetimeContext =
nullptr;
144 for (
size_t attempt = 0; attempt < 100; ++attempt) {
147 const size_t callsAfterRemoval = context.callbackCalls;
149 for (
size_t attempt = 0; attempt < 100; ++attempt) {
153 SelfRemovalContext selfRemoval;
156 for (
size_t attempt = 0; attempt < Attempts && !selfRemoval.calls; ++attempt) {
159 const size_t callsAfterSelfRemoval = selfRemoval.calls;
161 for (
size_t attempt = 0; attempt < 100; ++attempt) {
167 passed &= check(joined && context.failures == 0,
168 "the concurrent removal worker did not complete cleanly");
169 passed &= check(context.hookCalls == 1 && context.hookObservedDrain == 1,
170 "removal did not wait on the committed callback invocation");
171 passed &= check(context.callbackCalls == 1 && context.callbackCalls == callsAfterRemoval &&
172 context.callbackAfterRemoval == 0 && context.removalReturned == 2,
173 "a callback began after removeCallback returned");
174 passed &= check(callsAfterSelfRemoval == 1 && selfRemoval.calls == 1,
175 "self-removal did not retire the callback after its active invocation");
178 NOTICE(
"HOSTED-WAIT-TEST: PASS input-callback-lifetime");
184bool runHostedInputRegressions() {
185 return callbackLifetimeBarrier();
static Scheduler & instance()