libgit2/src/mwindow.c
Carlos Martín Nieto c19b1c0442 pack: clean up error returns
Set a message when we fail to lock.

Also make the put function void, since it's called from free, which
cannot report errors. The only errors we can experience here are
internal state corruption, so we assert that we are trying to put a
pack which we have previously got.
2014-06-25 21:35:58 +02:00

429 lines
9.2 KiB
C

/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "mwindow.h"
#include "vector.h"
#include "fileops.h"
#include "map.h"
#include "global.h"
#include "strmap.h"
#include "pack.h"
GIT__USE_STRMAP;
#define DEFAULT_WINDOW_SIZE \
(sizeof(void*) >= 8 \
? 1 * 1024 * 1024 * 1024 \
: 32 * 1024 * 1024)
#define DEFAULT_MAPPED_LIMIT \
((1024 * 1024) * (sizeof(void*) >= 8 ? 8192ULL : 256UL))
size_t git_mwindow__window_size = DEFAULT_WINDOW_SIZE;
size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT;
/* Whenever you want to read or modify this, grab git__mwindow_mutex */
static git_mwindow_ctl mem_ctl;
/* Global list of mwindow files, to open packs once across repos */
git_strmap *git__pack_cache = NULL;
/**
* Run under mwindow lock
*/
int git_mwindow_files_init(void)
{
if (git__pack_cache)
return 0;
return git_strmap_alloc(&git__pack_cache);
}
void git_mwindow_files_free(void)
{
git_strmap *tmp = git__pack_cache;
git__pack_cache = NULL;
git_strmap_free(tmp);
}
int git_mwindow_get_pack(struct git_pack_file **out, const char *path)
{
int error;
char *packname;
git_strmap_iter pos;
struct git_pack_file *pack;
if ((error = git_packfile__name(&packname, path)) < 0)
return error;
if (git_mutex_lock(&git__mwindow_mutex) < 0) {
giterr_set(GITERR_OS, "failed to lock mwindow mutex");
return -1;
}
if (git_mwindow_files_init() < 0) {
git_mutex_unlock(&git__mwindow_mutex);
git__free(packname);
return -1;
}
pos = git_strmap_lookup_index(git__pack_cache, packname);
git__free(packname);
if (git_strmap_valid_index(git__pack_cache, pos)) {
pack = git_strmap_value_at(git__pack_cache, pos);
git_atomic_inc(&pack->refcount);
git_mutex_unlock(&git__mwindow_mutex);
*out = pack;
return 0;
}
/* If we didn't find it, we need to create it */
if ((error = git_packfile_alloc(&pack, path)) < 0) {
git_mutex_unlock(&git__mwindow_mutex);
return error;
}
git_atomic_inc(&pack->refcount);
git_strmap_insert(git__pack_cache, pack->pack_name, pack, error);
git_mutex_unlock(&git__mwindow_mutex);
if (error < 0) {
git_packfile_free(pack);
return -1;
}
*out = pack;
return 0;
}
void git_mwindow_put_pack(struct git_pack_file *pack)
{
int count;
git_strmap_iter pos;
if (git_mutex_lock(&git__mwindow_mutex) < 0)
return;
/* put before get would be a corrupted state */
assert(git__pack_cache);
pos = git_strmap_lookup_index(git__pack_cache, pack->pack_name);
/* if we cannot find it, the state is corrupted */
assert(git_strmap_valid_index(git__pack_cache, pos));
count = git_atomic_dec(&pack->refcount);
if (count == 0) {
git_strmap_delete_at(git__pack_cache, pos);
git_packfile_free(pack);
}
git_mutex_unlock(&git__mwindow_mutex);
return;
}
void git_mwindow_free_all(git_mwindow_file *mwf)
{
if (git_mutex_lock(&git__mwindow_mutex)) {
giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
return;
}
git_mwindow_free_all_locked(mwf);
git_mutex_unlock(&git__mwindow_mutex);
}
/*
* Free all the windows in a sequence, typically because we're done
* with the file
*/
void git_mwindow_free_all_locked(git_mwindow_file *mwf)
{
git_mwindow_ctl *ctl = &mem_ctl;
size_t i;
/*
* Remove these windows from the global list
*/
for (i = 0; i < ctl->windowfiles.length; ++i){
if (git_vector_get(&ctl->windowfiles, i) == mwf) {
git_vector_remove(&ctl->windowfiles, i);
break;
}
}
if (ctl->windowfiles.length == 0) {
git_vector_free(&ctl->windowfiles);
ctl->windowfiles.contents = NULL;
}
while (mwf->windows) {
git_mwindow *w = mwf->windows;
assert(w->inuse_cnt == 0);
ctl->mapped -= w->window_map.len;
ctl->open_windows--;
git_futils_mmap_free(&w->window_map);
mwf->windows = w->next;
git__free(w);
}
}
/*
* Check if a window 'win' contains the address 'offset'
*/
int git_mwindow_contains(git_mwindow *win, git_off_t offset)
{
git_off_t win_off = win->offset;
return win_off <= offset
&& offset <= (git_off_t)(win_off + win->window_map.len);
}
/*
* Find the least-recently-used window in a file
*/
static void git_mwindow_scan_lru(
git_mwindow_file *mwf,
git_mwindow **lru_w,
git_mwindow **lru_l)
{
git_mwindow *w, *w_l;
for (w_l = NULL, w = mwf->windows; w; w = w->next) {
if (!w->inuse_cnt) {
/*
* If the current one is more recent than the last one,
* store it in the output parameter. If lru_w is NULL,
* it's the first loop, so store it as well.
*/
if (!*lru_w || w->last_used < (*lru_w)->last_used) {
*lru_w = w;
*lru_l = w_l;
}
}
w_l = w;
}
}
/*
* Close the least recently used window. You should check to see if
* the file descriptors need closing from time to time. Called under
* lock from new_window.
*/
static int git_mwindow_close_lru(git_mwindow_file *mwf)
{
git_mwindow_ctl *ctl = &mem_ctl;
size_t i;
git_mwindow *lru_w = NULL, *lru_l = NULL, **list = &mwf->windows;
/* FIXME: Does this give us any advantage? */
if(mwf->windows)
git_mwindow_scan_lru(mwf, &lru_w, &lru_l);
for (i = 0; i < ctl->windowfiles.length; ++i) {
git_mwindow *last = lru_w;
git_mwindow_file *cur = git_vector_get(&ctl->windowfiles, i);
git_mwindow_scan_lru(cur, &lru_w, &lru_l);
if (lru_w != last)
list = &cur->windows;
}
if (!lru_w) {
giterr_set(GITERR_OS, "Failed to close memory window. Couldn't find LRU");
return -1;
}
ctl->mapped -= lru_w->window_map.len;
git_futils_mmap_free(&lru_w->window_map);
if (lru_l)
lru_l->next = lru_w->next;
else
*list = lru_w->next;
git__free(lru_w);
ctl->open_windows--;
return 0;
}
/* This gets called under lock from git_mwindow_open */
static git_mwindow *new_window(
git_mwindow_file *mwf,
git_file fd,
git_off_t size,
git_off_t offset)
{
git_mwindow_ctl *ctl = &mem_ctl;
size_t walign = git_mwindow__window_size / 2;
git_off_t len;
git_mwindow *w;
w = git__malloc(sizeof(*w));
if (w == NULL)
return NULL;
memset(w, 0x0, sizeof(*w));
w->offset = (offset / walign) * walign;
len = size - w->offset;
if (len > (git_off_t)git_mwindow__window_size)
len = (git_off_t)git_mwindow__window_size;
ctl->mapped += (size_t)len;
while (git_mwindow__mapped_limit < ctl->mapped &&
git_mwindow_close_lru(mwf) == 0) /* nop */;
/*
* We treat `mapped_limit` as a soft limit. If we can't find a
* window to close and are above the limit, we still mmap the new
* window.
*/
if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) {
git__free(w);
return NULL;
}
ctl->mmap_calls++;
ctl->open_windows++;
if (ctl->mapped > ctl->peak_mapped)
ctl->peak_mapped = ctl->mapped;
if (ctl->open_windows > ctl->peak_open_windows)
ctl->peak_open_windows = ctl->open_windows;
return w;
}
/*
* Open a new window, closing the least recenty used until we have
* enough space. Don't forget to add it to your list
*/
unsigned char *git_mwindow_open(
git_mwindow_file *mwf,
git_mwindow **cursor,
git_off_t offset,
size_t extra,
unsigned int *left)
{
git_mwindow_ctl *ctl = &mem_ctl;
git_mwindow *w = *cursor;
if (git_mutex_lock(&git__mwindow_mutex)) {
giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
return NULL;
}
if (!w || !(git_mwindow_contains(w, offset) && git_mwindow_contains(w, offset + extra))) {
if (w) {
w->inuse_cnt--;
}
for (w = mwf->windows; w; w = w->next) {
if (git_mwindow_contains(w, offset) &&
git_mwindow_contains(w, offset + extra))
break;
}
/*
* If there isn't a suitable window, we need to create a new
* one.
*/
if (!w) {
w = new_window(mwf, mwf->fd, mwf->size, offset);
if (w == NULL) {
git_mutex_unlock(&git__mwindow_mutex);
return NULL;
}
w->next = mwf->windows;
mwf->windows = w;
}
}
/* If we changed w, store it in the cursor */
if (w != *cursor) {
w->last_used = ctl->used_ctr++;
w->inuse_cnt++;
*cursor = w;
}
offset -= w->offset;
if (left)
*left = (unsigned int)(w->window_map.len - offset);
git_mutex_unlock(&git__mwindow_mutex);
return (unsigned char *) w->window_map.data + offset;
}
int git_mwindow_file_register(git_mwindow_file *mwf)
{
git_mwindow_ctl *ctl = &mem_ctl;
int ret;
if (git_mutex_lock(&git__mwindow_mutex)) {
giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
return -1;
}
if (ctl->windowfiles.length == 0 &&
git_vector_init(&ctl->windowfiles, 8, NULL) < 0) {
git_mutex_unlock(&git__mwindow_mutex);
return -1;
}
ret = git_vector_insert(&ctl->windowfiles, mwf);
git_mutex_unlock(&git__mwindow_mutex);
return ret;
}
void git_mwindow_file_deregister(git_mwindow_file *mwf)
{
git_mwindow_ctl *ctl = &mem_ctl;
git_mwindow_file *cur;
size_t i;
if (git_mutex_lock(&git__mwindow_mutex))
return;
git_vector_foreach(&ctl->windowfiles, i, cur) {
if (cur == mwf) {
git_vector_remove(&ctl->windowfiles, i);
git_mutex_unlock(&git__mwindow_mutex);
return;
}
}
git_mutex_unlock(&git__mwindow_mutex);
}
void git_mwindow_close(git_mwindow **window)
{
git_mwindow *w = *window;
if (w) {
if (git_mutex_lock(&git__mwindow_mutex)) {
giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");
return;
}
w->inuse_cnt--;
git_mutex_unlock(&git__mwindow_mutex);
*window = NULL;
}
}