Commit ae5838f1 by Carlos Martín Nieto Committed by GitHub

Merge pull request #4010 from libgit2/ethomson/clar_threads

Introduce some clar helpers for child threads
parents 6b0510e5 6367c58c
...@@ -16,6 +16,12 @@ typedef struct { ...@@ -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 { ...@@ -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) ...@@ -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( ...@@ -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, ...@@ -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 @@ ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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
} }
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