Skip to content

Commit a83fd51

Browse files
authored
Merge pull request libgit2#5396 from lhchavez/mwindow-file-limit
mwindow: set limit on number of open files
2 parents 26b9e48 + 92d42eb commit a83fd51

File tree

4 files changed

+281
-54
lines changed

4 files changed

+281
-54
lines changed

include/git2/common.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,9 @@ typedef enum {
205205
GIT_OPT_GET_PACK_MAX_OBJECTS,
206206
GIT_OPT_SET_PACK_MAX_OBJECTS,
207207
GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS,
208-
GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE
208+
GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE,
209+
GIT_OPT_GET_MWINDOW_FILE_LIMIT,
210+
GIT_OPT_SET_MWINDOW_FILE_LIMIT
209211
} git_libgit2_opt_t;
210212

211213
/**
@@ -227,8 +229,18 @@ typedef enum {
227229
*
228230
* * opts(GIT_OPT_SET_MWINDOW_MAPPED_LIMIT, size_t):
229231
*
230-
* >Set the maximum amount of memory that can be mapped at any time
231-
* by the library
232+
* > Set the maximum amount of memory that can be mapped at any time
233+
* > by the library
234+
*
235+
* * opts(GIT_OPT_GET_MWINDOW_FILE_LIMIT, size_t *):
236+
*
237+
* > Get the maximum number of files that will be mapped at any time by the
238+
* > library
239+
*
240+
* * opts(GIT_OPT_SET_MWINDOW_FILE_LIMIT, size_t):
241+
*
242+
* > Set the maximum number of files that can be mapped at any time
243+
* > by the library. The default (0) is unlimited.
232244
*
233245
* * opts(GIT_OPT_GET_SEARCH_PATH, int level, git_buf *buf)
234246
*

src/mwindow.c

Lines changed: 121 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@
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+
2528
size_t git_mwindow__window_size = DEFAULT_WINDOW_SIZE;
2629
size_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 */
3236
git_strmap *git__pack_cache = NULL;
@@ -132,7 +136,7 @@ void git_mwindow_free_all(git_mwindow_file *mwf)
132136
*/
133137
void 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 */
246312
static 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

368433
int 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

390460
void 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

src/settings.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ int git_libgit2_features(void)
5959
/* Declarations for tuneable settings */
6060
extern size_t git_mwindow__window_size;
6161
extern size_t git_mwindow__mapped_limit;
62+
extern size_t git_mwindow__file_limit;
6263
extern size_t git_indexer__max_objects;
6364
extern bool git_disable_pack_keep_file_checks;
6465

@@ -124,6 +125,14 @@ int git_libgit2_opts(int key, ...)
124125
*(va_arg(ap, size_t *)) = git_mwindow__mapped_limit;
125126
break;
126127

128+
case GIT_OPT_SET_MWINDOW_FILE_LIMIT:
129+
git_mwindow__file_limit = va_arg(ap, size_t);
130+
break;
131+
132+
case GIT_OPT_GET_MWINDOW_FILE_LIMIT:
133+
*(va_arg(ap, size_t *)) = git_mwindow__file_limit;
134+
break;
135+
127136
case GIT_OPT_GET_SEARCH_PATH:
128137
if ((error = config_level_to_sysdir(va_arg(ap, int))) >= 0) {
129138
git_buf *out = va_arg(ap, git_buf *);

0 commit comments

Comments
 (0)