@@ -1391,9 +1391,10 @@ class NamespaceFS {
1391
1391
}
1392
1392
1393
1393
// 1. get latest version_id
1394
- // 2. if versioning is suspended -
1394
+ // 2. if versioning is suspended -
1395
1395
// 2.1 if version ID of the latest version is null -
1396
- // 2.1.1 remove latest version
1396
+ // 2.1.1. if it's POSIX backend - unlink the null version
1397
+ // 2.1.2. if it's GPFS backend - nothing to do, the linkatif will override it
1397
1398
// 2.2 else (version ID of the latest version is unique or there is no latest version) -
1398
1399
// 2.2.1 remove a version (or delete marker) with null version ID from .versions/ (if exists)
1399
1400
// 3. if latest version exists -
@@ -1417,10 +1418,8 @@ class NamespaceFS {
1417
1418
const versioned_path = latest_ver_info && this . _get_version_path ( key , latest_ver_info . version_id_str ) ;
1418
1419
const versioned_info = latest_ver_info && await this . _get_version_info ( fs_context , versioned_path ) ;
1419
1420
1420
- gpfs_options = is_gpfs ?
1421
- await this . _open_files_gpfs ( fs_context , new_ver_tmp_path , latest_ver_path , upload_file ,
1422
- latest_ver_info , open_mode , undefined , versioned_info ) :
1423
- undefined ;
1421
+ gpfs_options = await this . _open_files_gpfs ( fs_context , new_ver_tmp_path , latest_ver_path , upload_file ,
1422
+ latest_ver_info , open_mode , undefined , versioned_info ) ;
1424
1423
const bucket_tmp_dir_path = this . get_bucket_tmpdir_full_path ( ) ;
1425
1424
dbg . log1 ( 'Namespace_fs._move_to_dest_version:' , latest_ver_info , new_ver_info , gpfs_options ) ;
1426
1425
@@ -1439,13 +1438,20 @@ class NamespaceFS {
1439
1438
if ( latest_ver_info &&
1440
1439
( ( this . _is_versioning_enabled ( ) ) ||
1441
1440
( this . _is_versioning_suspended ( ) && latest_ver_info . version_id_str !== NULL_VERSION_ID ) ) ) {
1442
- dbg . log1 ( 'NamespaceFS._move_to_dest_version version ID of the latest version is a unique ID - the file will be moved it to .versions/ directory' ) ;
1441
+ dbg . log1 ( 'NamespaceFS._move_to_dest_version version ID of the latet version is a unique ID - the file will be moved it to .versions/ directory' ) ;
1442
+ await this . _check_version_moved ( fs_context , key , latest_ver_info . version_id_str , latest_ver_path ) ;
1443
1443
await native_fs_utils . _make_path_dirs ( versioned_path , fs_context ) ;
1444
1444
await native_fs_utils . safe_move ( fs_context , latest_ver_path , versioned_path , latest_ver_info ,
1445
- gpfs_options && gpfs_options . move_to_versions , bucket_tmp_dir_path ) ;
1445
+ gpfs_options ? .move_to_versions , bucket_tmp_dir_path ) ;
1446
1446
}
1447
1447
try {
1448
1448
// move new version to latest_ver_path (key path)
1449
+ if ( latest_ver_info ) {
1450
+ await this . _check_version_moved ( fs_context , key , latest_ver_info . version_id_str , latest_ver_path ) ;
1451
+ } else {
1452
+ const latest_ver_info_verification = await this . _get_version_info ( fs_context , latest_ver_path ) ;
1453
+ if ( latest_ver_info_verification ) throw new RpcError ( 'VERSION_MOVED' ) ;
1454
+ }
1449
1455
await native_fs_utils . safe_move ( fs_context , new_ver_tmp_path , latest_ver_path , new_ver_info ,
1450
1456
gpfs_options && gpfs_options . move_to_dst , bucket_tmp_dir_path ) ;
1451
1457
} catch ( err ) {
@@ -1460,6 +1466,7 @@ class NamespaceFS {
1460
1466
dbg . warn ( `NamespaceFS._move_to_dest_version retrying retries=${ retries } should_retry=${ should_retry } ` +
1461
1467
` new_ver_tmp_path=${ new_ver_tmp_path } latest_ver_path=${ latest_ver_path } ` , err ) ;
1462
1468
if ( ! should_retry || retries <= 0 ) throw err ;
1469
+ await P . delay ( 100 ) ;
1463
1470
} finally {
1464
1471
if ( gpfs_options ) await this . _close_files_gpfs ( fs_context , gpfs_options . move_to_dst , open_mode ) ;
1465
1472
}
@@ -2680,9 +2687,18 @@ class NamespaceFS {
2680
2687
// if stat failed, undefined will return
2681
2688
}
2682
2689
2683
- // 1. if version exists in .versions/ folder - return its path
2684
- // 2. else if version is latest version - return latest version path
2685
- // 3. throw ENOENT error
2690
+ /**
2691
+ * _find_version_path returns the path of the version
2692
+ * 1. if version_id is not defined, it returns the key file
2693
+ * 2. else,
2694
+ * 2.1. check version format
2695
+ * 2.2. check if latest version exists and it matches the versio_id parameter the latest version path returns
2696
+ * 2.3. else, return the version path under .versions/
2697
+ * @param {import('./nb').NativeFSContext } fs_context
2698
+ * @param {{key: string, version_id?: string} } params
2699
+ * @param {boolean } [return_md_path]
2700
+ * @returns {Promise<string> }
2701
+ */
2686
2702
async _find_version_path ( fs_context , { key, version_id } , return_md_path ) {
2687
2703
const cur_ver_path = return_md_path ? this . _get_file_md_path ( { key } ) : this . _get_file_path ( { key } ) ;
2688
2704
if ( ! version_id ) return cur_ver_path ;
@@ -2740,7 +2756,7 @@ class NamespaceFS {
2740
2756
*/
2741
2757
// we can use this function when versioning is enabled or suspended
2742
2758
async _delete_single_object_versioned ( fs_context , key , version_id ) {
2743
- let retries = config . NSFS_RENAME_RETRIES ;
2759
+ let retries = config . NSFS_DELETE_VERSION_RETRIES ;
2744
2760
const is_gpfs = native_fs_utils . _is_gpfs ( fs_context ) ;
2745
2761
const latest_version_path = this . _get_file_path ( { key } ) ;
2746
2762
for ( ; ; ) {
@@ -2754,15 +2770,15 @@ class NamespaceFS {
2754
2770
2755
2771
const deleted_latest = file_path === latest_version_path ;
2756
2772
if ( deleted_latest ) {
2757
- gpfs_options = is_gpfs ?
2758
- await this . _open_files_gpfs ( fs_context , file_path , undefined , undefined , undefined , undefined , true ) :
2759
- undefined ;
2773
+ gpfs_options = await this . _open_files_gpfs ( fs_context , file_path , undefined , undefined , undefined , undefined , true ) ;
2760
2774
const bucket_tmp_dir_path = this . get_bucket_tmpdir_full_path ( ) ;
2761
2775
await native_fs_utils . safe_unlink ( fs_context , file_path , version_info ,
2762
2776
gpfs_options ?. delete_version , bucket_tmp_dir_path ) ;
2777
+ await this . _check_version_moved ( fs_context , key , version_id , file_path ) ;
2763
2778
return { ...version_info , latest : true } ;
2764
2779
} else {
2765
2780
await native_fs_utils . unlink_ignore_enoent ( fs_context , file_path ) ;
2781
+ await this . _check_version_moved ( fs_context , key , version_id , file_path ) ;
2766
2782
}
2767
2783
return version_info ;
2768
2784
} catch ( err ) {
@@ -2776,6 +2792,7 @@ class NamespaceFS {
2776
2792
// 3. concurrent delete of this version - will get ENOENT, doing a retry will return successfully
2777
2793
// after we will see that the version was already deleted
2778
2794
if ( retries <= 0 || ! native_fs_utils . should_retry_link_unlink ( is_gpfs , err ) ) throw err ;
2795
+ await P . delay ( 100 ) ;
2779
2796
} finally {
2780
2797
if ( gpfs_options ) await this . _close_files_gpfs ( fs_context , gpfs_options . delete_version , undefined , true ) ;
2781
2798
}
@@ -2886,6 +2903,7 @@ class NamespaceFS {
2886
2903
}
2887
2904
if ( err . code !== 'ENOENT' ) throw err ;
2888
2905
dbg . warn ( `NamespaceFS: _promote_version_to_latest failed retries=${ retries } ` , err ) ;
2906
+ await P . delay ( 100 ) ;
2889
2907
}
2890
2908
}
2891
2909
}
@@ -2920,19 +2938,17 @@ class NamespaceFS {
2920
2938
2921
2939
dbg . log1 ( 'Namespace_fs._delete_latest_version:' , latest_ver_info , versioned_path , versioned_info ) ;
2922
2940
if ( latest_ver_info ) {
2923
- gpfs_options = is_gpfs ?
2924
- await this . _open_files_gpfs ( fs_context , latest_ver_path ,
2925
- undefined , undefined , undefined , undefined , true , versioned_info ) :
2926
- undefined ;
2941
+ gpfs_options = await this . _open_files_gpfs ( fs_context , latest_ver_path , undefined , undefined , undefined ,
2942
+ undefined , true , versioned_info ) ;
2927
2943
2928
2944
const suspended_and_latest_is_not_null = this . _is_versioning_suspended ( ) &&
2929
2945
latest_ver_info . version_id_str !== NULL_VERSION_ID ;
2930
2946
const bucket_tmp_dir_path = this . get_bucket_tmpdir_full_path ( ) ;
2931
2947
if ( this . _is_versioning_enabled ( ) || suspended_and_latest_is_not_null ) {
2932
2948
await native_fs_utils . _make_path_dirs ( versioned_path , fs_context ) ;
2933
- await native_fs_utils . safe_move ( fs_context , latest_ver_path , versioned_path , latest_ver_info ,
2934
- gpfs_options && gpfs_options . delete_version , bucket_tmp_dir_path ) ;
2935
- if ( suspended_and_latest_is_not_null ) {
2949
+ await native_fs_utils . safe_move ( fs_context , latest_ver_path , versioned_path , latest_ver_info ,
2950
+ gpfs_options ? .delete_version , bucket_tmp_dir_path ) ;
2951
+ if ( suspended_and_latest_is_not_null ) {
2936
2952
// remove a version (or delete marker) with null version ID from .versions/ (if exists)
2937
2953
await this . _delete_null_version_from_versions_directory ( params . key , fs_context ) ;
2938
2954
}
@@ -2948,6 +2964,7 @@ class NamespaceFS {
2948
2964
retries -= 1 ;
2949
2965
if ( retries <= 0 || ! native_fs_utils . should_retry_link_unlink ( is_gpfs , err ) ) throw err ;
2950
2966
dbg . warn ( `NamespaceFS._delete_latest_version: Retrying retries=${ retries } latest_ver_path=${ latest_ver_path } ` , err ) ;
2967
+ await P . delay ( 100 ) ;
2951
2968
} finally {
2952
2969
if ( gpfs_options ) await this . _close_files_gpfs ( fs_context , gpfs_options . delete_version , undefined , true ) ;
2953
2970
}
@@ -2965,29 +2982,29 @@ class NamespaceFS {
2965
2982
// This function removes an object version or delete marker with a null version ID inside .version/ directory
2966
2983
async _delete_null_version_from_versions_directory ( key , fs_context ) {
2967
2984
const is_gpfs = native_fs_utils . _is_gpfs ( fs_context ) ;
2968
- let retries = config . NSFS_RENAME_RETRIES ;
2969
2985
const null_versioned_path = this . _get_version_path ( key , NULL_VERSION_ID ) ;
2970
2986
await this . _check_path_in_bucket_boundaries ( fs_context , null_versioned_path ) ;
2971
-
2987
+ let gpfs_options ;
2988
+ let retries = config . NSFS_RENAME_RETRIES ;
2972
2989
for ( ; ; ) {
2973
2990
try {
2974
2991
const null_versioned_path_info = await this . _get_version_info ( fs_context , null_versioned_path ) ;
2975
2992
dbg . log1 ( 'Namespace_fs._delete_null_version_from_versions_directory:' , null_versioned_path , null_versioned_path_info ) ;
2976
- if ( null_versioned_path_info ) {
2977
- const gpfs_options = is_gpfs ?
2978
- await this . _open_files_gpfs ( fs_context , null_versioned_path , undefined , undefined , undefined , undefined , true ) :
2979
- undefined ;
2980
- const bucket_tmp_dir_path = this . get_bucket_tmpdir_full_path ( ) ;
2981
- await native_fs_utils . safe_unlink ( fs_context , null_versioned_path , null_versioned_path_info ,
2982
- gpfs_options ?. delete_version , bucket_tmp_dir_path ) ;
2993
+ if ( ! null_versioned_path_info ) return ;
2983
2994
2984
- if ( gpfs_options ) await this . _close_files_gpfs ( fs_context , gpfs_options ?. delete_version , undefined , true ) ;
2985
- }
2995
+ gpfs_options = await this . _open_files_gpfs ( fs_context , null_versioned_path , undefined , undefined , undefined ,
2996
+ undefined , true ) ;
2997
+ const bucket_tmp_dir_path = this . get_bucket_tmpdir_full_path ( ) ;
2998
+ await native_fs_utils . safe_unlink ( fs_context , null_versioned_path , null_versioned_path_info ,
2999
+ gpfs_options ?. delete_version , bucket_tmp_dir_path ) ;
2986
3000
break ;
2987
3001
} catch ( err ) {
2988
3002
retries -= 1 ;
2989
3003
if ( retries <= 0 || ! native_fs_utils . should_retry_link_unlink ( is_gpfs , err ) ) throw err ;
2990
3004
dbg . warn ( `NamespaceFS._delete_null_version_from_versions_directory Retrying retries=${ retries } null_versioned_path=${ null_versioned_path } ` , err ) ;
3005
+ } finally {
3006
+ if ( gpfs_options ) await this . _close_files_gpfs ( fs_context , gpfs_options . delete_version , undefined , true ) ;
3007
+ await P . delay ( 100 ) ;
2991
3008
}
2992
3009
}
2993
3010
}
@@ -3025,6 +3042,7 @@ class NamespaceFS {
3025
3042
return delete_marker_version_id ;
3026
3043
}
3027
3044
dbg . warn ( `NamespaceFS: _create_delete_marker failed retries=${ retries } ` , err ) ;
3045
+ await P . delay ( 100 ) ;
3028
3046
} finally {
3029
3047
if ( upload_params ) await this . complete_object_upload_finally ( undefined , undefined , upload_params . target_file , fs_context ) ;
3030
3048
}
@@ -3070,6 +3088,8 @@ class NamespaceFS {
3070
3088
// eslint-disable-next-line max-params
3071
3089
async _open_files_gpfs ( fs_context , src_path , dst_path , upload_or_dir_file , dst_ver_info , open_mode , delete_version , versioned_info ) {
3072
3090
dbg . log1 ( 'Namespace_fs._open_files_gpfs:' , src_path , src_path && path . dirname ( src_path ) , dst_path , upload_or_dir_file , dst_ver_info , open_mode , delete_version , versioned_info ) ;
3091
+ const is_gpfs = native_fs_utils . _is_gpfs ( fs_context ) ;
3092
+ if ( ! is_gpfs ) return ;
3073
3093
3074
3094
let src_file ;
3075
3095
let dst_file ;
@@ -3133,6 +3153,29 @@ class NamespaceFS {
3133
3153
}
3134
3154
}
3135
3155
3156
+ /**
3157
+ * _check_version_moved recieves cur_path and checks if the version moved
3158
+ * 1. if cur_path is in .versions/ - the function will check if the verison moved to latest
3159
+ * 2. if cur_path is the latest version - the function will check if the version moved to .versions/
3160
+ * @param {import('./nb').NativeFSContext } fs_context
3161
+ * @param {string } key
3162
+ * @param {string } version_id
3163
+ * @param {string } cur_path
3164
+ */
3165
+ async _check_version_moved ( fs_context , key , version_id , cur_path ) {
3166
+ const latest_version_path = this . _get_file_path ( { key } ) ;
3167
+ const verisoned_path = this . _get_version_path ( key , version_id ) ;
3168
+ if ( cur_path === latest_version_path ) {
3169
+ const verisoned_path_info = await this . _get_version_info ( fs_context , verisoned_path ) ;
3170
+ if ( verisoned_path_info ) throw error_utils . new_error_code ( 'VERSION_MOVED' , 'version file moved from latest to .versions/, retrying' ) ;
3171
+ } else if ( cur_path === verisoned_path ) {
3172
+ const latest_ver_info = await this . _get_version_info ( fs_context , latest_version_path ) ;
3173
+ if ( latest_ver_info && latest_ver_info . version_id_str === version_id ) throw error_utils . new_error_code ( 'VERSION_MOVED' , 'version file moved from .versions/ to latest, retrying' ) ;
3174
+ } else {
3175
+ throw new RpcError ( 'INTERNAL_ERROR' , `namespace_fs._check_version_moved failed, invalid cur_path=${ cur_path } ` ) ;
3176
+ }
3177
+ }
3178
+
3136
3179
async _throw_if_storage_class_not_supported ( storage_class ) {
3137
3180
if ( ! await this . _is_storage_class_supported ( storage_class ) ) {
3138
3181
throw new S3Error ( S3Error . InvalidStorageClass ) ;
0 commit comments