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.
19 #define DEFAULT_WINDOW_SIZE \
21 ? 1 * 1024 * 1024 * 1024 \
24 #define DEFAULT_MAPPED_LIMIT \
25 ((1024 * 1024) * (sizeof(void*) >= 8 ? 8192ULL : 256UL))
27 size_t git_mwindow__window_size
= DEFAULT_WINDOW_SIZE
;
28 size_t git_mwindow__mapped_limit
= DEFAULT_MAPPED_LIMIT
;
30 /* Whenever you want to read or modify this, grab git__mwindow_mutex */
31 static git_mwindow_ctl mem_ctl
;
33 /* Global list of mwindow files, to open packs once across repos */
34 git_strmap
*git__pack_cache
= NULL
;
37 * Run under mwindow lock
39 int git_mwindow_files_init(void)
44 git__on_shutdown(git_mwindow_files_free
);
46 return git_strmap_alloc(&git__pack_cache
);
49 void git_mwindow_files_free(void)
51 git_strmap
*tmp
= git__pack_cache
;
53 git__pack_cache
= NULL
;
57 int git_mwindow_get_pack(struct git_pack_file
**out
, const char *path
)
62 struct git_pack_file
*pack
;
64 if ((error
= git_packfile__name(&packname
, path
)) < 0)
67 if (git_mutex_lock(&git__mwindow_mutex
) < 0) {
68 giterr_set(GITERR_OS
, "failed to lock mwindow mutex");
72 if (git_mwindow_files_init() < 0) {
73 git_mutex_unlock(&git__mwindow_mutex
);
78 pos
= git_strmap_lookup_index(git__pack_cache
, packname
);
81 if (git_strmap_valid_index(git__pack_cache
, pos
)) {
82 pack
= git_strmap_value_at(git__pack_cache
, pos
);
83 git_atomic_inc(&pack
->refcount
);
85 git_mutex_unlock(&git__mwindow_mutex
);
90 /* If we didn't find it, we need to create it */
91 if ((error
= git_packfile_alloc(&pack
, path
)) < 0) {
92 git_mutex_unlock(&git__mwindow_mutex
);
96 git_atomic_inc(&pack
->refcount
);
98 git_strmap_insert(git__pack_cache
, pack
->pack_name
, pack
, error
);
99 git_mutex_unlock(&git__mwindow_mutex
);
102 git_packfile_free(pack
);
110 void git_mwindow_put_pack(struct git_pack_file
*pack
)
115 if (git_mutex_lock(&git__mwindow_mutex
) < 0)
118 /* put before get would be a corrupted state */
119 assert(git__pack_cache
);
121 pos
= git_strmap_lookup_index(git__pack_cache
, pack
->pack_name
);
122 /* if we cannot find it, the state is corrupted */
123 assert(git_strmap_valid_index(git__pack_cache
, pos
));
125 count
= git_atomic_dec(&pack
->refcount
);
127 git_strmap_delete_at(git__pack_cache
, pos
);
128 git_packfile_free(pack
);
131 git_mutex_unlock(&git__mwindow_mutex
);
135 void git_mwindow_free_all(git_mwindow_file
*mwf
)
137 if (git_mutex_lock(&git__mwindow_mutex
)) {
138 giterr_set(GITERR_THREAD
, "unable to lock mwindow mutex");
142 git_mwindow_free_all_locked(mwf
);
144 git_mutex_unlock(&git__mwindow_mutex
);
148 * Free all the windows in a sequence, typically because we're done
151 void git_mwindow_free_all_locked(git_mwindow_file
*mwf
)
153 git_mwindow_ctl
*ctl
= &mem_ctl
;
157 * Remove these windows from the global list
159 for (i
= 0; i
< ctl
->windowfiles
.length
; ++i
){
160 if (git_vector_get(&ctl
->windowfiles
, i
) == mwf
) {
161 git_vector_remove(&ctl
->windowfiles
, i
);
166 if (ctl
->windowfiles
.length
== 0) {
167 git_vector_free(&ctl
->windowfiles
);
168 ctl
->windowfiles
.contents
= NULL
;
171 while (mwf
->windows
) {
172 git_mwindow
*w
= mwf
->windows
;
173 assert(w
->inuse_cnt
== 0);
175 ctl
->mapped
-= w
->window_map
.len
;
178 git_futils_mmap_free(&w
->window_map
);
180 mwf
->windows
= w
->next
;
186 * Check if a window 'win' contains the address 'offset'
188 int git_mwindow_contains(git_mwindow
*win
, git_off_t offset
)
190 git_off_t win_off
= win
->offset
;
191 return win_off
<= offset
192 && offset
<= (git_off_t
)(win_off
+ win
->window_map
.len
);
196 * Find the least-recently-used window in a file
198 static void git_mwindow_scan_lru(
199 git_mwindow_file
*mwf
,
203 git_mwindow
*w
, *w_l
;
205 for (w_l
= NULL
, w
= mwf
->windows
; w
; w
= w
->next
) {
208 * If the current one is more recent than the last one,
209 * store it in the output parameter. If lru_w is NULL,
210 * it's the first loop, so store it as well.
212 if (!*lru_w
|| w
->last_used
< (*lru_w
)->last_used
) {
222 * Close the least recently used window. You should check to see if
223 * the file descriptors need closing from time to time. Called under
224 * lock from new_window.
226 static int git_mwindow_close_lru(git_mwindow_file
*mwf
)
228 git_mwindow_ctl
*ctl
= &mem_ctl
;
230 git_mwindow
*lru_w
= NULL
, *lru_l
= NULL
, **list
= &mwf
->windows
;
232 /* FIXME: Does this give us any advantage? */
234 git_mwindow_scan_lru(mwf
, &lru_w
, &lru_l
);
236 for (i
= 0; i
< ctl
->windowfiles
.length
; ++i
) {
237 git_mwindow
*last
= lru_w
;
238 git_mwindow_file
*cur
= git_vector_get(&ctl
->windowfiles
, i
);
239 git_mwindow_scan_lru(cur
, &lru_w
, &lru_l
);
241 list
= &cur
->windows
;
245 giterr_set(GITERR_OS
, "Failed to close memory window. Couldn't find LRU");
249 ctl
->mapped
-= lru_w
->window_map
.len
;
250 git_futils_mmap_free(&lru_w
->window_map
);
253 lru_l
->next
= lru_w
->next
;
263 /* This gets called under lock from git_mwindow_open */
264 static git_mwindow
*new_window(
265 git_mwindow_file
*mwf
,
270 git_mwindow_ctl
*ctl
= &mem_ctl
;
271 size_t walign
= git_mwindow__window_size
/ 2;
275 w
= git__malloc(sizeof(*w
));
280 memset(w
, 0x0, sizeof(*w
));
281 w
->offset
= (offset
/ walign
) * walign
;
283 len
= size
- w
->offset
;
284 if (len
> (git_off_t
)git_mwindow__window_size
)
285 len
= (git_off_t
)git_mwindow__window_size
;
287 ctl
->mapped
+= (size_t)len
;
289 while (git_mwindow__mapped_limit
< ctl
->mapped
&&
290 git_mwindow_close_lru(mwf
) == 0) /* nop */;
293 * We treat `mapped_limit` as a soft limit. If we can't find a
294 * window to close and are above the limit, we still mmap the new
298 if (git_futils_mmap_ro(&w
->window_map
, fd
, w
->offset
, (size_t)len
) < 0) {
306 if (ctl
->mapped
> ctl
->peak_mapped
)
307 ctl
->peak_mapped
= ctl
->mapped
;
309 if (ctl
->open_windows
> ctl
->peak_open_windows
)
310 ctl
->peak_open_windows
= ctl
->open_windows
;
316 * Open a new window, closing the least recenty used until we have
317 * enough space. Don't forget to add it to your list
319 unsigned char *git_mwindow_open(
320 git_mwindow_file
*mwf
,
321 git_mwindow
**cursor
,
326 git_mwindow_ctl
*ctl
= &mem_ctl
;
327 git_mwindow
*w
= *cursor
;
329 if (git_mutex_lock(&git__mwindow_mutex
)) {
330 giterr_set(GITERR_THREAD
, "unable to lock mwindow mutex");
334 if (!w
|| !(git_mwindow_contains(w
, offset
) && git_mwindow_contains(w
, offset
+ extra
))) {
339 for (w
= mwf
->windows
; w
; w
= w
->next
) {
340 if (git_mwindow_contains(w
, offset
) &&
341 git_mwindow_contains(w
, offset
+ extra
))
346 * If there isn't a suitable window, we need to create a new
350 w
= new_window(mwf
, mwf
->fd
, mwf
->size
, offset
);
352 git_mutex_unlock(&git__mwindow_mutex
);
355 w
->next
= mwf
->windows
;
360 /* If we changed w, store it in the cursor */
362 w
->last_used
= ctl
->used_ctr
++;
370 *left
= (unsigned int)(w
->window_map
.len
- offset
);
372 git_mutex_unlock(&git__mwindow_mutex
);
373 return (unsigned char *) w
->window_map
.data
+ offset
;
376 int git_mwindow_file_register(git_mwindow_file
*mwf
)
378 git_mwindow_ctl
*ctl
= &mem_ctl
;
381 if (git_mutex_lock(&git__mwindow_mutex
)) {
382 giterr_set(GITERR_THREAD
, "unable to lock mwindow mutex");
386 if (ctl
->windowfiles
.length
== 0 &&
387 git_vector_init(&ctl
->windowfiles
, 8, NULL
) < 0) {
388 git_mutex_unlock(&git__mwindow_mutex
);
392 ret
= git_vector_insert(&ctl
->windowfiles
, mwf
);
393 git_mutex_unlock(&git__mwindow_mutex
);
398 void git_mwindow_file_deregister(git_mwindow_file
*mwf
)
400 git_mwindow_ctl
*ctl
= &mem_ctl
;
401 git_mwindow_file
*cur
;
404 if (git_mutex_lock(&git__mwindow_mutex
))
407 git_vector_foreach(&ctl
->windowfiles
, i
, cur
) {
409 git_vector_remove(&ctl
->windowfiles
, i
);
410 git_mutex_unlock(&git__mwindow_mutex
);
414 git_mutex_unlock(&git__mwindow_mutex
);
417 void git_mwindow_close(git_mwindow
**window
)
419 git_mwindow
*w
= *window
;
421 if (git_mutex_lock(&git__mwindow_mutex
)) {
422 giterr_set(GITERR_THREAD
, "unable to lock mwindow mutex");
427 git_mutex_unlock(&git__mwindow_mutex
);