@@ -477,83 +477,91 @@ onboard_logging.initialize = function (callback) {
477
477
}
478
478
479
479
function flash_save_begin ( ) {
480
- if ( GUI . connected_to ) {
481
- self . blockSize = self . BLOCK_SIZE ;
482
-
483
- // Begin by refreshing the occupied size in case it changed while the tab was open
484
- flash_update_summary ( function ( ) {
485
- const maxBytes = FC . DATAFLASH . usedSize ;
486
-
487
- let openedFile ;
488
- prepare_file ( function ( fileWriter ) {
489
- let nextAddress = 0 ;
490
- let totalBytesCompressed = 0 ;
491
-
492
- show_saving_dialog ( ) ;
480
+ if ( ! GUI . connected_to ) return ;
481
+
482
+ self . blockSize = self . BLOCK_SIZE ;
483
+
484
+ flash_update_summary ( async ( ) => {
485
+ const maxBytes = FC . DATAFLASH . usedSize ;
486
+ let openedFile ;
487
+ let totalBytesCompressed = 0 ;
488
+ show_saving_dialog ( ) ;
489
+
490
+ const MAX_SIMPLE_RETRIES = 5 ;
491
+ const BASE_RETRY_BACKOFF_MS = 30 ; // starting backoff
492
+ const INTER_BLOCK_DELAY_MS = 2 ; // small delay between successful blocks
493
+ const startTime = new Date ( ) . getTime ( ) ;
494
+
495
+ prepare_file ( async ( fileWriter ) => {
496
+ openedFile = await FileSystem . openFile ( fileWriter ) ;
497
+ let nextAddress = 0 ;
498
+
499
+ async function readNextBlock ( ) {
500
+ if ( saveCancelled || nextAddress >= maxBytes ) {
501
+ mark_saving_dialog_done ( startTime , nextAddress , totalBytesCompressed ) ;
502
+ await FileSystem . closeFile ( openedFile ) ;
503
+ return ;
504
+ }
493
505
494
- // START PATCH: minimal retry for null/missing blocks
495
- const MAX_SIMPLE_RETRIES = 5 ;
496
506
let simpleRetryCount = 0 ;
497
507
498
- const startTime = new Date ( ) . getTime ( ) ; // Start timestamp
499
-
500
- function onChunkRead ( chunkAddress , chunkDataView , bytesCompressed ) {
501
- if ( chunkDataView && chunkDataView . byteLength > 0 ) {
502
- // Reset retry counter after a good block
503
- simpleRetryCount = 0 ;
504
-
505
- // --- ORIGINAL BLOCK WRITE LOGIC ---
506
- const blob = new Blob ( [ chunkDataView ] ) ;
507
- FileSystem . writeChunk ( openedFile , blob ) ;
508
-
509
- nextAddress += chunkDataView . byteLength ;
510
-
511
- if ( typeof bytesCompressed === "number" ) {
512
- if ( totalBytesCompressed == null ) totalBytesCompressed = 0 ;
513
- totalBytesCompressed += bytesCompressed ;
514
- }
515
-
516
- $ ( ".dataflash-saving progress" ) . attr ( "value" , ( nextAddress / maxBytes ) * 100 ) ;
517
-
518
- if ( saveCancelled || nextAddress >= maxBytes ) {
519
- mark_saving_dialog_done ( startTime , nextAddress , totalBytesCompressed ) ;
520
- FileSystem . closeFile ( openedFile ) ;
521
- } else {
522
- mspHelper . dataflashRead ( nextAddress , self . blockSize , onChunkRead ) ;
523
- }
524
- // --- END ORIGINAL LOGIC ---
525
- } else if ( chunkDataView && chunkDataView . byteLength === 0 ) {
526
- // Zero-length block → EOF
527
- mark_saving_dialog_done ( startTime , nextAddress , totalBytesCompressed ) ;
528
- FileSystem . closeFile ( openedFile ) ;
529
- } else {
530
- // Null/missing block
531
- if ( simpleRetryCount < MAX_SIMPLE_RETRIES ) {
532
- simpleRetryCount ++ ;
533
- if ( simpleRetryCount % 2 === 1 ) {
534
- console . warn ( `Null/missing block at ${ nextAddress } , retry ${ simpleRetryCount } ` ) ;
508
+ async function attemptRead ( ) {
509
+ mspHelper . dataflashRead (
510
+ nextAddress ,
511
+ self . blockSize ,
512
+ async ( chunkAddress , chunkDataView , bytesCompressed ) => {
513
+ if ( chunkDataView && chunkDataView . byteLength > 0 ) {
514
+ // Reset retry counter
515
+ simpleRetryCount = 0 ;
516
+
517
+ // Write and await completion to prevent Mac buffer stalls
518
+ const blob = new Blob ( [ chunkDataView ] ) ;
519
+ await FileSystem . writeChunk ( openedFile , blob ) ;
520
+
521
+ nextAddress += chunkDataView . byteLength ;
522
+ if ( typeof bytesCompressed === "number" ) {
523
+ totalBytesCompressed = ( totalBytesCompressed || 0 ) + bytesCompressed ;
524
+ }
525
+
526
+ $ ( ".dataflash-saving progress" ) . attr ( "value" , ( nextAddress / maxBytes ) * 100 ) ;
527
+
528
+ // Small delay between blocks to reduce Mac Chrome hangs
529
+ setTimeout ( readNextBlock , INTER_BLOCK_DELAY_MS ) ;
530
+ } else if ( chunkDataView && chunkDataView . byteLength === 0 ) {
531
+ // EOF
532
+ mark_saving_dialog_done ( startTime , nextAddress , totalBytesCompressed ) ;
533
+ await FileSystem . closeFile ( openedFile ) ;
534
+ } else {
535
+ // Null/missing block
536
+ if ( simpleRetryCount < MAX_SIMPLE_RETRIES ) {
537
+ simpleRetryCount ++ ;
538
+ const backoff = BASE_RETRY_BACKOFF_MS * simpleRetryCount ;
539
+ if ( simpleRetryCount % 2 === 1 ) {
540
+ console . warn (
541
+ `Null/missing block at ${ nextAddress } , retry ${ simpleRetryCount } , backoff ${ backoff } ms` ,
542
+ ) ;
543
+ }
544
+ setTimeout ( attemptRead , backoff ) ;
545
+ } else {
546
+ console . error (
547
+ `Skipping null block at ${ nextAddress } after ${ MAX_SIMPLE_RETRIES } retries` ,
548
+ ) ;
549
+ nextAddress += self . blockSize ;
550
+ readNextBlock ( ) ;
551
+ }
535
552
}
536
- mspHelper . dataflashRead ( nextAddress , self . blockSize , onChunkRead ) ;
537
- } else {
538
- console . error (
539
- `Skipping null block at ${ nextAddress } after ${ MAX_SIMPLE_RETRIES } retries` ,
540
- ) ;
541
- nextAddress += self . blockSize ; // Move to next block only after retries exhausted
542
- simpleRetryCount = 0 ;
543
- mspHelper . dataflashRead ( nextAddress , self . blockSize , onChunkRead ) ;
544
- }
545
- }
553
+ } ,
554
+ ) ;
546
555
}
547
556
548
- // Fetch the initial block
549
- FileSystem . openFile ( fileWriter ) . then ( ( file ) => {
550
- openedFile = file ;
551
- mspHelper . dataflashRead ( nextAddress , self . blockSize , onChunkRead ) ;
552
- } ) ;
553
- } ) ;
557
+ attemptRead ( ) ;
558
+ }
559
+
560
+ // Start reading the first block
561
+ readNextBlock ( ) ;
554
562
} ) ;
555
- }
556
- }
563
+ } ) ;
564
+ } // end of flash_save_begin
557
565
558
566
function prepare_file ( onComplete ) {
559
567
const prefix = "BLACKBOX_LOG" ;
0 commit comments