8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/TargetInfo.h"
11#include "pedigree/kernel/process/Scheduler.h"
12#include "pedigree/kernel/process/Semaphore.h"
13#include "pedigree/kernel/process/Thread.h"
14#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
15#include "pedigree/kernel/processor/VirtualAddressSpace.h"
16#include "pedigree/kernel/time/Time.h"
17#include "pedigree/kernel/utilities/Cache.h"
22 auto guard = manager.m_TrimWaiters.acquire();
27 manager.stopPeriodicWork();
30 static void tick(
CacheManager& manager, uint64_t delta,
bool pressure) {
31 manager.timerTick(delta, pressure);
34 static bool state(
CacheManager& manager,
bool requested, uint64_t delta) {
35 auto guard = manager.m_TrimWaiters.acquire();
36 return manager.m_bTrimRequested == requested && manager.m_TrimDelta == delta;
43bool checkNamed(
bool condition,
const char* test,
const char* detail) {
48 ERROR(
"HOSTED-WAIT-TEST: FAIL " << test <<
": " << detail);
52bool check(
bool condition,
const char* detail) {
53 return checkNamed(condition,
"cache-callback-lifetime", detail);
56bool waitUntilQueued(
Thread* thread,
size_t debugState) {
57 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
58 while (Time::getTicks() < deadline) {
60 uintptr_t debugAddress = 0;
70bool waitUntilQueuedAt(
Thread* thread,
size_t debugState, uintptr_t debugAddress) {
71 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
72 while (Time::getTicks() < deadline) {
74 uintptr_t address = 0;
76 thread->
getDebugState(address) == debugState && address == debugAddress) {
84struct CacheTrimWakeContext {
90CacheTrimWakeContext* g_CacheTrimWakeContext =
nullptr;
93 CacheTrimWakeContext* context = g_CacheTrimWakeContext;
94 if (!context || channel.owner != &context->manager)
97 constexpr const char* Test =
"cache-manager-trim-wake-gating";
98 constexpr uint64_t Tick = 1000000ULL;
99 constexpr uint64_t Period = CACHE_WRITEBACK_PERIOD * Tick;
101 const size_t block = ++context->blocks;
102 const uint64_t expectedDelta = block == 2 ? Period - Tick : 0;
103 context->passed &= checkNamed(CacheManagerTestPeer::state(manager,
false, expectedDelta), Test,
104 "worker lost elapsed time or did not consume pending work");
108 for (
size_t tick = 1; tick < CACHE_WRITEBACK_PERIOD; ++tick)
109 CacheManagerTestPeer::tick(manager, Tick,
false);
110 context->passed &= checkNamed(CacheManagerTestPeer::state(manager,
false, Period - Tick) &&
112 info.reason == WaitQueue::WakeReason::Waiting,
113 Test,
"healthy subperiod ticks woke the worker");
115 CacheManagerTestPeer::tick(manager, 0,
true);
116 CacheManagerTestPeer::tick(manager, 0,
false);
117 context->passed &= checkNamed(
118 CacheManagerTestPeer::state(manager,
true, Period - Tick) &&
119 thread->
getWaitDebugInfo(info) && info.reason == WaitQueue::WakeReason::Signalled,
120 Test,
"pressure did not wake the worker or a later tick erased the request");
121 }
else if (block == 2) {
122 CacheManagerTestPeer::tick(manager, Tick,
false);
123 context->passed &= checkNamed(
124 CacheManagerTestPeer::state(manager,
true, Period) && thread->
getWaitDebugInfo(info) &&
125 info.reason == WaitQueue::WakeReason::Signalled,
126 Test,
"writeback did not wake at the accumulated period boundary");
128 context->passed &= checkNamed(block == 3, Test,
"worker woke more often than requested");
129 CacheManagerTestPeer::stop(manager);
130 context->passed &= checkNamed(
131 thread->
getWaitDebugInfo(info) && info.reason == WaitQueue::WakeReason::Signalled, Test,
132 "stopping periodic work did not wake the idle worker");
136 if (!context->passed)
137 CacheManagerTestPeer::stop(manager);
140bool cacheManagerTrimWakeGating() {
141 constexpr const char* Test =
"cache-manager-trim-wake-gating";
142 constexpr uint64_t Period = CACHE_WRITEBACK_PERIOD * 1000000ULL;
143 CacheTrimWakeContext context;
144 CacheManagerTestPeer::tick(context.manager, Period,
false);
145 CacheManagerTestPeer::tick(context.manager, 0,
false);
146 context.passed = checkNamed(CacheManagerTestPeer::state(context.manager,
true, Period), Test,
147 "due work was lost while no worker was waiting");
148 CacheManagerTestPeer::start(context.manager);
149 g_CacheTrimWakeContext = &context;
150 WaitQueue::setBeforeBlockHook(cacheTrimBeforeBlock);
151 context.manager.trimThread();
152 WaitQueue::setBeforeBlockHook(
nullptr);
153 g_CacheTrimWakeContext =
nullptr;
155 context.passed &= checkNamed(context.blocks == 3, Test,
156 "worker did not complete pressure, writeback and stop transitions");
158 NOTICE(
"HOSTED-WAIT-TEST: PASS " << Test);
159 return context.passed;
162struct CacheLifetimeContext {
163 CacheLifetimeContext()
166 allowCallbackReturn(0),
181bool cacheCallback(CacheConstants::CallbackCause cause, uintptr_t loc, uintptr_t,
void* parameter) {
182 CacheLifetimeContext* context =
reinterpret_cast<CacheLifetimeContext*
>(parameter);
183 if (cause == CacheConstants::Eviction) {
184 context->evictionCalls += 1;
185 }
else if (cause == CacheConstants::WriteBack) {
186 uintptr_t page = context->cache->lookup(loc);
188 context->reentrantPins += 1;
189 context->cache->release(loc);
192 const size_t call = (context->callbackCalls += 1);
194 context->callbackEntered.release();
195 const bool released = context->allowCallbackReturn.acquireForCompletion();
201int deleteCache(
void* parameter) {
202 CacheLifetimeContext* context =
reinterpret_cast<CacheLifetimeContext*
>(parameter);
203 delete context->cache;
204 context->deleteReturned += 1;
208bool callbackLifetime() {
209 CacheLifetimeContext context;
210 context.cache =
new Cache;
211 context.cache->setCallback(cacheCallback, &context);
213 constexpr uintptr_t Key = 0xCA7E000;
214 const uintptr_t page = context.cache->insert(Key);
215 if (!check(page != 0,
"could not create the test cache page")) {
216 delete context.cache;
220 context.cache->markNoLongerEditing(Key);
221 context.cache->triggerChecksum(Key);
222 reinterpret_cast<uint8_t*
>(page)[0] ^= 0xA5;
223 context.cache->sync(Key,
true);
225 if (!check(context.callbackEntered.acquire(1, 2),
226 "the queued writeback callback did not start")) {
228 context.allowCallbackReturn.release();
229 delete context.cache;
234 nullptr,
false,
true);
235 deleter->setName(
"hosted Cache callback-drain deleter");
237 const bool drainPublished = waitUntilQueued(deleter, Thread::CallbackDrain);
238 const bool callbackPinnedObject = context.deleteReturned == 0;
240 context.allowCallbackReturn.release();
241 const bool joined = deleter->
join();
244 check(drainPublished,
"destruction did not publish its callback-drain wait") &&
245 check(callbackPinnedObject,
"Cache destruction returned while its callback was active") &&
246 check(joined,
"the Cache deleter did not become reapable") &&
247 check(context.deleteReturned == 1,
"Cache destruction did not complete exactly once") &&
248 check(context.callbackCalls == 2,
249 "writeback and eviction callbacks did not execute exactly once") &&
250 check(context.reentrantPins == 1,
"dirty writeback could not safely re-enter the Cache") &&
251 check(context.evictionCalls == 1,
"Cache destruction did not reclaim the inserted page");
254 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-callback-lifetime");
259struct QueuedLifetimeContext {
260 QueuedLifetimeContext()
263 allowBlockerReturn(0),
276bool blockerCallback(CacheConstants::CallbackCause, uintptr_t, uintptr_t,
void* parameter) {
277 QueuedLifetimeContext* context =
reinterpret_cast<QueuedLifetimeContext*
>(parameter);
278 if ((context->blockerCalls += 1) == 1) {
279 context->blockerEntered.release();
280 const bool released = context->allowBlockerReturn.acquireForCompletion();
286bool queuedTargetCallback(CacheConstants::CallbackCause, uintptr_t, uintptr_t,
void* parameter) {
287 QueuedLifetimeContext* context =
reinterpret_cast<QueuedLifetimeContext*
>(parameter);
288 context->targetCalls += 1;
292int deleteQueuedCache(
void* parameter) {
293 QueuedLifetimeContext* context =
reinterpret_cast<QueuedLifetimeContext*
>(parameter);
294 delete context->target;
295 context->deleteReturned += 1;
299bool queuedRequestLifetime() {
300 QueuedLifetimeContext context;
304 constexpr uintptr_t BlockerKey = 0xCA7E010;
305 constexpr uintptr_t TargetKey = 0xCA7E020;
306 if (!checkNamed(blocker.
insert(BlockerKey) != 0,
"cache-queued-lifetime",
307 "could not create the worker-blocking page")) {
312 blocker.
sync(BlockerKey,
true);
313 if (!checkNamed(context.blockerEntered.acquire(1, 2),
"cache-queued-lifetime",
314 "the blocking Cache callback did not start")) {
315 context.allowBlockerReturn.release();
319 context.target =
new Cache;
320 context.target->setCallback(queuedTargetCallback, &context);
321 if (!checkNamed(context.target->insert(TargetKey) != 0,
"cache-queued-lifetime",
322 "could not create the queued target page")) {
323 context.allowBlockerReturn.release();
324 delete context.target;
327 context.target->markNoLongerEditing(TargetKey);
328 context.target->markDirty(TargetKey);
329 context.target->sync(TargetKey,
true);
332 &context,
nullptr,
false,
true);
333 deleter->setName(
"hosted queued Cache lease deleter");
335 const bool queuedLeasePublished = waitUntilQueued(deleter, Thread::CallbackDrain);
336 const bool queuedRequestPinnedObject = context.deleteReturned == 0;
338 context.allowBlockerReturn.release();
339 const bool joined = deleter->
join();
341 const bool passed = checkNamed(queuedLeasePublished,
"cache-queued-lifetime",
342 "destruction did not wait for a queued request lease") &&
343 checkNamed(queuedRequestPinnedObject,
"cache-queued-lifetime",
344 "a queued request did not pin its Cache") &&
345 checkNamed(joined && context.deleteReturned == 1,
"cache-queued-lifetime",
346 "queued Cache destruction did not complete") &&
347 checkNamed(context.targetCalls == 2,
"cache-queued-lifetime",
348 "the queued writeback and final eviction did not both execute");
351 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-queued-lifetime");
356bool emptyAndReuse() {
358 constexpr uintptr_t FirstKey = 0xCA7E100;
359 constexpr uintptr_t SecondKey = 0xCA7E200;
361 const uintptr_t firstPage = cache.insert(FirstKey);
362 if (!checkNamed(firstPage != 0,
"cache-empty-reuse",
363 "could not create the first no-callback page")) {
367 cache.markNoLongerEditing(FirstKey);
368 cache.triggerChecksum(FirstKey);
369 reinterpret_cast<uint8_t*
>(firstPage)[0] ^= 0x5A;
372 const uintptr_t secondPage = cache.insert(SecondKey);
373 const bool reused = checkNamed(secondPage != 0,
"cache-empty-reuse",
374 "could not insert after emptying a dirty Cache") &&
375 checkNamed(cache.exists(SecondKey, PageSize),
"cache-empty-reuse",
376 "the replacement page was not published");
380 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-empty-reuse");
385struct RetirementContext {
389 allowEvictionReturn(0),
393 replacementPage(0) {}
401 uintptr_t replacementPage;
404bool retirementCallback(CacheConstants::CallbackCause cause, uintptr_t, uintptr_t,
406 RetirementContext* context =
reinterpret_cast<RetirementContext*
>(parameter);
407 if (cause == CacheConstants::Eviction && (context->evictionCalls += 1) == 1) {
408 context->evictionEntered.release();
409 const bool released = context->allowEvictionReturn.acquireForCompletion();
415int evictRetirementPage(
void* parameter) {
416 RetirementContext* context =
reinterpret_cast<RetirementContext*
>(parameter);
417 context->evictReturned += context->cache->evict(0xCA7E300) ? 1 : 2;
421int insertRetirementReplacement(
void* parameter) {
422 RetirementContext* context =
reinterpret_cast<RetirementContext*
>(parameter);
423 context->replacementPage = context->cache->insert(0xCA7E300);
424 context->insertReturned += 1;
428bool retirementPublication() {
429 RetirementContext context;
431 context.cache = &cache;
432 cache.setCallback(retirementCallback, &context);
434 constexpr uintptr_t Key = 0xCA7E300;
435 const uintptr_t originalPage = cache.insert(Key);
436 if (!checkNamed(originalPage != 0,
"cache-retirement-publication",
437 "could not create the original page")) {
440 cache.markNoLongerEditing(Key);
443 &context,
nullptr,
false,
true);
444 evictor->setName(
"hosted Cache retirement evictor");
446 const bool callbackEntered = context.evictionEntered.acquire(1, 2);
447 if (!callbackEntered) {
448 context.allowEvictionReturn.release();
451 return checkNamed(
false,
"cache-retirement-publication",
452 "the eviction callback did not publish retirement");
455 const bool retiringPinRejected = !cache.pin(Key);
456 const bool retiringLookupRejected = cache.lookup(Key) == 0;
459 insertRetirementReplacement, &context,
nullptr,
false,
true);
460 inserter->setName(
"hosted Cache same-key replacement");
462 const bool insertWaitPublished = waitUntilQueued(inserter, Thread::CallbackDrain);
463 const bool replacementBlocked = context.insertReturned == 0;
465 context.allowEvictionReturn.release();
466 const bool evictorJoined = evictor->
join();
467 const bool inserterJoined = inserter->
join();
470 checkNamed(retiringPinRejected && retiringLookupRejected,
"cache-retirement-publication",
471 "a retiring page remained available to a new consumer") &&
472 checkNamed(insertWaitPublished,
"cache-retirement-publication",
473 "same-key insertion did not wait for retirement publication") &&
474 checkNamed(replacementBlocked,
"cache-retirement-publication",
475 "same-key insertion returned the retiring page") &&
476 checkNamed(evictorJoined && context.evictReturned == 1,
"cache-retirement-publication",
477 "the original eviction did not complete successfully") &&
478 checkNamed(inserterJoined && context.insertReturned == 1 && context.replacementPage != 0,
479 "cache-retirement-publication",
"the replacement insertion did not complete") &&
480 checkNamed(cache.exists(Key, PageSize),
"cache-retirement-publication",
481 "the replacement page was invalidated by the old callback");
485 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-retirement-publication");
490struct DiscardEditingContext {
491 DiscardEditingContext() : writebacks(0), evictions(0) {}
497bool discardEditingCallback(CacheConstants::CallbackCause cause, uintptr_t, uintptr_t,
499 DiscardEditingContext* context =
reinterpret_cast<DiscardEditingContext*
>(parameter);
500 if (cause == CacheConstants::WriteBack) {
501 context->writebacks += 1;
502 }
else if (cause == CacheConstants::Eviction) {
503 context->evictions += 1;
508bool failedPublicationDiscard() {
509 constexpr uintptr_t Key = 0xCA7E400;
511 DiscardEditingContext context;
512 cache.setCallback(discardEditingCallback, &context);
514 const uintptr_t failedPage = cache.insert(Key);
515 const bool ordinaryRejected = failedPage != 0 && !cache.evict(Key) && cache.exists(Key, PageSize);
516 const bool discarded = ordinaryRejected && cache.discardEditing(Key);
517 const bool removed = !cache.exists(Key, PageSize);
518 const bool suppressedWriteback = context.writebacks == 0 && context.evictions == 1;
520 const uintptr_t pinnedPage = cache.insert(Key);
521 const bool pinned = pinnedPage != 0 && cache.pin(Key);
522 const bool rejectedPinned = pinned && !cache.discardEditing(Key);
526 const bool discardedAfterRelease = rejectedPinned && cache.discardEditing(Key);
528 const uintptr_t publishedPage = cache.insert(Key);
530 cache.markNoLongerEditing(Key);
532 const bool rejectedPublished = publishedPage != 0 && !cache.discardEditing(Key);
536 checkNamed(ordinaryRejected,
"cache-failed-publication-discard",
537 "ordinary eviction reclaimed an unpublished Editing page") &&
538 checkNamed(discarded && removed,
"cache-failed-publication-discard",
539 "an unpinned Editing page was not synchronously removed") &&
540 checkNamed(suppressedWriteback,
"cache-failed-publication-discard",
541 "discarding failed data invoked backing-store writeback") &&
542 checkNamed(discardedAfterRelease,
"cache-failed-publication-discard",
543 "discard removed a pinned page or could not remove it after release") &&
544 checkNamed(rejectedPublished,
"cache-failed-publication-discard",
545 "discard removed a page after successful publication");
548 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-failed-publication-discard");
553struct RetirePublicationContext {
554 RetirePublicationContext()
561 allowCallbackReturn(0),
564 queuedCallbackFinished(0),
567 retireSawQueuedCompletion(0),
568 retireArgumentsValid(0),
571 retireSucceeded(0) {}
592void retireAdmissionHook(
Cache* cache, uintptr_t key,
void* parameter) {
593 RetirePublicationContext* context =
reinterpret_cast<RetirePublicationContext*
>(parameter);
594 if (cache == context->cache && key == context->key && (context->admissionCalls += 1) == 1) {
595 context->admissionEntered.release();
596 const bool released = context->allowPublication.acquireForCompletion();
601bool retireQueuedCallback(CacheConstants::CallbackCause cause, uintptr_t, uintptr_t,
603 RetirePublicationContext* context =
reinterpret_cast<RetirePublicationContext*
>(parameter);
604 if (cause == CacheConstants::Eviction) {
605 context->evictionCalls += 1;
606 }
else if (cause == CacheConstants::WriteBack && (context->queuedCallbacks += 1) == 1) {
607 context->callbackEntered.release();
608 const bool released = context->allowCallbackReturn.acquireForCompletion();
610 context->queuedCallbackFinished = 1;
615bool retireSynchronousCallback(uintptr_t key, uintptr_t page,
void* parameter) {
616 RetirePublicationContext* context =
reinterpret_cast<RetirePublicationContext*
>(parameter);
617 context->retireCallbacks += 1;
618 context->retireSawQueuedCompletion = context->queuedCallbackFinished;
619 context->retireArgumentsValid = key == context->key && page == context->page;
623int publishRetireWriteback(
void* parameter) {
624 RetirePublicationContext* context =
reinterpret_cast<RetirePublicationContext*
>(parameter);
625 context->cache->sync(context->key,
true);
626 context->syncReturned += 1;
630int retirePublishedWriteback(
void* parameter) {
631 RetirePublicationContext* context =
reinterpret_cast<RetirePublicationContext*
>(parameter);
632 if (context->cache->retireWriteback(context->key, retireSynchronousCallback, context)) {
633 context->retireSucceeded += 1;
635 context->retireReturned += 1;
639bool retirePrepublicationWriteback() {
640 constexpr uintptr_t Key = 0xCA7E500;
641 RetirePublicationContext context;
643 context.cache = &cache;
645 cache.setCallback(retireQueuedCallback, &context);
647 context.page = cache.insert(Key);
648 if (!checkNamed(context.page != 0,
"cache-retire-prepublication",
649 "could not create the test page")) {
652 cache.markNoLongerEditing(Key);
653 cache.markDirty(Key);
655 cache.setWritebackAdmissionHookForTest(retireAdmissionHook, &context);
658 &context,
nullptr,
false,
true);
659 producer->setName(
"hosted Cache paused writeback producer");
660 const bool admissionPaused = context.admissionEntered.acquire(1, 2);
661 if (!admissionPaused) {
662 context.allowPublication.release();
663 context.allowCallbackReturn.release();
665 cache.setWritebackAdmissionHookForTest(
nullptr,
nullptr);
668 return checkNamed(
false,
"cache-retire-prepublication",
669 "sync did not pause after publishing its page reference");
673 &context,
nullptr,
false,
true);
674 retirer->setName(
"hosted Cache writeback retirer");
675 const bool drainPublished = waitUntilQueuedAt(retirer, Thread::CallbackDrain, Key);
676 const bool blockedBeforePublication = context.retireReturned == 0;
677 if (drainPublished) {
678 cache.sync(Key,
true);
680 const bool drainingSyncRejected = context.admissionCalls == 1;
682 context.allowPublication.release();
683 const bool queuedCallbackEntered = context.callbackEntered.acquire(1, 2);
684 const bool blockedThroughCallback = context.retireReturned == 0;
685 context.allowCallbackReturn.release();
687 const bool producerJoined = producer->
join();
688 const bool retirerJoined = retirer->
join();
689 cache.setWritebackAdmissionHookForTest(
nullptr,
nullptr);
693 checkNamed(drainPublished,
"cache-retire-prepublication",
694 "retirement did not publish its exact per-key CallbackDrain wait") &&
695 checkNamed(blockedBeforePublication,
"cache-retire-prepublication",
696 "retirement returned before a visible writeback was queued") &&
697 checkNamed(drainingSyncRejected,
"cache-retire-prepublication",
698 "sync admitted another writeback while retirement was draining") &&
699 checkNamed(queuedCallbackEntered && blockedThroughCallback,
"cache-retire-prepublication",
700 "retirement did not wait for the queued callback to finish") &&
701 checkNamed(producerJoined && retirerJoined,
"cache-retire-prepublication",
702 "writeback producer or retirer did not become reapable") &&
704 context.syncReturned == 1 && context.retireReturned == 1 && context.retireSucceeded == 1,
705 "cache-retire-prepublication",
"retirement did not complete exactly once") &&
706 checkNamed(context.queuedCallbacks == 1 && context.queuedCallbackFinished == 1 &&
707 context.retireCallbacks == 1 && context.retireSawQueuedCompletion == 1,
708 "cache-retire-prepublication",
709 "the synchronous retirement callback overtook queued writeback") &&
710 checkNamed(context.retireArgumentsValid == 1 && context.evictionCalls == 1,
711 "cache-retire-prepublication",
712 "retirement callback arguments or final eviction were incorrect") &&
713 checkNamed(!cache.exists(Key, PageSize) && cache.lookup(Key) == 0,
714 "cache-retire-prepublication",
"the successful page remained published");
716 if (cache.exists(Key, PageSize)) {
720 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-retire-prepublication");
725struct DiscardPublicationContext {
726 explicit DiscardPublicationContext(
bool cancelPlan)
727 : publication(), prepared(0), allowCompletion(0), completed(0), cancel(cancelPlan) {}
729 RetirePublicationContext publication;
736int preparePublishedDiscard(
void* parameter) {
737 auto* context =
reinterpret_cast<DiscardPublicationContext*
>(parameter);
738 auto& publication = context->publication;
741 const auto status = publication.cache->prepareDiscardFrom(publication.key, &reference, 1, plan);
742 if (status == Cache::DiscardStatus::Ready && plan) {
743 publication.retireSucceeded += 1;
745 publication.retireReturned += 1;
746 context->prepared.release();
747 const bool released = context->allowCompletion.acquireForCompletion();
749 if (plan && !context->cancel) {
750 plan.get()->commit();
754 context->completed += 1;
758bool discardPrefixUsable(
Cache& cache, uintptr_t key, uintptr_t expected) {
759 const uintptr_t lookup = cache.lookup(key);
760 const bool pinned = cache.pin(key);
761 bool unchanged = lookup && lookup == expected && pinned;
763 const auto* bytes =
reinterpret_cast<const uint8_t*
>(lookup);
764 for (
size_t n = 0; n < PageSize; ++n) {
765 unchanged = unchanged && bytes[n] ==
static_cast<uint8_t
>(n ^ 0x6d);
775bool preparedDiscardPublication(
bool cancel) {
776 constexpr uintptr_t PrefixKey = 0xCB000000;
777 constexpr uintptr_t Key = PrefixKey + PageSize;
778 const char* test = cancel ?
"cache-discard-cancel" :
"cache-discard-prepublication";
779 DiscardPublicationContext context(cancel);
780 auto& publication = context.publication;
782 publication.cache = &cache;
783 publication.key = Key;
784 cache.setCallback(retireQueuedCallback, &publication);
786 const uintptr_t prefix = cache.insert(PrefixKey);
787 publication.page = cache.insert(Key);
788 bool ownerPinned = publication.page && cache.pin(Key);
789 if (!prefix || !ownerPinned) {
793 publication.allowCallbackReturn.release();
796 return checkNamed(
false, test,
"could not create and pin the test cache pages");
798 for (
size_t n = 0; n < PageSize; ++n) {
799 reinterpret_cast<uint8_t*
>(prefix)[n] =
static_cast<uint8_t
>(n ^ 0x6d);
800 reinterpret_cast<uint8_t*
>(publication.page)[n] =
static_cast<uint8_t
>(n ^ 0xb2);
802 cache.markNoLongerEditing(PrefixKey);
803 cache.markNoLongerEditing(Key);
804 cache.markDirty(Key);
805 cache.setWritebackAdmissionHookForTest(retireAdmissionHook, &publication);
807 &publication,
nullptr,
false,
true);
808 producer->setName(
"hosted Cache discard writeback producer");
809 if (!publication.admissionEntered.acquire(1, 2)) {
810 publication.allowPublication.release();
811 publication.allowCallbackReturn.release();
813 cache.setWritebackAdmissionHookForTest(
nullptr,
nullptr);
817 return checkNamed(
false, test,
"writeback did not pause after publishing its page pin");
821 &context,
nullptr,
false,
true);
822 preparer->setName(
"hosted Cache prepared discard");
823 const bool drainPublished = waitUntilQueuedAt(preparer, Thread::CallbackDrain, Key);
824 const bool blockedBeforePublication = publication.retireReturned == 0;
825 const uintptr_t unexpectedLookup = drainPublished ? cache.lookup(Key) : 0;
826 const bool unexpectedPin = drainPublished && cache.pin(Key);
827 const bool syncRejected = drainPublished && !cache.sync(Key,
true);
828 const bool admissionRejected =
829 syncRejected && !unexpectedLookup && !unexpectedPin && publication.admissionCalls == 1;
830 if (unexpectedLookup) {
836 const bool prefixDuringDrain = discardPrefixUsable(cache, PrefixKey, prefix);
837 publication.allowPublication.release();
838 const bool callbackEntered = publication.callbackEntered.acquire(1, 2);
839 const bool blockedThroughCallback = callbackEntered &&
840 waitUntilQueuedAt(preparer, Thread::CallbackDrain, Key) &&
841 publication.retireReturned == 0;
842 publication.allowCallbackReturn.release();
843 const bool readyWithOwnerPin = context.prepared.acquire(1, 2) &&
844 publication.retireReturned == 1 &&
845 publication.retireSucceeded == 1;
846 if (readyWithOwnerPin) {
847 reinterpret_cast<uint8_t*
>(publication.page)[0] = 0xa7;
848 cache.markDirty(Key);
850 if (!cancel || !readyWithOwnerPin) {
854 context.allowCompletion.release();
855 const bool producerJoined = producer->
join();
856 const bool preparerJoined = preparer->
join();
857 cache.setWritebackAdmissionHookForTest(
nullptr,
nullptr);
859 bool completedCorrectly =
false;
860 if (cancel && readyWithOwnerPin) {
861 const uintptr_t restored = cache.lookup(Key);
862 const bool pinRestored = cache.pin(Key);
863 completedCorrectly = restored == publication.page && pinRestored &&
864 reinterpret_cast<const uint8_t*
>(publication.page)[0] == 0xa7 &&
865 publication.queuedCallbacks == 1 && publication.evictionCalls == 0;
874 const bool evicted = cache.evict(Key);
875 completedCorrectly = completedCorrectly && evicted && publication.queuedCallbacks == 2 &&
876 publication.evictionCalls == 1;
877 }
else if (!cancel) {
878 completedCorrectly = publication.queuedCallbacks == 1 && publication.evictionCalls == 1;
880 const bool suffixRemoved = !cache.exists(Key, PageSize);
881 const bool prefixAfterCompletion = discardPrefixUsable(cache, PrefixKey, prefix);
883 checkNamed(drainPublished && blockedBeforePublication, test,
884 "prepare did not wait at its exact CallbackDrain key before queue publication") &&
885 checkNamed(admissionRejected && prefixDuringDrain, test,
886 "draining suffix accepted a new consumer or blocked the retained prefix") &&
887 checkNamed(blockedThroughCallback, test,
"prepare returned before queued writeback ended") &&
888 checkNamed(readyWithOwnerPin, test,
"prepare did not become Ready with its owner pin held") &&
889 checkNamed(producerJoined && preparerJoined && context.completed == 1 &&
890 publication.syncReturned == 1 && publication.queuedCallbackFinished == 1,
891 test,
"producer or prepared-discard worker failed to complete exactly once") &&
892 checkNamed(completedCorrectly && suffixRemoved, test,
893 cancel ?
"rollback lost admission or dirty data needed by ordinary eviction"
894 :
"discard wrote dirty bytes back or failed to evict exactly once") &&
895 checkNamed(prefixAfterCompletion, test,
"completion changed or removed the retained prefix");
902 NOTICE(
"HOSTED-WAIT-TEST: PASS " << test);
907struct RejectedWritebackContext {
908 RejectedWritebackContext()
913 shutdownSucceeded(0) {}
915 RetirePublicationContext publication;
922int publishRejectedWriteback(
void* parameter) {
923 auto* context =
reinterpret_cast<RejectedWritebackContext*
>(parameter);
924 auto& publication = context->publication;
925 context->accepted = publication.cache->sync(publication.key,
true) ? 1 : 0;
926 publication.syncReturned += 1;
927 context->producerFinished.release();
931int shutdownRejectedWriteback(
void* parameter) {
932 auto* context =
reinterpret_cast<RejectedWritebackContext*
>(parameter);
933 context->shutdownSucceeded = context->publication.cache->shutdown() ? 1 : 0;
934 context->shutdownFinished.release();
938bool rejectedLastWritebackPin() {
939 constexpr uintptr_t Key = 0xCC000000;
940 constexpr const char* Test =
"cache-rejected-last-writeback";
941 RejectedWritebackContext context;
942 auto& publication = context.publication;
945 publication.cache = &cache;
946 publication.key = Key;
947 cache.setCallback(retireQueuedCallback, &publication);
950 publication.page = cache.insert(Key);
951 if (!checkNamed(publication.page != 0, Test,
"could not create the test page")) {
952 publication.allowCallbackReturn.release();
955 reinterpret_cast<uint8_t*
>(publication.page)[0] = 0xa7;
956 cache.markNoLongerEditing(Key);
957 cache.markDirty(Key);
958 cache.setWritebackAdmissionHookForTest(retireAdmissionHook, &publication);
960 &context,
nullptr,
false,
true);
961 producer->setName(
"hosted Cache rejected writeback producer");
962 const bool admissionPaused = publication.admissionEntered.acquire(1, 2);
963 if (admissionPaused) {
966 const bool halted = admissionPaused && manager.
halt();
969 publication.allowCallbackReturn.release();
970 publication.allowPublication.release();
971 const bool producerFinished = context.producerFinished.acquire(1, 2);
972 if (!producerFinished) {
973 const bool resumed = manager.
resume();
975 checkNamed(
false, Test,
"rejected writeback producer did not finish");
976 FATAL(
"Cache rejection fixture retained a live producer");
978 const bool producerJoined = producer->
join();
979 const bool rejected = context.accepted == 0 && publication.syncReturned == 1 &&
980 publication.queuedCallbacks == 0 && publication.evictionCalls == 0;
981 cache.setWritebackAdmissionHookForTest(
nullptr,
nullptr);
982 const bool resumed = manager.
resume();
983 if (!resumed && (!manager.
halt() || !manager.
resume())) {
984 checkNamed(
false, Test,
"could not restore the CacheManager worker");
985 FATAL(
"Cache rejection fixture could not resume CacheManager");
989 &context,
nullptr,
false,
true);
990 shutdown->setName(
"hosted Cache rejected-writeback shutdown");
991 if (!context.shutdownFinished.acquire(1, 2)) {
992 checkNamed(
false, Test,
"shutdown did not drain the rejected request's cache lease");
993 FATAL(
"Cache rejection fixture retained a live shutdown worker");
995 const bool shutdownJoined = shutdown->
join();
997 reinterpret_cast<void*
>(publication.page));
999 checkNamed(admissionPaused && stopped, Test,
1000 "writeback was not paused with its last pin before manager halt") &&
1001 checkNamed(producerFinished && producerJoined && rejected, Test,
1002 "the stopped queue executed or retained the rejected writeback") &&
1003 checkNamed(resumed, Test,
"the CacheManager worker did not resume") &&
1004 checkNamed(shutdownJoined && context.shutdownSucceeded == 1 &&
1005 publication.queuedCallbacks == 1 && publication.evictionCalls == 1 && unmapped,
1006 Test,
"terminal shutdown did not write back and remove the abandoned page");
1008 NOTICE(
"HOSTED-WAIT-TEST: PASS " << Test);
1013struct RetireContractContext {
1014 RetireContractContext()
1022 retireSucceeded(0) {}
1034bool retireContractCallback(uintptr_t key, uintptr_t page,
void* parameter) {
1035 RetireContractContext* context =
reinterpret_cast<RetireContractContext*
>(parameter);
1036 context->callbacks += 1;
1037 context->argumentsValid = key == context->key && page == context->page;
1038 return static_cast<size_t>(context->shouldSucceed) != 0;
1041int retirePinnedWriteback(
void* parameter) {
1042 RetireContractContext* context =
reinterpret_cast<RetireContractContext*
>(parameter);
1043 if (context->cache->retireWriteback(context->key, retireContractCallback, context)) {
1044 context->retireSucceeded += 1;
1046 context->retireReturned += 1;
1050bool retireWritebackContract() {
1051 constexpr uintptr_t EditingKey = 0xCA7E600;
1052 constexpr uintptr_t RetryKey = 0xCA7E700;
1053 constexpr uintptr_t PinnedKey = 0xCA7E800;
1054 constexpr uintptr_t MissingKey = 0xCA7E900;
1057 RetireContractContext editing;
1058 editing.cache = &cache;
1059 editing.key = EditingKey;
1060 editing.page = cache.insert(EditingKey);
1061 editing.shouldSucceed = 1;
1062 const bool editingRejected =
1063 editing.page && !cache.retireWriteback(EditingKey, retireContractCallback, &editing) &&
1064 editing.callbacks == 0 && cache.exists(EditingKey, PageSize);
1065 const bool editingDiscarded = editingRejected && cache.discardEditing(EditingKey);
1067 RetireContractContext retry;
1068 retry.cache = &cache;
1069 retry.key = RetryKey;
1070 retry.page = cache.insert(RetryKey);
1072 cache.markNoLongerEditing(RetryKey);
1074 const bool failureKeptPage = retry.page &&
1075 !cache.retireWriteback(RetryKey, retireContractCallback, &retry) &&
1076 retry.callbacks == 1 && cache.lookup(RetryKey) == retry.page;
1077 if (failureKeptPage) {
1078 cache.release(RetryKey);
1080 retry.shouldSucceed = 1;
1081 const bool retryRetired =
1082 failureKeptPage && cache.retireWriteback(RetryKey, retireContractCallback, &retry) &&
1083 retry.callbacks == 2 && retry.argumentsValid == 1 && !cache.exists(RetryKey, PageSize);
1085 RetireContractContext pinned;
1086 pinned.cache = &cache;
1087 pinned.key = PinnedKey;
1088 pinned.page = cache.insert(PinnedKey);
1090 cache.markNoLongerEditing(PinnedKey);
1092 pinned.shouldSucceed = 1;
1093 const bool pinnedReady = pinned.page && cache.pin(PinnedKey);
1094 Thread* retirer =
nullptr;
1097 nullptr,
false,
true);
1098 retirer->setName(
"hosted Cache pinned-page retirer");
1100 const bool pinDrainPublished =
1101 retirer && waitUntilQueuedAt(retirer, Thread::CallbackDrain, PinnedKey);
1102 const uintptr_t unexpectedLookup = pinDrainPublished ? cache.lookup(PinnedKey) : 0;
1103 const bool unexpectedPin = pinDrainPublished && cache.pin(PinnedKey);
1104 const bool newConsumersRejected =
1105 pinDrainPublished && pinned.retireReturned == 0 && !unexpectedLookup && !unexpectedPin;
1106 if (unexpectedLookup) {
1107 cache.release(PinnedKey);
1109 if (unexpectedPin) {
1110 cache.release(PinnedKey);
1113 cache.release(PinnedKey);
1115 const bool pinnedJoined = retirer && retirer->
join();
1116 const bool pinnedRetired = pinnedJoined && pinned.retireReturned == 1 &&
1117 pinned.retireSucceeded == 1 && pinned.callbacks == 1 &&
1118 pinned.argumentsValid == 1 && !cache.exists(PinnedKey, PageSize);
1120 RetireContractContext missing;
1121 missing.cache = &cache;
1122 missing.key = MissingKey;
1123 missing.shouldSucceed = 1;
1124 const bool missingSucceeded =
1125 cache.retireWriteback(MissingKey, retireContractCallback, &missing) && missing.callbacks == 0;
1128 checkNamed(editingDiscarded,
"cache-retire-contract",
1129 "retirement invoked writeback for an Editing page") &&
1130 checkNamed(retryRetired,
"cache-retire-contract",
1131 "failed writeback did not preserve a retryable page") &&
1132 checkNamed(pinDrainPublished && newConsumersRejected && pinnedRetired,
1133 "cache-retire-contract",
1134 "retirement did not drain the old pin while rejecting new consumers") &&
1135 checkNamed(missingSucceeded,
"cache-retire-contract",
1136 "retiring a missing page invoked the callback or failed");
1140 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-retire-contract");
1145struct SyncAllContext {
1152 blockWriteback(false),
1153 retirementSucceeds(false),
1154 nestedSucceeded(0) {}
1161 bool blockWriteback;
1162 bool retirementSucceeds;
1166bool syncAllCallback(CacheConstants::CallbackCause cause, uintptr_t, uintptr_t,
void* parameter) {
1167 auto* context =
static_cast<SyncAllContext*
>(parameter);
1168 if (cause != CacheConstants::WriteBack) {
1171 const size_t write = (context->writes += 1);
1172 if (context->blockWriteback && write == 1) {
1173 context->entered.release();
1174 const bool released = context->allowReturn.acquireForCompletion();
1177 if (context->lower) {
1178 const bool succeeded = context->lower->syncAll();
1179 context->nestedSucceeded = succeeded ? 1 : 0;
1185bool syncAllRetirementCallback(uintptr_t, uintptr_t,
void* parameter) {
1186 auto* context =
static_cast<SyncAllContext*
>(parameter);
1187 context->entered.release();
1188 const bool released = context->allowReturn.acquireForCompletion();
1190 return context->retirementSucceeds;
1194 enum Kind { All, QueuedPage, Retire };
1195 SyncAllCall(SyncAllContext& state, uintptr_t cacheKey, Kind operation)
1196 : context(state), key(cacheKey), kind(operation), done(0), result(0) {}
1197 SyncAllContext& context;
1204int syncAllWorker(
void* parameter) {
1205 auto* call =
static_cast<SyncAllCall*
>(parameter);
1206 bool succeeded =
false;
1207 if (call->kind == SyncAllCall::All) {
1208 succeeded = call->context.cache->syncAll();
1209 }
else if (call->kind == SyncAllCall::QueuedPage) {
1210 succeeded = call->context.cache->sync(call->key,
false);
1213 call->context.cache->retireWriteback(call->key, syncAllRetirementCallback, &call->context);
1215 call->result = succeeded ? 1 : 0;
1216 call->done.release();
1220bool syncAllJoinsCallback() {
1221 constexpr uintptr_t Key = 0xCA7EA00;
1222 SyncAllContext context;
1224 context.cache = &cache;
1225 context.blockWriteback =
true;
1226 cache.setCallback(syncAllCallback, &context);
1227 if (!cache.insert(Key)) {
1230 cache.markNoLongerEditing(Key);
1231 cache.markDirty(Key);
1232 SyncAllCall first(context, Key, SyncAllCall::All);
1234 nullptr,
false,
true);
1235 const bool entered = context.entered.acquire(1, 2);
1237 context.allowReturn.release();
1239 return checkNamed(
false,
"cache-sync-all",
"direct callback did not start");
1242 SyncAllCall queued(context, Key, SyncAllCall::QueuedPage);
1244 nullptr,
false,
true);
1245 const bool queueCompleted = queued.done.acquire(1, 2);
1246 const bool queueRejected = queueCompleted && queued.result == 0;
1247 SyncAllCall second(context, Key, SyncAllCall::All);
1249 nullptr,
false,
true);
1250 const bool joinedCallback = waitUntilQueuedAt(joiner, Thread::CallbackDrain, Key);
1251 context.allowReturn.release();
1252 const bool writerJoined = writer->
join();
1253 const bool producerJoined = producer->
join();
1254 const bool joinerJoined = joiner->
join();
1256 checkNamed(queueRejected && producerJoined,
"cache-sync-all",
1257 "the CacheManager worker blocked behind a direct callback") &&
1258 checkNamed(joinedCallback && writerJoined && joinerJoined && first.result == 1 &&
1259 second.result == 1 && context.writes == 1,
1260 "cache-sync-all",
"synchronous drain did not join an active callback");
1262 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-sync-all-callback");
1267bool syncAllJoinsRetirement(
bool succeeds) {
1268 constexpr uintptr_t Key = 0xCA7EB00;
1269 SyncAllContext context;
1271 context.cache = &cache;
1272 context.retirementSucceeds = succeeds;
1273 cache.setCallback(syncAllCallback, &context);
1274 if (!cache.insert(Key)) {
1277 cache.markNoLongerEditing(Key);
1278 cache.markDirty(Key);
1279 SyncAllCall retirement(context, Key, SyncAllCall::Retire);
1281 nullptr,
false,
true);
1282 const bool entered = context.entered.acquire(1, 2);
1284 context.allowReturn.release();
1286 return checkNamed(
false,
"cache-sync-all",
"retirement callback did not start");
1288 SyncAllCall sync(context, Key, SyncAllCall::All);
1290 nullptr,
false,
true);
1291 const bool joinedRetirement = waitUntilQueuedAt(joiner, Thread::CallbackDrain, Key);
1292 context.allowReturn.release();
1293 const bool retirerJoined = retirer->
join();
1294 const bool joinerJoined = joiner->
join();
1295 const bool passed = checkNamed(
1296 joinedRetirement && retirerJoined && joinerJoined && sync.result == 1 &&
1297 retirement.result == (succeeds ? 1U : 0U) && context.writes == (succeeds ? 0U : 1U) &&
1298 cache.exists(Key, PageSize) != succeeds,
1299 "cache-sync-all",
"drain did not join retirement or retry its retained failure");
1301 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-sync-all-retirement-" << (succeeds ?
"success" :
"retry"));
1306bool syncAllFromCacheManager() {
1307 constexpr uintptr_t Key = 0xCA7EC00;
1308 SyncAllContext lowerContext;
1310 lowerContext.cache = &lower;
1311 lower.
setCallback(syncAllCallback, &lowerContext);
1312 SyncAllContext upperContext;
1314 upperContext.cache = &upper;
1315 upperContext.lower = &lower;
1316 upper.
setCallback(syncAllCallback, &upperContext);
1324 const bool passed = checkNamed(
1325 upper.
sync(Key,
false) && upperContext.nestedSucceeded == 1 && lowerContext.writes == 1,
1326 "cache-sync-all",
"a CacheManager callback could not drain an independent lower cache");
1328 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-sync-all-nested");
1333bool rangeExistence() {
1334 constexpr uintptr_t Key = 0xCA7E500;
1335 constexpr size_t Length = 3 * PageSize;
1336 constexpr uintptr_t ProbeKey = Key + (8 * PageSize);
1337 constexpr uintptr_t SecondProbeKey = ProbeKey + (4 * PageSize);
1344 const uintptr_t allocatorExtent = cache.insert(ProbeKey, 2 * Length);
1345 const bool allocatorProbeReady = allocatorExtent != 0;
1348 const uintptr_t pages = cache.insert(Key, Length);
1349 const bool completeRange = pages != 0 && cache.exists(Key, Length);
1350 bool alreadyExisted =
false;
1351 const uintptr_t reused = cache.insert(Key, Length, &alreadyExisted);
1352 const bool completeRangeReused = alreadyExisted && reused == pages;
1353 const bool removedInterior = cache.discardEditing(Key + PageSize);
1354 const bool missingInteriorRejected = removedInterior && !cache.exists(Key, Length);
1357 const uintptr_t interior = cache.insert(Key + PageSize);
1358 bool overlapExisted =
true;
1359 const uintptr_t overlappingRange = cache.insert(Key, Length, &overlapExisted);
1360 const bool overlapRejectedBeforeAllocation =
1361 interior != 0 && !overlappingRange && !overlapExisted &&
1362 cache.exists(Key + PageSize, PageSize) && !cache.exists(Key, PageSize) &&
1363 !cache.exists(Key + (2 * PageSize), PageSize) && !cache.exists(Key, Length);
1366 const uintptr_t firstHalf = cache.insert(ProbeKey, Length);
1367 const uintptr_t secondHalf = cache.insert(SecondProbeKey, Length);
1368 const bool allocatorAndPageAccountingBalanced =
1369 allocatorProbeReady && firstHalf == allocatorExtent &&
1370 secondHalf == allocatorExtent + Length && cache.exists(ProbeKey, Length) &&
1371 cache.exists(SecondProbeKey, Length);
1374 const bool passed = checkNamed(completeRange && completeRangeReused,
"cache-range-existence",
1375 "a complete contiguous cache range was not detected or reused") &&
1376 checkNamed(missingInteriorRejected,
"cache-range-existence",
1377 "a range with a missing interior page was reported complete") &&
1378 checkNamed(overlapRejectedBeforeAllocation,
"cache-range-existence",
1379 "an interior overlap partially allocated or published a range") &&
1380 checkNamed(allocatorAndPageAccountingBalanced,
"cache-range-existence",
1381 "a rejected overlap leaked cache VA or page accounting");
1383 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-range-existence");
1388bool strictRangeGeometry() {
1389 constexpr uintptr_t InsertKey = 0xCA7F000;
1390 constexpr uintptr_t PublishKey = InsertKey + (4 * PageSize);
1393 bool alreadyExisted =
true;
1394 const uintptr_t invalid = cache.insert(InsertKey, PageSize + 1, &alreadyExisted);
1395 const bool insertionRejected = !invalid && !alreadyExisted && !cache.exists(InsertKey, PageSize);
1397 const uintptr_t editing = cache.insert(InsertKey);
1398 cache.markNoLongerEditing(InsertKey, PageSize + 1);
1399 const bool invalidPublishLeftEditing = editing && cache.discardEditing(InsertKey);
1401 const uintptr_t published = cache.insert(PublishKey);
1402 cache.markNoLongerEditing(PublishKey);
1403 cache.markEditing(PublishKey, PageSize + 1);
1404 const bool invalidEditLeftPublished = published && !cache.discardEditing(PublishKey);
1408 checkNamed(insertionRejected,
"cache-range-geometry",
1409 "a partial target-page insertion was truncated instead of rejected") &&
1410 checkNamed(invalidPublishLeftEditing,
"cache-range-geometry",
1411 "an invalid publish range changed the first cache page") &&
1412 checkNamed(invalidEditLeftPublished,
"cache-range-geometry",
1413 "an invalid edit range changed the first cache page");
1415 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-range-geometry");
1420struct TimerWritebackContext {
1421 Cache* cache =
nullptr;
1429 uint8_t lastWritten = 0;
1430 bool failFirst =
false;
1433void timerWritebackAdmission(
Cache*, uintptr_t,
void* parameter) {
1434 auto& context = *
static_cast<TimerWritebackContext*
>(parameter);
1435 if ((context.admissions += 1) == 1) {
1436 context.cache->startAtomic();
1437 context.admissionEntered.release();
1438 const bool released = context.allowPublication.acquireForCompletion();
1443bool timerWritebackCallback(CacheConstants::CallbackCause cause, uintptr_t, uintptr_t page,
1445 if (cause != CacheConstants::WriteBack) {
1448 auto& context = *
static_cast<TimerWritebackContext*
>(parameter);
1449 const size_t call = (context.callbacks += 1);
1450 context.lastWritten = *
reinterpret_cast<uint8_t*
>(page);
1452 context.callbackEntered.release();
1453 const bool released = context.allowCallbackReturn.acquireForCompletion();
1456 return !(context.failFirst && call == 1);
1459void tickWriteback(
Cache& cache) {
1461 cache.timer(CACHE_WRITEBACK_PERIOD * 1000000ULL);
1462 cache.startAtomic();
1465int publishTimerWriteback(
void* parameter) {
1466 auto& context = *
static_cast<TimerWritebackContext*
>(parameter);
1467 tickWriteback(*context.cache);
1471bool timerWritebackCoalescing(
bool failFirst,
bool mutateDuringWriteback) {
1472 const char* test = failFirst ?
"cache-timer-pending-failure"
1473 : (mutateDuringWriteback ?
"cache-timer-pending-mutation"
1474 :
"cache-timer-pending-success");
1475 TimerWritebackContext context;
1476 context.failFirst = failFirst;
1477 context.key = 0xCA7F800;
1479 context.cache = &cache;
1480 cache.startAtomic();
1481 cache.setCallback(timerWritebackCallback, &context);
1482 const uintptr_t page = cache.insert(context.key);
1483 if (!checkNamed(page != 0, test,
"could not create the test page")) {
1486 *
reinterpret_cast<uint8_t*
>(page) = 0x57;
1487 cache.markNoLongerEditing(context.key);
1488 tickWriteback(cache);
1489 cache.markDirty(context.key);
1490 cache.setWritebackAdmissionHookForTest(timerWritebackAdmission, &context);
1496 fence.
setCallback([](CacheConstants::CallbackCause, uintptr_t, uintptr_t,
void*) {
return true; },
1498 const uintptr_t fencePage = fence.
insert(0);
1499 if (!checkNamed(fencePage != 0, test,
"could not create the worker fence")) {
1500 cache.setWritebackAdmissionHookForTest(
nullptr,
nullptr);
1501 context.allowCallbackReturn.release();
1507 &context,
nullptr,
false,
true);
1508 producer->setName(
"hosted Cache paused timer producer");
1509 const bool admissionPaused = context.admissionEntered.acquire(1, 2);
1510 if (admissionPaused) {
1511 for (
size_t i = 0; i < 3; ++i) {
1512 tickWriteback(cache);
1515 const bool onePendingAdmission = context.admissions == 1;
1516 context.allowPublication.release();
1517 const bool producerJoined = producer->
join();
1518 const bool callbackPaused = context.callbackEntered.acquire(1, 2);
1519 if (callbackPaused) {
1520 for (
size_t i = 0; i < 3; ++i) {
1521 tickWriteback(cache);
1523 if (mutateDuringWriteback) {
1524 *
reinterpret_cast<uint8_t*
>(page) = 0xA6;
1527 const bool oneActiveAdmission = context.admissions == 1;
1528 context.allowCallbackReturn.release();
1530 const bool firstDrained = fence.
sync(0,
false);
1531 const bool oneInitialCallback = context.callbacks == 1;
1535 bool retriesDrained =
true;
1536 for (
size_t i = 0; i < 3; ++i) {
1537 tickWriteback(cache);
1539 retriesDrained = fence.
sync(0,
false) && retriesDrained;
1541 const size_t expected = failFirst || mutateDuringWriteback ? 2 : 1;
1542 const bool expectedCallbacks = context.callbacks == expected && context.admissions == expected;
1543 const bool expectedBytes = context.lastWritten == (mutateDuringWriteback ? 0xA6 : 0x57);
1544 cache.setWritebackAdmissionHookForTest(
nullptr,
nullptr);
1545 const bool reclaimed = cache.empty();
1548 checkNamed(admissionPaused && callbackPaused && producerJoined, test,
1549 "writeback did not reach both controlled publication phases") &&
1550 checkNamed(onePendingAdmission && oneActiveAdmission, test,
1551 "timer admitted duplicate work while writeback was pending or active") &&
1552 checkNamed(firstDrained && oneInitialCallback && retriesDrained, test,
1553 "worker did not drain exactly one initial writeback") &&
1554 checkNamed(expectedCallbacks && expectedBytes, test,
1555 "completion lost a mutation, failed to retry, or wrote clean data again") &&
1556 checkNamed(reclaimed, test,
"completed writeback retained a page pin");
1558 NOTICE(
"HOSTED-WAIT-TEST: PASS " << test);
1563struct ExplicitCacheCall {
1564 enum Kind { Sync, Lookup, Redirty };
1565 ExplicitCacheCall(TimerWritebackContext& state, Kind operation)
1566 : context(state), kind(operation) {}
1567 TimerWritebackContext& context;
1574int explicitCacheWorker(
void* parameter) {
1575 auto& call = *
static_cast<ExplicitCacheCall*
>(parameter);
1576 Cache& cache = *call.context.cache;
1577 const uintptr_t key = call.context.key;
1578 bool succeeded =
false;
1579 if (call.kind == ExplicitCacheCall::Sync) {
1580 succeeded = cache.syncAll();
1581 }
else if (call.kind == ExplicitCacheCall::Lookup) {
1583 succeeded = cache.lookupStable(key, call.page,
true);
1585 call.page = cache.lookup(key);
1587 *
reinterpret_cast<uint8_t*
>(call.page) = 0xA6;
1588 cache.markDirty(key);
1593 call.result = succeeded ? 1 : 0;
1594 call.done.release();
1598bool explicitWritebackThreading(
bool redirty) {
1599 const char* test = redirty ?
"cache-explicit-concurrent-redirty" :
"cache-explicit-lookup-wakeup";
1600 TimerWritebackContext context;
1601 context.key = 0xCA7F900;
1603 context.cache = &cache;
1604 cache.startAtomic();
1605 cache.setDirtyTracking(Cache::DirtyTracking::Explicit);
1606 cache.setCallback(timerWritebackCallback, &context);
1607 const uintptr_t page = cache.insert(context.key);
1608 if (!checkNamed(page != 0, test,
"could not create the test page")) {
1611 *
reinterpret_cast<uint8_t*
>(page) = 0x57;
1612 cache.markNoLongerEditing(context.key);
1613 cache.markDirty(context.key);
1615 ExplicitCacheCall first(context, ExplicitCacheCall::Sync);
1617 nullptr,
false,
true);
1618 writer->setName(
"hosted Cache explicit paused writer");
1619 const bool callbackPaused = context.callbackEntered.acquire(1, 2);
1620 if (!callbackPaused) {
1621 context.allowCallbackReturn.release();
1623 return checkNamed(
false, test,
"explicit dirty callback did not start");
1626 ExplicitCacheCall second(context,
1627 redirty ? ExplicitCacheCall::Redirty : ExplicitCacheCall::Lookup);
1629 nullptr,
false,
true);
1631 String(redirty ?
"hosted Cache concurrent explicit mutation" :
"hosted Cache stable lookup"));
1632 const bool concurrentPhase = redirty
1633 ? second.done.acquire(1, 2)
1634 : waitUntilQueuedAt(peer, Thread::CallbackDrain, context.key);
1635 context.allowCallbackReturn.release();
1637 bool peerCompleted = concurrentPhase;
1639 peerCompleted = second.done.acquire(1, 2);
1640 if (!peerCompleted) {
1644 uintptr_t address = 0;
1646 peer->
getDebugState(address) == Thread::CallbackDrain && address == context.key) {
1647 wait.queue->wakeAll(WaitQueue::WakeReason::Signalled,
1653 const bool firstWrite =
1654 first.result == 1 && context.callbacks == 1 && context.lastWritten == 0x57;
1655 const bool samePage = second.result == 1 && second.page == page;
1656 bool lookupPinned =
true;
1658 lookupPinned = samePage && !cache.evict(context.key);
1660 cache.release(context.key);
1664 const bool nextSynced = cache.sync(context.key,
false);
1665 const size_t expectedWrites = redirty ? 2 : 1;
1666 const bool latestWritten =
1667 context.callbacks == expectedWrites && context.lastWritten == (redirty ? 0xA6 : 0x57);
1668 const bool cleanSynced = cache.syncAll();
1669 const bool stayedClean = context.callbacks == expectedWrites;
1670 const bool reclaimed = cache.empty();
1672 checkNamed(concurrentPhase && peerCompleted && writerJoined && peerJoined, test,
1673 redirty ?
"mutation did not finish while the callback was paused"
1674 :
"stable lookup did not publish and complete its callback-drain wait") &&
1675 checkNamed(firstWrite && samePage && lookupPinned, test,
1676 "initial write or the concurrent page pin changed identity") &&
1677 checkNamed(nextSynced && latestWritten && cleanSynced && stayedClean, test,
1678 "callback completion lost a dirty generation or rewrote a clean page") &&
1679 checkNamed(reclaimed, test,
"completed workers retained a page pin");
1681 NOTICE(
"HOSTED-WAIT-TEST: PASS " << test);
1686struct DirectWritebackContext {
1687 Cache* cache =
nullptr;
1699bool directWritebackCallback(
const Cache::WritebackPage* pages,
size_t count,
void* parameter) {
1700 auto& context = *
static_cast<DirectWritebackContext*
>(parameter);
1701 if (count != 1 || pages[0].key != context.key) {
1705 if (!lease.acquire(*context.cache, pages[0].key, pages[0].location)) {
1708 context.direct = lease.physical() != 0;
1709 const size_t call = context.callbacks += 1;
1711 context.entered.release();
1712 const bool released = context.allowReturn.acquireForCompletion();
1715 context.observed = *
reinterpret_cast<const uint8_t*
>(pages[0].location);
1719int directWritebackSync(
void* parameter) {
1720 auto& context = *
static_cast<DirectWritebackContext*
>(parameter);
1721 context.syncResult =
1722 context.cache->syncBatch(&context.key, 1, directWritebackCallback, &context) ? 1 : 0;
1726int directWritebackWriter(
void* parameter) {
1727 auto& context = *
static_cast<DirectWritebackContext*
>(parameter);
1728 if (!context.cache->pin(context.key)) {
1731 if (context.cache->beginMutableLoan(context.key)) {
1732 *
reinterpret_cast<uint8_t*
>(context.page) = 0xA6;
1733 context.cache->markDirty(context.key);
1734 context.cache->endMutableLoan(context.key);
1735 context.writerResult = 1;
1737 context.cache->release(context.key);
1741bool directWritebackLoan() {
1742 constexpr const char* Test =
"cache-direct-writeback-loan";
1743 DirectWritebackContext context;
1745 context.cache = &cache;
1746 context.key = 0xCA7FA00;
1747 cache.setDirtyTracking(Cache::DirtyTracking::Explicit);
1748 cache.setCallback([](CacheConstants::CallbackCause, uintptr_t, uintptr_t,
void*) {
return true; },
1750 context.page = cache.insert(context.key);
1751 if (!checkNamed(context.page != 0, Test,
"could not create the test page")) {
1754 *
reinterpret_cast<uint8_t*
>(context.page) = 0x57;
1755 cache.markNoLongerEditing(context.key);
1756 cache.markDirty(context.key);
1759 nullptr,
false,
true);
1760 sync->setName(
"hosted Cache direct writeback");
1761 const bool entered = context.entered.acquire(1, 2);
1762 Thread* writer =
nullptr;
1763 bool queued =
false;
1764 bool frozen =
false;
1767 nullptr,
false,
true);
1768 writer->setName(
"hosted Cache direct writeback writer");
1769 queued = waitUntilQueuedAt(writer, Thread::CallbackDrain, context.key);
1770 frozen = *
reinterpret_cast<const uint8_t*
>(context.page) == 0x57;
1772 context.allowReturn.release();
1775 const bool first = context.syncResult == 1 && context.direct == 1 && context.observed == 0x57 &&
1776 context.writerResult == 1;
1777 const bool second = cache.syncBatch(&context.key, 1, directWritebackCallback, &context) &&
1778 context.callbacks == 2 && context.observed == 0xA6;
1779 const bool reclaimed = cache.empty();
1781 checkNamed(entered && queued && frozen && synced && writerJoined, Test,
1782 "writer did not wait for the immutable DMA lease") &&
1783 checkNamed(first && second, Test,
"writeback lost the old or new page contents") &&
1784 checkNamed(reclaimed, Test,
"test page retained a reference");
1786 NOTICE(
"HOSTED-WAIT-TEST: PASS " << Test);
1792bool runHostedCacheDiscardRegressions() {
1793 return preparedDiscardPublication(
false) && preparedDiscardPublication(
true) &&
1794 rejectedLastWritebackPin();
1797bool runHostedCacheSyncRegressions() {
1798 return syncAllJoinsCallback() && syncAllJoinsRetirement(
true) && syncAllJoinsRetirement(
false) &&
1799 syncAllFromCacheManager();
1802bool runHostedCacheDirectWritebackRegressions() {
1803 return directWritebackLoan();
1806bool runHostedCacheTimerRegressions() {
1807 return cacheManagerTrimWakeGating() && timerWritebackCoalescing(
false,
false) &&
1808 timerWritebackCoalescing(
true,
false) && timerWritebackCoalescing(
false,
true);
1811bool runHostedCacheRegressions() {
1812 return callbackLifetime() && queuedRequestLifetime() && emptyAndReuse() &&
1813 retirementPublication() && failedPublicationDiscard() && retirePrepublicationWriteback() &&
1814 runHostedCacheDiscardRegressions() && retireWritebackContract() && rangeExistence() &&
1815 strictRangeGeometry() && runHostedCacheSyncRegressions() &&
1816 runHostedCacheTimerRegressions() && explicitWritebackThreading(
false) &&
1817 explicitWritebackThreading(
true) && directWritebackLoan();
void setCallback(writeback_t newCallback, void *meta)
uintptr_t insert(uintptr_t key, bool *alreadyExisted=nullptr)
void markDirty(uintptr_t key)
bool sync(uintptr_t key, bool async)
void markNoLongerEditing(uintptr_t key, size_t length=0)
static const size_t below4GB
LifecycleState getLifecycleState()
MUST_USE_RESULT bool halt()
MUST_USE_RESULT bool resume()
static Scheduler & instance()
static constexpr size_t getPageSize() noexcept
bool getWaitDebugInfo(WaitDebugInfo &info)
DebugState getDebugState(uintptr_t &address)
virtual bool isMapped(void *virtualAddress)=0
static EXPORTED_PUBLIC VirtualAddressSpace & getKernelAddressSpace()