File Coverage

deps/libgit2/src/libgit2/mwindow.c
Criterion Covered Total %
statement 118 235 50.2
branch 49 138 35.5
condition n/a
subroutine n/a
pod n/a
total 167 373 44.7


line stmt bran cond sub pod time code
1             /*
2             * Copyright (C) the libgit2 contributors. All rights reserved.
3             *
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.
6             */
7              
8             #include "mwindow.h"
9              
10             #include "vector.h"
11             #include "futils.h"
12             #include "map.h"
13             #include "runtime.h"
14             #include "strmap.h"
15             #include "pack.h"
16              
17             #define DEFAULT_WINDOW_SIZE \
18             (sizeof(void*) >= 8 \
19             ? 1 * 1024 * 1024 * 1024 \
20             : 32 * 1024 * 1024)
21              
22             #define DEFAULT_MAPPED_LIMIT \
23             ((1024 * 1024) * (sizeof(void*) >= 8 ? UINT64_C(8192) : UINT64_C(256)))
24              
25             /* default is unlimited */
26             #define DEFAULT_FILE_LIMIT 0
27              
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;
31              
32             /* Mutex to control access to `git_mwindow__mem_ctl` and `git__pack_cache`. */
33             git_mutex git__mwindow_mutex;
34              
35             /* Whenever you want to read or modify this, grab `git__mwindow_mutex` */
36             git_mwindow_ctl git_mwindow__mem_ctl;
37              
38             /* Global list of mwindow files, to open packs once across repos */
39             git_strmap *git__pack_cache = NULL;
40              
41 0           static void git_mwindow_global_shutdown(void)
42             {
43 0           git_strmap *tmp = git__pack_cache;
44              
45 0           git_mutex_free(&git__mwindow_mutex);
46              
47 0           git__pack_cache = NULL;
48 0           git_strmap_free(tmp);
49 0           }
50              
51 87           int git_mwindow_global_init(void)
52             {
53             int error;
54              
55 87 50         GIT_ASSERT(!git__pack_cache);
56              
57 87 50         if ((error = git_mutex_init(&git__mwindow_mutex)) < 0 ||
    50          
58             (error = git_strmap_new(&git__pack_cache)) < 0)
59 0           return error;
60              
61 87           return git_runtime_shutdown_register(git_mwindow_global_shutdown);
62             }
63              
64 4           int git_mwindow_get_pack(struct git_pack_file **out, const char *path)
65             {
66             struct git_pack_file *pack;
67             char *packname;
68             int error;
69              
70 4 50         if ((error = git_packfile__name(&packname, path)) < 0)
71 0           return error;
72              
73 4 50         if (git_mutex_lock(&git__mwindow_mutex) < 0) {
74 0           git_error_set(GIT_ERROR_OS, "failed to lock mwindow mutex");
75 0           return -1;
76             }
77              
78 4           pack = git_strmap_get(git__pack_cache, packname);
79 4           git__free(packname);
80              
81 4 50         if (pack != NULL) {
82 0           git_atomic32_inc(&pack->refcount);
83 0           git_mutex_unlock(&git__mwindow_mutex);
84 0           *out = pack;
85 0           return 0;
86             }
87              
88             /* If we didn't find it, we need to create it */
89 4 50         if ((error = git_packfile_alloc(&pack, path)) < 0) {
90 0           git_mutex_unlock(&git__mwindow_mutex);
91 0           return error;
92             }
93              
94 4           git_atomic32_inc(&pack->refcount);
95              
96 4           error = git_strmap_set(git__pack_cache, pack->pack_name, pack);
97 4           git_mutex_unlock(&git__mwindow_mutex);
98 4 50         if (error < 0) {
99 0           git_packfile_free(pack, false);
100 0           return error;
101             }
102              
103 4           *out = pack;
104 4           return 0;
105             }
106              
107 3           int git_mwindow_put_pack(struct git_pack_file *pack)
108             {
109             int count, error;
110 3           struct git_pack_file *pack_to_delete = NULL;
111              
112 3 50         if ((error = git_mutex_lock(&git__mwindow_mutex)) < 0)
113 0           return error;
114              
115             /* put before get would be a corrupted state */
116 3 50         GIT_ASSERT(git__pack_cache);
117              
118             /* if we cannot find it, the state is corrupted */
119 3 50         GIT_ASSERT(git_strmap_exists(git__pack_cache, pack->pack_name));
120              
121 3           count = git_atomic32_dec(&pack->refcount);
122 3 50         if (count == 0) {
123 3           git_strmap_delete(git__pack_cache, pack->pack_name);
124 3           pack_to_delete = pack;
125             }
126 3           git_mutex_unlock(&git__mwindow_mutex);
127 3           git_packfile_free(pack_to_delete, false);
128              
129 3           return 0;
130             }
131              
132             /*
133             * Free all the windows in a sequence, typically because we're done
134             * with the file. Needs to hold the git__mwindow_mutex.
135             */
136 99           static int git_mwindow_free_all_locked(git_mwindow_file *mwf)
137             {
138 99           git_mwindow_ctl *ctl = &git_mwindow__mem_ctl;
139             size_t i;
140              
141             /*
142             * Remove these windows from the global list
143             */
144 99 100         for (i = 0; i < ctl->windowfiles.length; ++i){
145 8 50         if (git_vector_get(&ctl->windowfiles, i) == mwf) {
146 8           git_vector_remove(&ctl->windowfiles, i);
147 8           break;
148             }
149             }
150              
151 99 50         if (ctl->windowfiles.length == 0) {
152 99           git_vector_free(&ctl->windowfiles);
153 99           ctl->windowfiles.contents = NULL;
154             }
155              
156 147 100         while (mwf->windows) {
157 48           git_mwindow *w = mwf->windows;
158 48 50         GIT_ASSERT(w->inuse_cnt == 0);
159              
160 48           ctl->mapped -= w->window_map.len;
161 48           ctl->open_windows--;
162              
163 48           git_futils_mmap_free(&w->window_map);
164              
165 48           mwf->windows = w->next;
166 48           git__free(w);
167             }
168              
169 99           return 0;
170             }
171              
172 99           int git_mwindow_free_all(git_mwindow_file *mwf)
173             {
174             int error;
175              
176 99 50         if (git_mutex_lock(&git__mwindow_mutex)) {
177 0           git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex");
178 0           return -1;
179             }
180              
181 99           error = git_mwindow_free_all_locked(mwf);
182              
183 99           git_mutex_unlock(&git__mwindow_mutex);
184              
185 99           return error;
186             }
187              
188             /*
189             * Check if a window 'win' contains both the address 'offset' and 'extra'.
190             *
191             * 'extra' is the size of the hash we're using as we always want to make sure
192             * that it's contained.
193             */
194 106           int git_mwindow_contains(git_mwindow *win, off64_t offset, off64_t extra)
195             {
196 106           off64_t win_off = win->offset;
197 106           return win_off <= offset
198 106 50         && (offset + extra) <= (off64_t)(win_off + win->window_map.len);
    50          
199             }
200              
201             #define GIT_MWINDOW__LRU -1
202             #define GIT_MWINDOW__MRU 1
203              
204             /*
205             * Find the least- or most-recently-used window in a file that is not currently
206             * being used. The 'only_unused' flag controls whether the caller requires the
207             * file to only have unused windows. If '*out_window' is non-null, it is used as
208             * a starting point for the comparison.
209             *
210             * Returns whether such a window was found in the file.
211             */
212 0           static bool git_mwindow_scan_recently_used(
213             git_mwindow_file *mwf,
214             git_mwindow **out_window,
215             git_mwindow **out_last,
216             bool only_unused,
217             int comparison_sign)
218             {
219             git_mwindow *w, *w_last;
220 0           git_mwindow *lru_window = NULL, *lru_last = NULL;
221 0           bool found = false;
222              
223 0 0         GIT_ASSERT_ARG(mwf);
224 0 0         GIT_ASSERT_ARG(out_window);
225              
226 0           lru_window = *out_window;
227 0 0         if (out_last)
228 0           lru_last = *out_last;
229              
230 0 0         for (w_last = NULL, w = mwf->windows; w; w_last = w, w = w->next) {
231 0 0         if (w->inuse_cnt) {
232 0 0         if (only_unused)
233 0           return false;
234             /* This window is currently being used. Skip it. */
235 0           continue;
236             }
237              
238             /*
239             * If the current one is more (or less) recent than the last one,
240             * store it in the output parameter. If lru_window is NULL,
241             * it's the first loop, so store it as well.
242             */
243 0 0         if (!lru_window || (comparison_sign * w->last_used) > lru_window->last_used) {
    0          
244 0           lru_window = w;
245 0           lru_last = w_last;
246 0           found = true;
247             }
248             }
249              
250 0 0         if (!found)
251 0           return false;
252              
253 0           *out_window = lru_window;
254 0 0         if (out_last)
255 0           *out_last = lru_last;
256 0           return true;
257             }
258              
259             /*
260             * Close the least recently used window (that is currently not being used) out
261             * of all the files. Called under lock from new_window_locked.
262             */
263 0           static int git_mwindow_close_lru_window_locked(void)
264             {
265 0           git_mwindow_ctl *ctl = &git_mwindow__mem_ctl;
266             git_mwindow_file *cur;
267             size_t i;
268 0           git_mwindow *lru_window = NULL, *lru_last = NULL, **list = NULL;
269              
270 0 0         git_vector_foreach(&ctl->windowfiles, i, cur) {
271 0 0         if (git_mwindow_scan_recently_used(
272             cur, &lru_window, &lru_last, false, GIT_MWINDOW__LRU)) {
273 0           list = &cur->windows;
274             }
275             }
276              
277 0 0         if (!lru_window) {
278 0           git_error_set(GIT_ERROR_OS, "failed to close memory window; couldn't find LRU");
279 0           return -1;
280             }
281              
282 0           ctl->mapped -= lru_window->window_map.len;
283 0           git_futils_mmap_free(&lru_window->window_map);
284              
285 0 0         if (lru_last)
286 0           lru_last->next = lru_window->next;
287             else
288 0           *list = lru_window->next;
289              
290 0           git__free(lru_window);
291 0           ctl->open_windows--;
292              
293 0           return 0;
294             }
295              
296             /*
297             * Finds the file that does not have any open windows AND whose
298             * most-recently-used window is the least-recently used one across all
299             * currently open files.
300             *
301             * Called under lock from new_window_locked.
302             */
303 0           static int git_mwindow_find_lru_file_locked(git_mwindow_file **out)
304             {
305 0           git_mwindow_ctl *ctl = &git_mwindow__mem_ctl;
306 0           git_mwindow_file *lru_file = NULL, *current_file = NULL;
307 0           git_mwindow *lru_window = NULL;
308             size_t i;
309              
310 0 0         git_vector_foreach(&ctl->windowfiles, i, current_file) {
311 0           git_mwindow *mru_window = NULL;
312 0 0         if (!git_mwindow_scan_recently_used(
313             current_file, &mru_window, NULL, true, GIT_MWINDOW__MRU)) {
314 0           continue;
315             }
316 0 0         if (!lru_window || lru_window->last_used > mru_window->last_used) {
    0          
317 0           lru_window = mru_window;
318 0           lru_file = current_file;
319             }
320             }
321              
322 0 0         if (!lru_file) {
323 0           git_error_set(GIT_ERROR_OS, "failed to close memory window file; couldn't find LRU");
324 0           return -1;
325             }
326              
327 0           *out = lru_file;
328 0           return 0;
329             }
330              
331             /* This gets called under lock from git_mwindow_open */
332 48           static git_mwindow *new_window_locked(
333             git_file fd,
334             off64_t size,
335             off64_t offset)
336             {
337 48           git_mwindow_ctl *ctl = &git_mwindow__mem_ctl;
338 48           size_t walign = git_mwindow__window_size / 2;
339             off64_t len;
340             git_mwindow *w;
341              
342 48           w = git__calloc(1, sizeof(*w));
343              
344 48 50         if (w == NULL)
345 0           return NULL;
346              
347 48           w->offset = (offset / walign) * walign;
348              
349 48           len = size - w->offset;
350 48 50         if (len > (off64_t)git_mwindow__window_size)
351 0           len = (off64_t)git_mwindow__window_size;
352              
353 48           ctl->mapped += (size_t)len;
354              
355 48           while (git_mwindow__mapped_limit < ctl->mapped &&
356 0           git_mwindow_close_lru_window_locked() == 0) /* nop */;
357              
358             /*
359             * We treat `mapped_limit` as a soft limit. If we can't find a
360             * window to close and are above the limit, we still mmap the new
361             * window.
362             */
363              
364 48 50         if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) {
365             /*
366             * The first error might be down to memory fragmentation even if
367             * we're below our soft limits, so free up what we can and try again.
368             */
369              
370 0 0         while (git_mwindow_close_lru_window_locked() == 0)
371             /* nop */;
372              
373 0 0         if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) {
374 0           git__free(w);
375 0           return NULL;
376             }
377             }
378              
379 48           ctl->mmap_calls++;
380 48           ctl->open_windows++;
381              
382 48 100         if (ctl->mapped > ctl->peak_mapped)
383 23           ctl->peak_mapped = ctl->mapped;
384              
385 48 100         if (ctl->open_windows > ctl->peak_open_windows)
386 3           ctl->peak_open_windows = ctl->open_windows;
387              
388 48           return w;
389             }
390              
391             /*
392             * Open a new window, closing the least recenty used until we have
393             * enough space. Don't forget to add it to your list
394             */
395 154           unsigned char *git_mwindow_open(
396             git_mwindow_file *mwf,
397             git_mwindow **cursor,
398             off64_t offset,
399             size_t extra,
400             unsigned int *left)
401             {
402 154           git_mwindow_ctl *ctl = &git_mwindow__mem_ctl;
403 154           git_mwindow *w = *cursor;
404              
405 154 50         if (git_mutex_lock(&git__mwindow_mutex)) {
406 0           git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex");
407 0           return NULL;
408             }
409              
410 154 50         if (!w || !(git_mwindow_contains(w, offset, extra))) {
    0          
411 154 50         if (w) {
412 0           w->inuse_cnt--;
413             }
414              
415 154 100         for (w = mwf->windows; w; w = w->next) {
416 106 50         if (git_mwindow_contains(w, offset, extra))
417 106           break;
418             }
419              
420             /*
421             * If there isn't a suitable window, we need to create a new
422             * one.
423             */
424 154 100         if (!w) {
425 48           w = new_window_locked(mwf->fd, mwf->size, offset);
426 48 50         if (w == NULL) {
427 0           git_mutex_unlock(&git__mwindow_mutex);
428 0           return NULL;
429             }
430 48           w->next = mwf->windows;
431 48           mwf->windows = w;
432             }
433             }
434              
435             /* If we changed w, store it in the cursor */
436 154 50         if (w != *cursor) {
437 154           w->last_used = ctl->used_ctr++;
438 154           w->inuse_cnt++;
439 154           *cursor = w;
440             }
441              
442 154           offset -= w->offset;
443              
444 154 50         if (left)
445 154           *left = (unsigned int)(w->window_map.len - offset);
446              
447 154           git_mutex_unlock(&git__mwindow_mutex);
448 154           return (unsigned char *) w->window_map.data + offset;
449             }
450              
451 8           int git_mwindow_file_register(git_mwindow_file *mwf)
452             {
453 8           git_vector closed_files = GIT_VECTOR_INIT;
454 8           git_mwindow_ctl *ctl = &git_mwindow__mem_ctl;
455             int error;
456             size_t i;
457 8           git_mwindow_file *closed_file = NULL;
458              
459 8 50         if (git_mutex_lock(&git__mwindow_mutex)) {
460 0           git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex");
461 0           return -1;
462             }
463              
464 8 50         if (ctl->windowfiles.length == 0 &&
    50          
465 8           (error = git_vector_init(&ctl->windowfiles, 8, NULL)) < 0) {
466 0           git_mutex_unlock(&git__mwindow_mutex);
467 0           goto cleanup;
468             }
469              
470 8 50         if (git_mwindow__file_limit) {
471             git_mwindow_file *lru_file;
472 0           while (git_mwindow__file_limit <= ctl->windowfiles.length &&
473 0           git_mwindow_find_lru_file_locked(&lru_file) == 0) {
474 0 0         if ((error = git_vector_insert(&closed_files, lru_file)) < 0) {
475             /*
476             * Exceeding the file limit seems preferable to being open to
477             * data races that can end up corrupting the heap.
478             */
479 0           break;
480             }
481 0           git_mwindow_free_all_locked(lru_file);
482             }
483             }
484              
485 8           error = git_vector_insert(&ctl->windowfiles, mwf);
486 8           git_mutex_unlock(&git__mwindow_mutex);
487 8 50         if (error < 0)
488 0           goto cleanup;
489              
490             /*
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.
494             */
495 8 50         git_vector_foreach(&closed_files, i, closed_file) {
496 0           error = git_mutex_lock(&closed_file->lock);
497 0 0         if (error < 0)
498 0           continue;
499 0           p_close(closed_file->fd);
500 0           closed_file->fd = -1;
501 0           git_mutex_unlock(&closed_file->lock);
502             }
503              
504             cleanup:
505 8           git_vector_free(&closed_files);
506 8           return error;
507             }
508              
509 0           void git_mwindow_file_deregister(git_mwindow_file *mwf)
510             {
511 0           git_mwindow_ctl *ctl = &git_mwindow__mem_ctl;
512             git_mwindow_file *cur;
513             size_t i;
514              
515 0 0         if (git_mutex_lock(&git__mwindow_mutex))
516 0           return;
517              
518 0 0         git_vector_foreach(&ctl->windowfiles, i, cur) {
519 0 0         if (cur == mwf) {
520 0           git_vector_remove(&ctl->windowfiles, i);
521 0           git_mutex_unlock(&git__mwindow_mutex);
522 0           return;
523             }
524             }
525 0           git_mutex_unlock(&git__mwindow_mutex);
526             }
527              
528 204           void git_mwindow_close(git_mwindow **window)
529             {
530 204           git_mwindow *w = *window;
531 204 100         if (w) {
532 154 50         if (git_mutex_lock(&git__mwindow_mutex)) {
533 0           git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex");
534 0           return;
535             }
536              
537 154           w->inuse_cnt--;
538 154           git_mutex_unlock(&git__mwindow_mutex);
539 154           *window = NULL;
540             }
541             }