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