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