File Coverage

deps/libgit2/src/mwindow.c
Criterion Covered Total %
statement 105 176 59.6
branch 41 100 41.0
condition n/a
subroutine n/a
pod n/a
total 146 276 52.9


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             size_t git_mwindow__window_size = DEFAULT_WINDOW_SIZE;
26             size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT;
27              
28             /* Whenever you want to read or modify this, grab git__mwindow_mutex */
29             static git_mwindow_ctl mem_ctl;
30              
31             /* Global list of mwindow files, to open packs once across repos */
32             git_strmap *git__pack_cache = NULL;
33              
34 0           static void git_mwindow_files_free(void)
35             {
36 0           git_strmap *tmp = git__pack_cache;
37              
38 0           git__pack_cache = NULL;
39 0           git_strmap_free(tmp);
40 0           }
41              
42 86           int git_mwindow_global_init(void)
43             {
44 86 50         assert(!git__pack_cache);
45              
46 86           git__on_shutdown(git_mwindow_files_free);
47 86           return git_strmap_new(&git__pack_cache);
48             }
49              
50 4           int git_mwindow_get_pack(struct git_pack_file **out, const char *path)
51             {
52             struct git_pack_file *pack;
53             char *packname;
54             int error;
55              
56 4 50         if ((error = git_packfile__name(&packname, path)) < 0)
57 0           return error;
58              
59 4 50         if (git_mutex_lock(&git__mwindow_mutex) < 0) {
60 0           git_error_set(GIT_ERROR_OS, "failed to lock mwindow mutex");
61 0           return -1;
62             }
63              
64 4           pack = git_strmap_get(git__pack_cache, packname);
65 4           git__free(packname);
66              
67 4 50         if (pack != NULL) {
68 0           git_atomic_inc(&pack->refcount);
69             git_mutex_unlock(&git__mwindow_mutex);
70 0           *out = pack;
71 0           return 0;
72             }
73              
74             /* If we didn't find it, we need to create it */
75 4 50         if ((error = git_packfile_alloc(&pack, path)) < 0) {
76             git_mutex_unlock(&git__mwindow_mutex);
77 0           return error;
78             }
79              
80 4           git_atomic_inc(&pack->refcount);
81              
82 4           error = git_strmap_set(git__pack_cache, pack->pack_name, pack);
83             git_mutex_unlock(&git__mwindow_mutex);
84              
85 4 50         if (error < 0) {
86 0           git_packfile_free(pack);
87 0           return -1;
88             }
89              
90 4           *out = pack;
91 4           return 0;
92             }
93              
94 3           void git_mwindow_put_pack(struct git_pack_file *pack)
95             {
96             int count;
97              
98 3 50         if (git_mutex_lock(&git__mwindow_mutex) < 0)
99 0           return;
100              
101             /* put before get would be a corrupted state */
102 3 50         assert(git__pack_cache);
103              
104             /* if we cannot find it, the state is corrupted */
105 3 50         assert(git_strmap_exists(git__pack_cache, pack->pack_name));
106              
107 3           count = git_atomic_dec(&pack->refcount);
108 3 50         if (count == 0) {
109 3           git_strmap_delete(git__pack_cache, pack->pack_name);
110 3           git_packfile_free(pack);
111             }
112              
113             git_mutex_unlock(&git__mwindow_mutex);
114 3           return;
115             }
116              
117 96           void git_mwindow_free_all(git_mwindow_file *mwf)
118             {
119 96 50         if (git_mutex_lock(&git__mwindow_mutex)) {
120 0           git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex");
121 0           return;
122             }
123              
124 96           git_mwindow_free_all_locked(mwf);
125              
126             git_mutex_unlock(&git__mwindow_mutex);
127             }
128              
129             /*
130             * Free all the windows in a sequence, typically because we're done
131             * with the file
132             */
133 99           void git_mwindow_free_all_locked(git_mwindow_file *mwf)
134             {
135 99           git_mwindow_ctl *ctl = &mem_ctl;
136             size_t i;
137              
138             /*
139             * Remove these windows from the global list
140             */
141 99 100         for (i = 0; i < ctl->windowfiles.length; ++i){
142 8 50         if (git_vector_get(&ctl->windowfiles, i) == mwf) {
143 8           git_vector_remove(&ctl->windowfiles, i);
144 8           break;
145             }
146             }
147              
148 99 50         if (ctl->windowfiles.length == 0) {
149 99           git_vector_free(&ctl->windowfiles);
150 99           ctl->windowfiles.contents = NULL;
151             }
152              
153 147 100         while (mwf->windows) {
154 48           git_mwindow *w = mwf->windows;
155 48 50         assert(w->inuse_cnt == 0);
156              
157 48           ctl->mapped -= w->window_map.len;
158 48           ctl->open_windows--;
159              
160 48           git_futils_mmap_free(&w->window_map);
161              
162 48           mwf->windows = w->next;
163 48           git__free(w);
164             }
165 99           }
166              
167             /*
168             * Check if a window 'win' contains the address 'offset'
169             */
170 212           int git_mwindow_contains(git_mwindow *win, off64_t offset)
171             {
172 212           off64_t win_off = win->offset;
173 212           return win_off <= offset
174 212 50         && offset <= (off64_t)(win_off + win->window_map.len);
    50          
175             }
176              
177             /*
178             * Find the least-recently-used window in a file
179             */
180 0           static void git_mwindow_scan_lru(
181             git_mwindow_file *mwf,
182             git_mwindow **lru_w,
183             git_mwindow **lru_l)
184             {
185             git_mwindow *w, *w_l;
186              
187 0 0         for (w_l = NULL, w = mwf->windows; w; w = w->next) {
188 0 0         if (!w->inuse_cnt) {
189             /*
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.
193             */
194 0 0         if (!*lru_w || w->last_used < (*lru_w)->last_used) {
    0          
195 0           *lru_w = w;
196 0           *lru_l = w_l;
197             }
198             }
199 0           w_l = w;
200             }
201 0           }
202              
203             /*
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.
207             */
208 0           static int git_mwindow_close_lru(git_mwindow_file *mwf)
209             {
210 0           git_mwindow_ctl *ctl = &mem_ctl;
211             size_t i;
212 0           git_mwindow *lru_w = NULL, *lru_l = NULL, **list = &mwf->windows;
213              
214             /* FIXME: Does this give us any advantage? */
215 0 0         if(mwf->windows)
216 0           git_mwindow_scan_lru(mwf, &lru_w, &lru_l);
217              
218 0 0         for (i = 0; i < ctl->windowfiles.length; ++i) {
219 0           git_mwindow *last = lru_w;
220 0           git_mwindow_file *cur = git_vector_get(&ctl->windowfiles, i);
221 0           git_mwindow_scan_lru(cur, &lru_w, &lru_l);
222 0 0         if (lru_w != last)
223 0           list = &cur->windows;
224             }
225              
226 0 0         if (!lru_w) {
227 0           git_error_set(GIT_ERROR_OS, "failed to close memory window; couldn't find LRU");
228 0           return -1;
229             }
230              
231 0           ctl->mapped -= lru_w->window_map.len;
232 0           git_futils_mmap_free(&lru_w->window_map);
233              
234 0 0         if (lru_l)
235 0           lru_l->next = lru_w->next;
236             else
237 0           *list = lru_w->next;
238              
239 0           git__free(lru_w);
240 0           ctl->open_windows--;
241              
242 0           return 0;
243             }
244              
245             /* This gets called under lock from git_mwindow_open */
246 48           static git_mwindow *new_window(
247             git_mwindow_file *mwf,
248             git_file fd,
249             off64_t size,
250             off64_t offset)
251             {
252 48           git_mwindow_ctl *ctl = &mem_ctl;
253 48           size_t walign = git_mwindow__window_size / 2;
254             off64_t len;
255             git_mwindow *w;
256              
257 48           w = git__malloc(sizeof(*w));
258              
259 48 50         if (w == NULL)
260 0           return NULL;
261              
262 48           memset(w, 0x0, sizeof(*w));
263 48           w->offset = (offset / walign) * walign;
264              
265 48           len = size - w->offset;
266 48 50         if (len > (off64_t)git_mwindow__window_size)
267 0           len = (off64_t)git_mwindow__window_size;
268              
269 48           ctl->mapped += (size_t)len;
270              
271 48           while (git_mwindow__mapped_limit < ctl->mapped &&
272 0           git_mwindow_close_lru(mwf) == 0) /* nop */;
273              
274             /*
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
277             * window.
278             */
279              
280 48 50         if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) {
281             /*
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.
284             */
285              
286 0 0         while (git_mwindow_close_lru(mwf) == 0)
287             /* nop */;
288              
289 0 0         if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) {
290 0           git__free(w);
291 0           return NULL;
292             }
293             }
294              
295 48           ctl->mmap_calls++;
296 48           ctl->open_windows++;
297              
298 48 100         if (ctl->mapped > ctl->peak_mapped)
299 23           ctl->peak_mapped = ctl->mapped;
300              
301 48 100         if (ctl->open_windows > ctl->peak_open_windows)
302 3           ctl->peak_open_windows = ctl->open_windows;
303              
304 48           return w;
305             }
306              
307             /*
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
310             */
311 154           unsigned char *git_mwindow_open(
312             git_mwindow_file *mwf,
313             git_mwindow **cursor,
314             off64_t offset,
315             size_t extra,
316             unsigned int *left)
317             {
318 154           git_mwindow_ctl *ctl = &mem_ctl;
319 154           git_mwindow *w = *cursor;
320              
321 154 50         if (git_mutex_lock(&git__mwindow_mutex)) {
322 0           git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex");
323 0           return NULL;
324             }
325              
326 154 50         if (!w || !(git_mwindow_contains(w, offset) && git_mwindow_contains(w, offset + extra))) {
    0          
    0          
327 154 50         if (w) {
328 0           w->inuse_cnt--;
329             }
330              
331 154 100         for (w = mwf->windows; w; w = w->next) {
332 212           if (git_mwindow_contains(w, offset) &&
333 106           git_mwindow_contains(w, offset + extra))
334 106           break;
335             }
336              
337             /*
338             * If there isn't a suitable window, we need to create a new
339             * one.
340             */
341 154 100         if (!w) {
342 48           w = new_window(mwf, mwf->fd, mwf->size, offset);
343 48 50         if (w == NULL) {
344             git_mutex_unlock(&git__mwindow_mutex);
345 0           return NULL;
346             }
347 48           w->next = mwf->windows;
348 48           mwf->windows = w;
349             }
350             }
351              
352             /* If we changed w, store it in the cursor */
353 154 50         if (w != *cursor) {
354 154           w->last_used = ctl->used_ctr++;
355 154           w->inuse_cnt++;
356 154           *cursor = w;
357             }
358              
359 154           offset -= w->offset;
360              
361 154 50         if (left)
362 154           *left = (unsigned int)(w->window_map.len - offset);
363              
364             git_mutex_unlock(&git__mwindow_mutex);
365 154           return (unsigned char *) w->window_map.data + offset;
366             }
367              
368 8           int git_mwindow_file_register(git_mwindow_file *mwf)
369             {
370 8           git_mwindow_ctl *ctl = &mem_ctl;
371             int ret;
372              
373 8 50         if (git_mutex_lock(&git__mwindow_mutex)) {
374 0           git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex");
375 0           return -1;
376             }
377              
378 16           if (ctl->windowfiles.length == 0 &&
379 8           git_vector_init(&ctl->windowfiles, 8, NULL) < 0) {
380             git_mutex_unlock(&git__mwindow_mutex);
381 0           return -1;
382             }
383              
384 8           ret = git_vector_insert(&ctl->windowfiles, mwf);
385             git_mutex_unlock(&git__mwindow_mutex);
386              
387 8           return ret;
388             }
389              
390 0           void git_mwindow_file_deregister(git_mwindow_file *mwf)
391             {
392 0           git_mwindow_ctl *ctl = &mem_ctl;
393             git_mwindow_file *cur;
394             size_t i;
395              
396 0 0         if (git_mutex_lock(&git__mwindow_mutex))
397 0           return;
398              
399 0 0         git_vector_foreach(&ctl->windowfiles, i, cur) {
400 0 0         if (cur == mwf) {
401 0           git_vector_remove(&ctl->windowfiles, i);
402             git_mutex_unlock(&git__mwindow_mutex);
403 0           return;
404             }
405             }
406             git_mutex_unlock(&git__mwindow_mutex);
407             }
408              
409 204           void git_mwindow_close(git_mwindow **window)
410             {
411 204           git_mwindow *w = *window;
412 204 100         if (w) {
413 154 50         if (git_mutex_lock(&git__mwindow_mutex)) {
414 0           git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex");
415 0           return;
416             }
417              
418 154           w->inuse_cnt--;
419             git_mutex_unlock(&git__mwindow_mutex);
420 154           *window = NULL;
421             }
422             }