8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/process/Scheduler.h"
11#include "pedigree/kernel/process/Semaphore.h"
12#include "pedigree/kernel/process/Thread.h"
13#include "pedigree/kernel/utilities/String.h"
14#include "pedigree/kernel/utilities/lib.h"
17constexpr size_t Attempts = 10000;
18constexpr char SnapshotA[] =
"hosted-log-snapshot-a";
19constexpr char SnapshotB[] =
"hosted-log-snapshot-b";
21bool check(
bool condition,
const char* detail) {
26 ERROR(
"HOSTED-WAIT-TEST: FAIL log-callback-lifetime: " << detail);
34 explicit BlockingLogger(RemovalContext& context) : m_Context(context) {}
36 void callback(
const LogCord&,
bool)
override;
39 RemovalContext& m_Context;
43 RemovalContext* context;
46struct RemovalContext {
51 callbackAfterRemoval(0),
54 removersObservedWaiting(0),
58 workerA.context =
this;
59 workerB.context =
this;
62 BlockingLogger logger;
72 RemovalWorker workerA;
73 RemovalWorker workerB;
76RemovalContext* g_RemovalContext =
nullptr;
78void BlockingLogger::callback(
const LogCord&,
bool) {
79 m_Context.callbackCalls += 1;
80 if (m_Context.removerReturned) {
81 m_Context.callbackAfterRemoval += 1;
87 uintptr_t debugAddress = 0;
88 return thread && thread->
getWaitDebugInfo(info) && info.queue && info.channelOwner &&
89 info.queued && thread->
getDebugState(debugAddress) == Thread::CallbackDrain &&
90 debugAddress ==
reinterpret_cast<uintptr_t
>(callback);
94 RemovalContext* context = g_RemovalContext;
95 if (!context || callback != &context->logger || !context->phase.compareAndSwap(0, 1)) {
99 for (
size_t attempt = 0; attempt < Attempts; ++attempt) {
100 if (context->removerStarted ==
static_cast<size_t>(2) &&
101 removerWaiting(context->removerA, callback) &&
102 removerWaiting(context->removerB, callback)) {
103 context->removersObservedWaiting += 1;
109 if (!context->removersObservedWaiting) {
110 context->failures += 1;
115int removePinnedCallback(
void* parameter) {
116 RemovalWorker*
worker =
reinterpret_cast<RemovalWorker*
>(parameter);
117 RemovalContext* context =
worker->context;
118 for (
size_t attempt = 0; attempt < Attempts && context->phase ==
static_cast<size_t>(0);
123 if (context->phase ==
static_cast<size_t>(0)) {
124 context->failures += 1;
128 context->removerStarted += 1;
130 context->failures += 1;
132 context->removerReturned += 1;
138 SelfRemovingLogger() : calls(0), retryRequired(0) {}
140 void callback(
const LogCord&,
bool)
override {
151bool callbackLifetime() {
152 RemovalContext context;
153 if (!
Log::instance().installCallback(&context.logger,
true)) {
154 return check(
false,
"the test callback could not be registered");
159 new Thread(process, removePinnedCallback, &context.workerA,
nullptr,
false,
true);
161 new Thread(process, removePinnedCallback, &context.workerB,
nullptr,
false,
true);
162 context.removerA->setName(
"hosted log callback remover A");
163 context.removerB->setName(
"hosted log callback remover B");
165 g_RemovalContext = &context;
166 Log::setCallbackPinHook(callbackPinHook);
167 NOTICE(
"hosted-log-callback-drain");
169 const bool joinedA = context.removerA->join();
170 const bool joinedB = context.removerB->join();
171 Log::setCallbackPinHook(
nullptr);
172 g_RemovalContext =
nullptr;
174 NOTICE(
"hosted-log-callback-after-removal");
177 SelfRemovingLogger selfRemoving;
179 NOTICE(
"hosted-log-self-removal");
180 NOTICE(
"hosted-log-after-self-removal");
184 passed &= check(joinedA && joinedB && context.failures == 0,
185 "concurrent callback removers did not complete cleanly");
186 passed &= check(context.removersObservedWaiting == 1 && context.removerReturned == 2,
187 "both external removers did not join the same callback drain");
188 passed &= check(context.callbackCalls == 1 && context.callbackAfterRemoval == 0,
189 "a callback ran after synchronous removal returned");
190 passed &= check(selfInstalled && selfRemoving.calls == 1 && selfRemoving.retryRequired == 1,
191 "self-removal did not close admission and require an external retry");
194 NOTICE(
"HOSTED-WAIT-TEST: PASS log-callback-lifetime");
199struct SnapshotContext;
203 explicit SnapshotLogger(SnapshotContext& context) : m_Context(context) {}
205 void callback(
const LogCord& cord,
bool)
override;
208 SnapshotContext& m_Context;
211struct SnapshotContext {
212 SnapshotContext() : logger(*this), phase(0), sawA(0), sawB(0), unknown(0),
worker(nullptr) {}
214 SnapshotLogger logger;
222SnapshotContext* g_SnapshotContext =
nullptr;
224bool contains(
const String&
message,
const char* needle,
size_t length) {
225 if (
message.length() < length) {
229 for (
size_t i = 0; i <= (
message.length() - length); ++i) {
230 if (StringCompareN(
message.cstr() + i, needle, length) == 0) {
237struct ReciprocalBacklogContext;
241 ReciprocalBacklogLogger(ReciprocalBacklogContext& context,
bool first)
242 : m_Context(context), m_First(first) {}
244 void callback(
const LogCord& cord,
bool)
override;
247 ReciprocalBacklogContext& m_Context;
251struct ReciprocalBacklogWorker {
252 ReciprocalBacklogContext* context;
256struct ReciprocalBacklogContext {
257 ReciprocalBacklogContext()
258 : first(*this, true),
259 second(*this, false),
261 firstParticipated(0),
262 secondParticipated(0),
265 removalRejections(0),
267 installsSucceeded(0),
268 installersFinished(0),
270 firstInstaller(nullptr),
271 secondInstaller(nullptr) {
272 firstWorker = {
this,
true};
273 secondWorker = {
this,
false};
276 ReciprocalBacklogLogger first;
277 ReciprocalBacklogLogger second;
290 ReciprocalBacklogWorker firstWorker;
291 ReciprocalBacklogWorker secondWorker;
294void ReciprocalBacklogLogger::callback(
const LogCord& cord,
bool) {
296 m_First ? m_Context.firstParticipated : m_Context.secondParticipated;
297 if (!participated.compareAndSwap(0, 1)) {
302 if (contains(
message,
"(backlog) ", 10)) {
303 m_Context.backlogCallbacks += 1;
305 m_Context.failures += 1;
308 m_Context.callbacksEntered += 1;
309 if (!m_Context.beginRemoval.acquireForCompletion()) {
310 m_Context.failures += 1;
315 : static_cast<
Log::LogCallback*>(&m_Context.first);
317 m_Context.removalRejections += 1;
319 m_Context.failures += 1;
322 m_Context.removalsFinished += 1;
323 for (
size_t attempt = 0;
324 attempt < Attempts && m_Context.removalsFinished !=
static_cast<size_t>(2); ++attempt) {
327 if (m_Context.removalsFinished !=
static_cast<size_t>(2)) {
328 m_Context.failures += 1;
332int installReciprocalBacklogCallback(
void* parameter) {
333 ReciprocalBacklogWorker*
worker =
reinterpret_cast<ReciprocalBacklogWorker*
>(parameter);
336 : static_cast<
Log::LogCallback*>(&
worker->context->second);
338 worker->context->installsSucceeded += 1;
340 worker->context->failures += 1;
342 worker->context->installersFinished += 1;
346bool reciprocalBacklogRemoval() {
347 NOTICE(
"hosted-log-reciprocal-backlog-seed");
349 ReciprocalBacklogContext context;
351 context.firstInstaller =
new Thread(process, installReciprocalBacklogCallback,
352 &context.firstWorker,
nullptr,
false,
true);
353 context.secondInstaller =
new Thread(process, installReciprocalBacklogCallback,
354 &context.secondWorker,
nullptr,
false,
true);
355 context.firstInstaller->setName(
"hosted log reciprocal backlog A");
356 context.secondInstaller->setName(
"hosted log reciprocal backlog B");
358 for (
size_t attempt = 0; attempt < Attempts && context.callbacksEntered !=
static_cast<size_t>(2);
362 const bool bothEntered = context.callbacksEntered ==
static_cast<size_t>(2);
363 context.beginRemoval.release(2);
365 const bool firstJoined = context.firstInstaller->joinForCompletion();
366 const bool secondJoined = context.secondInstaller->joinForCompletion();
377 check(bothEntered && firstJoined && secondJoined && firstOwnershipPreserved &&
378 secondOwnershipPreserved && firstRetired && secondRetired && firstReused &&
379 firstReuseRetired && secondReused && secondReuseRetired &&
380 context.backlogCallbacks ==
static_cast<size_t>(2) &&
381 context.removalRejections ==
static_cast<size_t>(2) &&
382 context.removalsFinished ==
static_cast<size_t>(2) &&
383 context.installsSucceeded ==
static_cast<size_t>(2) &&
384 context.installersFinished ==
static_cast<size_t>(2) && !context.failures,
385 "reciprocal backlog callbacks did not retain peer removal ownership");
387 NOTICE(
"HOSTED-WAIT-TEST: PASS log-reciprocal-backlog-removal");
392void SnapshotLogger::callback(
const LogCord& cord,
bool) {
394 if (contains(
message, SnapshotA,
sizeof(SnapshotA) - 1)) {
396 }
else if (contains(
message, SnapshotB,
sizeof(SnapshotB) - 1)) {
399 m_Context.unknown += 1;
404 SnapshotContext* context = g_SnapshotContext;
405 if (!context || !(entry.str == SnapshotA) || !context->phase.compareAndSwap(0, 1)) {
409 for (
size_t attempt = 0; attempt < Attempts && context->phase !=
static_cast<size_t>(2);
415int writeSecondSnapshot(
void* parameter) {
416 SnapshotContext* context =
reinterpret_cast<SnapshotContext*
>(parameter);
417 for (
size_t attempt = 0; attempt < Attempts && context->phase !=
static_cast<size_t>(1);
421 if (context->phase !=
static_cast<size_t>(1)) {
430bool entrySnapshotIsolation() {
431 SnapshotContext context;
433 &context,
nullptr,
false,
true);
434 context.worker->setName(
"hosted concurrent log writer");
435 if (!
Log::instance().installCallback(&context.logger,
true)) {
437 context.worker->join();
438 return check(
false,
"the snapshot callback could not be registered");
441 g_SnapshotContext = &context;
442 Log::setEntrySnapshotHook(entrySnapshotHook);
444 const bool joined = context.worker->join();
445 Log::setEntrySnapshotHook(
nullptr);
446 g_SnapshotContext =
nullptr;
449 const bool passed = check(joined && context.phase ==
static_cast<size_t>(2) &&
450 context.sawA == 1 && context.sawB == 1 && context.unknown == 0,
451 "concurrent writers did not retain distinct entry snapshots");
453 NOTICE(
"HOSTED-WAIT-TEST: PASS log-entry-snapshot");
459bool runHostedLogRegressions() {
460 return callbackLifetime() && reciprocalBacklogRemoval() && entrySnapshotIsolation();
EXPORTED_PUBLIC bool removeCallback(LogCallback *pCallback)
static EXPORTED_PUBLIC Log & instance()
EXPORTED_PUBLIC bool installCallback(LogCallback *pCallback, bool bSkipBacklog=false)
static Scheduler & instance()
bool getWaitDebugInfo(WaitDebugInfo &info)
DebugState getDebugState(uintptr_t &address)