@@ -32,6 +32,80 @@ const DEFAULT_BUILDX_VERSION = "v0.23.0";
3232const mountPoint = "/var/lib/buildkit" ;
3333const 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 ( / ^ ( \/ d e v \/ \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+
35109async 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