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