mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-03 17:57:25 +00:00
Merge pull request #4010 from libgit2/ethomson/clar_threads
Introduce some clar helpers for child threads
This commit is contained in:
commit
ae5838f118
@ -16,6 +16,12 @@ typedef struct {
|
|||||||
git_error error_t;
|
git_error error_t;
|
||||||
git_buf error_buf;
|
git_buf error_buf;
|
||||||
char oid_fmt[GIT_OID_HEXSZ+1];
|
char oid_fmt[GIT_OID_HEXSZ+1];
|
||||||
|
|
||||||
|
/* On Windows, this is the current child thread that was started by
|
||||||
|
* `git_thread_create`. This is used to set the thread's exit code
|
||||||
|
* when terminated by `git_thread_exit`. It is unused on POSIX.
|
||||||
|
*/
|
||||||
|
git_thread *current_thread;
|
||||||
} git_global_st;
|
} git_global_st;
|
||||||
|
|
||||||
#ifdef GIT_OPENSSL
|
#ifdef GIT_OPENSSL
|
||||||
|
@ -17,6 +17,8 @@ typedef struct {
|
|||||||
pthread_create(&(git_thread_ptr)->thread, NULL, start_routine, arg)
|
pthread_create(&(git_thread_ptr)->thread, NULL, start_routine, arg)
|
||||||
#define git_thread_join(git_thread_ptr, status) \
|
#define git_thread_join(git_thread_ptr, status) \
|
||||||
pthread_join((git_thread_ptr)->thread, status)
|
pthread_join((git_thread_ptr)->thread, status)
|
||||||
|
#define git_thread_currentid() ((size_t)(pthread_self()))
|
||||||
|
#define git_thread_exit(retval) pthread_exit(retval)
|
||||||
|
|
||||||
/* Git Mutex */
|
/* Git Mutex */
|
||||||
#define git_mutex pthread_mutex_t
|
#define git_mutex pthread_mutex_t
|
||||||
|
@ -26,6 +26,9 @@ static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter)
|
|||||||
{
|
{
|
||||||
git_thread *thread = lpParameter;
|
git_thread *thread = lpParameter;
|
||||||
|
|
||||||
|
/* Set the current thread for `git_thread_exit` */
|
||||||
|
GIT_GLOBAL->current_thread = thread;
|
||||||
|
|
||||||
thread->result = thread->proc(thread->param);
|
thread->result = thread->proc(thread->param);
|
||||||
|
|
||||||
git__free_tls_data();
|
git__free_tls_data();
|
||||||
@ -95,6 +98,21 @@ int git_thread_join(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void git_thread_exit(void *value)
|
||||||
|
{
|
||||||
|
assert(GIT_GLOBAL->current_thread);
|
||||||
|
GIT_GLOBAL->current_thread->result = value;
|
||||||
|
|
||||||
|
git__free_tls_data();
|
||||||
|
|
||||||
|
ExitThread(CLEAN_THREAD_EXIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t git_thread_currentid(void)
|
||||||
|
{
|
||||||
|
return GetCurrentThreadId();
|
||||||
|
}
|
||||||
|
|
||||||
int git_mutex_init(git_mutex *GIT_RESTRICT mutex)
|
int git_mutex_init(git_mutex *GIT_RESTRICT mutex)
|
||||||
{
|
{
|
||||||
InitializeCriticalSection(mutex);
|
InitializeCriticalSection(mutex);
|
||||||
|
@ -41,6 +41,8 @@ int git_thread_create(git_thread *GIT_RESTRICT,
|
|||||||
void *(*) (void *),
|
void *(*) (void *),
|
||||||
void *GIT_RESTRICT);
|
void *GIT_RESTRICT);
|
||||||
int git_thread_join(git_thread *, void **);
|
int git_thread_join(git_thread *, void **);
|
||||||
|
size_t git_thread_currentid(void);
|
||||||
|
void git_thread_exit(void *);
|
||||||
|
|
||||||
int git_mutex_init(git_mutex *GIT_RESTRICT mutex);
|
int git_mutex_init(git_mutex *GIT_RESTRICT mutex);
|
||||||
int git_mutex_free(git_mutex *);
|
int git_mutex_free(git_mutex *);
|
||||||
|
@ -41,6 +41,51 @@
|
|||||||
} \
|
} \
|
||||||
} while(0)
|
} while(0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread safe assertions; you cannot use `cl_git_report_failure` from a
|
||||||
|
* child thread since it will try to `longjmp` to abort and "the effect of
|
||||||
|
* a call to longjmp() where initialization of the jmp_buf structure was
|
||||||
|
* not performed in the calling thread is undefined."
|
||||||
|
*
|
||||||
|
* Instead, callers can provide a clar thread error context to a thread,
|
||||||
|
* which will populate and return it on failure. Callers can check the
|
||||||
|
* status with `cl_git_thread_check`.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
int error;
|
||||||
|
const char *file;
|
||||||
|
int line;
|
||||||
|
const char *expr;
|
||||||
|
char error_msg[4096];
|
||||||
|
} cl_git_thread_err;
|
||||||
|
|
||||||
|
#ifdef GIT_THREADS
|
||||||
|
# define cl_git_thread_pass(threaderr, expr) cl_git_thread_pass_(threaderr, (expr), __FILE__, __LINE__)
|
||||||
|
#else
|
||||||
|
# define cl_git_thread_pass(threaderr, expr) cl_git_pass(expr)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define cl_git_thread_pass_(__threaderr, __expr, __file, __line) do { \
|
||||||
|
giterr_clear(); \
|
||||||
|
if ((((cl_git_thread_err *)__threaderr)->error = (__expr)) != 0) { \
|
||||||
|
const git_error *_last = giterr_last(); \
|
||||||
|
((cl_git_thread_err *)__threaderr)->file = __file; \
|
||||||
|
((cl_git_thread_err *)__threaderr)->line = __line; \
|
||||||
|
((cl_git_thread_err *)__threaderr)->expr = "Function call failed: " #__expr; \
|
||||||
|
p_snprintf(((cl_git_thread_err *)__threaderr)->error_msg, 4096, "thread 0x%" PRIxZ " - error %d - %s", \
|
||||||
|
git_thread_currentid(), ((cl_git_thread_err *)__threaderr)->error, \
|
||||||
|
_last ? _last->message : "<no message>"); \
|
||||||
|
git_thread_exit(__threaderr); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static void cl_git_thread_check(void *data)
|
||||||
|
{
|
||||||
|
cl_git_thread_err *threaderr = (cl_git_thread_err *)data;
|
||||||
|
if (threaderr->error != 0)
|
||||||
|
clar__assert(0, threaderr->file, threaderr->line, threaderr->expr, threaderr->error_msg, 1);
|
||||||
|
}
|
||||||
|
|
||||||
void cl_git_report_failure(int, const char *, int, const char *);
|
void cl_git_report_failure(int, const char *, int, const char *);
|
||||||
|
|
||||||
#define cl_assert_at_line(expr,file,line) \
|
#define cl_assert_at_line(expr,file,line) \
|
||||||
|
@ -39,14 +39,14 @@ void test_core_init__concurrent_init_succeeds(void)
|
|||||||
git_thread threads[10];
|
git_thread threads[10];
|
||||||
unsigned i;
|
unsigned i;
|
||||||
|
|
||||||
cl_assert_equal_i(0, git_libgit2_shutdown());
|
cl_assert_equal_i(2, git_libgit2_init());
|
||||||
|
|
||||||
for (i = 0; i < ARRAY_SIZE(threads); i++)
|
for (i = 0; i < ARRAY_SIZE(threads); i++)
|
||||||
git_thread_create(&threads[i], reinit, NULL);
|
git_thread_create(&threads[i], reinit, NULL);
|
||||||
for (i = 0; i < ARRAY_SIZE(threads); i++)
|
for (i = 0; i < ARRAY_SIZE(threads); i++)
|
||||||
git_thread_join(&threads[i], NULL);
|
git_thread_join(&threads[i], NULL);
|
||||||
|
|
||||||
cl_assert_equal_i(1, git_libgit2_init());
|
cl_assert_equal_i(1, git_libgit2_shutdown());
|
||||||
cl_sandbox_set_search_path_defaults();
|
cl_sandbox_set_search_path_defaults();
|
||||||
#else
|
#else
|
||||||
cl_skip();
|
cl_skip();
|
||||||
|
@ -48,3 +48,36 @@ void test_threads_basic__set_error(void)
|
|||||||
{
|
{
|
||||||
run_in_parallel(1, 4, set_error, NULL, NULL);
|
run_in_parallel(1, 4, set_error, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef GIT_THREADS
|
||||||
|
static void *return_normally(void *param)
|
||||||
|
{
|
||||||
|
return param;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *exit_abruptly(void *param)
|
||||||
|
{
|
||||||
|
git_thread_exit(param);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void test_threads_basic__exit(void)
|
||||||
|
{
|
||||||
|
#ifndef GIT_THREADS
|
||||||
|
clar__skip();
|
||||||
|
#else
|
||||||
|
git_thread thread;
|
||||||
|
void *result;
|
||||||
|
|
||||||
|
/* Ensure that the return value of the threadproc is returned. */
|
||||||
|
cl_git_pass(git_thread_create(&thread, return_normally, (void *)424242));
|
||||||
|
cl_git_pass(git_thread_join(&thread, &result));
|
||||||
|
cl_assert_equal_sz(424242, (size_t)result);
|
||||||
|
|
||||||
|
/* Ensure that the return value of `git_thread_exit` is returned. */
|
||||||
|
cl_git_pass(git_thread_create(&thread, return_normally, (void *)232323));
|
||||||
|
cl_git_pass(git_thread_join(&thread, &result));
|
||||||
|
cl_assert_equal_sz(232323, (size_t)result);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
@ -22,6 +22,7 @@ void test_threads_refdb__cleanup(void)
|
|||||||
#define NREFS 10
|
#define NREFS 10
|
||||||
|
|
||||||
struct th_data {
|
struct th_data {
|
||||||
|
cl_git_thread_err error;
|
||||||
int id;
|
int id;
|
||||||
const char *path;
|
const char *path;
|
||||||
};
|
};
|
||||||
@ -34,11 +35,11 @@ static void *iterate_refs(void *arg)
|
|||||||
int count = 0, error;
|
int count = 0, error;
|
||||||
git_repository *repo;
|
git_repository *repo;
|
||||||
|
|
||||||
cl_git_pass(git_repository_open(&repo, data->path));
|
cl_git_thread_pass(data, git_repository_open(&repo, data->path));
|
||||||
do {
|
do {
|
||||||
error = git_reference_iterator_new(&i, repo);
|
error = git_reference_iterator_new(&i, repo);
|
||||||
} while (error == GIT_ELOCKED);
|
} while (error == GIT_ELOCKED);
|
||||||
cl_git_pass(error);
|
cl_git_thread_pass(data, error);
|
||||||
|
|
||||||
for (count = 0; !git_reference_next(&ref, i); ++count) {
|
for (count = 0; !git_reference_next(&ref, i); ++count) {
|
||||||
cl_assert(ref != NULL);
|
cl_assert(ref != NULL);
|
||||||
@ -64,26 +65,27 @@ static void *create_refs(void *arg)
|
|||||||
git_reference *ref[NREFS];
|
git_reference *ref[NREFS];
|
||||||
git_repository *repo;
|
git_repository *repo;
|
||||||
|
|
||||||
cl_git_pass(git_repository_open(&repo, data->path));
|
cl_git_thread_pass(data, git_repository_open(&repo, data->path));
|
||||||
|
|
||||||
do {
|
do {
|
||||||
error = git_reference_name_to_id(&head, repo, "HEAD");
|
error = git_reference_name_to_id(&head, repo, "HEAD");
|
||||||
} while (error == GIT_ELOCKED);
|
} while (error == GIT_ELOCKED);
|
||||||
cl_git_pass(error);
|
cl_git_thread_pass(data, error);
|
||||||
|
|
||||||
for (i = 0; i < NREFS; ++i) {
|
for (i = 0; i < NREFS; ++i) {
|
||||||
p_snprintf(name, sizeof(name), "refs/heads/thread-%03d-%02d", data->id, i);
|
p_snprintf(name, sizeof(name), "refs/heads/thread-%03d-%02d", data->id, i);
|
||||||
do {
|
do {
|
||||||
error = git_reference_create(&ref[i], repo, name, &head, 0, NULL);
|
error = git_reference_create(&ref[i], repo, name, &head, 0, NULL);
|
||||||
} while (error == GIT_ELOCKED);
|
} while (error == GIT_ELOCKED);
|
||||||
cl_git_pass(error);
|
cl_git_thread_pass(data, error);
|
||||||
|
|
||||||
if (i == NREFS/2) {
|
if (i == NREFS/2) {
|
||||||
git_refdb *refdb;
|
git_refdb *refdb;
|
||||||
cl_git_pass(git_repository_refdb(&refdb, repo));
|
cl_git_thread_pass(data, git_repository_refdb(&refdb, repo));
|
||||||
do {
|
do {
|
||||||
error = git_refdb_compress(refdb);
|
error = git_refdb_compress(refdb);
|
||||||
} while (error == GIT_ELOCKED);
|
} while (error == GIT_ELOCKED);
|
||||||
|
cl_git_thread_pass(data, error);
|
||||||
git_refdb_free(refdb);
|
git_refdb_free(refdb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,7 +107,7 @@ static void *delete_refs(void *arg)
|
|||||||
char name[128];
|
char name[128];
|
||||||
git_repository *repo;
|
git_repository *repo;
|
||||||
|
|
||||||
cl_git_pass(git_repository_open(&repo, data->path));
|
cl_git_thread_pass(data, git_repository_open(&repo, data->path));
|
||||||
|
|
||||||
for (i = 0; i < NREFS; ++i) {
|
for (i = 0; i < NREFS; ++i) {
|
||||||
p_snprintf(
|
p_snprintf(
|
||||||
@ -119,17 +121,17 @@ static void *delete_refs(void *arg)
|
|||||||
if (error == GIT_ENOTFOUND)
|
if (error == GIT_ENOTFOUND)
|
||||||
error = 0;
|
error = 0;
|
||||||
|
|
||||||
cl_git_pass(error);
|
cl_git_thread_pass(data, error);
|
||||||
git_reference_free(ref);
|
git_reference_free(ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i == NREFS/2) {
|
if (i == NREFS/2) {
|
||||||
git_refdb *refdb;
|
git_refdb *refdb;
|
||||||
cl_git_pass(git_repository_refdb(&refdb, repo));
|
cl_git_thread_pass(data, git_repository_refdb(&refdb, repo));
|
||||||
do {
|
do {
|
||||||
error = git_refdb_compress(refdb);
|
error = git_refdb_compress(refdb);
|
||||||
} while (error == GIT_ELOCKED);
|
} while (error == GIT_ELOCKED);
|
||||||
cl_git_pass(error);
|
cl_git_thread_pass(data, error);
|
||||||
git_refdb_free(refdb);
|
git_refdb_free(refdb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,6 +196,7 @@ void test_threads_refdb__edit_while_iterate(void)
|
|||||||
#ifdef GIT_THREADS
|
#ifdef GIT_THREADS
|
||||||
for (t = 0; t < THREADS; ++t) {
|
for (t = 0; t < THREADS; ++t) {
|
||||||
cl_git_pass(git_thread_join(&th[t], NULL));
|
cl_git_pass(git_thread_join(&th[t], NULL));
|
||||||
|
cl_git_thread_check(&th_data[t]);
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(th, 0, sizeof(th));
|
memset(th, 0, sizeof(th));
|
||||||
@ -205,6 +208,7 @@ void test_threads_refdb__edit_while_iterate(void)
|
|||||||
|
|
||||||
for (t = 0; t < THREADS; ++t) {
|
for (t = 0; t < THREADS; ++t) {
|
||||||
cl_git_pass(git_thread_join(&th[t], NULL));
|
cl_git_pass(git_thread_join(&th[t], NULL));
|
||||||
|
cl_git_thread_check(&th_data[t]);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user