2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
17 #define DEFAULT_WINDOW_SIZE \
19 ? 1 * 1024 * 1024 * 1024 \
22 #define DEFAULT_MAPPED_LIMIT \
23 ((1024 * 1024) * (sizeof(void*) >= 8 ? 8192ULL : 256UL))
25 /* default is unlimited */
26 #define DEFAULT_FILE_LIMIT 0
28 size_t git_mwindow__window_size
= DEFAULT_WINDOW_SIZE
;
29 size_t git_mwindow__mapped_limit
= DEFAULT_MAPPED_LIMIT
;
30 size_t git_mwindow__file_limit
= DEFAULT_FILE_LIMIT
;
32 /* Whenever you want to read or modify this, grab git__mwindow_mutex */
33 git_mwindow_ctl git_mwindow__mem_ctl
;
35 /* Global list of mwindow files, to open packs once across repos */
36 git_strmap
*git__pack_cache
= NULL
;
38 static void git_mwindow_files_free(void)
40 git_strmap
*tmp
= git__pack_cache
;
42 git__pack_cache
= NULL
;
46 int git_mwindow_global_init(void)
48 assert(!git__pack_cache
);
50 git__on_shutdown(git_mwindow_files_free
);
51 return git_strmap_new(&git__pack_cache
);
54 int git_mwindow_get_pack(struct git_pack_file
**out
, const char *path
)
56 struct git_pack_file
*pack
;
60 if ((error
= git_packfile__name(&packname
, path
)) < 0)
63 if (git_mutex_lock(&git__mwindow_mutex
) < 0) {
64 git_error_set(GIT_ERROR_OS
, "failed to lock mwindow mutex");
68 pack
= git_strmap_get(git__pack_cache
, packname
);
72 git_atomic_inc(&pack
->refcount
);
73 git_mutex_unlock(&git__mwindow_mutex
);
78 /* If we didn't find it, we need to create it */
79 if ((error
= git_packfile_alloc(&pack
, path
)) < 0) {
80 git_mutex_unlock(&git__mwindow_mutex
);
84 git_atomic_inc(&pack
->refcount
);
86 error
= git_strmap_set(git__pack_cache
, pack
->pack_name
, pack
);
87 git_mutex_unlock(&git__mwindow_mutex
);
90 git_packfile_free(pack
);
98 void git_mwindow_put_pack(struct git_pack_file
*pack
)
102 if (git_mutex_lock(&git__mwindow_mutex
) < 0)
105 /* put before get would be a corrupted state */
106 assert(git__pack_cache
);
108 /* if we cannot find it, the state is corrupted */
109 assert(git_strmap_exists(git__pack_cache
, pack
->pack_name
));
111 count
= git_atomic_dec(&pack
->refcount
);
113 git_strmap_delete(git__pack_cache
, pack
->pack_name
);
114 git_packfile_free(pack
);
117 git_mutex_unlock(&git__mwindow_mutex
);
121 void git_mwindow_free_all(git_mwindow_file
*mwf
)
123 if (git_mutex_lock(&git__mwindow_mutex
)) {
124 git_error_set(GIT_ERROR_THREAD
, "unable to lock mwindow mutex");
128 git_mwindow_free_all_locked(mwf
);
130 git_mutex_unlock(&git__mwindow_mutex
);
134 * Free all the windows in a sequence, typically because we're done
137 void git_mwindow_free_all_locked(git_mwindow_file
*mwf
)
139 git_mwindow_ctl
*ctl
= &git_mwindow__mem_ctl
;
143 * Remove these windows from the global list
145 for (i
= 0; i
< ctl
->windowfiles
.length
; ++i
){
146 if (git_vector_get(&ctl
->windowfiles
, i
) == mwf
) {
147 git_vector_remove(&ctl
->windowfiles
, i
);
152 if (ctl
->windowfiles
.length
== 0) {
153 git_vector_free(&ctl
->windowfiles
);
154 ctl
->windowfiles
.contents
= NULL
;
157 while (mwf
->windows
) {
158 git_mwindow
*w
= mwf
->windows
;
159 assert(w
->inuse_cnt
== 0);
161 ctl
->mapped
-= w
->window_map
.len
;
164 git_futils_mmap_free(&w
->window_map
);
166 mwf
->windows
= w
->next
;
172 * Check if a window 'win' contains the address 'offset'
174 int git_mwindow_contains(git_mwindow
*win
, off64_t offset
)
176 off64_t win_off
= win
->offset
;
177 return win_off
<= offset
178 && offset
<= (off64_t
)(win_off
+ win
->window_map
.len
);
181 #define GIT_MWINDOW__LRU -1
182 #define GIT_MWINDOW__MRU 1
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. If '*out_window' is non-null, it is used as
188 * a starting point for the comparison.
190 * Returns whether such a window was found in the file.
192 static bool git_mwindow_scan_recently_used(
193 git_mwindow_file
*mwf
,
194 git_mwindow
**out_window
,
195 git_mwindow
**out_last
,
199 git_mwindow
*w
, *w_last
;
200 git_mwindow
*lru_window
= NULL
, *lru_last
= NULL
;
206 lru_window
= *out_window
;
208 lru_last
= *out_last
;
210 for (w_last
= NULL
, w
= mwf
->windows
; w
; w_last
= w
, w
= w
->next
) {
214 /* This window is currently being used. Skip it. */
219 * If the current one is more (or less) recent than the last one,
220 * store it in the output parameter. If lru_window is NULL,
221 * it's the first loop, so store it as well.
224 (comparison_sign
== GIT_MWINDOW__LRU
&& lru_window
->last_used
> w
->last_used
) ||
225 (comparison_sign
== GIT_MWINDOW__MRU
&& lru_window
->last_used
< w
->last_used
)) {
235 *out_window
= lru_window
;
237 *out_last
= lru_last
;
242 * Close the least recently used window (that is currently not being used) out
243 * of all the files. Called under lock from new_window.
245 static int git_mwindow_close_lru_window(void)
247 git_mwindow_ctl
*ctl
= &git_mwindow__mem_ctl
;
248 git_mwindow_file
*cur
;
250 git_mwindow
*lru_window
= NULL
, *lru_last
= NULL
, **list
= NULL
;
252 git_vector_foreach(&ctl
->windowfiles
, i
, cur
) {
253 if (git_mwindow_scan_recently_used(
254 cur
, &lru_window
, &lru_last
, false, GIT_MWINDOW__LRU
)) {
255 list
= &cur
->windows
;
260 git_error_set(GIT_ERROR_OS
, "failed to close memory window; couldn't find LRU");
264 ctl
->mapped
-= lru_window
->window_map
.len
;
265 git_futils_mmap_free(&lru_window
->window_map
);
268 lru_last
->next
= lru_window
->next
;
270 *list
= lru_window
->next
;
272 git__free(lru_window
);
279 * Close the file that does not have any open windows AND whose
280 * most-recently-used window is the least-recently used one across all
281 * currently open files.
283 * Called under lock from new_window.
285 static int git_mwindow_close_lru_file(void)
287 git_mwindow_ctl
*ctl
= &git_mwindow__mem_ctl
;
288 git_mwindow_file
*lru_file
= NULL
, *current_file
= NULL
;
289 git_mwindow
*lru_window
= NULL
;
292 git_vector_foreach(&ctl
->windowfiles
, i
, current_file
) {
293 git_mwindow
*mru_window
= NULL
;
294 if (!git_mwindow_scan_recently_used(
295 current_file
, &mru_window
, NULL
, true, GIT_MWINDOW__MRU
)) {
298 if (!lru_window
|| lru_window
->last_used
> mru_window
->last_used
)
299 lru_file
= current_file
;
303 git_error_set(GIT_ERROR_OS
, "failed to close memory window file; couldn't find LRU");
307 git_mwindow_free_all_locked(lru_file
);
308 p_close(lru_file
->fd
);
314 /* This gets called under lock from git_mwindow_open */
315 static git_mwindow
*new_window(
320 git_mwindow_ctl
*ctl
= &git_mwindow__mem_ctl
;
321 size_t walign
= git_mwindow__window_size
/ 2;
325 w
= git__malloc(sizeof(*w
));
330 memset(w
, 0x0, sizeof(*w
));
331 w
->offset
= (offset
/ walign
) * walign
;
333 len
= size
- w
->offset
;
334 if (len
> (off64_t
)git_mwindow__window_size
)
335 len
= (off64_t
)git_mwindow__window_size
;
337 ctl
->mapped
+= (size_t)len
;
339 while (git_mwindow__mapped_limit
< ctl
->mapped
&&
340 git_mwindow_close_lru_window() == 0) /* nop */;
343 * We treat `mapped_limit` as a soft limit. If we can't find a
344 * window to close and are above the limit, we still mmap the new
348 if (git_futils_mmap_ro(&w
->window_map
, fd
, w
->offset
, (size_t)len
) < 0) {
350 * The first error might be down to memory fragmentation even if
351 * we're below our soft limits, so free up what we can and try again.
354 while (git_mwindow_close_lru_window() == 0)
357 if (git_futils_mmap_ro(&w
->window_map
, fd
, w
->offset
, (size_t)len
) < 0) {
366 if (ctl
->mapped
> ctl
->peak_mapped
)
367 ctl
->peak_mapped
= ctl
->mapped
;
369 if (ctl
->open_windows
> ctl
->peak_open_windows
)
370 ctl
->peak_open_windows
= ctl
->open_windows
;
376 * Open a new window, closing the least recenty used until we have
377 * enough space. Don't forget to add it to your list
379 unsigned char *git_mwindow_open(
380 git_mwindow_file
*mwf
,
381 git_mwindow
**cursor
,
386 git_mwindow_ctl
*ctl
= &git_mwindow__mem_ctl
;
387 git_mwindow
*w
= *cursor
;
389 if (git_mutex_lock(&git__mwindow_mutex
)) {
390 git_error_set(GIT_ERROR_THREAD
, "unable to lock mwindow mutex");
394 if (!w
|| !(git_mwindow_contains(w
, offset
) && git_mwindow_contains(w
, offset
+ extra
))) {
399 for (w
= mwf
->windows
; w
; w
= w
->next
) {
400 if (git_mwindow_contains(w
, offset
) &&
401 git_mwindow_contains(w
, offset
+ extra
))
406 * If there isn't a suitable window, we need to create a new
410 w
= new_window(mwf
->fd
, mwf
->size
, offset
);
412 git_mutex_unlock(&git__mwindow_mutex
);
415 w
->next
= mwf
->windows
;
420 /* If we changed w, store it in the cursor */
422 w
->last_used
= ctl
->used_ctr
++;
430 *left
= (unsigned int)(w
->window_map
.len
- offset
);
432 git_mutex_unlock(&git__mwindow_mutex
);
433 return (unsigned char *) w
->window_map
.data
+ offset
;
436 int git_mwindow_file_register(git_mwindow_file
*mwf
)
438 git_mwindow_ctl
*ctl
= &git_mwindow__mem_ctl
;
441 if (git_mutex_lock(&git__mwindow_mutex
)) {
442 git_error_set(GIT_ERROR_THREAD
, "unable to lock mwindow mutex");
446 if (ctl
->windowfiles
.length
== 0 &&
447 git_vector_init(&ctl
->windowfiles
, 8, NULL
) < 0) {
448 git_mutex_unlock(&git__mwindow_mutex
);
452 if (git_mwindow__file_limit
) {
453 while (git_mwindow__file_limit
<= ctl
->windowfiles
.length
&&
454 git_mwindow_close_lru_file() == 0) /* nop */;
457 ret
= git_vector_insert(&ctl
->windowfiles
, mwf
);
458 git_mutex_unlock(&git__mwindow_mutex
);
463 void git_mwindow_file_deregister(git_mwindow_file
*mwf
)
465 git_mwindow_ctl
*ctl
= &git_mwindow__mem_ctl
;
466 git_mwindow_file
*cur
;
469 if (git_mutex_lock(&git__mwindow_mutex
))
472 git_vector_foreach(&ctl
->windowfiles
, i
, cur
) {
474 git_vector_remove(&ctl
->windowfiles
, i
);
475 git_mutex_unlock(&git__mwindow_mutex
);
479 git_mutex_unlock(&git__mwindow_mutex
);
482 void git_mwindow_close(git_mwindow
**window
)
484 git_mwindow
*w
= *window
;
486 if (git_mutex_lock(&git__mwindow_mutex
)) {
487 git_error_set(GIT_ERROR_THREAD
, "unable to lock mwindow mutex");
492 git_mutex_unlock(&git__mwindow_mutex
);