@@ -18,6 +18,7 @@ const stream_utils = require('../util/stream_utils');
18
18
const buffer_utils = require ( '../util/buffer_utils' ) ;
19
19
const size_utils = require ( '../util/size_utils' ) ;
20
20
const native_fs_utils = require ( '../util/native_fs_utils' ) ;
21
+ const js_utils = require ( '../util/js_utils' ) ;
21
22
const ChunkFS = require ( '../util/chunk_fs' ) ;
22
23
const LRUCache = require ( '../util/lru_cache' ) ;
23
24
const nb_native = require ( '../util/nb_native' ) ;
@@ -90,14 +91,48 @@ const XATTR_METADATA_IGNORE_LIST = [
90
91
] ;
91
92
92
93
/**
93
- * @param {fs.Dirent } a
94
- * @param {fs.Dirent } b
95
- * @returns {1|-1|0 }
94
+ * get_simple_cache returns a simple function
95
+ * which can be used to access the cached data
96
+ *
97
+ * If the cached data doesn't exist then the
98
+ * given loader function is called with the
99
+ * requested key and the result is cached.
100
+ *
101
+ * There is no cache invalidation.
102
+ * @template {string | number | symbol} T
103
+ * @template U
104
+ *
105
+ * @param {(key: T) => U } loader
106
+ * @param {Partial<Record<T, U>> } [cache={}]
107
+ * @returns {(key: T) => U }
96
108
*/
97
- function sort_entries_by_name ( a , b ) {
98
- if ( a . name < b . name ) return - 1 ;
99
- if ( a . name > b . name ) return 1 ;
100
- return 0 ;
109
+ function get_simple_cache ( loader , cache = { } ) {
110
+ return key => {
111
+ const cached = cache [ key ] ;
112
+ if ( cached ) return cached ;
113
+
114
+ const computed = loader ( key ) ;
115
+ cache [ key ] = computed ;
116
+ return computed ;
117
+ } ;
118
+ }
119
+
120
+ /**
121
+ * sort_entries_by_name_with_cache generates a sorter which
122
+ * sorts by UTF8 and caches the intermediate buffers generated
123
+ * to avoid recomputation
124
+ * @param {Record<string, Buffer> } [cache={}]
125
+ * @returns {(a: fs.Dirent, b: fs.Dirent) => -1|0|1 }
126
+ */
127
+ function sort_entries_by_name_with_cache ( cache = { } ) {
128
+ const get_cached_name = get_simple_cache ( name => Buffer . from ( name , 'utf8' ) , cache ) ;
129
+
130
+ return function ( a , b ) {
131
+ const a_name = get_cached_name ( a . name ) ;
132
+ const b_name = get_cached_name ( b . name ) ;
133
+
134
+ return Buffer . compare ( a_name , b_name ) ;
135
+ } ;
101
136
}
102
137
103
138
function _get_version_id_by_stat ( { ino, mtimeNsBigint} ) {
@@ -144,27 +179,33 @@ function _get_filename(file_name) {
144
179
}
145
180
return file_name ;
146
181
}
182
+
147
183
/**
148
- * @param {fs.Dirent } first_entry
149
- * @param {fs.Dirent } second_entry
150
- * @returns {Number }
184
+ * sort_entries_by_name_and_time_with_cache generates a sorter which sorts
185
+ * items but UTF8 encoding of the name and in case of conflict uses mtime
186
+ * to resolve it.
187
+ * @param {Record<string, Buffer> } [cache={}]
188
+ * @returns {(first_entry: fs.Dirent, second_entry: fs.Dirent) => -1|0|1 }
151
189
*/
152
- function sort_entries_by_name_and_time ( first_entry , second_entry ) {
153
- const first_entry_name = _get_filename ( first_entry . name ) ;
154
- const second_entry_name = _get_filename ( second_entry . name ) ;
155
- if ( first_entry_name === second_entry_name ) {
156
- const first_entry_mtime = _get_mtime_from_filename ( first_entry . name ) ;
157
- const second_entry_mtime = _get_mtime_from_filename ( second_entry . name ) ;
158
- // To sort the versions in the latest first order,
159
- // below logic is followed
160
- if ( second_entry_mtime < first_entry_mtime ) return - 1 ;
161
- if ( second_entry_mtime > first_entry_mtime ) return 1 ;
162
- return 0 ;
163
- } else {
164
- if ( first_entry_name < second_entry_name ) return - 1 ;
165
- if ( first_entry_name > second_entry_name ) return 1 ;
166
- return 0 ;
167
- }
190
+ function sort_entries_by_name_and_time_with_cache ( cache = { } ) {
191
+ const _get_filename_with_cache = get_simple_cache ( name => Buffer . from ( _get_filename ( name ) , 'utf8' ) , cache ) ;
192
+ return function ( first_entry , second_entry ) {
193
+ const first_entry_name = _get_filename_with_cache ( first_entry . name ) ;
194
+ const second_entry_name = _get_filename_with_cache ( second_entry . name ) ;
195
+
196
+ const compare_result = Buffer . compare ( first_entry_name , second_entry_name ) ;
197
+ if ( compare_result === 0 ) {
198
+ const first_entry_mtime = _get_mtime_from_filename ( first_entry . name ) ;
199
+ const second_entry_mtime = _get_mtime_from_filename ( second_entry . name ) ;
200
+ // To sort the versions in the latest first order,
201
+ // below logic is followed
202
+ if ( second_entry_mtime < first_entry_mtime ) return - 1 ;
203
+ if ( second_entry_mtime > first_entry_mtime ) return 1 ;
204
+ return 0 ;
205
+ }
206
+
207
+ return compare_result ;
208
+ } ;
168
209
}
169
210
170
211
// This is helper function for list object version
@@ -249,14 +290,6 @@ function is_sparse_file(stat) {
249
290
return ( stat . blocks * 512 < stat . size ) ;
250
291
}
251
292
252
- /**
253
- * @param {fs.Dirent } e
254
- * @returns {string }
255
- */
256
- function get_entry_name ( e ) {
257
- return e . name ;
258
- }
259
-
260
293
/**
261
294
* @param {string } name
262
295
* @returns {fs.Dirent }
@@ -306,14 +339,14 @@ function to_fs_xattr(xattr) {
306
339
const dir_cache = new LRUCache ( {
307
340
name : 'nsfs-dir-cache' ,
308
341
make_key : ( { dir_path } ) => dir_path ,
309
- load : async ( { dir_path, fs_context } ) => {
342
+ load : async ( { dir_path, fs_context, dirent_name_cache = { } } ) => {
310
343
const time = Date . now ( ) ;
311
344
const stat = await nb_native ( ) . fs . stat ( fs_context , dir_path ) ;
312
345
let sorted_entries ;
313
346
let usage = config . NSFS_DIR_CACHE_MIN_DIR_SIZE ;
314
347
if ( stat . size <= config . NSFS_DIR_CACHE_MAX_DIR_SIZE ) {
315
348
sorted_entries = await nb_native ( ) . fs . readdir ( fs_context , dir_path ) ;
316
- sorted_entries . sort ( sort_entries_by_name ) ;
349
+ sorted_entries . sort ( sort_entries_by_name_with_cache ( dirent_name_cache ) ) ;
317
350
for ( const ent of sorted_entries ) {
318
351
usage += ent . name . length + 4 ;
319
352
}
@@ -341,7 +374,7 @@ const dir_cache = new LRUCache({
341
374
const versions_dir_cache = new LRUCache ( {
342
375
name : 'nsfs-versions-dir-cache' ,
343
376
make_key : ( { dir_path } ) => dir_path ,
344
- load : async ( { dir_path, fs_context } ) => {
377
+ load : async ( { dir_path, fs_context, dirent_name_cache = { } } ) => {
345
378
const time = Date . now ( ) ;
346
379
const stat = await nb_native ( ) . fs . stat ( fs_context , dir_path ) ;
347
380
const version_path = dir_path + "/" + HIDDEN_VERSIONS_PATH ;
@@ -375,7 +408,7 @@ const versions_dir_cache = new LRUCache({
375
408
old_versions_after_rename
376
409
} = await _rename_null_version ( old_versions , fs_context , version_path ) ;
377
410
const entries = latest_versions . concat ( old_versions_after_rename ) ;
378
- sorted_entries = entries . sort ( sort_entries_by_name_and_time ) ;
411
+ sorted_entries = entries . sort ( sort_entries_by_name_and_time_with_cache ( dirent_name_cache ) ) ;
379
412
// rename back version to include 'null' suffix.
380
413
if ( renamed_null_versions_set . size > 0 ) {
381
414
for ( const ent of sorted_entries ) {
@@ -387,7 +420,7 @@ const versions_dir_cache = new LRUCache({
387
420
}
388
421
}
389
422
} else {
390
- sorted_entries = latest_versions . sort ( sort_entries_by_name ) ;
423
+ sorted_entries = latest_versions . sort ( sort_entries_by_name_with_cache ( dirent_name_cache ) ) ;
391
424
}
392
425
/*eslint no-unused-expressions: ["error", { "allowTernary": true }]*/
393
426
for ( const ent of sorted_entries ) {
@@ -647,6 +680,10 @@ class NamespaceFS {
647
680
/** @type {Result[] } */
648
681
const results = [ ] ;
649
682
683
+ const _dirent_name_cache = { } ;
684
+ /** @type {(key: string) => Buffer } */
685
+ const dirent_name_cache = get_simple_cache ( key => Buffer . from ( key , 'utf8' ) , _dirent_name_cache ) ;
686
+
650
687
/**
651
688
* @param {string } dir_key
652
689
* @returns {Promise<void> }
@@ -655,6 +692,7 @@ class NamespaceFS {
655
692
if ( this . _is_hidden_version_path ( dir_key ) ) {
656
693
return ;
657
694
}
695
+
658
696
// /** @type {fs.Dir } */
659
697
let dir_handle ;
660
698
/** @type {ReaddirCacheItem } */
@@ -688,9 +726,11 @@ class NamespaceFS {
688
726
// Since versions are arranged next to latest object in the latest first order,
689
727
// no need to find the sorted last index. Push the ".versions/#VERSION_OBJECT" as
690
728
// they are in order
691
- if ( results . length && r . key < results [ results . length - 1 ] . key &&
729
+
730
+ const r_key_buf = dirent_name_cache ( r . key ) ;
731
+ if ( results . length && Buffer . compare ( r_key_buf , dirent_name_cache ( results [ results . length - 1 ] . key ) ) === - 1 &&
692
732
! this . _is_hidden_version_path ( r . key ) ) {
693
- pos = _ . sortedLastIndexBy ( results , r , a => a . key ) ;
733
+ pos = js_utils . sortedLastIndexBy ( results , curr => Buffer . compare ( dirent_name_cache ( curr . key ) , r_key_buf ) === - 1 ) ;
694
734
} else {
695
735
pos = results . length ;
696
736
}
@@ -720,7 +760,7 @@ class NamespaceFS {
720
760
const process_entry = async ent => {
721
761
// dbg.log0('process_entry', dir_key, ent.name);
722
762
if ( ( ! ent . name . startsWith ( prefix_ent ) ||
723
- ent . name < marker_curr ||
763
+ Buffer . compare ( dirent_name_cache ( ent . name ) , dirent_name_cache ( marker_curr ) ) === - 1 ||
724
764
ent . name === this . get_bucket_tmpdir_name ( ) ||
725
765
ent . name === config . NSFS_FOLDER_OBJECT_NAME ) ||
726
766
this . _is_hidden_version_path ( ent . name ) ) {
@@ -748,9 +788,17 @@ class NamespaceFS {
748
788
if ( ! ( await this . check_access ( fs_context , dir_path ) ) ) return ;
749
789
try {
750
790
if ( list_versions ) {
751
- cached_dir = await versions_dir_cache . get_with_cache ( { dir_path, fs_context } ) ;
791
+ cached_dir = await versions_dir_cache . get_with_cache ( {
792
+ dir_path,
793
+ fs_context,
794
+ dirent_name_cache : _dirent_name_cache ,
795
+ } ) ;
752
796
} else {
753
- cached_dir = await dir_cache . get_with_cache ( { dir_path, fs_context } ) ;
797
+ cached_dir = await dir_cache . get_with_cache ( {
798
+ dir_path,
799
+ fs_context,
800
+ dirent_name_cache : _dirent_name_cache ,
801
+ } ) ;
754
802
}
755
803
} catch ( err ) {
756
804
if ( [ 'ENOENT' , 'ENOTDIR' ] . includes ( err . code ) ) {
@@ -786,11 +834,13 @@ class NamespaceFS {
786
834
{ name : start_marker }
787
835
) + 1 ;
788
836
} else {
789
- marker_index = _ . sortedLastIndexBy (
790
- sorted_entries ,
791
- make_named_dirent ( marker_curr ) ,
792
- get_entry_name
793
- ) ;
837
+ const marker_curr_buf = dirent_name_cache ( make_named_dirent ( marker_curr ) . name ) ;
838
+
839
+ marker_index = js_utils . sortedLastIndexBy ( sorted_entries , curr => {
840
+ const curr_cache_buf = dirent_name_cache ( curr . name ) ;
841
+ const compare_res = Buffer . compare ( curr_cache_buf , marker_curr_buf ) ;
842
+ return compare_res === - 1 || compare_res === 0 ;
843
+ } ) ;
794
844
}
795
845
796
846
// handling a scenario in which key_marker points to an object inside a directory
0 commit comments