Check that a thaw fetches the armed ranges of its regions without the freeze lock, which the review found untested.

memory.c mem_thaw calls lazy_release_regions before it takes frz_lock, so the copy a file read's thaw needs (the
buffer's armed ranges with stale bytes) no longer holds up the other threads' faults, freezes, thaws and arms. All 20
scenarios of tests/lazy_fetch_check.py passed with that call taken out: the thaw scenario reaches the same end state
through thaw_begin_locked's lazy_drop_region_locked under the lock. The new thaw_unlocked scenario has a thread thaw a
page of a range armed with stale bytes while the fetch takes 300 ms, and freezes another region meanwhile: the freeze
returns at once, and took 300 ms with the call taken out. locked_thaw runs it with NX2NAVI_WB_LAZY_UNLOCKED=0, where
the freeze waits for the fetch as in 0.10.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-13 20:00:02 -05:00
parent 92121a11e8
commit 0eee849948
3 changed files with 22 additions and 2 deletions

View File

@@ -694,8 +694,9 @@ which builds memory.c with the Windows fault handler and a fetch that only waits
process, so an access violation fails it. A thread that reads a range over and over while it is armed and given back
again never reaches the crash handler, also when the arm's clean test takes 100 ms, and a read of a range being armed
does not either when a range in flight in its region ends during that test. A fetch holds up nothing but the
touches of its range: freezes, thaws, arms, releases and copies of other ranges return at once meanwhile, two ranges
fetch at once, and more touching threads than the flight table holds all end. The touches of a range in flight wait
touches of its range: freezes, thaws, arms, releases and copies of other ranges return at once meanwhile, also while
a thaw fetches the armed ranges of its own regions, two ranges fetch at once, and more touching threads than the
flight table holds all end. The touches of a range in flight wait
for it and run again, a write among them without releasing the region's other ranges; thaws, mapping changes,
releases of its buffer, arms and freezes see it. The same with `wb_lazy_chunk = 1`, and with `wb_lazy_unlocked = 0`
the fetch holds the freeze lock again. The "mem: lazy write-back" lines give the longest fetch, the longest wait of a

View File

@@ -155,6 +155,8 @@ static void releaser(void *arg) {
// the completion thread's arm (vk_memory.c vk_gpu_written_back) of 64 KiB at arg, written back from buffer 1
static volatile uint64_t arm_n;
static void armer(void *arg) { arm_n = arm_buf((uint64_t)(uintptr_t)arg, 0x10000, 1); }
// a file read's thaw of the page at arg (fs_host.c, romfs.c: before the kernel writes the buffer)
static void thawer(void *arg) { mem_thaw((uint64_t)(uintptr_t)arg, 0x1000); }
int main(int argc, char **argv) {
setvbuf(stdout, NULL, _IONBF, 0);
@@ -292,6 +294,21 @@ int main(int argc, char **argv) {
CHECK(page_rw(s + 0x1000), "the page is not read-write after the thaw");
plat_thread_join(ta);
CHECK(fetch_calls == 1, "%d fetches", fetch_calls);
} else if (!strcmp(what, "thaw_unlocked")) {
// A thaw (a file read into a buffer) of a range armed with stale bytes fetches them before it takes frz_lock
// (memory.c lazy_release_regions): a freeze of another region meanwhile does not wait for the copy. With
// NX2NAVI_WB_LAZY_UNLOCKED=0 the thaw fetches under the lock, and the freeze waits.
uint64_t s = heap + 124 * M, frz_at = heap + 16 * M;
CHECK(arm_buf(s, K64, 1) == K64 && stale(s, K64, 1) == 1, "arm");
void *th = plat_thread_new(thawer, (void *)(uintptr_t)(s + 0x1000), "thawer");
wait_fetch();
uint64_t t0 = plat_now_ns();
int f = mem_freeze(frz_at);
double d = ms(t0);
if (lz_unlocked) CHECK(d < 50 && f == 1, "mem_freeze during the thaw's fetch took %.1f ms", d);
else CHECK(d > 200, "mem_freeze took %.1f ms, not the fetch's time", d);
plat_thread_join(th);
CHECK(fetch_calls == 1 && page_rw(s) && !armed(s, K64), "%d fetches, pages %d", fetch_calls, page_rw(s));
} else if (!strcmp(what, "mapping")) {
// a mapping change (a commit, decommit or protect: thaw_range_locked under frz_lock) in a region with a range
// in flight and another armed: the armed one is fetched under the lock, the flight ends first, both given back

View File

@@ -29,6 +29,8 @@ SCENARIOS = {
'loser': ('loser', {}),
'concurrent': ('concurrent', {}),
'thaw': ('thaw', {}),
'thaw_unlocked': ('thaw_unlocked', {}),
'locked_thaw': ('thaw_unlocked', LOCKED),
'mapping': ('mapping', {}),
'release': ('release', {}),
'release_join': ('release_join', {}),