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 ? UINT64_C(8192) : UINT64_C(256)))
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 /* Mutex to control access to `git_mwindow__mem_ctl` and `git__pack_cache`. */
33 git_mutex git__mwindow_mutex
;
35 /* Whenever you want to read or modify this, grab `git__mwindow_mutex` */
36 git_mwindow_ctl git_mwindow__mem_ctl
;
38 /* Global list of mwindow files, to open packs once across repos */
39 git_strmap
*git__pack_cache
= NULL
;
41 static void git_mwindow_global_shutdown(void)
43 git_strmap
*tmp
= git__pack_cache
;
45 git_mutex_free(&git__mwindow_mutex
);
47 git__pack_cache
= NULL
;
51 int git_mwindow_global_init(void)
55 GIT_ASSERT(!git__pack_cache
);
57 if ((error
= git_mutex_init(&git__mwindow_mutex
)) < 0 ||
58 (error
= git_strmap_new(&git__pack_cache
)) < 0)
61 return git_runtime_shutdown_register(git_mwindow_global_shutdown
);
64 int git_mwindow_get_pack(struct git_pack_file
**out
, const char *path
)
66 struct git_pack_file
*pack
;
70 if ((error
= git_packfile__name(&packname
, path
)) < 0)
73 if (git_mutex_lock(&git__mwindow_mutex
) < 0) {
74 git_error_set(GIT_ERROR_OS
, "failed to lock mwindow mutex");
78 pack
= git_strmap_get(git__pack_cache
, packname
);
82 git_atomic32_inc(&pack
->refcount
);
83 git_mutex_unlock(&git__mwindow_mutex
);
88 /* If we didn't find it, we need to create it */
89 if ((error
= git_packfile_alloc(&pack
, path
)) < 0) {
90 git_mutex_unlock(&git__mwindow_mutex
);
94 git_atomic32_inc(&pack
->refcount
);
96 error
= git_strmap_set(git__pack_cache
, pack
->pack_name
, pack
);
97 git_mutex_unlock(&git__mwindow_mutex
);
99 git_packfile_free(pack
, false);
107 int git_mwindow_put_pack(struct git_pack_file
*pack
)
110 struct git_pack_file
*pack_to_delete
= NULL
;
112 if ((error
= git_mutex_lock(&git__mwindow_mutex
)) < 0)
115 /* put before get would be a corrupted state */
116 GIT_ASSERT(git__pack_cache
);
118 /* if we cannot find it, the state is corrupted */
119 GIT_ASSERT(git_strmap_exists(git__pack_cache
, pack
->pack_name
));
121 count
= git_atomic32_dec(&pack
->refcount
);
123 git_strmap_delete(git__pack_cache
, pack
->pack_name
);
124 pack_to_delete
= pack
;
126 git_mutex_unlock(&git__mwindow_mutex
);
127 git_packfile_free(pack_to_delete
, false);
133 * Free all the windows in a sequence, typically because we're done
134 * with the file. Needs to hold the git__mwindow_mutex.
136 static int git_mwindow_free_all_locked(git_mwindow_file
*mwf
)
138 git_mwindow_ctl
*ctl
= &git_mwindow__mem_ctl
;
142 * Remove these windows from the global list
144 for (i
= 0; i
< ctl
->windowfiles
.length
; ++i
){
145 if (git_vector_get(&ctl
->windowfiles
, i
) == mwf
) {
146 git_vector_remove(&ctl
->windowfiles
, i
);
151 if (ctl
->windowfiles
.length
== 0) {
152 git_vector_free(&ctl
->windowfiles
);
153 ctl
->windowfiles
.contents
= NULL
;
156 while (mwf
->windows
) {
157 git_mwindow
*w
= mwf
->windows
;
158 GIT_ASSERT(w
->inuse_cnt
== 0);
160 ctl
->mapped
-= w
->window_map
.len
;
163 git_futils_mmap_free(&w
->window_map
);
165 mwf
->windows
= w
->next
;
172 int git_mwindow_free_all(git_mwindow_file
*mwf
)
176 if (git_mutex_lock(&git__mwindow_mutex
)) {
177 git_error_set(GIT_ERROR_THREAD
, "unable to lock mwindow mutex");
181 error
= git_mwindow_free_all_locked(mwf
);
183 git_mutex_unlock(&git__mwindow_mutex
);
189 * Check if a window 'win' contains the address 'offset'
191 int git_mwindow_contains(git_mwindow
*win
, off64_t offset
)
193 off64_t win_off
= win
->offset
;
194 return win_off
<= offset
195 && offset
<= (off64_t
)(win_off
+ win
->window_map
.len
);
198 #define GIT_MWINDOW__LRU -1
199 #define GIT_MWINDOW__MRU 1
202 * Find the least- or most-recently-used window in a file that is not currently
203 * being used. The 'only_unused' flag controls whether the caller requires the
204 * file to only have unused windows. If '*out_window' is non-null, it is used as
205 * a starting point for the comparison.
207 * Returns whether such a window was found in the file.
209 static bool git_mwindow_scan_recently_used(
210 git_mwindow_file
*mwf
,
211 git_mwindow
**out_window
,
212 git_mwindow
**out_last
,
216 git_mwindow
*w
, *w_last
;
217 git_mwindow
*lru_window
= NULL
, *lru_last
= NULL
;
221 GIT_ASSERT_ARG(out_window
);
223 lru_window
= *out_window
;
225 lru_last
= *out_last
;
227 for (w_last
= NULL
, w
= mwf
->windows
; w
; w_last
= w
, w
= w
->next
) {
231 /* This window is currently being used. Skip it. */
236 * If the current one is more (or less) recent than the last one,
237 * store it in the output parameter. If lru_window is NULL,
238 * it's the first loop, so store it as well.
241 (comparison_sign
== GIT_MWINDOW__LRU
&& lru_window
->last_used
> w
->last_used
) ||
242 (comparison_sign
== GIT_MWINDOW__MRU
&& lru_window
->last_used
< w
->last_used
)) {
252 *out_window
= lru_window
;
254 *out_last
= lru_last
;
259 * Close the least recently used window (that is currently not being used) out
260 * of all the files. Called under lock from new_window_locked.
262 static int git_mwindow_close_lru_window_locked(void)
264 git_mwindow_ctl
*ctl
= &git_mwindow__mem_ctl
;
265 git_mwindow_file
*cur
;
267 git_mwindow
*lru_window
= NULL
, *lru_last
= NULL
, **list
= NULL
;
269 git_vector_foreach(&ctl
->windowfiles
, i
, cur
) {
270 if (git_mwindow_scan_recently_used(
271 cur
, &lru_window
, &lru_last
, false, GIT_MWINDOW__LRU
)) {
272 list
= &cur
->windows
;
277 git_error_set(GIT_ERROR_OS
, "failed to close memory window; couldn't find LRU");
281 ctl
->mapped
-= lru_window
->window_map
.len
;
282 git_futils_mmap_free(&lru_window
->window_map
);
285 lru_last
->next
= lru_window
->next
;
287 *list
= lru_window
->next
;
289 git__free(lru_window
);
296 * Finds the file that does not have any open windows AND whose
297 * most-recently-used window is the least-recently used one across all
298 * currently open files.
300 * Called under lock from new_window_locked.
302 static int git_mwindow_find_lru_file_locked(git_mwindow_file
**out
)
304 git_mwindow_ctl
*ctl
= &git_mwindow__mem_ctl
;
305 git_mwindow_file
*lru_file
= NULL
, *current_file
= NULL
;
306 git_mwindow
*lru_window
= NULL
;
309 git_vector_foreach(&ctl
->windowfiles
, i
, current_file
) {
310 git_mwindow
*mru_window
= NULL
;
311 if (!git_mwindow_scan_recently_used(
312 current_file
, &mru_window
, NULL
, true, GIT_MWINDOW__MRU
)) {
315 if (!lru_window
|| lru_window
->last_used
> mru_window
->last_used
) {
316 lru_window
= mru_window
;
317 lru_file
= current_file
;
322 git_error_set(GIT_ERROR_OS
, "failed to close memory window file; couldn't find LRU");
330 /* This gets called under lock from git_mwindow_open */
331 static git_mwindow
*new_window_locked(
336 git_mwindow_ctl
*ctl
= &git_mwindow__mem_ctl
;
337 size_t walign
= git_mwindow__window_size
/ 2;
341 w
= git__calloc(1, sizeof(*w
));
346 w
->offset
= (offset
/ walign
) * walign
;
348 len
= size
- w
->offset
;
349 if (len
> (off64_t
)git_mwindow__window_size
)
350 len
= (off64_t
)git_mwindow__window_size
;
352 ctl
->mapped
+= (size_t)len
;
354 while (git_mwindow__mapped_limit
< ctl
->mapped
&&
355 git_mwindow_close_lru_window_locked() == 0) /* nop */;
358 * We treat `mapped_limit` as a soft limit. If we can't find a
359 * window to close and are above the limit, we still mmap the new
363 if (git_futils_mmap_ro(&w
->window_map
, fd
, w
->offset
, (size_t)len
) < 0) {
365 * The first error might be down to memory fragmentation even if
366 * we're below our soft limits, so free up what we can and try again.
369 while (git_mwindow_close_lru_window_locked() == 0)
372 if (git_futils_mmap_ro(&w
->window_map
, fd
, w
->offset
, (size_t)len
) < 0) {
381 if (ctl
->mapped
> ctl
->peak_mapped
)
382 ctl
->peak_mapped
= ctl
->mapped
;
384 if (ctl
->open_windows
> ctl
->peak_open_windows
)
385 ctl
->peak_open_windows
= ctl
->open_windows
;
391 * Open a new window, closing the least recenty used until we have
392 * enough space. Don't forget to add it to your list
394 unsigned char *git_mwindow_open(
395 git_mwindow_file
*mwf
,
396 git_mwindow
**cursor
,
401 git_mwindow_ctl
*ctl
= &git_mwindow__mem_ctl
;
402 git_mwindow
*w
= *cursor
;
404 if (git_mutex_lock(&git__mwindow_mutex
)) {
405 git_error_set(GIT_ERROR_THREAD
, "unable to lock mwindow mutex");
409 if (!w
|| !(git_mwindow_contains(w
, offset
) && git_mwindow_contains(w
, offset
+ extra
))) {
414 for (w
= mwf
->windows
; w
; w
= w
->next
) {
415 if (git_mwindow_contains(w
, offset
) &&
416 git_mwindow_contains(w
, offset
+ extra
))
421 * If there isn't a suitable window, we need to create a new
425 w
= new_window_locked(mwf
->fd
, mwf
->size
, offset
);
427 git_mutex_unlock(&git__mwindow_mutex
);
430 w
->next
= mwf
->windows
;
435 /* If we changed w, store it in the cursor */
437 w
->last_used
= ctl
->used_ctr
++;
445 *left
= (unsigned int)(w
->window_map
.len
- offset
);
447 git_mutex_unlock(&git__mwindow_mutex
);
448 return (unsigned char *) w
->window_map
.data
+ offset
;
451 int git_mwindow_file_register(git_mwindow_file
*mwf
)
453 git_vector closed_files
= GIT_VECTOR_INIT
;
454 git_mwindow_ctl
*ctl
= &git_mwindow__mem_ctl
;
457 git_mwindow_file
*closed_file
= NULL
;
459 if (git_mutex_lock(&git__mwindow_mutex
)) {
460 git_error_set(GIT_ERROR_THREAD
, "unable to lock mwindow mutex");
464 if (ctl
->windowfiles
.length
== 0 &&
465 (error
= git_vector_init(&ctl
->windowfiles
, 8, NULL
)) < 0) {
466 git_mutex_unlock(&git__mwindow_mutex
);
470 if (git_mwindow__file_limit
) {
471 git_mwindow_file
*lru_file
;
472 while (git_mwindow__file_limit
<= ctl
->windowfiles
.length
&&
473 git_mwindow_find_lru_file_locked(&lru_file
) == 0) {
474 if ((error
= git_vector_insert(&closed_files
, lru_file
)) < 0) {
476 * Exceeding the file limit seems preferrable to being open to
477 * data races that can end up corrupting the heap.
481 git_mwindow_free_all_locked(lru_file
);
485 error
= git_vector_insert(&ctl
->windowfiles
, mwf
);
486 git_mutex_unlock(&git__mwindow_mutex
);
491 * Once we have released the global windowfiles lock, we can close each
492 * individual file. Before doing so, acquire that file's lock to avoid
493 * closing a file that is currently being used.
495 git_vector_foreach(&closed_files
, i
, closed_file
) {
496 error
= git_mutex_lock(&closed_file
->lock
);
499 p_close(closed_file
->fd
);
500 closed_file
->fd
= -1;
501 git_mutex_unlock(&closed_file
->lock
);
505 git_vector_free(&closed_files
);
509 void git_mwindow_file_deregister(git_mwindow_file
*mwf
)
511 git_mwindow_ctl
*ctl
= &git_mwindow__mem_ctl
;
512 git_mwindow_file
*cur
;
515 if (git_mutex_lock(&git__mwindow_mutex
))
518 git_vector_foreach(&ctl
->windowfiles
, i
, cur
) {
520 git_vector_remove(&ctl
->windowfiles
, i
);
521 git_mutex_unlock(&git__mwindow_mutex
);
525 git_mutex_unlock(&git__mwindow_mutex
);
528 void git_mwindow_close(git_mwindow
**window
)
530 git_mwindow
*w
= *window
;
532 if (git_mutex_lock(&git__mwindow_mutex
)) {
533 git_error_set(GIT_ERROR_THREAD
, "unable to lock mwindow mutex");
538 git_mutex_unlock(&git__mwindow_mutex
);