The Pedigree Project 0.1
cache-regressions.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "pedigree/kernel/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"
17
19 public:
20 static void start(CacheManager& manager) {
21 auto guard = manager.m_TrimWaiters.acquire();
22 manager.m_bActive = true;
23 }
24
25 static void stop(CacheManager& manager) {
26 manager.stopPeriodicWork();
27 }
28
29 static void tick(CacheManager& manager, uint64_t delta, bool pressure) {
30 manager.timerTick(delta, pressure);
31 }
32
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;
36 }
37};
38
39namespace {
40constexpr size_t PageSize = TargetInfo::getPageSize();
41
42bool checkNamed(bool condition, const char* test, const char* detail) {
43 if (condition) {
44 return true;
45 }
46
47 ERROR("HOSTED-WAIT-TEST: FAIL " << test << ": " << detail);
48 return false;
49}
50
51bool check(bool condition, const char* detail) {
52 return checkNamed(condition, "cache-callback-lifetime", detail);
53}
54
55bool waitUntilQueued(Thread* thread, size_t debugState) {
56 const Time::Timestamp deadline = Time::getTicks() + (500 * Time::Multiplier::Millisecond);
57 while (Time::getTicks() < deadline) {
58 Thread::WaitDebugInfo info = {};
59 uintptr_t debugAddress = 0;
60 if (thread->getWaitDebugInfo(info) && info.queue && info.queued &&
61 thread->getDebugState(debugAddress) == debugState) {
62 return true;
63 }
65 }
66 return false;
67}
68
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) {
72 Thread::WaitDebugInfo info = {};
73 uintptr_t address = 0;
74 if (thread->getWaitDebugInfo(info) && info.queue && info.queued &&
75 thread->getDebugState(address) == debugState && address == debugAddress) {
76 return true;
77 }
79 }
80 return false;
81}
82
83struct CacheTrimWakeContext {
84 CacheManager manager;
85 size_t blocks = 0;
86 bool passed = true;
87};
88
89CacheTrimWakeContext* g_CacheTrimWakeContext = nullptr;
90
91void cacheTrimBeforeBlock(WaitQueue*, Thread* thread, const WaitQueue::Channel& channel, size_t) {
92 CacheTrimWakeContext* context = g_CacheTrimWakeContext;
93 if (!context || channel.owner != &context->manager)
94 return;
95
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;
99 CacheManager& manager = context->manager;
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");
104
105 Thread::WaitDebugInfo info = {};
106 if (block == 1) {
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) &&
110 thread->getWaitDebugInfo(info) && info.queued &&
111 info.reason == WaitQueue::WakeReason::Waiting,
112 Test, "healthy subperiod ticks woke the worker");
113
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");
126 } else {
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");
132 }
133
134 // Always release the published wait, including when the assertion fails.
135 if (!context->passed)
136 CacheManagerTestPeer::stop(manager);
137}
138
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;
153
154 context.passed &= checkNamed(context.blocks == 3, Test,
155 "worker did not complete pressure, writeback and stop transitions");
156 if (context.passed)
157 NOTICE("HOSTED-WAIT-TEST: PASS " << Test);
158 return context.passed;
159}
160
161struct CacheLifetimeContext {
162 CacheLifetimeContext()
163 : cache(nullptr),
164 callbackEntered(0),
165 allowCallbackReturn(0),
166 callbackCalls(0),
167 evictionCalls(0),
168 reentrantPins(0),
169 deleteReturned(0) {}
170
171 Cache* cache;
172 Semaphore callbackEntered;
173 Semaphore allowCallbackReturn;
174 Atomic<size_t> callbackCalls;
175 Atomic<size_t> evictionCalls;
176 Atomic<size_t> reentrantPins;
177 Atomic<size_t> deleteReturned;
178};
179
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);
186 if (page) {
187 context->reentrantPins += 1;
188 context->cache->release(loc);
189 }
190 }
191 const size_t call = (context->callbackCalls += 1);
192 if (call == 1) {
193 context->callbackEntered.release();
194 const bool released = context->allowCallbackReturn.acquireForCompletion();
195 (void)released;
196 }
197 return true;
198}
199
200int deleteCache(void* parameter) {
201 CacheLifetimeContext* context = reinterpret_cast<CacheLifetimeContext*>(parameter);
202 delete context->cache;
203 context->deleteReturned += 1;
204 return 0;
205}
206
207bool callbackLifetime() {
208 CacheLifetimeContext context;
209 context.cache = new Cache;
210 context.cache->setCallback(cacheCallback, &context);
211
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;
216 return false;
217 }
218
219 context.cache->markNoLongerEditing(Key);
220 context.cache->triggerChecksum(Key);
221 reinterpret_cast<uint8_t*>(page)[0] ^= 0xA5;
222 context.cache->sync(Key, true);
223
224 if (!check(context.callbackEntered.acquire(1, 2),
225 "the queued writeback callback did not start")) {
226 // Keep cleanup safe if the callback crossed the timeout boundary.
227 context.allowCallbackReturn.release();
228 delete context.cache;
229 return false;
230 }
231
232 Thread* deleter = new Thread(Scheduler::instance().getKernelProcess(), deleteCache, &context,
233 nullptr, false, true);
234 deleter->setName("hosted Cache callback-drain deleter");
235
236 const bool drainPublished = waitUntilQueued(deleter, Thread::CallbackDrain);
237 const bool callbackPinnedObject = context.deleteReturned == 0;
238
239 context.allowCallbackReturn.release();
240 const bool joined = deleter->join();
241
242 const bool passed =
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");
251
252 if (passed) {
253 NOTICE("HOSTED-WAIT-TEST: PASS cache-callback-lifetime");
254 }
255 return passed;
256}
257
258struct QueuedLifetimeContext {
259 QueuedLifetimeContext()
260 : target(nullptr),
261 blockerEntered(0),
262 allowBlockerReturn(0),
263 blockerCalls(0),
264 targetCalls(0),
265 deleteReturned(0) {}
266
267 Cache* target;
268 Semaphore blockerEntered;
269 Semaphore allowBlockerReturn;
270 Atomic<size_t> blockerCalls;
271 Atomic<size_t> targetCalls;
272 Atomic<size_t> deleteReturned;
273};
274
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();
280 (void)released;
281 }
282 return true;
283}
284
285bool queuedTargetCallback(CacheConstants::CallbackCause, uintptr_t, uintptr_t, void* parameter) {
286 QueuedLifetimeContext* context = reinterpret_cast<QueuedLifetimeContext*>(parameter);
287 context->targetCalls += 1;
288 return true;
289}
290
291int deleteQueuedCache(void* parameter) {
292 QueuedLifetimeContext* context = reinterpret_cast<QueuedLifetimeContext*>(parameter);
293 delete context->target;
294 context->deleteReturned += 1;
295 return 0;
296}
297
298bool queuedRequestLifetime() {
299 QueuedLifetimeContext context;
300 Cache blocker;
301 blocker.setCallback(blockerCallback, &context);
302
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")) {
307 return false;
308 }
309 blocker.markNoLongerEditing(BlockerKey);
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();
314 return false;
315 }
316
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;
323 return false;
324 }
325 context.target->markNoLongerEditing(TargetKey);
326 context.target->sync(TargetKey, true);
327
328 Thread* deleter = new Thread(Scheduler::instance().getKernelProcess(), deleteQueuedCache,
329 &context, nullptr, false, true);
330 deleter->setName("hosted queued Cache lease deleter");
331
332 const bool queuedLeasePublished = waitUntilQueued(deleter, Thread::CallbackDrain);
333 const bool queuedRequestPinnedObject = context.deleteReturned == 0;
334
335 context.allowBlockerReturn.release();
336 const bool joined = deleter->join();
337
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");
346
347 if (passed) {
348 NOTICE("HOSTED-WAIT-TEST: PASS cache-queued-lifetime");
349 }
350 return passed;
351}
352
353bool emptyAndReuse() {
354 Cache cache;
355 constexpr uintptr_t FirstKey = 0xCA7E100;
356 constexpr uintptr_t SecondKey = 0xCA7E200;
357
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")) {
361 return false;
362 }
363
364 cache.markNoLongerEditing(FirstKey);
365 cache.triggerChecksum(FirstKey);
366 reinterpret_cast<uint8_t*>(firstPage)[0] ^= 0x5A;
367 cache.empty();
368
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");
374 cache.empty();
375
376 if (reused) {
377 NOTICE("HOSTED-WAIT-TEST: PASS cache-empty-reuse");
378 }
379 return reused;
380}
381
382struct RetirementContext {
383 RetirementContext()
384 : cache(nullptr),
385 evictionEntered(0),
386 allowEvictionReturn(0),
387 evictionCalls(0),
388 evictReturned(0),
389 insertReturned(0),
390 replacementPage(0) {}
391
392 Cache* cache;
393 Semaphore evictionEntered;
394 Semaphore allowEvictionReturn;
395 Atomic<size_t> evictionCalls;
396 Atomic<size_t> evictReturned;
397 Atomic<size_t> insertReturned;
398 uintptr_t replacementPage;
399};
400
401bool retirementCallback(CacheConstants::CallbackCause cause, uintptr_t, uintptr_t,
402 void* parameter) {
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();
407 (void)released;
408 }
409 return true;
410}
411
412int evictRetirementPage(void* parameter) {
413 RetirementContext* context = reinterpret_cast<RetirementContext*>(parameter);
414 context->evictReturned += context->cache->evict(0xCA7E300) ? 1 : 2;
415 return 0;
416}
417
418int insertRetirementReplacement(void* parameter) {
419 RetirementContext* context = reinterpret_cast<RetirementContext*>(parameter);
420 context->replacementPage = context->cache->insert(0xCA7E300);
421 context->insertReturned += 1;
422 return 0;
423}
424
425bool retirementPublication() {
426 RetirementContext context;
427 Cache cache;
428 context.cache = &cache;
429 cache.setCallback(retirementCallback, &context);
430
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")) {
435 return false;
436 }
437 cache.markNoLongerEditing(Key);
438
439 Thread* evictor = new Thread(Scheduler::instance().getKernelProcess(), evictRetirementPage,
440 &context, nullptr, false, true);
441 evictor->setName("hosted Cache retirement evictor");
442
443 const bool callbackEntered = context.evictionEntered.acquire(1, 2);
444 if (!callbackEntered) {
445 context.allowEvictionReturn.release();
446 evictor->join();
447 cache.empty();
448 return checkNamed(false, "cache-retirement-publication",
449 "the eviction callback did not publish retirement");
450 }
451
452 const bool retiringPinRejected = !cache.pin(Key);
453 const bool retiringLookupRejected = cache.lookup(Key) == 0;
454
455 Thread* inserter = new Thread(Scheduler::instance().getKernelProcess(),
456 insertRetirementReplacement, &context, nullptr, false, true);
457 inserter->setName("hosted Cache same-key replacement");
458
459 const bool insertWaitPublished = waitUntilQueued(inserter, Thread::CallbackDrain);
460 const bool replacementBlocked = context.insertReturned == 0;
461
462 context.allowEvictionReturn.release();
463 const bool evictorJoined = evictor->join();
464 const bool inserterJoined = inserter->join();
465
466 const bool passed =
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");
479
480 cache.empty();
481 if (passed) {
482 NOTICE("HOSTED-WAIT-TEST: PASS cache-retirement-publication");
483 }
484 return passed;
485}
486
487struct DiscardEditingContext {
488 DiscardEditingContext() : writebacks(0), evictions(0) {}
489
490 Atomic<size_t> writebacks;
491 Atomic<size_t> evictions;
492};
493
494bool discardEditingCallback(CacheConstants::CallbackCause cause, uintptr_t, uintptr_t,
495 void* parameter) {
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;
501 }
502 return true;
503}
504
505bool failedPublicationDiscard() {
506 constexpr uintptr_t Key = 0xCA7E400;
507 Cache cache;
508 DiscardEditingContext context;
509 cache.setCallback(discardEditingCallback, &context);
510
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;
515
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);
519 if (pinned) {
520 cache.release(Key);
521 }
522 const bool discardedAfterRelease = rejectedPinned && cache.discardEditing(Key);
523
524 const uintptr_t publishedPage = cache.insert(Key);
525 if (publishedPage) {
526 cache.markNoLongerEditing(Key);
527 }
528 const bool rejectedPublished = publishedPage != 0 && !cache.discardEditing(Key);
529 cache.empty();
530
531 const bool passed =
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");
540
541 if (passed) {
542 NOTICE("HOSTED-WAIT-TEST: PASS cache-failed-publication-discard");
543 }
544 return passed;
545}
546
547struct RetirePublicationContext {
548 RetirePublicationContext()
549 : cache(nullptr),
550 key(0),
551 page(0),
552 admissionEntered(0),
553 allowPublication(0),
554 callbackEntered(0),
555 allowCallbackReturn(0),
556 admissionCalls(0),
557 queuedCallbacks(0),
558 queuedCallbackFinished(0),
559 evictionCalls(0),
560 retireCallbacks(0),
561 retireSawQueuedCompletion(0),
562 retireArgumentsValid(0),
563 syncReturned(0),
564 retireReturned(0),
565 retireSucceeded(0) {}
566
567 Cache* cache;
568 uintptr_t key;
569 uintptr_t page;
570 Semaphore admissionEntered;
571 Semaphore allowPublication;
572 Semaphore callbackEntered;
573 Semaphore allowCallbackReturn;
574 Atomic<size_t> admissionCalls;
575 Atomic<size_t> queuedCallbacks;
576 Atomic<size_t> queuedCallbackFinished;
577 Atomic<size_t> evictionCalls;
578 Atomic<size_t> retireCallbacks;
579 Atomic<size_t> retireSawQueuedCompletion;
580 Atomic<size_t> retireArgumentsValid;
581 Atomic<size_t> syncReturned;
582 Atomic<size_t> retireReturned;
583 Atomic<size_t> retireSucceeded;
584};
585
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();
591 (void)released;
592 }
593}
594
595bool retireQueuedCallback(CacheConstants::CallbackCause cause, uintptr_t, uintptr_t,
596 void* parameter) {
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();
603 (void)released;
604 context->queuedCallbackFinished = 1;
605 }
606 return true;
607}
608
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;
614 return true;
615}
616
617int publishRetireWriteback(void* parameter) {
618 RetirePublicationContext* context = reinterpret_cast<RetirePublicationContext*>(parameter);
619 context->cache->sync(context->key, true);
620 context->syncReturned += 1;
621 return 0;
622}
623
624int retirePublishedWriteback(void* parameter) {
625 RetirePublicationContext* context = reinterpret_cast<RetirePublicationContext*>(parameter);
626 if (context->cache->retireWriteback(context->key, retireSynchronousCallback, context)) {
627 context->retireSucceeded += 1;
628 }
629 context->retireReturned += 1;
630 return 0;
631}
632
633bool retirePrepublicationWriteback() {
634 constexpr uintptr_t Key = 0xCA7E500;
635 RetirePublicationContext context;
636 Cache cache;
637 context.cache = &cache;
638 context.key = Key;
639 cache.setCallback(retireQueuedCallback, &context);
640
641 context.page = cache.insert(Key);
642 if (!checkNamed(context.page != 0, "cache-retire-prepublication",
643 "could not create the test page")) {
644 return false;
645 }
646 cache.markNoLongerEditing(Key);
647 cache.startAtomic();
648 cache.setWritebackAdmissionHookForTest(retireAdmissionHook, &context);
649
650 Thread* producer = new Thread(Scheduler::instance().getKernelProcess(), publishRetireWriteback,
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();
657 producer->join();
658 cache.setWritebackAdmissionHookForTest(nullptr, nullptr);
659 cache.endAtomic();
660 cache.empty();
661 return checkNamed(false, "cache-retire-prepublication",
662 "sync did not pause after publishing its page reference");
663 }
664
665 Thread* retirer = new Thread(Scheduler::instance().getKernelProcess(), retirePublishedWriteback,
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);
672 }
673 const bool drainingSyncRejected = context.admissionCalls == 1;
674
675 context.allowPublication.release();
676 const bool queuedCallbackEntered = context.callbackEntered.acquire(1, 2);
677 const bool blockedThroughCallback = context.retireReturned == 0;
678 context.allowCallbackReturn.release();
679
680 const bool producerJoined = producer->join();
681 const bool retirerJoined = retirer->join();
682 cache.setWritebackAdmissionHookForTest(nullptr, nullptr);
683 cache.endAtomic();
684
685 const bool passed =
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") &&
696 checkNamed(
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");
708
709 if (cache.exists(Key, PageSize)) {
710 cache.empty();
711 }
712 if (passed) {
713 NOTICE("HOSTED-WAIT-TEST: PASS cache-retire-prepublication");
714 }
715 return passed;
716}
717
718struct DiscardPublicationContext {
719 explicit DiscardPublicationContext(bool cancelPlan)
720 : publication(), prepared(0), allowCompletion(0), completed(0), cancel(cancelPlan) {}
721
722 RetirePublicationContext publication;
723 Semaphore prepared;
724 Semaphore allowCompletion;
725 Atomic<size_t> completed;
726 bool cancel;
727};
728
729int preparePublishedDiscard(void* parameter) {
730 auto* context = reinterpret_cast<DiscardPublicationContext*>(parameter);
731 auto& publication = context->publication;
732 const Cache::DiscardReference reference = {publication.key, 1};
734 const auto status = publication.cache->prepareDiscardFrom(publication.key, &reference, 1, plan);
735 if (status == Cache::DiscardStatus::Ready && plan) {
736 publication.retireSucceeded += 1;
737 }
738 publication.retireReturned += 1;
739 context->prepared.release();
740 const bool released = context->allowCompletion.acquireForCompletion();
741 (void)released;
742 if (plan && !context->cancel) {
743 plan.get()->commit();
744 }
745 // The plan retains a thread-owned termination deferral through commit or rollback.
746 plan.reset();
747 context->completed += 1;
748 return 0;
749}
750
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;
755 if (lookup) {
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);
759 }
760 cache.release(key);
761 }
762 if (pinned) {
763 cache.release(key);
764 }
765 return unchanged;
766}
767
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;
774 Cache cache;
775 publication.cache = &cache;
776 publication.key = Key;
777 cache.setCallback(retireQueuedCallback, &publication);
778 cache.startAtomic();
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) {
783 if (ownerPinned) {
784 cache.release(Key);
785 }
786 publication.allowCallbackReturn.release();
787 cache.empty();
788 cache.endAtomic();
789 return checkNamed(false, test, "could not create and pin the test cache pages");
790 }
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);
794 }
795 cache.markNoLongerEditing(PrefixKey);
796 cache.markNoLongerEditing(Key);
797 cache.setWritebackAdmissionHookForTest(retireAdmissionHook, &publication);
798 Thread* producer = new Thread(Scheduler::instance().getKernelProcess(), publishRetireWriteback,
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();
804 producer->join();
805 cache.setWritebackAdmissionHookForTest(nullptr, nullptr);
806 cache.release(Key);
807 cache.empty();
808 cache.endAtomic();
809 return checkNamed(false, test, "writeback did not pause after publishing its page pin");
810 }
811
812 Thread* preparer = new Thread(Scheduler::instance().getKernelProcess(), preparePublishedDiscard,
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) {
823 cache.release(Key);
824 }
825 if (unexpectedPin) {
826 cache.release(Key);
827 }
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);
841 }
842 if (!cancel || !readyWithOwnerPin) {
843 cache.release(Key);
844 ownerPinned = false;
845 }
846 context.allowCompletion.release();
847 const bool producerJoined = producer->join();
848 const bool preparerJoined = preparer->join();
849 cache.setWritebackAdmissionHookForTest(nullptr, nullptr);
850
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;
858 if (restored) {
859 cache.release(Key);
860 }
861 if (pinRestored) {
862 cache.release(Key);
863 }
864 cache.release(Key);
865 ownerPinned = false;
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;
871 }
872 const bool suffixRemoved = !cache.exists(Key, PageSize);
873 const bool prefixAfterCompletion = discardPrefixUsable(cache, PrefixKey, prefix);
874 const bool passed =
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");
888 if (ownerPinned) {
889 cache.release(Key);
890 }
891 cache.empty();
892 cache.endAtomic();
893 if (passed) {
894 NOTICE("HOSTED-WAIT-TEST: PASS " << test);
895 }
896 return passed;
897}
898
899struct RejectedWritebackContext {
900 RejectedWritebackContext()
901 : publication(),
902 producerFinished(0),
903 shutdownFinished(0),
904 accepted(0),
905 shutdownSucceeded(0) {}
906
907 RetirePublicationContext publication;
908 Semaphore producerFinished;
909 Semaphore shutdownFinished;
910 Atomic<size_t> accepted;
911 Atomic<size_t> shutdownSucceeded;
912};
913
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();
920 return 0;
921}
922
923int shutdownRejectedWriteback(void* parameter) {
924 auto* context = reinterpret_cast<RejectedWritebackContext*>(parameter);
925 context->shutdownSucceeded = context->publication.cache->shutdown() ? 1 : 0;
926 context->shutdownFinished.release();
927 return 0;
928}
929
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;
935 Cache cache;
936 CacheManager& manager = CacheManager::instance();
937 publication.cache = &cache;
938 publication.key = Key;
939 cache.setCallback(retireQueuedCallback, &publication);
940 // Keep timer publication quiesced through this cache's terminal shutdown.
941 cache.startAtomic();
942 publication.page = cache.insert(Key);
943 if (!checkNamed(publication.page != 0, Test, "could not create the test page")) {
944 publication.allowCallbackReturn.release();
945 return false;
946 }
947 reinterpret_cast<uint8_t*>(publication.page)[0] = 0xa7;
948 cache.markNoLongerEditing(Key);
949 cache.setWritebackAdmissionHookForTest(retireAdmissionHook, &publication);
950 Thread* producer = new Thread(Scheduler::instance().getKernelProcess(), publishRejectedWriteback,
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) {
955 cache.release(Key);
956 }
957 const bool halted = admissionPaused && manager.halt();
958 const bool stopped =
959 halted && manager.getLifecycleState() == RequestQueue::LifecycleState::Stopped;
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();
965 (void)resumed;
966 checkNamed(false, Test, "rejected writeback producer did not finish");
967 FATAL("Cache rejection fixture retained a live producer");
968 }
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");
977 }
978
979 Thread* shutdown = new Thread(Scheduler::instance().getKernelProcess(), shutdownRejectedWriteback,
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");
985 }
986 const bool shutdownJoined = shutdown->join();
988 reinterpret_cast<void*>(publication.page));
989 const bool passed =
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");
998 if (passed) {
999 NOTICE("HOSTED-WAIT-TEST: PASS " << Test);
1000 }
1001 return passed;
1002}
1003
1004struct RetireContractContext {
1005 RetireContractContext()
1006 : cache(nullptr),
1007 key(0),
1008 page(0),
1009 shouldSucceed(0),
1010 callbacks(0),
1011 argumentsValid(0),
1012 retireReturned(0),
1013 retireSucceeded(0) {}
1014
1015 Cache* cache;
1016 uintptr_t key;
1017 uintptr_t page;
1018 Atomic<size_t> shouldSucceed;
1019 Atomic<size_t> callbacks;
1020 Atomic<size_t> argumentsValid;
1021 Atomic<size_t> retireReturned;
1022 Atomic<size_t> retireSucceeded;
1023};
1024
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;
1030}
1031
1032int retirePinnedWriteback(void* parameter) {
1033 RetireContractContext* context = reinterpret_cast<RetireContractContext*>(parameter);
1034 if (context->cache->retireWriteback(context->key, retireContractCallback, context)) {
1035 context->retireSucceeded += 1;
1036 }
1037 context->retireReturned += 1;
1038 return 0;
1039}
1040
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;
1046 Cache cache;
1047
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);
1057
1058 RetireContractContext retry;
1059 retry.cache = &cache;
1060 retry.key = RetryKey;
1061 retry.page = cache.insert(RetryKey);
1062 if (retry.page) {
1063 cache.markNoLongerEditing(RetryKey);
1064 }
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);
1070 }
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);
1075
1076 RetireContractContext pinned;
1077 pinned.cache = &cache;
1078 pinned.key = PinnedKey;
1079 pinned.page = cache.insert(PinnedKey);
1080 if (pinned.page) {
1081 cache.markNoLongerEditing(PinnedKey);
1082 }
1083 pinned.shouldSucceed = 1;
1084 const bool pinnedReady = pinned.page && cache.pin(PinnedKey);
1085 Thread* retirer = nullptr;
1086 if (pinnedReady) {
1087 retirer = new Thread(Scheduler::instance().getKernelProcess(), retirePinnedWriteback, &pinned,
1088 nullptr, false, true);
1089 retirer->setName("hosted Cache pinned-page retirer");
1090 }
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);
1099 }
1100 if (unexpectedPin) {
1101 cache.release(PinnedKey);
1102 }
1103 if (pinnedReady) {
1104 cache.release(PinnedKey);
1105 }
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);
1110
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;
1117
1118 const bool passed =
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");
1128
1129 cache.empty();
1130 if (passed) {
1131 NOTICE("HOSTED-WAIT-TEST: PASS cache-retire-contract");
1132 }
1133 return passed;
1134}
1135
1136struct SyncAllContext {
1137 SyncAllContext()
1138 : cache(nullptr),
1139 lower(nullptr),
1140 entered(0),
1141 allowReturn(0),
1142 writes(0),
1143 blockWriteback(false),
1144 retirementSucceeds(false),
1145 nestedSucceeded(0) {}
1146
1147 Cache* cache;
1148 Cache* lower;
1149 Semaphore entered;
1150 Semaphore allowReturn;
1151 Atomic<size_t> writes;
1152 bool blockWriteback;
1153 bool retirementSucceeds;
1154 Atomic<size_t> nestedSucceeded;
1155};
1156
1157bool syncAllCallback(CacheConstants::CallbackCause cause, uintptr_t, uintptr_t, void* parameter) {
1158 auto* context = static_cast<SyncAllContext*>(parameter);
1159 if (cause != CacheConstants::WriteBack) {
1160 return true;
1161 }
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();
1166 (void)released;
1167 }
1168 if (context->lower) {
1169 const bool succeeded = context->lower->syncAll();
1170 context->nestedSucceeded = succeeded ? 1 : 0;
1171 return succeeded;
1172 }
1173 return true;
1174}
1175
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();
1180 (void)released;
1181 return context->retirementSucceeds;
1182}
1183
1184struct SyncAllCall {
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;
1189 uintptr_t key;
1190 Kind kind;
1191 Semaphore done;
1192 Atomic<size_t> result;
1193};
1194
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);
1202 } else {
1203 succeeded =
1204 call->context.cache->retireWriteback(call->key, syncAllRetirementCallback, &call->context);
1205 }
1206 call->result = succeeded ? 1 : 0;
1207 call->done.release();
1208 return 0;
1209}
1210
1211bool syncAllJoinsCallback() {
1212 constexpr uintptr_t Key = 0xCA7EA00;
1213 SyncAllContext context;
1214 Cache cache;
1215 context.cache = &cache;
1216 context.blockWriteback = true;
1217 cache.setCallback(syncAllCallback, &context);
1218 if (!cache.insert(Key)) {
1219 return false;
1220 }
1221 cache.markNoLongerEditing(Key);
1222 SyncAllCall first(context, Key, SyncAllCall::All);
1223 Thread* writer = new Thread(Scheduler::instance().getKernelProcess(), syncAllWorker, &first,
1224 nullptr, false, true);
1225 const bool entered = context.entered.acquire(1, 2);
1226 if (!entered) {
1227 context.allowReturn.release();
1228 writer->join();
1229 return checkNamed(false, "cache-sync-all", "direct callback did not start");
1230 }
1231
1232 SyncAllCall queued(context, Key, SyncAllCall::QueuedPage);
1233 Thread* producer = new Thread(Scheduler::instance().getKernelProcess(), syncAllWorker, &queued,
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);
1238 Thread* joiner = new Thread(Scheduler::instance().getKernelProcess(), syncAllWorker, &second,
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();
1245 const bool passed =
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");
1251 if (passed) {
1252 NOTICE("HOSTED-WAIT-TEST: PASS cache-sync-all-callback");
1253 }
1254 return passed;
1255}
1256
1257bool syncAllJoinsRetirement(bool succeeds) {
1258 constexpr uintptr_t Key = 0xCA7EB00;
1259 SyncAllContext context;
1260 Cache cache;
1261 context.cache = &cache;
1262 context.retirementSucceeds = succeeds;
1263 cache.setCallback(syncAllCallback, &context);
1264 if (!cache.insert(Key)) {
1265 return false;
1266 }
1267 cache.markNoLongerEditing(Key);
1268 SyncAllCall retirement(context, Key, SyncAllCall::Retire);
1269 Thread* retirer = new Thread(Scheduler::instance().getKernelProcess(), syncAllWorker, &retirement,
1270 nullptr, false, true);
1271 const bool entered = context.entered.acquire(1, 2);
1272 if (!entered) {
1273 context.allowReturn.release();
1274 retirer->join();
1275 return checkNamed(false, "cache-sync-all", "retirement callback did not start");
1276 }
1277 SyncAllCall sync(context, Key, SyncAllCall::All);
1278 Thread* joiner = new Thread(Scheduler::instance().getKernelProcess(), syncAllWorker, &sync,
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");
1289 if (passed) {
1290 NOTICE("HOSTED-WAIT-TEST: PASS cache-sync-all-retirement-" << (succeeds ? "success" : "retry"));
1291 }
1292 return passed;
1293}
1294
1295bool syncAllFromCacheManager() {
1296 constexpr uintptr_t Key = 0xCA7EC00;
1297 SyncAllContext lowerContext;
1298 Cache lower;
1299 lowerContext.cache = &lower;
1300 lower.setCallback(syncAllCallback, &lowerContext);
1301 SyncAllContext upperContext;
1302 Cache upper;
1303 upperContext.cache = &upper;
1304 upperContext.lower = &lower;
1305 upper.setCallback(syncAllCallback, &upperContext);
1306 if (!lower.insert(Key) || !upper.insert(Key)) {
1307 return false;
1308 }
1309 lower.markNoLongerEditing(Key);
1310 upper.markNoLongerEditing(Key);
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");
1314 if (passed) {
1315 NOTICE("HOSTED-WAIT-TEST: PASS cache-sync-all-nested");
1316 }
1317 return passed;
1318}
1319
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);
1325 Cache cache;
1326
1327 // Reserve and return a known six-page allocator extent. After a rejected
1328 // overlap, the same extent must still split into the same two halves.
1329 // The old partial-publication path leaked the skipped virtual page, which
1330 // forced the second half to be allocated elsewhere.
1331 const uintptr_t allocatorExtent = cache.insert(ProbeKey, 2 * Length);
1332 const bool allocatorProbeReady = allocatorExtent != 0;
1333 cache.empty();
1334
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);
1342 cache.empty();
1343
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);
1351 cache.empty();
1352
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);
1359 cache.empty();
1360
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");
1369 if (passed) {
1370 NOTICE("HOSTED-WAIT-TEST: PASS cache-range-existence");
1371 }
1372 return passed;
1373}
1374
1375bool strictRangeGeometry() {
1376 constexpr uintptr_t InsertKey = 0xCA7F000;
1377 constexpr uintptr_t PublishKey = InsertKey + (4 * PageSize);
1378 Cache cache;
1379
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);
1383
1384 const uintptr_t editing = cache.insert(InsertKey);
1385 cache.markNoLongerEditing(InsertKey, PageSize + 1);
1386 const bool invalidPublishLeftEditing = editing && cache.discardEditing(InsertKey);
1387
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);
1392
1393 cache.empty();
1394 const bool passed =
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");
1401 if (passed) {
1402 NOTICE("HOSTED-WAIT-TEST: PASS cache-range-geometry");
1403 }
1404 return passed;
1405}
1406
1407struct TimerWritebackContext {
1408 Cache* cache = nullptr;
1409 uintptr_t key = 0;
1410 Semaphore admissionEntered{0};
1411 Semaphore allowPublication{0};
1412 Semaphore callbackEntered{0};
1413 Semaphore allowCallbackReturn{0};
1414 Atomic<size_t> admissions{0};
1415 Atomic<size_t> callbacks{0};
1416 uint8_t lastWritten = 0;
1417 bool failFirst = false;
1418};
1419
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();
1426 (void)released;
1427 }
1428}
1429
1430bool timerWritebackCallback(CacheConstants::CallbackCause cause, uintptr_t, uintptr_t page,
1431 void* parameter) {
1432 if (cause != CacheConstants::WriteBack) {
1433 return true;
1434 }
1435 auto& context = *static_cast<TimerWritebackContext*>(parameter);
1436 const size_t call = (context.callbacks += 1);
1437 context.lastWritten = *reinterpret_cast<uint8_t*>(page);
1438 if (call == 1) {
1439 context.callbackEntered.release();
1440 const bool released = context.allowCallbackReturn.acquireForCompletion();
1441 (void)released;
1442 }
1443 return !(context.failFirst && call == 1);
1444}
1445
1446void tickWriteback(Cache& cache) {
1447 cache.endAtomic();
1448 cache.timer(CACHE_WRITEBACK_PERIOD * 1000000ULL);
1449 cache.startAtomic();
1450}
1451
1452int publishTimerWriteback(void* parameter) {
1453 auto& context = *static_cast<TimerWritebackContext*>(parameter);
1454 tickWriteback(*context.cache);
1455 return 0;
1456}
1457
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;
1465 Cache cache;
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")) {
1471 return false;
1472 }
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);
1478
1479 // A separate synchronous request fences the worker after each released
1480 // callback, including checksum publication and writeback-pin retirement.
1481 Cache fence;
1482 fence.startAtomic();
1483 fence.setCallback([](CacheConstants::CallbackCause, uintptr_t, uintptr_t, void*) { return true; },
1484 nullptr);
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();
1489 return false;
1490 }
1491 fence.markNoLongerEditing(0);
1492
1493 Thread* producer = new Thread(Scheduler::instance().getKernelProcess(), publishTimerWriteback,
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);
1500 }
1501 }
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);
1509 }
1510 if (mutateDuringWriteback) {
1511 *reinterpret_cast<uint8_t*>(page) = 0xA6;
1512 }
1513 }
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;
1518
1519 // Failure is immediately retryable; a mutation must first be detected by
1520 // the checksum scan. Drain each epoch so queued work cannot mask a retry.
1521 bool retriesDrained = true;
1522 for (size_t i = 0; i < 3; ++i) {
1523 tickWriteback(cache);
1524 retriesDrained = fence.sync(0, false) && retriesDrained;
1525 }
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();
1531
1532 const bool passed =
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");
1542 if (passed) {
1543 NOTICE("HOSTED-WAIT-TEST: PASS " << test);
1544 }
1545 return passed;
1546}
1547
1548struct ExplicitCacheCall {
1549 enum Kind { Sync, Lookup, Redirty };
1550 ExplicitCacheCall(TimerWritebackContext& state, Kind operation)
1551 : context(state), kind(operation) {}
1552 TimerWritebackContext& context;
1553 Kind kind;
1554 uintptr_t page = 0;
1555 Semaphore done{0};
1556 Atomic<size_t> result{0};
1557};
1558
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) {
1567 // Transfer the lookup pin to the test so it can verify eviction refusal.
1568 succeeded = cache.lookupStable(key, call.page, true);
1569 } else {
1570 call.page = cache.lookup(key);
1571 if (call.page) {
1572 *reinterpret_cast<uint8_t*>(call.page) = 0xA6;
1573 cache.markDirty(key);
1574 cache.release(key);
1575 succeeded = true;
1576 }
1577 }
1578 call.result = succeeded ? 1 : 0;
1579 call.done.release();
1580 return 0;
1581}
1582
1583bool explicitWritebackThreading(bool redirty) {
1584 const char* test = redirty ? "cache-explicit-concurrent-redirty" : "cache-explicit-lookup-wakeup";
1585 TimerWritebackContext context;
1586 context.key = 0xCA7F900;
1587 Cache cache;
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")) {
1594 return false;
1595 }
1596 *reinterpret_cast<uint8_t*>(page) = 0x57;
1597 cache.markNoLongerEditing(context.key);
1598 cache.markDirty(context.key);
1599
1600 ExplicitCacheCall first(context, ExplicitCacheCall::Sync);
1601 Thread* writer = new Thread(Scheduler::instance().getKernelProcess(), explicitCacheWorker, &first,
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();
1607 writer->joinForCompletion();
1608 return checkNamed(false, test, "explicit dirty callback did not start");
1609 }
1610
1611 ExplicitCacheCall second(context,
1612 redirty ? ExplicitCacheCall::Redirty : ExplicitCacheCall::Lookup);
1613 Thread* peer = new Thread(Scheduler::instance().getKernelProcess(), explicitCacheWorker, &second,
1614 nullptr, false, true);
1615 peer->setName(
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();
1621 const bool writerJoined = writer->joinForCompletion();
1622 bool peerCompleted = concurrentPhase;
1623 if (!redirty) {
1624 peerCompleted = second.done.acquire(1, 2);
1625 if (!peerCompleted) {
1626 // A lost completion wake must fail the test without stranding its worker.
1627 // The cache and page remain alive, and the writer has left the callback.
1628 Thread::WaitDebugInfo wait = {};
1629 uintptr_t address = 0;
1630 if (peer->getWaitDebugInfo(wait) && wait.queue && wait.queued &&
1631 peer->getDebugState(address) == Thread::CallbackDrain && address == context.key) {
1632 wait.queue->wakeAll(WaitQueue::WakeReason::Signalled,
1633 WaitQueue::Channel(wait.channelOwner, wait.channelValue));
1634 }
1635 }
1636 }
1637 const bool peerJoined = peer->joinForCompletion();
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;
1642 if (!redirty) {
1643 lookupPinned = samePage && !cache.evict(context.key);
1644 if (second.page) {
1645 cache.release(context.key);
1646 }
1647 }
1648
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();
1656 const bool passed =
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");
1665 if (passed) {
1666 NOTICE("HOSTED-WAIT-TEST: PASS " << test);
1667 }
1668 return passed;
1669}
1670} // namespace
1671
1672bool runHostedCacheDiscardRegressions() {
1673 return preparedDiscardPublication(false) && preparedDiscardPublication(true) &&
1674 rejectedLastWritebackPin();
1675}
1676
1677bool runHostedCacheSyncRegressions() {
1678 return syncAllJoinsCallback() && syncAllJoinsRetirement(true) && syncAllJoinsRetirement(false) &&
1679 syncAllFromCacheManager();
1680}
1681
1682bool runHostedCacheTimerRegressions() {
1683 return cacheManagerTrimWakeGating() && timerWritebackCoalescing(false, false) &&
1684 timerWritebackCoalescing(true, false) && timerWritebackCoalescing(false, true);
1685}
1686
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);
1694}
bool m_bActive
Definition Cache.h:199
Definition Cache.h:207
void setCallback(writeback_t newCallback, void *meta)
Definition Cache.cc:2138
uintptr_t insert(uintptr_t key, bool *alreadyExisted=nullptr)
Definition Cache.cc:783
void startAtomic()
Definition Cache.h:552
bool sync(uintptr_t key, bool async)
Definition Cache.cc:1467
void markNoLongerEditing(uintptr_t key, size_t length=0)
Definition Cache.cc:2394
LifecycleState getLifecycleState()
MUST_USE_RESULT bool halt()
MUST_USE_RESULT bool resume()
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:226
static constexpr size_t getPageSize() noexcept
Definition TargetInfo.h:40
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3184
bool joinForCompletion()
Definition Thread.cc:2771
bool join()
Definition Thread.cc:2767
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:570
virtual bool isMapped(void *virtualAddress)=0
static EXPORTED_PUBLIC VirtualAddressSpace & getKernelAddressSpace()