Skip to content

Commit 097bde4

Browse files
feat: add explicit durability flush after unmount (BLA-3202)
Add blockdev --flushbufs operation on guest side after unmounting the sticky disk to ensure data durability before Ceph RBD snapshots are taken. Changes: - Add getDeviceFromMount() to extract device path from mount point - Add flushBlockDevice() that runs blockdev --flushbufs with stats logging - Log I/O stats from /sys/block/{device}/stat before and after flush - Add ENABLE_DURABILITY_FLUSH env var for feature flag (defaults to enabled) - Handle errors gracefully - log warnings but don't fail the cleanup flow Co-Authored-By: maru@blacksmith.sh <adityamaru@gmail.com>
1 parent 33fed32 commit 097bde4

3 files changed

Lines changed: 99 additions & 2 deletions

File tree

dist/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,80 @@ const DEFAULT_BUILDX_VERSION = "v0.23.0";
3232
const mountPoint = "/var/lib/buildkit";
3333
const execAsync = promisify(exec);
3434

35+
async function getDeviceFromMount(mountPath: string): Promise<string | null> {
36+
try {
37+
const { stdout } = await execAsync(`findmnt -n -o SOURCE "${mountPath}"`);
38+
const device = stdout.trim();
39+
if (device) {
40+
return device;
41+
}
42+
} catch {
43+
core.debug(`findmnt failed for ${mountPath}, trying mount command`);
44+
}
45+
46+
try {
47+
const { stdout } = await execAsync(`mount | grep " ${mountPath} "`);
48+
const match = stdout.match(/^(\/dev\/\S+)/);
49+
if (match) {
50+
return match[1];
51+
}
52+
} catch {
53+
core.debug(`mount grep failed for ${mountPath}`);
54+
}
55+
56+
return null;
57+
}
58+
59+
async function flushBlockDevice(devicePath: string): Promise<void> {
60+
const enableFlush = process.env.ENABLE_DURABILITY_FLUSH !== "false";
61+
if (!enableFlush) {
62+
core.debug("Durability flush disabled via ENABLE_DURABILITY_FLUSH=false");
63+
return;
64+
}
65+
66+
const deviceName = devicePath.replace("/dev/", "");
67+
if (!deviceName) {
68+
core.warning(`Could not extract device name from ${devicePath}`);
69+
return;
70+
}
71+
72+
const statPath = `/sys/block/${deviceName}/stat`;
73+
74+
let beforeStats = "";
75+
try {
76+
const { stdout } = await execAsync(`cat ${statPath}`);
77+
beforeStats = stdout.trim();
78+
} catch {
79+
core.debug(`Could not read block device stats before flush: ${statPath}`);
80+
}
81+
82+
const startTime = Date.now();
83+
try {
84+
await execAsync(`sudo blockdev --flushbufs ${devicePath}`, {
85+
timeout: 30000,
86+
});
87+
const duration = Date.now() - startTime;
88+
89+
let afterStats = "";
90+
try {
91+
const { stdout } = await execAsync(`cat ${statPath}`);
92+
afterStats = stdout.trim();
93+
} catch {
94+
core.debug(`Could not read block device stats after flush: ${statPath}`);
95+
}
96+
97+
core.info(
98+
`guest flush duration: ${duration}ms, device: ${devicePath}, before_stats: ${beforeStats}, after_stats: ${afterStats}`,
99+
);
100+
} catch (error) {
101+
const duration = Date.now() - startTime;
102+
const errorMsg = error instanceof Error ? error.message : String(error);
103+
core.warning(
104+
`guest flush failed for ${devicePath} after ${duration}ms: ${errorMsg}`,
105+
);
106+
}
107+
}
108+
35109
async function checkBoltDbIntegrity(skip = false): Promise<boolean> {
36110
if (skip) {
37111
core.info(
@@ -604,6 +678,19 @@ void actionsToolkit.run(
604678
// Step 2: Sync and unmount sticky disk
605679
await execAsync("sync");
606680

681+
// BLA-3202: Get device path before unmount for durability flush
682+
let devicePath: string | null = null;
683+
try {
684+
devicePath = await getDeviceFromMount(mountPoint);
685+
if (devicePath) {
686+
core.debug(
687+
`Found device ${devicePath} for mount point ${mountPoint}`,
688+
);
689+
}
690+
} catch {
691+
core.debug(`Could not determine device for ${mountPoint}`);
692+
}
693+
607694
try {
608695
const { stdout: mountOutput } = await execAsync(
609696
`mount | grep "${mountPoint}"`,
@@ -666,6 +753,16 @@ void actionsToolkit.run(
666753
await new Promise((resolve) => setTimeout(resolve, 100));
667754
}
668755
}
756+
757+
// BLA-3202: Flush block device buffers after unmount to ensure data durability
758+
// before the Ceph RBD snapshot is taken. The device is still mapped even though unmounted.
759+
if (devicePath) {
760+
await flushBlockDevice(devicePath);
761+
} else {
762+
core.debug(
763+
"Skipping durability flush: device path not found for mount point",
764+
);
765+
}
669766
} else {
670767
core.debug("No sticky disk mount found");
671768
}

0 commit comments

Comments
 (0)