2222#define DEFAULT_MAPPED_LIMIT \
2323 ((1024 * 1024) * (sizeof(void*) >= 8 ? 8192ULL : 256UL))
2424
25+ /* default is unlimited */
26+ #define DEFAULT_FILE_LIMIT 0
27+
2528size_t git_mwindow__window_size = DEFAULT_WINDOW_SIZE ;
2629size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT ;
30+ size_t git_mwindow__file_limit = DEFAULT_FILE_LIMIT ;
2731
2832/* Whenever you want to read or modify this, grab git__mwindow_mutex */
29- static git_mwindow_ctl mem_ctl ;
33+ git_mwindow_ctl git_mwindow__mem_ctl ;
3034
3135/* Global list of mwindow files, to open packs once across repos */
3236git_strmap * git__pack_cache = NULL ;
@@ -132,7 +136,7 @@ void git_mwindow_free_all(git_mwindow_file *mwf)
132136 */
133137void git_mwindow_free_all_locked (git_mwindow_file * mwf )
134138{
135- git_mwindow_ctl * ctl = & mem_ctl ;
139+ git_mwindow_ctl * ctl = & git_mwindow__mem_ctl ;
136140 size_t i ;
137141
138142 /*
@@ -174,82 +178,143 @@ int git_mwindow_contains(git_mwindow *win, off64_t offset)
174178 && offset <= (off64_t )(win_off + win -> window_map .len );
175179}
176180
181+ #define GIT_MWINDOW__LRU -1
182+ #define GIT_MWINDOW__MRU 1
183+
177184/*
178- * Find the least-recently-used window in a file
185+ * Find the least- or most-recently-used window in a file that is not currently
186+ * being used. The 'only_unused' flag controls whether the caller requires the
187+ * file to only have unused windows.
188+ *
189+ * Returns whether such a window was found in the file.
179190 */
180- static void git_mwindow_scan_lru (
181- git_mwindow_file * mwf ,
182- git_mwindow * * lru_w ,
183- git_mwindow * * lru_l )
191+ static bool git_mwindow_scan_recently_used (
192+ git_mwindow_file * mwf ,
193+ git_mwindow * * out_window ,
194+ git_mwindow * * out_last ,
195+ bool only_unused ,
196+ int comparison_sign )
184197{
185- git_mwindow * w , * w_l ;
186-
187- for (w_l = NULL , w = mwf -> windows ; w ; w = w -> next ) {
188- if (!w -> inuse_cnt ) {
189- /*
190- * If the current one is more recent than the last one,
191- * store it in the output parameter. If lru_w is NULL,
192- * it's the first loop, so store it as well.
193- */
194- if (!* lru_w || w -> last_used < (* lru_w )-> last_used ) {
195- * lru_w = w ;
196- * lru_l = w_l ;
197- }
198+ git_mwindow * w , * w_last ;
199+ git_mwindow * lru_window = NULL , * lru_last = NULL ;
200+
201+ assert (mwf );
202+ assert (out_window );
203+
204+ lru_window = * out_window ;
205+ if (out_last )
206+ lru_last = * out_last ;
207+
208+ for (w_last = NULL , w = mwf -> windows ; w ; w_last = w , w = w -> next ) {
209+ if (w -> inuse_cnt ) {
210+ if (only_unused )
211+ return false;
212+ /* This window is currently being used. Skip it. */
213+ continue ;
214+ }
215+
216+ /*
217+ * If the current one is more (or less) recent than the last one,
218+ * store it in the output parameter. If lru_window is NULL,
219+ * it's the first loop, so store it as well.
220+ */
221+ if (!lru_window ||
222+ (comparison_sign == GIT_MWINDOW__LRU && lru_window -> last_used > w -> last_used ) ||
223+ (comparison_sign == GIT_MWINDOW__MRU && lru_window -> last_used < w -> last_used )) {
224+ lru_window = w ;
225+ lru_last = w_last ;
198226 }
199- w_l = w ;
200227 }
228+
229+ if (!lru_window && !lru_last )
230+ return false;
231+
232+ * out_window = lru_window ;
233+ if (out_last )
234+ * out_last = lru_last ;
235+ return true;
201236}
202237
203238/*
204- * Close the least recently used window. You should check to see if
205- * the file descriptors need closing from time to time. Called under
206- * lock from new_window.
239+ * Close the least recently used window (that is currently not being used) out
240+ * of all the files. Called under lock from new_window.
207241 */
208- static int git_mwindow_close_lru ( git_mwindow_file * mwf )
242+ static int git_mwindow_close_lru_window ( void )
209243{
210- git_mwindow_ctl * ctl = & mem_ctl ;
244+ git_mwindow_ctl * ctl = & git_mwindow__mem_ctl ;
245+ git_mwindow_file * cur ;
211246 size_t i ;
212- git_mwindow * lru_w = NULL , * lru_l = NULL , * * list = & mwf -> windows ;
213-
214- /* FIXME: Does this give us any advantage? */
215- if (mwf -> windows )
216- git_mwindow_scan_lru (mwf , & lru_w , & lru_l );
247+ git_mwindow * lru_window = NULL , * lru_last = NULL , * * list = NULL ;
217248
218- for (i = 0 ; i < ctl -> windowfiles .length ; ++ i ) {
219- git_mwindow * last = lru_w ;
220- git_mwindow_file * cur = git_vector_get (& ctl -> windowfiles , i );
221- git_mwindow_scan_lru (cur , & lru_w , & lru_l );
222- if (lru_w != last )
249+ git_vector_foreach (& ctl -> windowfiles , i , cur ) {
250+ if (git_mwindow_scan_recently_used (
251+ cur , & lru_window , & lru_last , false, GIT_MWINDOW__LRU )) {
223252 list = & cur -> windows ;
253+ }
224254 }
225255
226- if (!lru_w ) {
256+ if (!lru_window ) {
227257 git_error_set (GIT_ERROR_OS , "failed to close memory window; couldn't find LRU" );
228258 return -1 ;
229259 }
230260
231- ctl -> mapped -= lru_w -> window_map .len ;
232- git_futils_mmap_free (& lru_w -> window_map );
261+ ctl -> mapped -= lru_window -> window_map .len ;
262+ git_futils_mmap_free (& lru_window -> window_map );
233263
234- if (lru_l )
235- lru_l -> next = lru_w -> next ;
264+ if (lru_last )
265+ lru_last -> next = lru_window -> next ;
236266 else
237- * list = lru_w -> next ;
267+ * list = lru_window -> next ;
238268
239- git__free (lru_w );
269+ git__free (lru_window );
240270 ctl -> open_windows -- ;
241271
242272 return 0 ;
243273}
244274
275+ /*
276+ * Close the file that does not have any open windows AND whose
277+ * most-recently-used window is the least-recently used one across all
278+ * currently open files.
279+ *
280+ * Called under lock from new_window.
281+ */
282+ static int git_mwindow_close_lru_file (void )
283+ {
284+ git_mwindow_ctl * ctl = & git_mwindow__mem_ctl ;
285+ git_mwindow_file * lru_file = NULL , * current_file = NULL ;
286+ git_mwindow * lru_window = NULL ;
287+ size_t i ;
288+
289+ git_vector_foreach (& ctl -> windowfiles , i , current_file ) {
290+ git_mwindow * mru_window = NULL ;
291+ if (!git_mwindow_scan_recently_used (
292+ current_file , & mru_window , NULL , true, GIT_MWINDOW__MRU )) {
293+ continue ;
294+ }
295+ if (!lru_window || lru_window -> last_used > mru_window -> last_used )
296+ lru_file = current_file ;
297+ }
298+
299+ if (!lru_file ) {
300+ git_error_set (GIT_ERROR_OS , "failed to close memory window file; couldn't find LRU" );
301+ return -1 ;
302+ }
303+
304+ git_mwindow_free_all_locked (lru_file );
305+ p_close (lru_file -> fd );
306+ lru_file -> fd = -1 ;
307+
308+ return 0 ;
309+ }
310+
245311/* This gets called under lock from git_mwindow_open */
246312static git_mwindow * new_window (
247- git_mwindow_file * mwf ,
248313 git_file fd ,
249314 off64_t size ,
250315 off64_t offset )
251316{
252- git_mwindow_ctl * ctl = & mem_ctl ;
317+ git_mwindow_ctl * ctl = & git_mwindow__mem_ctl ;
253318 size_t walign = git_mwindow__window_size / 2 ;
254319 off64_t len ;
255320 git_mwindow * w ;
@@ -269,7 +334,7 @@ static git_mwindow *new_window(
269334 ctl -> mapped += (size_t )len ;
270335
271336 while (git_mwindow__mapped_limit < ctl -> mapped &&
272- git_mwindow_close_lru ( mwf ) == 0 ) /* nop */ ;
337+ git_mwindow_close_lru_window ( ) == 0 ) /* nop */ ;
273338
274339 /*
275340 * We treat `mapped_limit` as a soft limit. If we can't find a
@@ -283,7 +348,7 @@ static git_mwindow *new_window(
283348 * we're below our soft limits, so free up what we can and try again.
284349 */
285350
286- while (git_mwindow_close_lru ( mwf ) == 0 )
351+ while (git_mwindow_close_lru_window ( ) == 0 )
287352 /* nop */ ;
288353
289354 if (git_futils_mmap_ro (& w -> window_map , fd , w -> offset , (size_t )len ) < 0 ) {
@@ -315,7 +380,7 @@ unsigned char *git_mwindow_open(
315380 size_t extra ,
316381 unsigned int * left )
317382{
318- git_mwindow_ctl * ctl = & mem_ctl ;
383+ git_mwindow_ctl * ctl = & git_mwindow__mem_ctl ;
319384 git_mwindow * w = * cursor ;
320385
321386 if (git_mutex_lock (& git__mwindow_mutex )) {
@@ -339,7 +404,7 @@ unsigned char *git_mwindow_open(
339404 * one.
340405 */
341406 if (!w ) {
342- w = new_window (mwf , mwf -> fd , mwf -> size , offset );
407+ w = new_window (mwf -> fd , mwf -> size , offset );
343408 if (w == NULL ) {
344409 git_mutex_unlock (& git__mwindow_mutex );
345410 return NULL ;
@@ -367,7 +432,7 @@ unsigned char *git_mwindow_open(
367432
368433int git_mwindow_file_register (git_mwindow_file * mwf )
369434{
370- git_mwindow_ctl * ctl = & mem_ctl ;
435+ git_mwindow_ctl * ctl = & git_mwindow__mem_ctl ;
371436 int ret ;
372437
373438 if (git_mutex_lock (& git__mwindow_mutex )) {
@@ -381,6 +446,11 @@ int git_mwindow_file_register(git_mwindow_file *mwf)
381446 return -1 ;
382447 }
383448
449+ if (git_mwindow__file_limit ) {
450+ while (git_mwindow__file_limit <= ctl -> windowfiles .length &&
451+ git_mwindow_close_lru_file () == 0 ) /* nop */ ;
452+ }
453+
384454 ret = git_vector_insert (& ctl -> windowfiles , mwf );
385455 git_mutex_unlock (& git__mwindow_mutex );
386456
@@ -389,7 +459,7 @@ int git_mwindow_file_register(git_mwindow_file *mwf)
389459
390460void git_mwindow_file_deregister (git_mwindow_file * mwf )
391461{
392- git_mwindow_ctl * ctl = & mem_ctl ;
462+ git_mwindow_ctl * ctl = & git_mwindow__mem_ctl ;
393463 git_mwindow_file * cur ;
394464 size_t i ;
395465
0 commit comments