@@ -591,7 +591,12 @@ impl<'a> SupportBundleManager<'a> {
591
591
return Ok ( digest. as_slice ( ) == expected. as_ref ( ) ) ;
592
592
}
593
593
594
- // A helper function which streams the contents of a bundle to a file.
594
+ // A helper function which streams the contents of a bundle to a file,
595
+ // and flushes it to the filesystem.
596
+ //
597
+ // Note that this doesn't necessarily "sync" it to the underlying disk,
598
+ // but that's fine -- we just want to make sure the next call to "finalize"
599
+ // will reliably see what we last wrote.
595
600
async fn stream_bundle (
596
601
mut tmp_file : tokio:: fs:: File ,
597
602
stream : impl Stream < Item = Result < Bytes , HttpError > > ,
@@ -603,6 +608,18 @@ impl<'a> SupportBundleManager<'a> {
603
608
let chunk = chunk?;
604
609
tmp_file. write_all ( & chunk) . await ?;
605
610
}
611
+
612
+ // From the tokio docs:
613
+ //
614
+ // > A file will not be closed immediately when it goes out of scope if
615
+ // > there are any IO operations that have not yet completed. To ensure
616
+ // > that a file is closed immediately when it is dropped, you should
617
+ // > call flush before dropping it.
618
+ //
619
+ // It is possible, although uncommon, for us to write to this file,
620
+ // drop the handle to it, and for it to have not been fully written to
621
+ // storage.
622
+ tmp_file. flush ( ) . await ?;
606
623
Ok ( ( ) )
607
624
}
608
625
@@ -726,9 +743,7 @@ impl<'a> SupportBundleManager<'a> {
726
743
. open ( & support_bundle_path_tmp)
727
744
. await ?;
728
745
729
- tmp_file
730
- . seek ( tokio:: io:: SeekFrom :: Current ( i64:: try_from ( offset) ?) )
731
- . await ?;
746
+ tmp_file. seek ( tokio:: io:: SeekFrom :: Start ( offset) ) . await ?;
732
747
733
748
if let Err ( err) = Self :: stream_bundle ( tmp_file, stream) . await {
734
749
warn ! ( log, "Failed to write bundle to storage" ; "error" => ?err) ;
@@ -743,7 +758,7 @@ impl<'a> SupportBundleManager<'a> {
743
758
info ! ( log, "Bundle written successfully" ) ;
744
759
let metadata = SupportBundleMetadata {
745
760
support_bundle_id,
746
- state : SupportBundleState :: Complete ,
761
+ state : SupportBundleState :: Incomplete ,
747
762
} ;
748
763
Ok ( metadata)
749
764
}
@@ -1551,26 +1566,31 @@ mod tests {
1551
1566
. expect ( "Should have started creation" ) ;
1552
1567
1553
1568
// Split the zipfile into halves, so we can transfer it in two chunks
1554
- let len1 = zipfile_data. len ( ) / 2 ;
1555
- let stream1 = stream:: once ( async {
1556
- Ok ( Bytes :: copy_from_slice ( & zipfile_data. as_slice ( ) [ ..len1] ) )
1557
- } ) ;
1558
- let stream2 = stream:: once ( async {
1559
- Ok ( Bytes :: copy_from_slice ( & zipfile_data. as_slice ( ) [ len1..] ) )
1560
- } ) ;
1561
-
1562
- mgr. transfer ( zpool_id, dataset_id, support_bundle_id, 0 , stream1)
1569
+ let total_len = zipfile_data. len ( ) ;
1570
+ let chunk_size = total_len / 2 ;
1571
+
1572
+ let mut offset = 0 ;
1573
+ while offset < total_len {
1574
+ let end_offset = std:: cmp:: min ( offset + chunk_size, total_len) ;
1575
+ let chunk = & zipfile_data[ offset..end_offset] ;
1576
+
1577
+ let stream =
1578
+ stream:: once ( async move { Ok ( Bytes :: copy_from_slice ( chunk) ) } ) ;
1579
+
1580
+ mgr. transfer (
1581
+ zpool_id,
1582
+ dataset_id,
1583
+ support_bundle_id,
1584
+ offset as u64 ,
1585
+ stream,
1586
+ )
1563
1587
. await
1564
- . expect ( "Should have transferred bundle (part1)" ) ;
1565
- mgr. transfer (
1566
- zpool_id,
1567
- dataset_id,
1568
- support_bundle_id,
1569
- len1 as u64 ,
1570
- stream2,
1571
- )
1572
- . await
1573
- . expect ( "Should have transferred bundle (part2)" ) ;
1588
+ . unwrap_or_else ( |_| {
1589
+ panic ! ( "Should have transferred chunk at offset {}" , offset)
1590
+ } ) ;
1591
+
1592
+ offset = end_offset;
1593
+ }
1574
1594
let bundle = mgr
1575
1595
. finalize ( zpool_id, dataset_id, support_bundle_id, hash)
1576
1596
. await
0 commit comments