fix(ci): release verified RAW Recovery file caches before RAM admission

This commit is contained in:
dh
2026-10-05 13:41:11 +02:00
parent ec5508e978
commit cae38b014f
2 changed files with 27 additions and 3 deletions
+3 -1
View File
@@ -4,9 +4,11 @@ The isolated RAW Recovery comparison starts from Full candidate `2e2d702e294c39f
The existing pinned container's `qemu-img` converts the already-patched DMG, requires sector equality with `qemu-img compare`, and checks that conversion preserved the DMG's SHA256. Both images stay in this run's owned storage. A retained JSON receipt binds both hashes and the successful comparison. The original readonly virtio attachment and I/O thread are preserved; cleanup removes both images with that owned volume. Local `--validate --full --recovery-format raw --source <pristine-pinned-dockur-checkout> --output <fresh-folder>` exercises conversion failures, source mutation and strict backend selection with a mocked QEMU boundary. The generated `raw-recovery-real-qemu-fixture.sh` accepts an already-patched **disposable writable DMG copy** plus destination for an actual container QEMU comparison; it does not patch, mount or boot a guest. This comparison does not yet prove faster guest operation or native application tests.
Run 4216 passed the RAW hash and sector-equality gates, then exited before VM startup: Dockur subtracts the container's entire `memory.current`, including reclaimable file cache, from its unchanged 6-GiB limit. After the final verification reads, RAW preparation now uses the existing [GNU `dd` whole-file cache request](https://www.gnu.org/s/coreutils/manual/html_node/dd-invocation.html), `oflag=nocache conv=nocreat,notrunc,fdatasync count=0`, only on the two verified owned Recovery files. This synchronizes their pending writes and requests removal of their data cache without changing file bytes, global caches or memory limits. Either failure rejects preparation; the runtime log records cgroup usage before and after the request. The DMG default path is unchanged. A local 6-GiB-container reproduction with QEMU 11.1.0 reduced `memory.current` from 4,220,035,072 to 132,902,912 bytes and changed Dockur's available-RAM result from 2,223,198,208 to 5,826,265,088 bytes; hashes/equality passed and OOM counters stayed zero. Run 4216 used QEMU 11.1.1, so this local result still needs confirmation on that runtime and proves no guest boot or application test.
Run 4204 stopped before creating a VM because the shared host exposed 5,138,696 KiB available memory, just below the previous arbitrary 5-GiB admission threshold. Admission now budgets the unchanged 4-GiB guest plus 512 MiB for QEMU (4.5 GiB); previous guest recordings peaked below 3 GiB. The 6-GiB container cap and all guest parameters remain unchanged. Offline validation replays that captured host value and still rejects a host with only 4 GiB available. This is an admission budget, not a reservation against other host workloads; actual performance and memory remain subject to the remote result.
This manual candidate uses the existing Ubuntu/x64 Docker runner. Before downloading Apple Recovery it tests actual AVX/AVX2 instruction execution in the pinned QEMU binary, then probes a fresh macOS 14+ Recovery guest. It does not install macOS, erase a disk, provision .NET/CLT or run Meeting Assistant tests. Readiness is only a prerequisite for full native CI.
In readiness mode, this manual candidate uses the existing Ubuntu/x64 Docker runner. Before downloading Apple Recovery it tests actual AVX/AVX2 instruction execution in the pinned QEMU binary, then probes a fresh macOS 14+ Recovery guest. It does not install macOS, erase a disk, provision .NET/CLT or run Meeting Assistant tests. Readiness is only a prerequisite for full native CI.
## Profile and evidence
+24 -2
View File
@@ -503,7 +503,7 @@ static class NativeDiagnostic
}
static readonly string RawRecoveryPreparation = """
local raw="$dest.raw" pending="$dest.raw.tmp" source_hash after_hash raw_hash
local raw="$dest.raw" pending="$dest.raw.tmp" source_hash after_hash raw_hash cache_before cache_after
[ ! -e "$raw" ] && [ ! -e "$pending" ] && [ ! -e "$raw.json" ] || {
error 'RAW Recovery output already exists.'; return 1;
}
@@ -522,6 +522,17 @@ static class NativeDiagnostic
error 'Patched DMG changed during RAW conversion.'
return 1
fi
# These verified owned files are not guest RAM. Release only their
# persisted data cache before Dockur computes its cgroup RAM allowance.
cache_before=$(cat /sys/fs/cgroup/memory.current 2>/dev/null || printf unavailable)
if ! dd of="$pending" oflag=nocache conv=nocreat,notrunc,fdatasync count=0 status=none ||
! dd of="$source" oflag=nocache conv=nocreat,notrunc,fdatasync count=0 status=none; then
rm -f "$pending"
error 'Failed to release the verified Recovery file caches before RAM admission.'
return 1
fi
cache_after=$(cat /sys/fs/cgroup/memory.current 2>/dev/null || printf unavailable)
info "[recovery-raw-cache] files=two-owned memory-current-before=$cache_before memory-current-after=$cache_after"
if ! mv -f "$source" "$dest" || ! mv -f "$pending" "$raw"; then
rm -f "$pending"
error 'Failed to retain the patched DMG and equivalent RAW Recovery.'
@@ -572,10 +583,19 @@ static class NativeDiagnostic
*) return 65 ;;
esac
}
dd() {
[ "$2 $3 $4 $5" = 'oflag=nocache conv=nocreat,notrunc,fdatasync count=0 status=none' ] || return 65
case "$1" in
"of=$pending") [ "$TEST_CASE" != raw-cache-failed ] || return 1 ;;
"of=$source") [ "$TEST_CASE" != source-cache-failed ] || return 1 ;;
*) return 65 ;;
esac
printf '%s\n' "$1" >> "$dest.cache-eviction"
}
""";
var script = realScript.Replace(preparation, mock + "\n" + preparation, StringComparison.Ordinal);
File.WriteAllText(Path.Combine(output, "raw-recovery-mock-fixture.sh"), script);
var cases = new[] { "equal", "convert-failed", "compare-failed", "source-mutated" };
var cases = new[] { "equal", "convert-failed", "compare-failed", "source-mutated", "raw-cache-failed", "source-cache-failed" };
foreach (var name in cases)
{
var folder = Path.Combine(output, "raw-recovery-" + name);
@@ -588,6 +608,8 @@ static class NativeDiagnostic
if ((result.ExitCode == 0) != passed || passed && (!File.Exists(destination + ".raw") || !File.ReadAllBytes(destination + ".raw").SequenceEqual(bytes) || !File.ReadAllBytes(destination).SequenceEqual(bytes))
|| !passed && File.Exists(destination + ".raw"))
throw new InvalidOperationException("RAW Recovery preparation behavior failed: " + name);
if (passed && (!File.Exists(destination + ".cache-eviction") || !File.ReadAllLines(destination + ".cache-eviction").SequenceEqual(new[] { "of=" + destination + ".raw.tmp", "of=" + input })))
throw new InvalidOperationException("RAW Recovery must release only the two verified files' caches before retaining them.");
}
var entry = File.ReadAllText(Path.Combine(output, "container-entry.sh"));
var selectionStart = entry.IndexOf("# BEGIN raw Recovery backend", StringComparison.Ordinal);