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/VirtualAddressSpace.h"
15#include "pedigree/kernel/time/Time.h"
16#include "pedigree/kernel/utilities/Cache.h"
21 auto guard = manager.m_TrimWaiters.acquire();
26 manager.stopPeriodicWork();
29 static void tick(
CacheManager& manager, uint64_t delta,
bool pressure) {
30 manager.timerTick(delta, pressure);
33 static bool state(
CacheManager& manager,
bool requested, uint64_t delta) {
34 auto guard = manager.m_TrimWaiters.acquire();
35 return manager.m_bTrimRequested == requested && manager.m_TrimDelta == delta;
42bool checkNamed(
bool condition,
const char* test,
const char* detail) {
47 ERROR(
"HOSTED-WAIT-TEST: FAIL " << test <<
": " << detail);
51bool check(
bool condition,
const char* detail) {
52 return checkNamed(condition,
"cache-callback-lifetime", detail);
55bool waitUntilQueued(
Thread* thread,
size_t debugState) {
56 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
57 while (Time::getTicks() < deadline) {
59 uintptr_t debugAddress = 0;
69bool waitUntilQueuedAt(
Thread* thread,
size_t debugState, uintptr_t debugAddress) {
70 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
71 while (Time::getTicks() < deadline) {
73 uintptr_t address = 0;
75 thread->
getDebugState(address) == debugState && address == debugAddress) {
83struct CacheTrimWakeContext {
89CacheTrimWakeContext* g_CacheTrimWakeContext =
nullptr;
92 CacheTrimWakeContext* context = g_CacheTrimWakeContext;
93 if (!context || channel.owner != &context->manager)
96 constexpr const char* Test =
"cache-manager-trim-wake-gating";
97 constexpr uint64_t Tick = 1000000ULL;
98 constexpr uint64_t Period = CACHE_WRITEBACK_PERIOD * Tick;
100 const size_t block = ++context->blocks;
101 const uint64_t expectedDelta = block == 2 ? Period - Tick : 0;
102 context->passed &= checkNamed(CacheManagerTestPeer::state(manager,
false, expectedDelta), Test,
103 "worker lost elapsed time or did not consume pending work");
107 for (
size_t tick = 1; tick < CACHE_WRITEBACK_PERIOD; ++tick)
108 CacheManagerTestPeer::tick(manager, Tick,
false);
109 context->passed &= checkNamed(CacheManagerTestPeer::state(manager,
false, Period - Tick) &&
111 info.reason == WaitQueue::WakeReason::Waiting,
112 Test,
"healthy subperiod ticks woke the worker");
114 CacheManagerTestPeer::tick(manager, 0,
true);
115 CacheManagerTestPeer::tick(manager, 0,
false);
116 context->passed &= checkNamed(
117 CacheManagerTestPeer::state(manager,
true, Period - Tick) &&
118 thread->
getWaitDebugInfo(info) && info.reason == WaitQueue::WakeReason::Signalled,
119 Test,
"pressure did not wake the worker or a later tick erased the request");
120 }
else if (block == 2) {
121 CacheManagerTestPeer::tick(manager, Tick,
false);
122 context->passed &= checkNamed(
123 CacheManagerTestPeer::state(manager,
true, Period) && thread->
getWaitDebugInfo(info) &&
124 info.reason == WaitQueue::WakeReason::Signalled,
125 Test,
"writeback did not wake at the accumulated period boundary");
127 context->passed &= checkNamed(block == 3, Test,
"worker woke more often than requested");
128 CacheManagerTestPeer::stop(manager);
129 context->passed &= checkNamed(
130 thread->
getWaitDebugInfo(info) && info.reason == WaitQueue::WakeReason::Signalled, Test,
131 "stopping periodic work did not wake the idle worker");
135 if (!context->passed)
136 CacheManagerTestPeer::stop(manager);
139bool cacheManagerTrimWakeGating() {
140 constexpr const char* Test =
"cache-manager-trim-wake-gating";
141 constexpr uint64_t Period = CACHE_WRITEBACK_PERIOD * 1000000ULL;
142 CacheTrimWakeContext context;
143 CacheManagerTestPeer::tick(context.manager, Period,
false);
144 CacheManagerTestPeer::tick(context.manager, 0,
false);
145 context.passed = checkNamed(CacheManagerTestPeer::state(context.manager,
true, Period), Test,
146 "due work was lost while no worker was waiting");
147 CacheManagerTestPeer::start(context.manager);
148 g_CacheTrimWakeContext = &context;
149 WaitQueue::setBeforeBlockHook(cacheTrimBeforeBlock);
150 context.manager.trimThread();
151 WaitQueue::setBeforeBlockHook(
nullptr);
152 g_CacheTrimWakeContext =
nullptr;
154 context.passed &= checkNamed(context.blocks == 3, Test,
155 "worker did not complete pressure, writeback and stop transitions");
157 NOTICE(
"HOSTED-WAIT-TEST: PASS " << Test);
158 return context.passed;
161struct CacheLifetimeContext {
162 CacheLifetimeContext()
165 allowCallbackReturn(0),
180bool cacheCallback(CacheConstants::CallbackCause cause, uintptr_t loc, uintptr_t,
void* parameter) {
181 CacheLifetimeContext* context =
reinterpret_cast<CacheLifetimeContext*
>(parameter);
182 if (cause == CacheConstants::Eviction) {
183 context->evictionCalls += 1;
184 }
else if (cause == CacheConstants::WriteBack) {
185 uintptr_t page = context->cache->lookup(loc);
187 context->reentrantPins += 1;
188 context->cache->release(loc);
191 const size_t call = (context->callbackCalls += 1);
193 context->callbackEntered.release();
194 const bool released = context->allowCallbackReturn.acquireForCompletion();
200int deleteCache(
void* parameter) {
201 CacheLifetimeContext* context =
reinterpret_cast<CacheLifetimeContext*
>(parameter);
202 delete context->cache;
203 context->deleteReturned += 1;
207bool callbackLifetime() {
208 CacheLifetimeContext context;
209 context.cache =
new Cache;
210 context.cache->setCallback(cacheCallback, &context);
212 constexpr uintptr_t Key = 0xCA7E000;
213 const uintptr_t page = context.cache->insert(Key);
214 if (!check(page != 0,
"could not create the test cache page")) {
215 delete context.cache;
219 context.cache->markNoLongerEditing(Key);
220 context.cache->triggerChecksum(Key);
221 reinterpret_cast<uint8_t*
>(page)[0] ^= 0xA5;
222 context.cache->sync(Key,
true);
224 if (!check(context.callbackEntered.acquire(1, 2),
225 "the queued writeback callback did not start")) {
227 context.allowCallbackReturn.release();
228 delete context.cache;
233 nullptr,
false,
true);
234 deleter->setName(
"hosted Cache callback-drain deleter");
236 const bool drainPublished = waitUntilQueued(deleter, Thread::CallbackDrain);
237 const bool callbackPinnedObject = context.deleteReturned == 0;
239 context.allowCallbackReturn.release();
240 const bool joined = deleter->
join();
243 check(drainPublished,
"destruction did not publish its callback-drain wait") &&
244 check(callbackPinnedObject,
"Cache destruction returned while its callback was active") &&
245 check(joined,
"the Cache deleter did not become reapable") &&
246 check(context.deleteReturned == 1,
"Cache destruction did not complete exactly once") &&
247 check(context.callbackCalls == 2,
248 "writeback and eviction callbacks did not execute exactly once") &&
249 check(context.reentrantPins == 1,
"dirty writeback could not safely re-enter the Cache") &&
250 check(context.evictionCalls == 1,
"Cache destruction did not reclaim the inserted page");
253 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-callback-lifetime");
258struct QueuedLifetimeContext {
259 QueuedLifetimeContext()
262 allowBlockerReturn(0),
275bool blockerCallback(CacheConstants::CallbackCause, uintptr_t, uintptr_t,
void* parameter) {
276 QueuedLifetimeContext* context =
reinterpret_cast<QueuedLifetimeContext*
>(parameter);
277 if ((context->blockerCalls += 1) == 1) {
278 context->blockerEntered.release();
279 const bool released = context->allowBlockerReturn.acquireForCompletion();
285bool queuedTargetCallback(CacheConstants::CallbackCause, uintptr_t, uintptr_t,
void* parameter) {
286 QueuedLifetimeContext* context =
reinterpret_cast<QueuedLifetimeContext*
>(parameter);
287 context->targetCalls += 1;
291int deleteQueuedCache(
void* parameter) {
292 QueuedLifetimeContext* context =
reinterpret_cast<QueuedLifetimeContext*
>(parameter);
293 delete context->target;
294 context->deleteReturned += 1;
298bool queuedRequestLifetime() {
299 QueuedLifetimeContext context;
303 constexpr uintptr_t BlockerKey = 0xCA7E010;
304 constexpr uintptr_t TargetKey = 0xCA7E020;
305 if (!checkNamed(blocker.
insert(BlockerKey) != 0,
"cache-queued-lifetime",
306 "could not create the worker-blocking page")) {
310 blocker.
sync(BlockerKey,
true);
311 if (!checkNamed(context.blockerEntered.acquire(1, 2),
"cache-queued-lifetime",
312 "the blocking Cache callback did not start")) {
313 context.allowBlockerReturn.release();
317 context.target =
new Cache;
318 context.target->setCallback(queuedTargetCallback, &context);
319 if (!checkNamed(context.target->insert(TargetKey) != 0,
"cache-queued-lifetime",
320 "could not create the queued target page")) {
321 context.allowBlockerReturn.release();
322 delete context.target;
325 context.target->markNoLongerEditing(TargetKey);
326 context.target->sync(TargetKey,
true);
329 &context,
nullptr,
false,
true);
330 deleter->setName(
"hosted queued Cache lease deleter");
332 const bool queuedLeasePublished = waitUntilQueued(deleter, Thread::CallbackDrain);
333 const bool queuedRequestPinnedObject = context.deleteReturned == 0;
335 context.allowBlockerReturn.release();
336 const bool joined = deleter->
join();
338 const bool passed = checkNamed(queuedLeasePublished,
"cache-queued-lifetime",
339 "destruction did not wait for a queued request lease") &&
340 checkNamed(queuedRequestPinnedObject,
"cache-queued-lifetime",
341 "a queued request did not pin its Cache") &&
342 checkNamed(joined && context.deleteReturned == 1,
"cache-queued-lifetime",
343 "queued Cache destruction did not complete") &&
344 checkNamed(context.targetCalls == 2,
"cache-queued-lifetime",
345 "the queued writeback and final eviction did not both execute");
348 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-queued-lifetime");
353bool emptyAndReuse() {
355 constexpr uintptr_t FirstKey = 0xCA7E100;
356 constexpr uintptr_t SecondKey = 0xCA7E200;
358 const uintptr_t firstPage = cache.insert(FirstKey);
359 if (!checkNamed(firstPage != 0,
"cache-empty-reuse",
360 "could not create the first no-callback page")) {
364 cache.markNoLongerEditing(FirstKey);
365 cache.triggerChecksum(FirstKey);
366 reinterpret_cast<uint8_t*
>(firstPage)[0] ^= 0x5A;
369 const uintptr_t secondPage = cache.insert(SecondKey);
370 const bool reused = checkNamed(secondPage != 0,
"cache-empty-reuse",
371 "could not insert after emptying a dirty Cache") &&
372 checkNamed(cache.exists(SecondKey, PageSize),
"cache-empty-reuse",
373 "the replacement page was not published");
377 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-empty-reuse");
382struct RetirementContext {
386 allowEvictionReturn(0),
390 replacementPage(0) {}
398 uintptr_t replacementPage;
401bool retirementCallback(CacheConstants::CallbackCause cause, uintptr_t, uintptr_t,
403 RetirementContext* context =
reinterpret_cast<RetirementContext*
>(parameter);
404 if (cause == CacheConstants::Eviction && (context->evictionCalls += 1) == 1) {
405 context->evictionEntered.release();
406 const bool released = context->allowEvictionReturn.acquireForCompletion();
412int evictRetirementPage(
void* parameter) {
413 RetirementContext* context =
reinterpret_cast<RetirementContext*
>(parameter);
414 context->evictReturned += context->cache->evict(0xCA7E300) ? 1 : 2;
418int insertRetirementReplacement(
void* parameter) {
419 RetirementContext* context =
reinterpret_cast<RetirementContext*
>(parameter);
420 context->replacementPage = context->cache->insert(0xCA7E300);
421 context->insertReturned += 1;
425bool retirementPublication() {
426 RetirementContext context;
428 context.cache = &cache;
429 cache.setCallback(retirementCallback, &context);
431 constexpr uintptr_t Key = 0xCA7E300;
432 const uintptr_t originalPage = cache.insert(Key);
433 if (!checkNamed(originalPage != 0,
"cache-retirement-publication",
434 "could not create the original page")) {
437 cache.markNoLongerEditing(Key);
440 &context,
nullptr,
false,
true);
441 evictor->setName(
"hosted Cache retirement evictor");
443 const bool callbackEntered = context.evictionEntered.acquire(1, 2);
444 if (!callbackEntered) {
445 context.allowEvictionReturn.release();
448 return checkNamed(
false,
"cache-retirement-publication",
449 "the eviction callback did not publish retirement");
452 const bool retiringPinRejected = !cache.pin(Key);
453 const bool retiringLookupRejected = cache.lookup(Key) == 0;
456 insertRetirementReplacement, &context,
nullptr,
false,
true);
457 inserter->setName(
"hosted Cache same-key replacement");
459 const bool insertWaitPublished = waitUntilQueued(inserter, Thread::CallbackDrain);
460 const bool replacementBlocked = context.insertReturned == 0;
462 context.allowEvictionReturn.release();
463 const bool evictorJoined = evictor->
join();
464 const bool inserterJoined = inserter->
join();
467 checkNamed(retiringPinRejected && retiringLookupRejected,
"cache-retirement-publication",
468 "a retiring page remained available to a new consumer") &&
469 checkNamed(insertWaitPublished,
"cache-retirement-publication",
470 "same-key insertion did not wait for retirement publication") &&
471 checkNamed(replacementBlocked,
"cache-retirement-publication",
472 "same-key insertion returned the retiring page") &&
473 checkNamed(evictorJoined && context.evictReturned == 1,
"cache-retirement-publication",
474 "the original eviction did not complete successfully") &&
475 checkNamed(inserterJoined && context.insertReturned == 1 && context.replacementPage != 0,
476 "cache-retirement-publication",
"the replacement insertion did not complete") &&
477 checkNamed(cache.exists(Key, PageSize),
"cache-retirement-publication",
478 "the replacement page was invalidated by the old callback");
482 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-retirement-publication");
487struct DiscardEditingContext {
488 DiscardEditingContext() : writebacks(0), evictions(0) {}
494bool discardEditingCallback(CacheConstants::CallbackCause cause, uintptr_t, uintptr_t,
496 DiscardEditingContext* context =
reinterpret_cast<DiscardEditingContext*
>(parameter);
497 if (cause == CacheConstants::WriteBack) {
498 context->writebacks += 1;
499 }
else if (cause == CacheConstants::Eviction) {
500 context->evictions += 1;
505bool failedPublicationDiscard() {
506 constexpr uintptr_t Key = 0xCA7E400;
508 DiscardEditingContext context;
509 cache.setCallback(discardEditingCallback, &context);
511 const uintptr_t failedPage = cache.insert(Key);
512 const bool discarded = failedPage != 0 && cache.discardEditing(Key);
513 const bool removed = !cache.exists(Key, PageSize);
514 const bool suppressedWriteback = context.writebacks == 0 && context.evictions == 1;
516 const uintptr_t pinnedPage = cache.insert(Key);
517 const bool pinned = pinnedPage != 0 && cache.pin(Key);
518 const bool rejectedPinned = pinned && !cache.discardEditing(Key);
522 const bool discardedAfterRelease = rejectedPinned && cache.discardEditing(Key);
524 const uintptr_t publishedPage = cache.insert(Key);
526 cache.markNoLongerEditing(Key);
528 const bool rejectedPublished = publishedPage != 0 && !cache.discardEditing(Key);
532 checkNamed(discarded && removed,
"cache-failed-publication-discard",
533 "an unpinned Editing page was not synchronously removed") &&
534 checkNamed(suppressedWriteback,
"cache-failed-publication-discard",
535 "discarding failed data invoked backing-store writeback") &&
536 checkNamed(discardedAfterRelease,
"cache-failed-publication-discard",
537 "discard removed a pinned page or could not remove it after release") &&
538 checkNamed(rejectedPublished,
"cache-failed-publication-discard",
539 "discard removed a page after successful publication");
542 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-failed-publication-discard");
547struct RetirePublicationContext {
548 RetirePublicationContext()
555 allowCallbackReturn(0),
558 queuedCallbackFinished(0),
561 retireSawQueuedCompletion(0),
562 retireArgumentsValid(0),
565 retireSucceeded(0) {}
586void retireAdmissionHook(
Cache* cache, uintptr_t key,
void* parameter) {
587 RetirePublicationContext* context =
reinterpret_cast<RetirePublicationContext*
>(parameter);
588 if (cache == context->cache && key == context->key && (context->admissionCalls += 1) == 1) {
589 context->admissionEntered.release();
590 const bool released = context->allowPublication.acquireForCompletion();
595bool retireQueuedCallback(CacheConstants::CallbackCause cause, uintptr_t, uintptr_t,
597 RetirePublicationContext* context =
reinterpret_cast<RetirePublicationContext*
>(parameter);
598 if (cause == CacheConstants::Eviction) {
599 context->evictionCalls += 1;
600 }
else if (cause == CacheConstants::WriteBack && (context->queuedCallbacks += 1) == 1) {
601 context->callbackEntered.release();
602 const bool released = context->allowCallbackReturn.acquireForCompletion();
604 context->queuedCallbackFinished = 1;
609bool retireSynchronousCallback(uintptr_t key, uintptr_t page,
void* parameter) {
610 RetirePublicationContext* context =
reinterpret_cast<RetirePublicationContext*
>(parameter);
611 context->retireCallbacks += 1;
612 context->retireSawQueuedCompletion = context->queuedCallbackFinished;
613 context->retireArgumentsValid = key == context->key && page == context->page;
617int publishRetireWriteback(
void* parameter) {
618 RetirePublicationContext* context =
reinterpret_cast<RetirePublicationContext*
>(parameter);
619 context->cache->sync(context->key,
true);
620 context->syncReturned += 1;
624int retirePublishedWriteback(
void* parameter) {
625 RetirePublicationContext* context =
reinterpret_cast<RetirePublicationContext*
>(parameter);
626 if (context->cache->retireWriteback(context->key, retireSynchronousCallback, context)) {
627 context->retireSucceeded += 1;
629 context->retireReturned += 1;
633bool retirePrepublicationWriteback() {
634 constexpr uintptr_t Key = 0xCA7E500;
635 RetirePublicationContext context;
637 context.cache = &cache;
639 cache.setCallback(retireQueuedCallback, &context);
641 context.page = cache.insert(Key);
642 if (!checkNamed(context.page != 0,
"cache-retire-prepublication",
643 "could not create the test page")) {
646 cache.markNoLongerEditing(Key);
648 cache.setWritebackAdmissionHookForTest(retireAdmissionHook, &context);
651 &context,
nullptr,
false,
true);
652 producer->setName(
"hosted Cache paused writeback producer");
653 const bool admissionPaused = context.admissionEntered.acquire(1, 2);
654 if (!admissionPaused) {
655 context.allowPublication.release();
656 context.allowCallbackReturn.release();
658 cache.setWritebackAdmissionHookForTest(
nullptr,
nullptr);
661 return checkNamed(
false,
"cache-retire-prepublication",
662 "sync did not pause after publishing its page reference");
666 &context,
nullptr,
false,
true);
667 retirer->setName(
"hosted Cache writeback retirer");
668 const bool drainPublished = waitUntilQueuedAt(retirer, Thread::CallbackDrain, Key);
669 const bool blockedBeforePublication = context.retireReturned == 0;
670 if (drainPublished) {
671 cache.sync(Key,
true);
673 const bool drainingSyncRejected = context.admissionCalls == 1;
675 context.allowPublication.release();
676 const bool queuedCallbackEntered = context.callbackEntered.acquire(1, 2);
677 const bool blockedThroughCallback = context.retireReturned == 0;
678 context.allowCallbackReturn.release();
680 const bool producerJoined = producer->
join();
681 const bool retirerJoined = retirer->
join();
682 cache.setWritebackAdmissionHookForTest(
nullptr,
nullptr);
686 checkNamed(drainPublished,
"cache-retire-prepublication",
687 "retirement did not publish its exact per-key CallbackDrain wait") &&
688 checkNamed(blockedBeforePublication,
"cache-retire-prepublication",
689 "retirement returned before a visible writeback was queued") &&
690 checkNamed(drainingSyncRejected,
"cache-retire-prepublication",
691 "sync admitted another writeback while retirement was draining") &&
692 checkNamed(queuedCallbackEntered && blockedThroughCallback,
"cache-retire-prepublication",
693 "retirement did not wait for the queued callback to finish") &&
694 checkNamed(producerJoined && retirerJoined,
"cache-retire-prepublication",
695 "writeback producer or retirer did not become reapable") &&
697 context.syncReturned == 1 && context.retireReturned == 1 && context.retireSucceeded == 1,
698 "cache-retire-prepublication",
"retirement did not complete exactly once") &&
699 checkNamed(context.queuedCallbacks == 1 && context.queuedCallbackFinished == 1 &&
700 context.retireCallbacks == 1 && context.retireSawQueuedCompletion == 1,
701 "cache-retire-prepublication",
702 "the synchronous retirement callback overtook queued writeback") &&
703 checkNamed(context.retireArgumentsValid == 1 && context.evictionCalls == 1,
704 "cache-retire-prepublication",
705 "retirement callback arguments or final eviction were incorrect") &&
706 checkNamed(!cache.exists(Key, PageSize) && cache.lookup(Key) == 0,
707 "cache-retire-prepublication",
"the successful page remained published");
709 if (cache.exists(Key, PageSize)) {
713 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-retire-prepublication");
718struct DiscardPublicationContext {
719 explicit DiscardPublicationContext(
bool cancelPlan)
720 : publication(), prepared(0), allowCompletion(0), completed(0), cancel(cancelPlan) {}
722 RetirePublicationContext publication;
729int preparePublishedDiscard(
void* parameter) {
730 auto* context =
reinterpret_cast<DiscardPublicationContext*
>(parameter);
731 auto& publication = context->publication;
734 const auto status = publication.cache->prepareDiscardFrom(publication.key, &reference, 1, plan);
735 if (status == Cache::DiscardStatus::Ready && plan) {
736 publication.retireSucceeded += 1;
738 publication.retireReturned += 1;
739 context->prepared.release();
740 const bool released = context->allowCompletion.acquireForCompletion();
742 if (plan && !context->cancel) {
743 plan.get()->commit();
747 context->completed += 1;
751bool discardPrefixUsable(
Cache& cache, uintptr_t key, uintptr_t expected) {
752 const uintptr_t lookup = cache.lookup(key);
753 const bool pinned = cache.pin(key);
754 bool unchanged = lookup && lookup == expected && pinned;
756 const auto* bytes =
reinterpret_cast<const uint8_t*
>(lookup);
757 for (
size_t n = 0; n < PageSize; ++n) {
758 unchanged = unchanged && bytes[n] ==
static_cast<uint8_t
>(n ^ 0x6d);
768bool preparedDiscardPublication(
bool cancel) {
769 constexpr uintptr_t PrefixKey = 0xCB000000;
770 constexpr uintptr_t Key = PrefixKey + PageSize;
771 const char* test = cancel ?
"cache-discard-cancel" :
"cache-discard-prepublication";
772 DiscardPublicationContext context(cancel);
773 auto& publication = context.publication;
775 publication.cache = &cache;
776 publication.key = Key;
777 cache.setCallback(retireQueuedCallback, &publication);
779 const uintptr_t prefix = cache.insert(PrefixKey);
780 publication.page = cache.insert(Key);
781 bool ownerPinned = publication.page && cache.pin(Key);
782 if (!prefix || !ownerPinned) {
786 publication.allowCallbackReturn.release();
789 return checkNamed(
false, test,
"could not create and pin the test cache pages");
791 for (
size_t n = 0; n < PageSize; ++n) {
792 reinterpret_cast<uint8_t*
>(prefix)[n] =
static_cast<uint8_t
>(n ^ 0x6d);
793 reinterpret_cast<uint8_t*
>(publication.page)[n] =
static_cast<uint8_t
>(n ^ 0xb2);
795 cache.markNoLongerEditing(PrefixKey);
796 cache.markNoLongerEditing(Key);
797 cache.setWritebackAdmissionHookForTest(retireAdmissionHook, &publication);
799 &publication,
nullptr,
false,
true);
800 producer->setName(
"hosted Cache discard writeback producer");
801 if (!publication.admissionEntered.acquire(1, 2)) {
802 publication.allowPublication.release();
803 publication.allowCallbackReturn.release();
805 cache.setWritebackAdmissionHookForTest(
nullptr,
nullptr);
809 return checkNamed(
false, test,
"writeback did not pause after publishing its page pin");
813 &context,
nullptr,
false,
true);
814 preparer->setName(
"hosted Cache prepared discard");
815 const bool drainPublished = waitUntilQueuedAt(preparer, Thread::CallbackDrain, Key);
816 const bool blockedBeforePublication = publication.retireReturned == 0;
817 const uintptr_t unexpectedLookup = drainPublished ? cache.lookup(Key) : 0;
818 const bool unexpectedPin = drainPublished && cache.pin(Key);
819 const bool syncRejected = drainPublished && !cache.sync(Key,
true);
820 const bool admissionRejected =
821 syncRejected && !unexpectedLookup && !unexpectedPin && publication.admissionCalls == 1;
822 if (unexpectedLookup) {
828 const bool prefixDuringDrain = discardPrefixUsable(cache, PrefixKey, prefix);
829 publication.allowPublication.release();
830 const bool callbackEntered = publication.callbackEntered.acquire(1, 2);
831 const bool blockedThroughCallback = callbackEntered &&
832 waitUntilQueuedAt(preparer, Thread::CallbackDrain, Key) &&
833 publication.retireReturned == 0;
834 publication.allowCallbackReturn.release();
835 const bool readyWithOwnerPin = context.prepared.acquire(1, 2) &&
836 publication.retireReturned == 1 &&
837 publication.retireSucceeded == 1;
838 if (readyWithOwnerPin) {
839 reinterpret_cast<uint8_t*
>(publication.page)[0] = 0xa7;
840 cache.markDirty(Key);
842 if (!cancel || !readyWithOwnerPin) {
846 context.allowCompletion.release();
847 const bool producerJoined = producer->
join();
848 const bool preparerJoined = preparer->
join();
849 cache.setWritebackAdmissionHookForTest(
nullptr,
nullptr);
851 bool completedCorrectly =
false;
852 if (cancel && readyWithOwnerPin) {
853 const uintptr_t restored = cache.lookup(Key);
854 const bool pinRestored = cache.pin(Key);
855 completedCorrectly = restored == publication.page && pinRestored &&
856 reinterpret_cast<const uint8_t*
>(publication.page)[0] == 0xa7 &&
857 publication.queuedCallbacks == 1 && publication.evictionCalls == 0;
866 const bool evicted = cache.evict(Key);
867 completedCorrectly = completedCorrectly && evicted && publication.queuedCallbacks == 2 &&
868 publication.evictionCalls == 1;
869 }
else if (!cancel) {
870 completedCorrectly = publication.queuedCallbacks == 1 && publication.evictionCalls == 1;
872 const bool suffixRemoved = !cache.exists(Key, PageSize);
873 const bool prefixAfterCompletion = discardPrefixUsable(cache, PrefixKey, prefix);
875 checkNamed(drainPublished && blockedBeforePublication, test,
876 "prepare did not wait at its exact CallbackDrain key before queue publication") &&
877 checkNamed(admissionRejected && prefixDuringDrain, test,
878 "draining suffix accepted a new consumer or blocked the retained prefix") &&
879 checkNamed(blockedThroughCallback, test,
"prepare returned before queued writeback ended") &&
880 checkNamed(readyWithOwnerPin, test,
"prepare did not become Ready with its owner pin held") &&
881 checkNamed(producerJoined && preparerJoined && context.completed == 1 &&
882 publication.syncReturned == 1 && publication.queuedCallbackFinished == 1,
883 test,
"producer or prepared-discard worker failed to complete exactly once") &&
884 checkNamed(completedCorrectly && suffixRemoved, test,
885 cancel ?
"rollback lost admission or dirty data needed by ordinary eviction"
886 :
"discard wrote dirty bytes back or failed to evict exactly once") &&
887 checkNamed(prefixAfterCompletion, test,
"completion changed or removed the retained prefix");
894 NOTICE(
"HOSTED-WAIT-TEST: PASS " << test);
899struct RejectedWritebackContext {
900 RejectedWritebackContext()
905 shutdownSucceeded(0) {}
907 RetirePublicationContext publication;
914int publishRejectedWriteback(
void* parameter) {
915 auto* context =
reinterpret_cast<RejectedWritebackContext*
>(parameter);
916 auto& publication = context->publication;
917 context->accepted = publication.cache->sync(publication.key,
true) ? 1 : 0;
918 publication.syncReturned += 1;
919 context->producerFinished.release();
923int shutdownRejectedWriteback(
void* parameter) {
924 auto* context =
reinterpret_cast<RejectedWritebackContext*
>(parameter);
925 context->shutdownSucceeded = context->publication.cache->shutdown() ? 1 : 0;
926 context->shutdownFinished.release();
930bool rejectedLastWritebackPin() {
931 constexpr uintptr_t Key = 0xCC000000;
932 constexpr const char* Test =
"cache-rejected-last-writeback";
933 RejectedWritebackContext context;
934 auto& publication = context.publication;
937 publication.cache = &cache;
938 publication.key = Key;
939 cache.setCallback(retireQueuedCallback, &publication);
942 publication.page = cache.insert(Key);
943 if (!checkNamed(publication.page != 0, Test,
"could not create the test page")) {
944 publication.allowCallbackReturn.release();
947 reinterpret_cast<uint8_t*
>(publication.page)[0] = 0xa7;
948 cache.markNoLongerEditing(Key);
949 cache.setWritebackAdmissionHookForTest(retireAdmissionHook, &publication);
951 &context,
nullptr,
false,
true);
952 producer->setName(
"hosted Cache rejected writeback producer");
953 const bool admissionPaused = publication.admissionEntered.acquire(1, 2);
954 if (admissionPaused) {
957 const bool halted = admissionPaused && manager.
halt();
960 publication.allowCallbackReturn.release();
961 publication.allowPublication.release();
962 const bool producerFinished = context.producerFinished.acquire(1, 2);
963 if (!producerFinished) {
964 const bool resumed = manager.
resume();
966 checkNamed(
false, Test,
"rejected writeback producer did not finish");
967 FATAL(
"Cache rejection fixture retained a live producer");
969 const bool producerJoined = producer->
join();
970 const bool rejected = context.accepted == 0 && publication.syncReturned == 1 &&
971 publication.queuedCallbacks == 0 && publication.evictionCalls == 0;
972 cache.setWritebackAdmissionHookForTest(
nullptr,
nullptr);
973 const bool resumed = manager.
resume();
974 if (!resumed && (!manager.
halt() || !manager.
resume())) {
975 checkNamed(
false, Test,
"could not restore the CacheManager worker");
976 FATAL(
"Cache rejection fixture could not resume CacheManager");
980 &context,
nullptr,
false,
true);
981 shutdown->setName(
"hosted Cache rejected-writeback shutdown");
982 if (!context.shutdownFinished.acquire(1, 2)) {
983 checkNamed(
false, Test,
"shutdown did not drain the rejected request's cache lease");
984 FATAL(
"Cache rejection fixture retained a live shutdown worker");
986 const bool shutdownJoined = shutdown->
join();
988 reinterpret_cast<void*
>(publication.page));
990 checkNamed(admissionPaused && stopped, Test,
991 "writeback was not paused with its last pin before manager halt") &&
992 checkNamed(producerFinished && producerJoined && rejected, Test,
993 "the stopped queue executed or retained the rejected writeback") &&
994 checkNamed(resumed, Test,
"the CacheManager worker did not resume") &&
995 checkNamed(shutdownJoined && context.shutdownSucceeded == 1 &&
996 publication.queuedCallbacks == 1 && publication.evictionCalls == 1 && unmapped,
997 Test,
"terminal shutdown did not write back and remove the abandoned page");
999 NOTICE(
"HOSTED-WAIT-TEST: PASS " << Test);
1004struct RetireContractContext {
1005 RetireContractContext()
1013 retireSucceeded(0) {}
1025bool retireContractCallback(uintptr_t key, uintptr_t page,
void* parameter) {
1026 RetireContractContext* context =
reinterpret_cast<RetireContractContext*
>(parameter);
1027 context->callbacks += 1;
1028 context->argumentsValid = key == context->key && page == context->page;
1029 return static_cast<size_t>(context->shouldSucceed) != 0;
1032int retirePinnedWriteback(
void* parameter) {
1033 RetireContractContext* context =
reinterpret_cast<RetireContractContext*
>(parameter);
1034 if (context->cache->retireWriteback(context->key, retireContractCallback, context)) {
1035 context->retireSucceeded += 1;
1037 context->retireReturned += 1;
1041bool retireWritebackContract() {
1042 constexpr uintptr_t EditingKey = 0xCA7E600;
1043 constexpr uintptr_t RetryKey = 0xCA7E700;
1044 constexpr uintptr_t PinnedKey = 0xCA7E800;
1045 constexpr uintptr_t MissingKey = 0xCA7E900;
1048 RetireContractContext editing;
1049 editing.cache = &cache;
1050 editing.key = EditingKey;
1051 editing.page = cache.insert(EditingKey);
1052 editing.shouldSucceed = 1;
1053 const bool editingRejected =
1054 editing.page && !cache.retireWriteback(EditingKey, retireContractCallback, &editing) &&
1055 editing.callbacks == 0 && cache.exists(EditingKey, PageSize);
1056 const bool editingDiscarded = editingRejected && cache.discardEditing(EditingKey);
1058 RetireContractContext retry;
1059 retry.cache = &cache;
1060 retry.key = RetryKey;
1061 retry.page = cache.insert(RetryKey);
1063 cache.markNoLongerEditing(RetryKey);
1065 const bool failureKeptPage = retry.page &&
1066 !cache.retireWriteback(RetryKey, retireContractCallback, &retry) &&
1067 retry.callbacks == 1 && cache.lookup(RetryKey) == retry.page;
1068 if (failureKeptPage) {
1069 cache.release(RetryKey);
1071 retry.shouldSucceed = 1;
1072 const bool retryRetired =
1073 failureKeptPage && cache.retireWriteback(RetryKey, retireContractCallback, &retry) &&
1074 retry.callbacks == 2 && retry.argumentsValid == 1 && !cache.exists(RetryKey, PageSize);
1076 RetireContractContext pinned;
1077 pinned.cache = &cache;
1078 pinned.key = PinnedKey;
1079 pinned.page = cache.insert(PinnedKey);
1081 cache.markNoLongerEditing(PinnedKey);
1083 pinned.shouldSucceed = 1;
1084 const bool pinnedReady = pinned.page && cache.pin(PinnedKey);
1085 Thread* retirer =
nullptr;
1088 nullptr,
false,
true);
1089 retirer->setName(
"hosted Cache pinned-page retirer");
1091 const bool pinDrainPublished =
1092 retirer && waitUntilQueuedAt(retirer, Thread::CallbackDrain, PinnedKey);
1093 const uintptr_t unexpectedLookup = pinDrainPublished ? cache.lookup(PinnedKey) : 0;
1094 const bool unexpectedPin = pinDrainPublished && cache.pin(PinnedKey);
1095 const bool newConsumersRejected =
1096 pinDrainPublished && pinned.retireReturned == 0 && !unexpectedLookup && !unexpectedPin;
1097 if (unexpectedLookup) {
1098 cache.release(PinnedKey);
1100 if (unexpectedPin) {
1101 cache.release(PinnedKey);
1104 cache.release(PinnedKey);
1106 const bool pinnedJoined = retirer && retirer->
join();
1107 const bool pinnedRetired = pinnedJoined && pinned.retireReturned == 1 &&
1108 pinned.retireSucceeded == 1 && pinned.callbacks == 1 &&
1109 pinned.argumentsValid == 1 && !cache.exists(PinnedKey, PageSize);
1111 RetireContractContext missing;
1112 missing.cache = &cache;
1113 missing.key = MissingKey;
1114 missing.shouldSucceed = 1;
1115 const bool missingSucceeded =
1116 cache.retireWriteback(MissingKey, retireContractCallback, &missing) && missing.callbacks == 0;
1119 checkNamed(editingDiscarded,
"cache-retire-contract",
1120 "retirement invoked writeback for an Editing page") &&
1121 checkNamed(retryRetired,
"cache-retire-contract",
1122 "failed writeback did not preserve a retryable page") &&
1123 checkNamed(pinDrainPublished && newConsumersRejected && pinnedRetired,
1124 "cache-retire-contract",
1125 "retirement did not drain the old pin while rejecting new consumers") &&
1126 checkNamed(missingSucceeded,
"cache-retire-contract",
1127 "retiring a missing page invoked the callback or failed");
1131 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-retire-contract");
1136struct SyncAllContext {
1143 blockWriteback(false),
1144 retirementSucceeds(false),
1145 nestedSucceeded(0) {}
1152 bool blockWriteback;
1153 bool retirementSucceeds;
1157bool syncAllCallback(CacheConstants::CallbackCause cause, uintptr_t, uintptr_t,
void* parameter) {
1158 auto* context =
static_cast<SyncAllContext*
>(parameter);
1159 if (cause != CacheConstants::WriteBack) {
1162 const size_t write = (context->writes += 1);
1163 if (context->blockWriteback && write == 1) {
1164 context->entered.release();
1165 const bool released = context->allowReturn.acquireForCompletion();
1168 if (context->lower) {
1169 const bool succeeded = context->lower->syncAll();
1170 context->nestedSucceeded = succeeded ? 1 : 0;
1176bool syncAllRetirementCallback(uintptr_t, uintptr_t,
void* parameter) {
1177 auto* context =
static_cast<SyncAllContext*
>(parameter);
1178 context->entered.release();
1179 const bool released = context->allowReturn.acquireForCompletion();
1181 return context->retirementSucceeds;
1185 enum Kind { All, QueuedPage, Retire };
1186 SyncAllCall(SyncAllContext& state, uintptr_t cacheKey, Kind operation)
1187 : context(state), key(cacheKey), kind(operation), done(0), result(0) {}
1188 SyncAllContext& context;
1195int syncAllWorker(
void* parameter) {
1196 auto* call =
static_cast<SyncAllCall*
>(parameter);
1197 bool succeeded =
false;
1198 if (call->kind == SyncAllCall::All) {
1199 succeeded = call->context.cache->syncAll();
1200 }
else if (call->kind == SyncAllCall::QueuedPage) {
1201 succeeded = call->context.cache->sync(call->key,
false);
1204 call->context.cache->retireWriteback(call->key, syncAllRetirementCallback, &call->context);
1206 call->result = succeeded ? 1 : 0;
1207 call->done.release();
1211bool syncAllJoinsCallback() {
1212 constexpr uintptr_t Key = 0xCA7EA00;
1213 SyncAllContext context;
1215 context.cache = &cache;
1216 context.blockWriteback =
true;
1217 cache.setCallback(syncAllCallback, &context);
1218 if (!cache.insert(Key)) {
1221 cache.markNoLongerEditing(Key);
1222 SyncAllCall first(context, Key, SyncAllCall::All);
1224 nullptr,
false,
true);
1225 const bool entered = context.entered.acquire(1, 2);
1227 context.allowReturn.release();
1229 return checkNamed(
false,
"cache-sync-all",
"direct callback did not start");
1232 SyncAllCall queued(context, Key, SyncAllCall::QueuedPage);
1234 nullptr,
false,
true);
1235 const bool queueCompleted = queued.done.acquire(1, 2);
1236 const bool queueRejected = queueCompleted && queued.result == 0;
1237 SyncAllCall second(context, Key, SyncAllCall::All);
1239 nullptr,
false,
true);
1240 const bool joinedCallback = waitUntilQueuedAt(joiner, Thread::CallbackDrain, Key);
1241 context.allowReturn.release();
1242 const bool writerJoined = writer->
join();
1243 const bool producerJoined = producer->
join();
1244 const bool joinerJoined = joiner->
join();
1246 checkNamed(queueRejected && producerJoined,
"cache-sync-all",
1247 "the CacheManager worker blocked behind a direct callback") &&
1248 checkNamed(joinedCallback && writerJoined && joinerJoined && first.result == 1 &&
1249 second.result == 1 && context.writes == 2,
1250 "cache-sync-all",
"synchronous drain did not join an active callback");
1252 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-sync-all-callback");
1257bool syncAllJoinsRetirement(
bool succeeds) {
1258 constexpr uintptr_t Key = 0xCA7EB00;
1259 SyncAllContext context;
1261 context.cache = &cache;
1262 context.retirementSucceeds = succeeds;
1263 cache.setCallback(syncAllCallback, &context);
1264 if (!cache.insert(Key)) {
1267 cache.markNoLongerEditing(Key);
1268 SyncAllCall retirement(context, Key, SyncAllCall::Retire);
1270 nullptr,
false,
true);
1271 const bool entered = context.entered.acquire(1, 2);
1273 context.allowReturn.release();
1275 return checkNamed(
false,
"cache-sync-all",
"retirement callback did not start");
1277 SyncAllCall sync(context, Key, SyncAllCall::All);
1279 nullptr,
false,
true);
1280 const bool joinedRetirement = waitUntilQueuedAt(joiner, Thread::CallbackDrain, Key);
1281 context.allowReturn.release();
1282 const bool retirerJoined = retirer->
join();
1283 const bool joinerJoined = joiner->
join();
1284 const bool passed = checkNamed(
1285 joinedRetirement && retirerJoined && joinerJoined && sync.result == 1 &&
1286 retirement.result == (succeeds ? 1U : 0U) && context.writes == (succeeds ? 0U : 1U) &&
1287 cache.exists(Key, PageSize) != succeeds,
1288 "cache-sync-all",
"drain did not join retirement or retry its retained failure");
1290 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-sync-all-retirement-" << (succeeds ?
"success" :
"retry"));
1295bool syncAllFromCacheManager() {
1296 constexpr uintptr_t Key = 0xCA7EC00;
1297 SyncAllContext lowerContext;
1299 lowerContext.cache = &lower;
1300 lower.
setCallback(syncAllCallback, &lowerContext);
1301 SyncAllContext upperContext;
1303 upperContext.cache = &upper;
1304 upperContext.lower = &lower;
1305 upper.
setCallback(syncAllCallback, &upperContext);
1311 const bool passed = checkNamed(
1312 upper.
sync(Key,
false) && upperContext.nestedSucceeded == 1 && lowerContext.writes == 1,
1313 "cache-sync-all",
"a CacheManager callback could not drain an independent lower cache");
1315 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-sync-all-nested");
1320bool rangeExistence() {
1321 constexpr uintptr_t Key = 0xCA7E500;
1322 constexpr size_t Length = 3 * PageSize;
1323 constexpr uintptr_t ProbeKey = Key + (8 * PageSize);
1324 constexpr uintptr_t SecondProbeKey = ProbeKey + (4 * PageSize);
1331 const uintptr_t allocatorExtent = cache.insert(ProbeKey, 2 * Length);
1332 const bool allocatorProbeReady = allocatorExtent != 0;
1335 const uintptr_t pages = cache.insert(Key, Length);
1336 const bool completeRange = pages != 0 && cache.exists(Key, Length);
1337 bool alreadyExisted =
false;
1338 const uintptr_t reused = cache.insert(Key, Length, &alreadyExisted);
1339 const bool completeRangeReused = alreadyExisted && reused == pages;
1340 const bool removedInterior = cache.discardEditing(Key + PageSize);
1341 const bool missingInteriorRejected = removedInterior && !cache.exists(Key, Length);
1344 const uintptr_t interior = cache.insert(Key + PageSize);
1345 bool overlapExisted =
true;
1346 const uintptr_t overlappingRange = cache.insert(Key, Length, &overlapExisted);
1347 const bool overlapRejectedBeforeAllocation =
1348 interior != 0 && !overlappingRange && !overlapExisted &&
1349 cache.exists(Key + PageSize, PageSize) && !cache.exists(Key, PageSize) &&
1350 !cache.exists(Key + (2 * PageSize), PageSize) && !cache.exists(Key, Length);
1353 const uintptr_t firstHalf = cache.insert(ProbeKey, Length);
1354 const uintptr_t secondHalf = cache.insert(SecondProbeKey, Length);
1355 const bool allocatorAndPageAccountingBalanced =
1356 allocatorProbeReady && firstHalf == allocatorExtent &&
1357 secondHalf == allocatorExtent + Length && cache.exists(ProbeKey, Length) &&
1358 cache.exists(SecondProbeKey, Length);
1361 const bool passed = checkNamed(completeRange && completeRangeReused,
"cache-range-existence",
1362 "a complete contiguous cache range was not detected or reused") &&
1363 checkNamed(missingInteriorRejected,
"cache-range-existence",
1364 "a range with a missing interior page was reported complete") &&
1365 checkNamed(overlapRejectedBeforeAllocation,
"cache-range-existence",
1366 "an interior overlap partially allocated or published a range") &&
1367 checkNamed(allocatorAndPageAccountingBalanced,
"cache-range-existence",
1368 "a rejected overlap leaked cache VA or page accounting");
1370 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-range-existence");
1375bool strictRangeGeometry() {
1376 constexpr uintptr_t InsertKey = 0xCA7F000;
1377 constexpr uintptr_t PublishKey = InsertKey + (4 * PageSize);
1380 bool alreadyExisted =
true;
1381 const uintptr_t invalid = cache.insert(InsertKey, PageSize + 1, &alreadyExisted);
1382 const bool insertionRejected = !invalid && !alreadyExisted && !cache.exists(InsertKey, PageSize);
1384 const uintptr_t editing = cache.insert(InsertKey);
1385 cache.markNoLongerEditing(InsertKey, PageSize + 1);
1386 const bool invalidPublishLeftEditing = editing && cache.discardEditing(InsertKey);
1388 const uintptr_t published = cache.insert(PublishKey);
1389 cache.markNoLongerEditing(PublishKey);
1390 cache.markEditing(PublishKey, PageSize + 1);
1391 const bool invalidEditLeftPublished = published && !cache.discardEditing(PublishKey);
1395 checkNamed(insertionRejected,
"cache-range-geometry",
1396 "a partial target-page insertion was truncated instead of rejected") &&
1397 checkNamed(invalidPublishLeftEditing,
"cache-range-geometry",
1398 "an invalid publish range changed the first cache page") &&
1399 checkNamed(invalidEditLeftPublished,
"cache-range-geometry",
1400 "an invalid edit range changed the first cache page");
1402 NOTICE(
"HOSTED-WAIT-TEST: PASS cache-range-geometry");
1407struct TimerWritebackContext {
1408 Cache* cache =
nullptr;
1416 uint8_t lastWritten = 0;
1417 bool failFirst =
false;
1420void timerWritebackAdmission(
Cache*, uintptr_t,
void* parameter) {
1421 auto& context = *
static_cast<TimerWritebackContext*
>(parameter);
1422 if ((context.admissions += 1) == 1) {
1423 context.cache->startAtomic();
1424 context.admissionEntered.release();
1425 const bool released = context.allowPublication.acquireForCompletion();
1430bool timerWritebackCallback(CacheConstants::CallbackCause cause, uintptr_t, uintptr_t page,
1432 if (cause != CacheConstants::WriteBack) {
1435 auto& context = *
static_cast<TimerWritebackContext*
>(parameter);
1436 const size_t call = (context.callbacks += 1);
1437 context.lastWritten = *
reinterpret_cast<uint8_t*
>(page);
1439 context.callbackEntered.release();
1440 const bool released = context.allowCallbackReturn.acquireForCompletion();
1443 return !(context.failFirst && call == 1);
1446void tickWriteback(
Cache& cache) {
1448 cache.timer(CACHE_WRITEBACK_PERIOD * 1000000ULL);
1449 cache.startAtomic();
1452int publishTimerWriteback(
void* parameter) {
1453 auto& context = *
static_cast<TimerWritebackContext*
>(parameter);
1454 tickWriteback(*context.cache);
1458bool timerWritebackCoalescing(
bool failFirst,
bool mutateDuringWriteback) {
1459 const char* test = failFirst ?
"cache-timer-pending-failure"
1460 : (mutateDuringWriteback ?
"cache-timer-pending-mutation"
1461 :
"cache-timer-pending-success");
1462 TimerWritebackContext context;
1463 context.failFirst = failFirst;
1464 context.key = 0xCA7F800;
1466 context.cache = &cache;
1467 cache.startAtomic();
1468 cache.setCallback(timerWritebackCallback, &context);
1469 const uintptr_t page = cache.insert(context.key);
1470 if (!checkNamed(page != 0, test,
"could not create the test page")) {
1473 *
reinterpret_cast<uint8_t*
>(page) = 0x57;
1474 cache.markNoLongerEditing(context.key);
1475 tickWriteback(cache);
1476 cache.markDirty(context.key);
1477 cache.setWritebackAdmissionHookForTest(timerWritebackAdmission, &context);
1483 fence.
setCallback([](CacheConstants::CallbackCause, uintptr_t, uintptr_t,
void*) {
return true; },
1485 const uintptr_t fencePage = fence.
insert(0);
1486 if (!checkNamed(fencePage != 0, test,
"could not create the worker fence")) {
1487 cache.setWritebackAdmissionHookForTest(
nullptr,
nullptr);
1488 context.allowCallbackReturn.release();
1494 &context,
nullptr,
false,
true);
1495 producer->setName(
"hosted Cache paused timer producer");
1496 const bool admissionPaused = context.admissionEntered.acquire(1, 2);
1497 if (admissionPaused) {
1498 for (
size_t i = 0; i < 3; ++i) {
1499 tickWriteback(cache);
1502 const bool onePendingAdmission = context.admissions == 1;
1503 context.allowPublication.release();
1504 const bool producerJoined = producer->
join();
1505 const bool callbackPaused = context.callbackEntered.acquire(1, 2);
1506 if (callbackPaused) {
1507 for (
size_t i = 0; i < 3; ++i) {
1508 tickWriteback(cache);
1510 if (mutateDuringWriteback) {
1511 *
reinterpret_cast<uint8_t*
>(page) = 0xA6;
1514 const bool oneActiveAdmission = context.admissions == 1;
1515 context.allowCallbackReturn.release();
1516 const bool firstDrained = fence.
sync(0,
false);
1517 const bool oneInitialCallback = context.callbacks == 1;
1521 bool retriesDrained =
true;
1522 for (
size_t i = 0; i < 3; ++i) {
1523 tickWriteback(cache);
1524 retriesDrained = fence.
sync(0,
false) && retriesDrained;
1526 const size_t expected = failFirst || mutateDuringWriteback ? 2 : 1;
1527 const bool expectedCallbacks = context.callbacks == expected && context.admissions == expected;
1528 const bool expectedBytes = context.lastWritten == (mutateDuringWriteback ? 0xA6 : 0x57);
1529 cache.setWritebackAdmissionHookForTest(
nullptr,
nullptr);
1530 const bool reclaimed = cache.empty();
1533 checkNamed(admissionPaused && callbackPaused && producerJoined, test,
1534 "writeback did not reach both controlled publication phases") &&
1535 checkNamed(onePendingAdmission && oneActiveAdmission, test,
1536 "timer admitted duplicate work while writeback was pending or active") &&
1537 checkNamed(firstDrained && oneInitialCallback && retriesDrained, test,
1538 "worker did not drain exactly one initial writeback") &&
1539 checkNamed(expectedCallbacks && expectedBytes, test,
1540 "completion lost a mutation, failed to retry, or wrote clean data again") &&
1541 checkNamed(reclaimed, test,
"completed writeback retained a page pin");
1543 NOTICE(
"HOSTED-WAIT-TEST: PASS " << test);
1548struct ExplicitCacheCall {
1549 enum Kind { Sync, Lookup, Redirty };
1550 ExplicitCacheCall(TimerWritebackContext& state, Kind operation)
1551 : context(state), kind(operation) {}
1552 TimerWritebackContext& context;
1559int explicitCacheWorker(
void* parameter) {
1560 auto& call = *
static_cast<ExplicitCacheCall*
>(parameter);
1561 Cache& cache = *call.context.cache;
1562 const uintptr_t key = call.context.key;
1563 bool succeeded =
false;
1564 if (call.kind == ExplicitCacheCall::Sync) {
1565 succeeded = cache.syncAll();
1566 }
else if (call.kind == ExplicitCacheCall::Lookup) {
1568 succeeded = cache.lookupStable(key, call.page,
true);
1570 call.page = cache.lookup(key);
1572 *
reinterpret_cast<uint8_t*
>(call.page) = 0xA6;
1573 cache.markDirty(key);
1578 call.result = succeeded ? 1 : 0;
1579 call.done.release();
1583bool explicitWritebackThreading(
bool redirty) {
1584 const char* test = redirty ?
"cache-explicit-concurrent-redirty" :
"cache-explicit-lookup-wakeup";
1585 TimerWritebackContext context;
1586 context.key = 0xCA7F900;
1588 context.cache = &cache;
1589 cache.startAtomic();
1590 cache.setDirtyTracking(Cache::DirtyTracking::Explicit);
1591 cache.setCallback(timerWritebackCallback, &context);
1592 const uintptr_t page = cache.insert(context.key);
1593 if (!checkNamed(page != 0, test,
"could not create the test page")) {
1596 *
reinterpret_cast<uint8_t*
>(page) = 0x57;
1597 cache.markNoLongerEditing(context.key);
1598 cache.markDirty(context.key);
1600 ExplicitCacheCall first(context, ExplicitCacheCall::Sync);
1602 nullptr,
false,
true);
1603 writer->setName(
"hosted Cache explicit paused writer");
1604 const bool callbackPaused = context.callbackEntered.acquire(1, 2);
1605 if (!callbackPaused) {
1606 context.allowCallbackReturn.release();
1608 return checkNamed(
false, test,
"explicit dirty callback did not start");
1611 ExplicitCacheCall second(context,
1612 redirty ? ExplicitCacheCall::Redirty : ExplicitCacheCall::Lookup);
1614 nullptr,
false,
true);
1616 String(redirty ?
"hosted Cache concurrent explicit mutation" :
"hosted Cache stable lookup"));
1617 const bool concurrentPhase = redirty
1618 ? second.done.acquire(1, 2)
1619 : waitUntilQueuedAt(peer, Thread::CallbackDrain, context.key);
1620 context.allowCallbackReturn.release();
1622 bool peerCompleted = concurrentPhase;
1624 peerCompleted = second.done.acquire(1, 2);
1625 if (!peerCompleted) {
1629 uintptr_t address = 0;
1631 peer->
getDebugState(address) == Thread::CallbackDrain && address == context.key) {
1632 wait.queue->wakeAll(WaitQueue::WakeReason::Signalled,
1638 const bool firstWrite =
1639 first.result == 1 && context.callbacks == 1 && context.lastWritten == 0x57;
1640 const bool samePage = second.result == 1 && second.page == page;
1641 bool lookupPinned =
true;
1643 lookupPinned = samePage && !cache.evict(context.key);
1645 cache.release(context.key);
1649 const bool nextSynced = cache.sync(context.key,
false);
1650 const size_t expectedWrites = redirty ? 2 : 1;
1651 const bool latestWritten =
1652 context.callbacks == expectedWrites && context.lastWritten == (redirty ? 0xA6 : 0x57);
1653 const bool cleanSynced = cache.syncAll();
1654 const bool stayedClean = context.callbacks == expectedWrites;
1655 const bool reclaimed = cache.empty();
1657 checkNamed(concurrentPhase && peerCompleted && writerJoined && peerJoined, test,
1658 redirty ?
"mutation did not finish while the callback was paused"
1659 :
"stable lookup did not publish and complete its callback-drain wait") &&
1660 checkNamed(firstWrite && samePage && lookupPinned, test,
1661 "initial write or the concurrent page pin changed identity") &&
1662 checkNamed(nextSynced && latestWritten && cleanSynced && stayedClean, test,
1663 "callback completion lost a dirty generation or rewrote a clean page") &&
1664 checkNamed(reclaimed, test,
"completed workers retained a page pin");
1666 NOTICE(
"HOSTED-WAIT-TEST: PASS " << test);
1672bool runHostedCacheDiscardRegressions() {
1673 return preparedDiscardPublication(
false) && preparedDiscardPublication(
true) &&
1674 rejectedLastWritebackPin();
1677bool runHostedCacheSyncRegressions() {
1678 return syncAllJoinsCallback() && syncAllJoinsRetirement(
true) && syncAllJoinsRetirement(
false) &&
1679 syncAllFromCacheManager();
1682bool runHostedCacheTimerRegressions() {
1683 return cacheManagerTrimWakeGating() && timerWritebackCoalescing(
false,
false) &&
1684 timerWritebackCoalescing(
true,
false) && timerWritebackCoalescing(
false,
true);
1687bool runHostedCacheRegressions() {
1688 return callbackLifetime() && queuedRequestLifetime() && emptyAndReuse() &&
1689 retirementPublication() && failedPublicationDiscard() && retirePrepublicationWriteback() &&
1690 runHostedCacheDiscardRegressions() && retireWritebackContract() && rangeExistence() &&
1691 strictRangeGeometry() && runHostedCacheSyncRegressions() &&
1692 runHostedCacheTimerRegressions() && explicitWritebackThreading(
false) &&
1693 explicitWritebackThreading(
true);
void setCallback(writeback_t newCallback, void *meta)
uintptr_t insert(uintptr_t key, bool *alreadyExisted=nullptr)
bool sync(uintptr_t key, bool async)
void markNoLongerEditing(uintptr_t key, size_t length=0)
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()