Commit a15c550d by Vicent Marti

threads: Fix the shared global state with TLS

See `global.c` for a description of what we're doing.

When libgit2 is built with GIT_THREADS support, the threading system
must be explicitly initialized with `git_threads_init()`.
parent b0b2dd5e
......@@ -11,6 +11,7 @@
#include "git2/version.h"
#include "git2/common.h"
#include "git2/threads.h"
#include "git2/errors.h"
#include "git2/zlib.h"
......
......@@ -7,7 +7,6 @@
#ifndef INCLUDE_git_common_h__
#define INCLUDE_git_common_h__
#include "thread-utils.h"
#include <time.h>
#include <stdlib.h>
......@@ -38,18 +37,6 @@
# define GIT_EXTERN(type) extern type
#endif
/** Declare a public TLS symbol exported for application use. */
#if __GNUC__ >= 4
# define GIT_EXTERN_TLS(type) extern \
__attribute__((visibility("default"))) \
GIT_TLS \
type
#elif defined(_MSC_VER)
# define GIT_EXTERN_TLS(type) __declspec(dllexport) GIT_TLS type
#else
# define GIT_EXTERN_TLS(type) extern GIT_TLS type
#endif
/** Declare a function as always inlined. */
#if defined(_MSC_VER)
# define GIT_INLINE(type) static __inline type
......
/*
* Copyright (C) 2009-2011 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_git_thread_utils_h__
#define INCLUDE_git_thread_utils_h__
/*
* How TLS works is compiler+platform dependant
* Sources: http://en.wikipedia.org/wiki/Thread-Specific_Storage
* http://predef.sourceforge.net/precomp.html
*/
#ifdef GIT_THREADS
# define GIT_HAS_TLS 1
/* No TLS in Cygwin */
# if defined(__CHECKER__) || defined(__CYGWIN__)
# undef GIT_HAS_TLS
# define GIT_TLS
/* No TLS in Mach binaries for Mac OS X */
# elif defined(__APPLE__) && defined(__MACH__)
# undef GIT_TLS
# define GIT_TLS
/* Normal TLS for GCC */
# elif defined(__GNUC__) || \
defined(__SUNPRO_C) || \
defined(__SUNPRO_CC) || \
defined(__xlc__) || \
defined(__xlC__)
# define GIT_TLS __thread
/* ICC may run on Windows or Linux */
# elif defined(__INTEL_COMPILER)
# if defined(_WIN32) || defined(_WIN32_CE)
# define GIT_TLS __declspec(thread)
# else
# define GIT_TLS __thread
# endif
/* Declspec for MSVC in Win32 */
# elif defined(_WIN32) || \
defined(_WIN32_CE) || \
defined(__BORLANDC__)
# define GIT_TLS __declspec(thread)
/* Other platform; no TLS */
# else
# undef GIT_HAS_TLS
# define GIT_TLS /* nothing: tls vars are thread-global */
# endif
#else /* Disable TLS if libgit2 is not threadsafe */
# define GIT_TLS
#endif /* GIT_THREADS */
#endif /* INCLUDE_git_thread_utils_h__ */
/*
* Copyright (C) 2009-2011 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_git_threads_h__
#define INCLUDE_git_threads_h__
#include "common.h"
/**
* @file git2/threads.h
* @brief Library level thread functions
* @defgroup git_thread Threading functions
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
/**
* Init the threading system.
*
* If libgit2 has been built with GIT_THREADS
* on, this function must be called once before
* any other library functions.
*
* If libgit2 has been built without GIT_THREADS
* support, this function is a no-op.
*/
GIT_EXTERN(void) git_threads_init(void);
/**
* Shutdown the threading system.
*
* If libgit2 has been built with GIT_THREADS
* on, this function must be called before shutting
* down the library.
*
* If libgit2 has been built without GIT_THREADS
* support, this function is a no-op.
*/
GIT_EXTERN(void) git_threads_shutdown(void);
/** @} */
GIT_END_DECL
#endif
......@@ -8,7 +8,6 @@
#define INCLUDE_common_h__
#include "git2/common.h"
#include "git2/thread-utils.h"
#include "cc-compat.h"
#include <assert.h>
......
......@@ -5,13 +5,9 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "git2/thread-utils.h" /* for GIT_TLS */
#include "thread-utils.h" /* for GIT_TLS */
#include "global.h"
#include <stdarg.h>
static GIT_TLS char g_last_error[1024];
static struct {
int num;
const char *str;
......@@ -59,19 +55,26 @@ const char *git_strerror(int num)
return "Unknown error";
}
#define ERROR_MAX_LEN 1024
void git___rethrow(const char *msg, ...)
{
char new_error[1024];
char new_error[ERROR_MAX_LEN];
char *last_error;
char *old_error = NULL;
va_list va;
last_error = GIT_GLOBAL->error.last;
va_start(va, msg);
vsnprintf(new_error, sizeof(new_error), msg, va);
vsnprintf(new_error, ERROR_MAX_LEN, msg, va);
va_end(va);
old_error = git__strdup(g_last_error);
snprintf(g_last_error, sizeof(g_last_error), "%s \n - %s", new_error, old_error);
old_error = git__strdup(last_error);
snprintf(last_error, ERROR_MAX_LEN, "%s \n - %s", new_error, old_error);
git__free(old_error);
}
......@@ -80,19 +83,22 @@ void git___throw(const char *msg, ...)
va_list va;
va_start(va, msg);
vsnprintf(g_last_error, sizeof(g_last_error), msg, va);
vsnprintf(GIT_GLOBAL->error.last, ERROR_MAX_LEN, msg, va);
va_end(va);
}
const char *git_lasterror(void)
{
if (!g_last_error[0])
char *last_error = GIT_GLOBAL->error.last;
if (!last_error[0])
return NULL;
return g_last_error;
return last_error;
}
void git_clearerror(void)
{
g_last_error[0] = '\0';
char *last_error = GIT_GLOBAL->error.last;
last_error[0] = '\0';
}
/*
* Copyright (C) 2009-2011 the libgit2 contributors
*
* 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 "global.h"
#include "git2/threads.h"
#include "thread-utils.h"
/**
* Handle the global state with TLS
*
* If libgit2 is built with GIT_THREADS enabled,
* the `git_threads_init()` function must be called
* before calling any other function of the library.
*
* This function allocates a TLS index (using pthreads
* or the native Win32 API) to store the global state
* on a per-thread basis.
*
* Any internal method that requires global state will
* then call `git__global_state()` which returns a pointer
* to the global state structure; this pointer is lazily
* allocated on each thread.
*
* Before shutting down the library, the
* `git_threads_shutdown` method must be called to free
* the previously reserved TLS index.
*
* If libgit2 is built without threading support, the
* `git__global_statestate()` call returns a pointer to a single,
* statically allocated global state. The `git_thread_`
* functions are not available in that case.
*/
#if defined(GIT_THREADS) && defined(GIT_WIN32)
static DWORD _tls_index;
static int _tls_init = 0;
void git_threads_init(void)
{
if (_tls_init)
return;
_tls_index = TlsAlloc();
_tls_init = 1;
}
void git_threads_shutdown(void)
{
TlsFree(_tls_index);
_tls_init = 0;
}
git_global_st *git__global_state(void)
{
void *ptr;
if ((ptr = TlsGetValue(_tls_index)) != NULL)
return ptr;
ptr = malloc(sizeof(git_global_st));
if (!ptr)
return NULL;
memset(ptr, 0x0, sizeof(git_global_st));
TlsSetValue(_tls_index, ptr);
return ptr;
}
#elif defined(GIT_THREADS) && defined(_POSIX_THREADS)
static pthread_key_t _tls_key;
static int _tls_init = 0;
static void cb__free_status(void *st)
{
free(st);
}
void git_threads_init(void)
{
if (_tls_init)
return;
pthread_key_create(&_tls_key, &cb__free_status);
_tls_init = 1;
}
void git_threads_shutdown(void)
{
pthread_key_delete(_tls_key);
_tls_init = 0;
}
git_global_st *git__global_state(void)
{
void *ptr;
if ((ptr = pthread_getspecific(_tls_key)) != NULL)
return ptr;
ptr = malloc(sizeof(git_global_st));
if (!ptr)
return NULL;
memset(ptr, 0x0, sizeof(git_global_st));
pthread_setspecific(_tls_key, ptr);
return ptr;
}
#else
static git_global_st __state;
void git_threads_init(void)
{
/* noop */
}
void git_threads_shutdown(void)
{
/* noop */
}
git_global_st *git__global_state(void)
{
return &__state;
}
#endif /* GIT_THREADS */
/*
* Copyright (C) 2009-2011 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_global_h__
#define INCLUDE_global_h__
#include "mwindow.h"
typedef struct {
struct {
char last[1024];
} error;
git_mwindow_ctl mem_ctl;
} git_global_st;
git_global_st *git__global_state(void);
#define GIT_GLOBAL (git__global_state())
#endif
......@@ -10,6 +10,7 @@
#include "vector.h"
#include "fileops.h"
#include "map.h"
#include "global.h"
#define DEFAULT_WINDOW_SIZE \
(sizeof(void*) >= 8 \
......@@ -20,21 +21,15 @@
((1024 * 1024) * (sizeof(void*) >= 8 ? 8192ULL : 256UL))
/*
* We need this because each process is only allowed a specific amount
* of memory. Making it writable should generate one instance per
* process, but we still need to set a couple of variables.
* These are the global options for mmmap limits.
* TODO: allow the user to change these
*/
static git_mwindow_ctl ctl = {
0,
0,
static struct {
size_t window_size;
size_t mapped_limit;
} _mw_options = {
DEFAULT_WINDOW_SIZE,
DEFAULT_MAPPED_LIMIT,
0,
0,
0,
0,
{0, 0, 0, 0, 0}
};
/*
......@@ -43,28 +38,29 @@ static git_mwindow_ctl ctl = {
*/
void git_mwindow_free_all(git_mwindow_file *mwf)
{
git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl;
unsigned int 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);
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;
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--;
ctl->mapped -= w->window_map.len;
ctl->open_windows--;
git_futils_mmap_free(&w->window_map);
......@@ -115,6 +111,7 @@ void git_mwindow_scan_lru(
*/
static int git_mwindow_close_lru(git_mwindow_file *mwf)
{
git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl;
unsigned int i;
git_mwindow *lru_w = NULL, *lru_l = NULL, **list = &mwf->windows;
......@@ -122,16 +119,16 @@ static int git_mwindow_close_lru(git_mwindow_file *mwf)
if(mwf->windows)
git_mwindow_scan_lru(mwf, &lru_w, &lru_l);
for (i = 0; i < ctl.windowfiles.length; ++i) {
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_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) {
ctl.mapped -= lru_w->window_map.len;
ctl->mapped -= lru_w->window_map.len;
git_futils_mmap_free(&lru_w->window_map);
if (lru_l)
......@@ -140,7 +137,7 @@ static int git_mwindow_close_lru(git_mwindow_file *mwf)
*list = lru_w->next;
git__free(lru_w);
ctl.open_windows--;
ctl->open_windows--;
return GIT_SUCCESS;
}
......@@ -148,9 +145,14 @@ static int git_mwindow_close_lru(git_mwindow_file *mwf)
return git__throw(GIT_ERROR, "Failed to close memory window. Couln't find LRU");
}
static git_mwindow *new_window(git_mwindow_file *mwf, git_file fd, git_off_t size, git_off_t offset)
static git_mwindow *new_window(
git_mwindow_file *mwf,
git_file fd,
git_off_t size,
git_off_t offset)
{
size_t walign = ctl.window_size / 2;
git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl;
size_t walign = _mw_options.window_size / 2;
git_off_t len;
git_mwindow *w;
......@@ -162,16 +164,16 @@ static git_mwindow *new_window(git_mwindow_file *mwf, git_file fd, git_off_t siz
w->offset = (offset / walign) * walign;
len = size - w->offset;
if (len > (git_off_t)ctl.window_size)
len = (git_off_t)ctl.window_size;
if (len > (git_off_t)_mw_options.window_size)
len = (git_off_t)_mw_options.window_size;
ctl.mapped += (size_t)len;
ctl->mapped += (size_t)len;
while(ctl.mapped_limit < ctl.mapped &&
git_mwindow_close_lru(mwf) == GIT_SUCCESS) {}
while (_mw_options.mapped_limit < ctl->mapped &&
git_mwindow_close_lru(mwf) == GIT_SUCCESS) /* nop */;
/*
* We treat ctl.mapped_limit as a soft limit. If we can't find a
* We treat _mw_options.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.
*/
......@@ -179,14 +181,14 @@ static git_mwindow *new_window(git_mwindow_file *mwf, git_file fd, git_off_t siz
if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < GIT_SUCCESS)
goto cleanup;
ctl.mmap_calls++;
ctl.open_windows++;
ctl->mmap_calls++;
ctl->open_windows++;
if (ctl.mapped > ctl.peak_mapped)
ctl.peak_mapped = ctl.mapped;
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;
if (ctl->open_windows > ctl->peak_open_windows)
ctl->peak_open_windows = ctl->open_windows;
return w;
......@@ -199,9 +201,14 @@ cleanup:
* 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, int extra, unsigned int *left)
unsigned char *git_mwindow_open(
git_mwindow_file *mwf,
git_mwindow **cursor,
git_off_t offset,
int extra,
unsigned int *left)
{
git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl;
git_mwindow *w = *cursor;
if (!w || !git_mwindow_contains(w, offset + extra)) {
......@@ -229,7 +236,7 @@ unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor,
/* If we changed w, store it in the cursor */
if (w != *cursor) {
w->last_used = ctl.used_ctr++;
w->last_used = ctl->used_ctr++;
w->inuse_cnt++;
*cursor = w;
}
......@@ -245,13 +252,14 @@ unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor,
int git_mwindow_file_register(git_mwindow_file *mwf)
{
git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl;
int error;
if (ctl.windowfiles.length == 0 &&
(error = git_vector_init(&ctl.windowfiles, 8, NULL)) < GIT_SUCCESS)
if (ctl->windowfiles.length == 0 &&
(error = git_vector_init(&ctl->windowfiles, 8, NULL)) < GIT_SUCCESS)
return error;
return git_vector_insert(&ctl.windowfiles, mwf);
return git_vector_insert(&ctl->windowfiles, mwf);
}
void git_mwindow_close(git_mwindow **window)
......
......@@ -10,7 +10,6 @@
#include "map.h"
#include "vector.h"
#include "fileops.h"
typedef struct git_mwindow {
struct git_mwindow *next;
......@@ -29,8 +28,6 @@ typedef struct git_mwindow_file {
typedef struct git_mwindow_ctl {
size_t mapped;
unsigned int open_windows;
size_t window_size; /* needs default value */
size_t mapped_limit; /* needs default value */
unsigned int mmap_calls;
unsigned int peak_open_windows;
size_t peak_mapped;
......
......@@ -5,11 +5,13 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "mwindow.h"
#include "common.h"
#include "odb.h"
#include "pack.h"
#include "delta-apply.h"
#include "sha1_lookup.h"
#include "mwindow.h"
#include "fileops.h"
#include "git2/oid.h"
#include "git2/zlib.h"
......
......@@ -7,7 +7,7 @@
#ifndef INCLUDE_thread_utils_h__
#define INCLUDE_thread_utils_h__
#include "common.h"
/* Common operations even if threading has been disabled */
typedef struct {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment