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