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