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 size_t git_mwindow__window_size
= DEFAULT_WINDOW_SIZE
;
26 size_t git_mwindow__mapped_limit
= DEFAULT_MAPPED_LIMIT
;
28 /* Whenever you want to read or modify this, grab git__mwindow_mutex */
29 static git_mwindow_ctl mem_ctl
;
31 /* Global list of mwindow files, to open packs once across repos */
32 git_strmap
*git__pack_cache
= NULL
;
34 static void git_mwindow_files_free(void)
36 git_strmap
*tmp
= git__pack_cache
;
38 git__pack_cache
= NULL
;
42 int git_mwindow_global_init(void)
44 assert(!git__pack_cache
);
46 git__on_shutdown(git_mwindow_files_free
);
47 return git_strmap_new(&git__pack_cache
);
50 int git_mwindow_get_pack(struct git_pack_file
**out
, const char *path
)
52 struct git_pack_file
*pack
;
56 if ((error
= git_packfile__name(&packname
, path
)) < 0)
59 if (git_mutex_lock(&git__mwindow_mutex
) < 0) {
60 git_error_set(GIT_ERROR_OS
, "failed to lock mwindow mutex");
64 pack
= git_strmap_get(git__pack_cache
, packname
);
68 git_atomic_inc(&pack
->refcount
);
69 git_mutex_unlock(&git__mwindow_mutex
);
74 /* If we didn't find it, we need to create it */
75 if ((error
= git_packfile_alloc(&pack
, path
)) < 0) {
76 git_mutex_unlock(&git__mwindow_mutex
);
80 git_atomic_inc(&pack
->refcount
);
82 error
= git_strmap_set(git__pack_cache
, pack
->pack_name
, pack
);
83 git_mutex_unlock(&git__mwindow_mutex
);
86 git_packfile_free(pack
);
94 void git_mwindow_put_pack(struct git_pack_file
*pack
)
98 if (git_mutex_lock(&git__mwindow_mutex
) < 0)
101 /* put before get would be a corrupted state */
102 assert(git__pack_cache
);
104 /* if we cannot find it, the state is corrupted */
105 assert(git_strmap_exists(git__pack_cache
, pack
->pack_name
));
107 count
= git_atomic_dec(&pack
->refcount
);
109 git_strmap_delete(git__pack_cache
, pack
->pack_name
);
110 git_packfile_free(pack
);
113 git_mutex_unlock(&git__mwindow_mutex
);
117 void git_mwindow_free_all(git_mwindow_file
*mwf
)
119 if (git_mutex_lock(&git__mwindow_mutex
)) {
120 git_error_set(GIT_ERROR_THREAD
, "unable to lock mwindow mutex");
124 git_mwindow_free_all_locked(mwf
);
126 git_mutex_unlock(&git__mwindow_mutex
);
130 * Free all the windows in a sequence, typically because we're done
133 void git_mwindow_free_all_locked(git_mwindow_file
*mwf
)
135 git_mwindow_ctl
*ctl
= &mem_ctl
;
139 * Remove these windows from the global list
141 for (i
= 0; i
< ctl
->windowfiles
.length
; ++i
){
142 if (git_vector_get(&ctl
->windowfiles
, i
) == mwf
) {
143 git_vector_remove(&ctl
->windowfiles
, i
);
148 if (ctl
->windowfiles
.length
== 0) {
149 git_vector_free(&ctl
->windowfiles
);
150 ctl
->windowfiles
.contents
= NULL
;
153 while (mwf
->windows
) {
154 git_mwindow
*w
= mwf
->windows
;
155 assert(w
->inuse_cnt
== 0);
157 ctl
->mapped
-= w
->window_map
.len
;
160 git_futils_mmap_free(&w
->window_map
);
162 mwf
->windows
= w
->next
;
168 * Check if a window 'win' contains the address 'offset'
170 int git_mwindow_contains(git_mwindow
*win
, off64_t offset
)
172 off64_t win_off
= win
->offset
;
173 return win_off
<= offset
174 && offset
<= (off64_t
)(win_off
+ win
->window_map
.len
);
178 * Find the least-recently-used window in a file
180 static void git_mwindow_scan_lru(
181 git_mwindow_file
*mwf
,
185 git_mwindow
*w
, *w_l
;
187 for (w_l
= NULL
, w
= mwf
->windows
; w
; w
= w
->next
) {
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.
194 if (!*lru_w
|| w
->last_used
< (*lru_w
)->last_used
) {
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.
208 static int git_mwindow_close_lru(git_mwindow_file
*mwf
)
210 git_mwindow_ctl
*ctl
= &mem_ctl
;
212 git_mwindow
*lru_w
= NULL
, *lru_l
= NULL
, **list
= &mwf
->windows
;
214 /* FIXME: Does this give us any advantage? */
216 git_mwindow_scan_lru(mwf
, &lru_w
, &lru_l
);
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
);
223 list
= &cur
->windows
;
227 git_error_set(GIT_ERROR_OS
, "failed to close memory window; couldn't find LRU");
231 ctl
->mapped
-= lru_w
->window_map
.len
;
232 git_futils_mmap_free(&lru_w
->window_map
);
235 lru_l
->next
= lru_w
->next
;
245 /* This gets called under lock from git_mwindow_open */
246 static git_mwindow
*new_window(
247 git_mwindow_file
*mwf
,
252 git_mwindow_ctl
*ctl
= &mem_ctl
;
253 size_t walign
= git_mwindow__window_size
/ 2;
257 w
= git__malloc(sizeof(*w
));
262 memset(w
, 0x0, sizeof(*w
));
263 w
->offset
= (offset
/ walign
) * walign
;
265 len
= size
- w
->offset
;
266 if (len
> (off64_t
)git_mwindow__window_size
)
267 len
= (off64_t
)git_mwindow__window_size
;
269 ctl
->mapped
+= (size_t)len
;
271 while (git_mwindow__mapped_limit
< ctl
->mapped
&&
272 git_mwindow_close_lru(mwf
) == 0) /* nop */;
275 * We treat `mapped_limit` as a soft limit. If we can't find a
276 * window to close and are above the limit, we still mmap the new
280 if (git_futils_mmap_ro(&w
->window_map
, fd
, w
->offset
, (size_t)len
) < 0) {
282 * The first error might be down to memory fragmentation even if
283 * we're below our soft limits, so free up what we can and try again.
286 while (git_mwindow_close_lru(mwf
) == 0)
289 if (git_futils_mmap_ro(&w
->window_map
, fd
, w
->offset
, (size_t)len
) < 0) {
298 if (ctl
->mapped
> ctl
->peak_mapped
)
299 ctl
->peak_mapped
= ctl
->mapped
;
301 if (ctl
->open_windows
> ctl
->peak_open_windows
)
302 ctl
->peak_open_windows
= ctl
->open_windows
;
308 * Open a new window, closing the least recenty used until we have
309 * enough space. Don't forget to add it to your list
311 unsigned char *git_mwindow_open(
312 git_mwindow_file
*mwf
,
313 git_mwindow
**cursor
,
318 git_mwindow_ctl
*ctl
= &mem_ctl
;
319 git_mwindow
*w
= *cursor
;
321 if (git_mutex_lock(&git__mwindow_mutex
)) {
322 git_error_set(GIT_ERROR_THREAD
, "unable to lock mwindow mutex");
326 if (!w
|| !(git_mwindow_contains(w
, offset
) && git_mwindow_contains(w
, offset
+ extra
))) {
331 for (w
= mwf
->windows
; w
; w
= w
->next
) {
332 if (git_mwindow_contains(w
, offset
) &&
333 git_mwindow_contains(w
, offset
+ extra
))
338 * If there isn't a suitable window, we need to create a new
342 w
= new_window(mwf
, mwf
->fd
, mwf
->size
, offset
);
344 git_mutex_unlock(&git__mwindow_mutex
);
347 w
->next
= mwf
->windows
;
352 /* If we changed w, store it in the cursor */
354 w
->last_used
= ctl
->used_ctr
++;
362 *left
= (unsigned int)(w
->window_map
.len
- offset
);
364 git_mutex_unlock(&git__mwindow_mutex
);
365 return (unsigned char *) w
->window_map
.data
+ offset
;
368 int git_mwindow_file_register(git_mwindow_file
*mwf
)
370 git_mwindow_ctl
*ctl
= &mem_ctl
;
373 if (git_mutex_lock(&git__mwindow_mutex
)) {
374 git_error_set(GIT_ERROR_THREAD
, "unable to lock mwindow mutex");
378 if (ctl
->windowfiles
.length
== 0 &&
379 git_vector_init(&ctl
->windowfiles
, 8, NULL
) < 0) {
380 git_mutex_unlock(&git__mwindow_mutex
);
384 ret
= git_vector_insert(&ctl
->windowfiles
, mwf
);
385 git_mutex_unlock(&git__mwindow_mutex
);
390 void git_mwindow_file_deregister(git_mwindow_file
*mwf
)
392 git_mwindow_ctl
*ctl
= &mem_ctl
;
393 git_mwindow_file
*cur
;
396 if (git_mutex_lock(&git__mwindow_mutex
))
399 git_vector_foreach(&ctl
->windowfiles
, i
, cur
) {
401 git_vector_remove(&ctl
->windowfiles
, i
);
402 git_mutex_unlock(&git__mwindow_mutex
);
406 git_mutex_unlock(&git__mwindow_mutex
);
409 void git_mwindow_close(git_mwindow
**window
)
411 git_mwindow
*w
= *window
;
413 if (git_mutex_lock(&git__mwindow_mutex
)) {
414 git_error_set(GIT_ERROR_THREAD
, "unable to lock mwindow mutex");
419 git_mutex_unlock(&git__mwindow_mutex
);