@@ -74,6 +74,58 @@ impl ZfsBackup {
7474
7575 Ok ( ignore_builder. build ( ) ?)
7676 }
77+
78+ async fn destroy_snapshot ( target : & str ) -> Result < ( ) , anyhow:: Error > {
79+ let backoffs_ms = [ 100u64 , 200 , 400 , 800 , 1600 , 3200 ] ;
80+
81+ for backoff_ms in backoffs_ms {
82+ let output = Command :: new ( "zfs" )
83+ . arg ( "destroy" )
84+ . arg ( "-f" )
85+ . arg ( target)
86+ . env ( "LC_ALL" , "C" )
87+ . output ( )
88+ . await ?;
89+
90+ if output. status . success ( ) {
91+ return Ok ( ( ) ) ;
92+ }
93+
94+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
95+
96+ if stderr. contains ( "could not find any snapshots to destroy" ) {
97+ return Ok ( ( ) ) ;
98+ }
99+
100+ if !stderr. contains ( "dataset is busy" ) {
101+ return Err ( anyhow:: anyhow!( "failed to delete zfs snapshot: {}" , stderr) ) ;
102+ }
103+
104+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( backoff_ms) ) . await ;
105+ }
106+
107+ // Deferred destroy so the user-visible delete completes even if a kernel reference lingers.
108+ let output = Command :: new ( "zfs" )
109+ . arg ( "destroy" )
110+ . arg ( "-d" )
111+ . arg ( target)
112+ . env ( "LC_ALL" , "C" )
113+ . output ( )
114+ . await ?;
115+
116+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
117+
118+ if !output. status . success ( ) && !stderr. contains ( "could not find any snapshots to destroy" ) {
119+ return Err ( anyhow:: anyhow!( "failed to delete zfs snapshot: {}" , stderr) ) ;
120+ }
121+
122+ tracing:: warn!(
123+ snapshot = %target,
124+ "zfs snapshot still busy after retries; marked for deferred destroy"
125+ ) ;
126+
127+ Ok ( ( ) )
128+ }
77129}
78130
79131#[ async_trait:: async_trait]
@@ -433,21 +485,7 @@ impl BackupExt for ZfsBackup {
433485 . await ;
434486
435487 if let Ok ( dataset_name) = tokio:: fs:: read_to_string ( dataset_path) . await {
436- let output = Command :: new ( "zfs" )
437- . arg ( "destroy" )
438- . arg ( "-f" )
439- . arg ( format ! ( "{}@{}" , dataset_name. trim( ) , snapshot_name) )
440- . env ( "LC_ALL" , "C" )
441- . output ( )
442- . await ?;
443-
444- let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
445-
446- if !output. status . success ( )
447- && !stderr. contains ( "could not find any snapshots to destroy" )
448- {
449- return Err ( anyhow:: anyhow!( "failed to delete zfs snapshot: {}" , stderr) ) ;
450- }
488+ Self :: destroy_snapshot ( & format ! ( "{}@{}" , dataset_name. trim( ) , snapshot_name) ) . await ?;
451489 }
452490
453491 tokio:: fs:: remove_dir_all ( backup_path) . await ?;
@@ -491,22 +529,14 @@ impl BackupCleanExt for ZfsBackup {
491529 return Ok ( ( ) ) ;
492530 }
493531
494- if let Ok ( dataset_name) = tokio:: fs:: read_to_string ( dataset_path) . await {
495- let output = Command :: new ( "zfs" )
496- . arg ( "destroy" )
497- . arg ( "-f" )
498- . arg ( format ! ( "{}@{}" , dataset_name. trim( ) , snapshot_name) )
499- . env ( "LC_ALL" , "C" )
500- . output ( )
501- . await ?;
502-
503- let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
532+ server
533+ . app_state
534+ . backup_manager
535+ . invalidate_cached_browse ( uuid)
536+ . await ;
504537
505- if !output. status . success ( )
506- && !stderr. contains ( "could not find any snapshots to destroy" )
507- {
508- return Err ( anyhow:: anyhow!( "failed to delete zfs snapshot: {}" , stderr) ) ;
509- }
538+ if let Ok ( dataset_name) = tokio:: fs:: read_to_string ( dataset_path) . await {
539+ Self :: destroy_snapshot ( & format ! ( "{}@{}" , dataset_name. trim( ) , snapshot_name) ) . await ?;
510540 }
511541
512542 tokio:: fs:: remove_dir_all ( backup_path) . await ?;
0 commit comments