File Coverage

deps/libgit2/src/mwindow.c
Criterion Covered Total %
statement 106 208 50.9
branch 42 128 32.8
condition n/a
subroutine n/a
pod n/a
total 148 336 44.0


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 "global.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 ? 8192ULL : 256UL))
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             /* Whenever you want to read or modify this, grab git__mwindow_mutex */
33             git_mwindow_ctl git_mwindow__mem_ctl;
34              
35             /* Global list of mwindow files, to open packs once across repos */
36             git_strmap *git__pack_cache = NULL;
37              
38 0           static void git_mwindow_files_free(void)
39             {
40 0           git_strmap *tmp = git__pack_cache;
41              
42 0           git__pack_cache = NULL;
43 0           git_strmap_free(tmp);
44 0           }
45              
46 86           int git_mwindow_global_init(void)
47             {
48 86 50         assert(!git__pack_cache);
49              
50 86           git__on_shutdown(git_mwindow_files_free);
51 86           return git_strmap_new(&git__pack_cache);
52             }
53              
54 4           int git_mwindow_get_pack(struct git_pack_file **out, const char *path)
55             {
56             struct git_pack_file *pack;
57             char *packname;
58             int error;
59              
60 4 50         if ((error = git_packfile__name(&packname, path)) < 0)
61 0           return error;
62              
63 4 50         if (git_mutex_lock(&git__mwindow_mutex) < 0) {
64 0           git_error_set(GIT_ERROR_OS, "failed to lock mwindow mutex");
65 0           return -1;
66             }
67              
68 4           pack = git_strmap_get(git__pack_cache, packname);
69 4           git__free(packname);
70              
71 4 50         if (pack != NULL) {
72 0           git_atomic_inc(&pack->refcount);
73             git_mutex_unlock(&git__mwindow_mutex);
74 0           *out = pack;
75 0           return 0;
76             }
77              
78             /* If we didn't find it, we need to create it */
79 4 50         if ((error = git_packfile_alloc(&pack, path)) < 0) {
80             git_mutex_unlock(&git__mwindow_mutex);
81 0           return error;
82             }
83              
84 4           git_atomic_inc(&pack->refcount);
85              
86 4           error = git_strmap_set(git__pack_cache, pack->pack_name, pack);
87             git_mutex_unlock(&git__mwindow_mutex);
88              
89 4 50         if (error < 0) {
90 0           git_packfile_free(pack);
91 0           return -1;
92             }
93              
94 4           *out = pack;
95 4           return 0;
96             }
97              
98 3           void git_mwindow_put_pack(struct git_pack_file *pack)
99             {
100             int count;
101              
102 3 50         if (git_mutex_lock(&git__mwindow_mutex) < 0)
103 0           return;
104              
105             /* put before get would be a corrupted state */
106 3 50         assert(git__pack_cache);
107              
108             /* if we cannot find it, the state is corrupted */
109 3 50         assert(git_strmap_exists(git__pack_cache, pack->pack_name));
110              
111 3           count = git_atomic_dec(&pack->refcount);
112 3 50         if (count == 0) {
113 3           git_strmap_delete(git__pack_cache, pack->pack_name);
114 3           git_packfile_free(pack);
115             }
116              
117             git_mutex_unlock(&git__mwindow_mutex);
118 3           return;
119             }
120              
121 96           void git_mwindow_free_all(git_mwindow_file *mwf)
122             {
123 96 50         if (git_mutex_lock(&git__mwindow_mutex)) {
124 0           git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex");
125 0           return;
126             }
127              
128 96           git_mwindow_free_all_locked(mwf);
129              
130             git_mutex_unlock(&git__mwindow_mutex);
131             }
132              
133             /*
134             * Free all the windows in a sequence, typically because we're done
135             * with the file
136             */
137 99           void git_mwindow_free_all_locked(git_mwindow_file *mwf)
138             {
139 99           git_mwindow_ctl *ctl = &git_mwindow__mem_ctl;
140             size_t i;
141              
142             /*
143             * Remove these windows from the global list
144             */
145 99 100         for (i = 0; i < ctl->windowfiles.length; ++i){
146 8 50         if (git_vector_get(&ctl->windowfiles, i) == mwf) {
147 8           git_vector_remove(&ctl->windowfiles, i);
148 8           break;
149             }
150             }
151              
152 99 50         if (ctl->windowfiles.length == 0) {
153 99           git_vector_free(&ctl->windowfiles);
154 99           ctl->windowfiles.contents = NULL;
155             }
156              
157 147 100         while (mwf->windows) {
158 48           git_mwindow *w = mwf->windows;
159 48 50         assert(w->inuse_cnt == 0);
160              
161 48           ctl->mapped -= w->window_map.len;
162 48           ctl->open_windows--;
163              
164 48           git_futils_mmap_free(&w->window_map);
165              
166 48           mwf->windows = w->next;
167 48           git__free(w);
168             }
169 99           }
170              
171             /*
172             * Check if a window 'win' contains the address 'offset'
173             */
174 212           int git_mwindow_contains(git_mwindow *win, off64_t offset)
175             {
176 212           off64_t win_off = win->offset;
177 212           return win_off <= offset
178 212 50         && offset <= (off64_t)(win_off + win->window_map.len);
    50          
179             }
180              
181             #define GIT_MWINDOW__LRU -1
182             #define GIT_MWINDOW__MRU 1
183              
184             /*
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.
189             *
190             * Returns whether such a window was found in the file.
191             */
192 0           static bool git_mwindow_scan_recently_used(
193             git_mwindow_file *mwf,
194             git_mwindow **out_window,
195             git_mwindow **out_last,
196             bool only_unused,
197             int comparison_sign)
198             {
199             git_mwindow *w, *w_last;
200 0           git_mwindow *lru_window = NULL, *lru_last = NULL;
201 0           bool found = false;
202              
203 0 0         assert(mwf);
204 0 0         assert(out_window);
205              
206 0           lru_window = *out_window;
207 0 0         if (out_last)
208 0           lru_last = *out_last;
209              
210 0 0         for (w_last = NULL, w = mwf->windows; w; w_last = w, w = w->next) {
211 0 0         if (w->inuse_cnt) {
212 0 0         if (only_unused)
213 0           return false;
214             /* This window is currently being used. Skip it. */
215 0           continue;
216             }
217              
218             /*
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.
222             */
223 0 0         if (!lru_window ||
    0          
224 0 0         (comparison_sign == GIT_MWINDOW__LRU && lru_window->last_used > w->last_used) ||
    0          
225 0 0         (comparison_sign == GIT_MWINDOW__MRU && lru_window->last_used < w->last_used)) {
226 0           lru_window = w;
227 0           lru_last = w_last;
228 0           found = true;
229             }
230             }
231              
232 0 0         if (!found)
233 0           return false;
234              
235 0           *out_window = lru_window;
236 0 0         if (out_last)
237 0           *out_last = lru_last;
238 0           return true;
239             }
240              
241             /*
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.
244             */
245 0           static int git_mwindow_close_lru_window(void)
246             {
247 0           git_mwindow_ctl *ctl = &git_mwindow__mem_ctl;
248             git_mwindow_file *cur;
249             size_t i;
250 0           git_mwindow *lru_window = NULL, *lru_last = NULL, **list = NULL;
251              
252 0 0         git_vector_foreach(&ctl->windowfiles, i, cur) {
253 0 0         if (git_mwindow_scan_recently_used(
254             cur, &lru_window, &lru_last, false, GIT_MWINDOW__LRU)) {
255 0           list = &cur->windows;
256             }
257             }
258              
259 0 0         if (!lru_window) {
260 0           git_error_set(GIT_ERROR_OS, "failed to close memory window; couldn't find LRU");
261 0           return -1;
262             }
263              
264 0           ctl->mapped -= lru_window->window_map.len;
265 0           git_futils_mmap_free(&lru_window->window_map);
266              
267 0 0         if (lru_last)
268 0           lru_last->next = lru_window->next;
269             else
270 0           *list = lru_window->next;
271              
272 0           git__free(lru_window);
273 0           ctl->open_windows--;
274              
275 0           return 0;
276             }
277              
278             /*
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.
282             *
283             * Called under lock from new_window.
284             */
285 0           static int git_mwindow_close_lru_file(void)
286             {
287 0           git_mwindow_ctl *ctl = &git_mwindow__mem_ctl;
288 0           git_mwindow_file *lru_file = NULL, *current_file = NULL;
289 0           git_mwindow *lru_window = NULL;
290             size_t i;
291              
292 0 0         git_vector_foreach(&ctl->windowfiles, i, current_file) {
293 0           git_mwindow *mru_window = NULL;
294 0 0         if (!git_mwindow_scan_recently_used(
295             current_file, &mru_window, NULL, true, GIT_MWINDOW__MRU)) {
296 0           continue;
297             }
298 0 0         if (!lru_window || lru_window->last_used > mru_window->last_used)
    0          
299 0           lru_file = current_file;
300             }
301              
302 0 0         if (!lru_file) {
303 0           git_error_set(GIT_ERROR_OS, "failed to close memory window file; couldn't find LRU");
304 0           return -1;
305             }
306              
307 0           git_mwindow_free_all_locked(lru_file);
308 0           p_close(lru_file->fd);
309 0           lru_file->fd = -1;
310              
311 0           return 0;
312             }
313              
314             /* This gets called under lock from git_mwindow_open */
315 48           static git_mwindow *new_window(
316             git_file fd,
317             off64_t size,
318             off64_t offset)
319             {
320 48           git_mwindow_ctl *ctl = &git_mwindow__mem_ctl;
321 48           size_t walign = git_mwindow__window_size / 2;
322             off64_t len;
323             git_mwindow *w;
324              
325 48           w = git__malloc(sizeof(*w));
326              
327 48 50         if (w == NULL)
328 0           return NULL;
329              
330 48           memset(w, 0x0, sizeof(*w));
331 48           w->offset = (offset / walign) * walign;
332              
333 48           len = size - w->offset;
334 48 50         if (len > (off64_t)git_mwindow__window_size)
335 0           len = (off64_t)git_mwindow__window_size;
336              
337 48           ctl->mapped += (size_t)len;
338              
339 48           while (git_mwindow__mapped_limit < ctl->mapped &&
340 0           git_mwindow_close_lru_window() == 0) /* nop */;
341              
342             /*
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
345             * window.
346             */
347              
348 48 50         if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) {
349             /*
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.
352             */
353              
354 0 0         while (git_mwindow_close_lru_window() == 0)
355             /* nop */;
356              
357 0 0         if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) {
358 0           git__free(w);
359 0           return NULL;
360             }
361             }
362              
363 48           ctl->mmap_calls++;
364 48           ctl->open_windows++;
365              
366 48 100         if (ctl->mapped > ctl->peak_mapped)
367 23           ctl->peak_mapped = ctl->mapped;
368              
369 48 100         if (ctl->open_windows > ctl->peak_open_windows)
370 3           ctl->peak_open_windows = ctl->open_windows;
371              
372 48           return w;
373             }
374              
375             /*
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
378             */
379 154           unsigned char *git_mwindow_open(
380             git_mwindow_file *mwf,
381             git_mwindow **cursor,
382             off64_t offset,
383             size_t extra,
384             unsigned int *left)
385             {
386 154           git_mwindow_ctl *ctl = &git_mwindow__mem_ctl;
387 154           git_mwindow *w = *cursor;
388              
389 154 50         if (git_mutex_lock(&git__mwindow_mutex)) {
390 0           git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex");
391 0           return NULL;
392             }
393              
394 154 50         if (!w || !(git_mwindow_contains(w, offset) && git_mwindow_contains(w, offset + extra))) {
    0          
    0          
395 154 50         if (w) {
396 0           w->inuse_cnt--;
397             }
398              
399 154 100         for (w = mwf->windows; w; w = w->next) {
400 212           if (git_mwindow_contains(w, offset) &&
401 106           git_mwindow_contains(w, offset + extra))
402 106           break;
403             }
404              
405             /*
406             * If there isn't a suitable window, we need to create a new
407             * one.
408             */
409 154 100         if (!w) {
410 48           w = new_window(mwf->fd, mwf->size, offset);
411 48 50         if (w == NULL) {
412             git_mutex_unlock(&git__mwindow_mutex);
413 0           return NULL;
414             }
415 48           w->next = mwf->windows;
416 48           mwf->windows = w;
417             }
418             }
419              
420             /* If we changed w, store it in the cursor */
421 154 50         if (w != *cursor) {
422 154           w->last_used = ctl->used_ctr++;
423 154           w->inuse_cnt++;
424 154           *cursor = w;
425             }
426              
427 154           offset -= w->offset;
428              
429 154 50         if (left)
430 154           *left = (unsigned int)(w->window_map.len - offset);
431              
432             git_mutex_unlock(&git__mwindow_mutex);
433 154           return (unsigned char *) w->window_map.data + offset;
434             }
435              
436 8           int git_mwindow_file_register(git_mwindow_file *mwf)
437             {
438 8           git_mwindow_ctl *ctl = &git_mwindow__mem_ctl;
439             int ret;
440              
441 8 50         if (git_mutex_lock(&git__mwindow_mutex)) {
442 0           git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex");
443 0           return -1;
444             }
445              
446 16           if (ctl->windowfiles.length == 0 &&
447 8           git_vector_init(&ctl->windowfiles, 8, NULL) < 0) {
448             git_mutex_unlock(&git__mwindow_mutex);
449 0           return -1;
450             }
451              
452 8 50         if (git_mwindow__file_limit) {
453 0           while (git_mwindow__file_limit <= ctl->windowfiles.length &&
454 0           git_mwindow_close_lru_file() == 0) /* nop */;
455             }
456              
457 8           ret = git_vector_insert(&ctl->windowfiles, mwf);
458             git_mutex_unlock(&git__mwindow_mutex);
459              
460 8           return ret;
461             }
462              
463 0           void git_mwindow_file_deregister(git_mwindow_file *mwf)
464             {
465 0           git_mwindow_ctl *ctl = &git_mwindow__mem_ctl;
466             git_mwindow_file *cur;
467             size_t i;
468              
469 0 0         if (git_mutex_lock(&git__mwindow_mutex))
470 0           return;
471              
472 0 0         git_vector_foreach(&ctl->windowfiles, i, cur) {
473 0 0         if (cur == mwf) {
474 0           git_vector_remove(&ctl->windowfiles, i);
475             git_mutex_unlock(&git__mwindow_mutex);
476 0           return;
477             }
478             }
479             git_mutex_unlock(&git__mwindow_mutex);
480             }
481              
482 204           void git_mwindow_close(git_mwindow **window)
483             {
484 204           git_mwindow *w = *window;
485 204 100         if (w) {
486 154 50         if (git_mutex_lock(&git__mwindow_mutex)) {
487 0           git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex");
488 0           return;
489             }
490              
491 154           w->inuse_cnt--;
492             git_mutex_unlock(&git__mwindow_mutex);
493 154           *window = NULL;
494             }
495             }