]>
Commit | Line | Data |
---|---|---|
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" |
0c9c969a | 11 | #include "futils.h" |
7bfdb3d2 | 12 | #include "map.h" |
a15c550d | 13 | #include "global.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 \ | |
18136d83 | 23 | ((1024 * 1024) * (sizeof(void*) >= 8 ? 8192ULL : 256UL)) |
7bfdb3d2 | 24 | |
59853eff VM |
25 | size_t git_mwindow__window_size = DEFAULT_WINDOW_SIZE; |
26 | size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT; | |
7bfdb3d2 | 27 | |
8cef828d CMN |
28 | /* Whenever you want to read or modify this, grab git__mwindow_mutex */ |
29 | static git_mwindow_ctl mem_ctl; | |
30 | ||
b3b66c57 CMN |
31 | /* Global list of mwindow files, to open packs once across repos */ |
32 | git_strmap *git__pack_cache = NULL; | |
33 | ||
2381d9e4 | 34 | static void git_mwindow_files_free(void) |
7bfdb3d2 | 35 | { |
2381d9e4 | 36 | git_strmap *tmp = git__pack_cache; |
8c8ca730 | 37 | |
2381d9e4 ET |
38 | git__pack_cache = NULL; |
39 | git_strmap_free(tmp); | |
b3b66c57 CMN |
40 | } |
41 | ||
2381d9e4 | 42 | int git_mwindow_global_init(void) |
b3b66c57 | 43 | { |
2381d9e4 | 44 | assert(!git__pack_cache); |
b3b66c57 | 45 | |
2381d9e4 | 46 | git__on_shutdown(git_mwindow_files_free); |
0c9c969a | 47 | return git_strmap_new(&git__pack_cache); |
b3b66c57 CMN |
48 | } |
49 | ||
50 | int git_mwindow_get_pack(struct git_pack_file **out, const char *path) | |
51 | { | |
b3b66c57 | 52 | struct git_pack_file *pack; |
0c9c969a UG |
53 | char *packname; |
54 | int error; | |
b3b66c57 CMN |
55 | |
56 | if ((error = git_packfile__name(&packname, path)) < 0) | |
57 | return error; | |
58 | ||
c19b1c04 | 59 | if (git_mutex_lock(&git__mwindow_mutex) < 0) { |
ac3d33df | 60 | git_error_set(GIT_ERROR_OS, "failed to lock mwindow mutex"); |
b3b66c57 | 61 | return -1; |
c19b1c04 | 62 | } |
b3b66c57 | 63 | |
0c9c969a | 64 | pack = git_strmap_get(git__pack_cache, packname); |
b3b66c57 CMN |
65 | git__free(packname); |
66 | ||
0c9c969a | 67 | if (pack != NULL) { |
b3b66c57 | 68 | git_atomic_inc(&pack->refcount); |
b3b66c57 CMN |
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 | ||
0c9c969a | 82 | error = git_strmap_set(git__pack_cache, pack->pack_name, pack); |
b3b66c57 CMN |
83 | git_mutex_unlock(&git__mwindow_mutex); |
84 | ||
5e0f47c3 CMN |
85 | if (error < 0) { |
86 | git_packfile_free(pack); | |
b3b66c57 | 87 | return -1; |
5e0f47c3 | 88 | } |
8cef828d | 89 | |
b3b66c57 CMN |
90 | *out = pack; |
91 | return 0; | |
92 | } | |
93 | ||
c19b1c04 | 94 | void git_mwindow_put_pack(struct git_pack_file *pack) |
b3b66c57 CMN |
95 | { |
96 | int count; | |
b3b66c57 CMN |
97 | |
98 | if (git_mutex_lock(&git__mwindow_mutex) < 0) | |
c19b1c04 | 99 | return; |
b3b66c57 | 100 | |
c19b1c04 CMN |
101 | /* put before get would be a corrupted state */ |
102 | assert(git__pack_cache); | |
b3b66c57 | 103 | |
c19b1c04 | 104 | /* if we cannot find it, the state is corrupted */ |
0c9c969a | 105 | assert(git_strmap_exists(git__pack_cache, pack->pack_name)); |
b3b66c57 CMN |
106 | |
107 | count = git_atomic_dec(&pack->refcount); | |
108 | if (count == 0) { | |
0c9c969a | 109 | git_strmap_delete(git__pack_cache, pack->pack_name); |
b3b66c57 CMN |
110 | git_packfile_free(pack); |
111 | } | |
112 | ||
113 | git_mutex_unlock(&git__mwindow_mutex); | |
c19b1c04 | 114 | return; |
b3b66c57 CMN |
115 | } |
116 | ||
117 | void git_mwindow_free_all(git_mwindow_file *mwf) | |
118 | { | |
a35b3864 | 119 | if (git_mutex_lock(&git__mwindow_mutex)) { |
ac3d33df | 120 | git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); |
a35b3864 JSS |
121 | return; |
122 | } | |
8cef828d | 123 | |
b3b66c57 CMN |
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 | ||
7bfdb3d2 CMN |
138 | /* |
139 | * Remove these windows from the global list | |
140 | */ | |
a15c550d VM |
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); | |
7bfdb3d2 CMN |
144 | break; |
145 | } | |
146 | } | |
147 | ||
a15c550d VM |
148 | if (ctl->windowfiles.length == 0) { |
149 | git_vector_free(&ctl->windowfiles); | |
150 | ctl->windowfiles.contents = NULL; | |
7bfdb3d2 CMN |
151 | } |
152 | ||
153 | while (mwf->windows) { | |
154 | git_mwindow *w = mwf->windows; | |
155 | assert(w->inuse_cnt == 0); | |
156 | ||
a15c550d VM |
157 | ctl->mapped -= w->window_map.len; |
158 | ctl->open_windows--; | |
7bfdb3d2 CMN |
159 | |
160 | git_futils_mmap_free(&w->window_map); | |
161 | ||
162 | mwf->windows = w->next; | |
3286c408 | 163 | git__free(w); |
7bfdb3d2 CMN |
164 | } |
165 | } | |
166 | ||
167 | /* | |
168 | * Check if a window 'win' contains the address 'offset' | |
169 | */ | |
0c9c969a | 170 | int git_mwindow_contains(git_mwindow *win, off64_t offset) |
7bfdb3d2 | 171 | { |
0c9c969a | 172 | off64_t win_off = win->offset; |
7bfdb3d2 | 173 | return win_off <= offset |
0c9c969a | 174 | && offset <= (off64_t)(win_off + win->window_map.len); |
7bfdb3d2 CMN |
175 | } |
176 | ||
177 | /* | |
178 | * Find the least-recently-used window in a file | |
179 | */ | |
8cef828d | 180 | static void git_mwindow_scan_lru( |
7bfdb3d2 CMN |
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 | |
8cef828d CMN |
205 | * the file descriptors need closing from time to time. Called under |
206 | * lock from new_window. | |
7bfdb3d2 | 207 | */ |
d568d585 | 208 | static int git_mwindow_close_lru(git_mwindow_file *mwf) |
7bfdb3d2 | 209 | { |
8cef828d | 210 | git_mwindow_ctl *ctl = &mem_ctl; |
10c06114 | 211 | size_t i; |
5fa1bed0 | 212 | git_mwindow *lru_w = NULL, *lru_l = NULL, **list = &mwf->windows; |
7bfdb3d2 | 213 | |
0d0fa7c3 | 214 | /* FIXME: Does this give us any advantage? */ |
7bfdb3d2 CMN |
215 | if(mwf->windows) |
216 | git_mwindow_scan_lru(mwf, &lru_w, &lru_l); | |
217 | ||
a15c550d | 218 | for (i = 0; i < ctl->windowfiles.length; ++i) { |
5fa1bed0 | 219 | git_mwindow *last = lru_w; |
a15c550d | 220 | git_mwindow_file *cur = git_vector_get(&ctl->windowfiles, i); |
5fa1bed0 CMN |
221 | git_mwindow_scan_lru(cur, &lru_w, &lru_l); |
222 | if (lru_w != last) | |
223 | list = &cur->windows; | |
7bfdb3d2 CMN |
224 | } |
225 | ||
0d0fa7c3 | 226 | if (!lru_w) { |
ac3d33df | 227 | git_error_set(GIT_ERROR_OS, "failed to close memory window; couldn't find LRU"); |
0d0fa7c3 RB |
228 | return -1; |
229 | } | |
7bfdb3d2 | 230 | |
0d0fa7c3 RB |
231 | ctl->mapped -= lru_w->window_map.len; |
232 | git_futils_mmap_free(&lru_w->window_map); | |
7bfdb3d2 | 233 | |
0d0fa7c3 RB |
234 | if (lru_l) |
235 | lru_l->next = lru_w->next; | |
236 | else | |
237 | *list = lru_w->next; | |
7bfdb3d2 | 238 | |
0d0fa7c3 RB |
239 | git__free(lru_w); |
240 | ctl->open_windows--; | |
7bfdb3d2 | 241 | |
0d0fa7c3 | 242 | return 0; |
7bfdb3d2 CMN |
243 | } |
244 | ||
8cef828d | 245 | /* This gets called under lock from git_mwindow_open */ |
a15c550d VM |
246 | static git_mwindow *new_window( |
247 | git_mwindow_file *mwf, | |
248 | git_file fd, | |
0c9c969a UG |
249 | off64_t size, |
250 | off64_t offset) | |
7bfdb3d2 | 251 | { |
8cef828d | 252 | git_mwindow_ctl *ctl = &mem_ctl; |
59853eff | 253 | size_t walign = git_mwindow__window_size / 2; |
0c9c969a | 254 | off64_t len; |
7bfdb3d2 CMN |
255 | git_mwindow *w; |
256 | ||
257 | w = git__malloc(sizeof(*w)); | |
53607868 | 258 | |
7bfdb3d2 | 259 | if (w == NULL) |
0d0fa7c3 | 260 | return NULL; |
7bfdb3d2 CMN |
261 | |
262 | memset(w, 0x0, sizeof(*w)); | |
263 | w->offset = (offset / walign) * walign; | |
264 | ||
265 | len = size - w->offset; | |
0c9c969a UG |
266 | if (len > (off64_t)git_mwindow__window_size) |
267 | len = (off64_t)git_mwindow__window_size; | |
7bfdb3d2 | 268 | |
a15c550d | 269 | ctl->mapped += (size_t)len; |
7bfdb3d2 | 270 | |
59853eff | 271 | while (git_mwindow__mapped_limit < ctl->mapped && |
0d0fa7c3 | 272 | git_mwindow_close_lru(mwf) == 0) /* nop */; |
7bfdb3d2 | 273 | |
5fa1bed0 | 274 | /* |
59853eff | 275 | * We treat `mapped_limit` as a soft limit. If we can't find a |
5fa1bed0 CMN |
276 | * window to close and are above the limit, we still mmap the new |
277 | * window. | |
278 | */ | |
7bfdb3d2 | 279 | |
e1de726c | 280 | if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) { |
d50fd571 CMN |
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 | } | |
e1de726c | 293 | } |
7bfdb3d2 | 294 | |
a15c550d VM |
295 | ctl->mmap_calls++; |
296 | ctl->open_windows++; | |
7bfdb3d2 | 297 | |
a15c550d VM |
298 | if (ctl->mapped > ctl->peak_mapped) |
299 | ctl->peak_mapped = ctl->mapped; | |
7bfdb3d2 | 300 | |
a15c550d VM |
301 | if (ctl->open_windows > ctl->peak_open_windows) |
302 | ctl->peak_open_windows = ctl->open_windows; | |
7bfdb3d2 CMN |
303 | |
304 | return w; | |
7bfdb3d2 CMN |
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 | */ | |
a15c550d VM |
311 | unsigned char *git_mwindow_open( |
312 | git_mwindow_file *mwf, | |
313 | git_mwindow **cursor, | |
0c9c969a | 314 | off64_t offset, |
deafee7b | 315 | size_t extra, |
a15c550d | 316 | unsigned int *left) |
7bfdb3d2 | 317 | { |
8cef828d | 318 | git_mwindow_ctl *ctl = &mem_ctl; |
7bfdb3d2 CMN |
319 | git_mwindow *w = *cursor; |
320 | ||
a35b3864 | 321 | if (git_mutex_lock(&git__mwindow_mutex)) { |
ac3d33df | 322 | git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); |
a35b3864 JSS |
323 | return NULL; |
324 | } | |
325 | ||
40879fac | 326 | if (!w || !(git_mwindow_contains(w, offset) && git_mwindow_contains(w, offset + extra))) { |
7bfdb3d2 CMN |
327 | if (w) { |
328 | w->inuse_cnt--; | |
329 | } | |
330 | ||
331 | for (w = mwf->windows; w; w = w->next) { | |
31e80290 | 332 | if (git_mwindow_contains(w, offset) && |
946a6dc4 | 333 | git_mwindow_contains(w, offset + extra)) |
7bfdb3d2 CMN |
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) { | |
7d0cdf82 | 342 | w = new_window(mwf, mwf->fd, mwf->size, offset); |
8cef828d CMN |
343 | if (w == NULL) { |
344 | git_mutex_unlock(&git__mwindow_mutex); | |
7bfdb3d2 | 345 | return NULL; |
8cef828d | 346 | } |
7bfdb3d2 CMN |
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) { | |
a15c550d | 354 | w->last_used = ctl->used_ctr++; |
7bfdb3d2 CMN |
355 | w->inuse_cnt++; |
356 | *cursor = w; | |
357 | } | |
358 | ||
359 | offset -= w->offset; | |
7bfdb3d2 CMN |
360 | |
361 | if (left) | |
f6867e63 | 362 | *left = (unsigned int)(w->window_map.len - offset); |
7bfdb3d2 | 363 | |
8cef828d | 364 | git_mutex_unlock(&git__mwindow_mutex); |
7bfdb3d2 | 365 | return (unsigned char *) w->window_map.data + offset; |
7bfdb3d2 CMN |
366 | } |
367 | ||
368 | int git_mwindow_file_register(git_mwindow_file *mwf) | |
369 | { | |
8cef828d CMN |
370 | git_mwindow_ctl *ctl = &mem_ctl; |
371 | int ret; | |
7bfdb3d2 | 372 | |
a35b3864 | 373 | if (git_mutex_lock(&git__mwindow_mutex)) { |
ac3d33df | 374 | git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); |
a35b3864 JSS |
375 | return -1; |
376 | } | |
377 | ||
a15c550d | 378 | if (ctl->windowfiles.length == 0 && |
8cef828d CMN |
379 | git_vector_init(&ctl->windowfiles, 8, NULL) < 0) { |
380 | git_mutex_unlock(&git__mwindow_mutex); | |
e1de726c | 381 | return -1; |
8cef828d CMN |
382 | } |
383 | ||
384 | ret = git_vector_insert(&ctl->windowfiles, mwf); | |
385 | git_mutex_unlock(&git__mwindow_mutex); | |
7bfdb3d2 | 386 | |
8cef828d | 387 | return ret; |
7bfdb3d2 CMN |
388 | } |
389 | ||
f9b55bcb | 390 | void git_mwindow_file_deregister(git_mwindow_file *mwf) |
1d8943c6 | 391 | { |
8cef828d | 392 | git_mwindow_ctl *ctl = &mem_ctl; |
1d8943c6 | 393 | git_mwindow_file *cur; |
10c06114 | 394 | size_t i; |
1d8943c6 | 395 | |
f9b55bcb SG |
396 | if (git_mutex_lock(&git__mwindow_mutex)) |
397 | return; | |
a35b3864 | 398 | |
1d8943c6 CMN |
399 | git_vector_foreach(&ctl->windowfiles, i, cur) { |
400 | if (cur == mwf) { | |
401 | git_vector_remove(&ctl->windowfiles, i); | |
8cef828d | 402 | git_mutex_unlock(&git__mwindow_mutex); |
f9b55bcb | 403 | return; |
1d8943c6 CMN |
404 | } |
405 | } | |
8cef828d | 406 | git_mutex_unlock(&git__mwindow_mutex); |
1d8943c6 CMN |
407 | } |
408 | ||
7bfdb3d2 CMN |
409 | void git_mwindow_close(git_mwindow **window) |
410 | { | |
411 | git_mwindow *w = *window; | |
412 | if (w) { | |
a35b3864 | 413 | if (git_mutex_lock(&git__mwindow_mutex)) { |
ac3d33df | 414 | git_error_set(GIT_ERROR_THREAD, "unable to lock mwindow mutex"); |
a35b3864 JSS |
415 | return; |
416 | } | |
417 | ||
7bfdb3d2 | 418 | w->inuse_cnt--; |
8cef828d | 419 | git_mutex_unlock(&git__mwindow_mutex); |
7bfdb3d2 CMN |
420 | *window = NULL; |
421 | } | |
422 | } |