Skip to content

Commit ae5838f

Browse files
authored
Merge pull request libgit2#4010 from libgit2/ethomson/clar_threads
Introduce some clar helpers for child threads
2 parents 6b0510e + 6367c58 commit ae5838f

File tree

8 files changed

+122
-12
lines changed

8 files changed

+122
-12
lines changed

src/global.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ typedef struct {
1616
git_error error_t;
1717
git_buf error_buf;
1818
char oid_fmt[GIT_OID_HEXSZ+1];
19+
20+
/* On Windows, this is the current child thread that was started by
21+
* `git_thread_create`. This is used to set the thread's exit code
22+
* when terminated by `git_thread_exit`. It is unused on POSIX.
23+
*/
24+
git_thread *current_thread;
1925
} git_global_st;
2026

2127
#ifdef GIT_OPENSSL

src/unix/pthread.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ typedef struct {
1717
pthread_create(&(git_thread_ptr)->thread, NULL, start_routine, arg)
1818
#define git_thread_join(git_thread_ptr, status) \
1919
pthread_join((git_thread_ptr)->thread, status)
20+
#define git_thread_currentid() ((size_t)(pthread_self()))
21+
#define git_thread_exit(retval) pthread_exit(retval)
2022

2123
/* Git Mutex */
2224
#define git_mutex pthread_mutex_t

src/win32/thread.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter)
2626
{
2727
git_thread *thread = lpParameter;
2828

29+
/* Set the current thread for `git_thread_exit` */
30+
GIT_GLOBAL->current_thread = thread;
31+
2932
thread->result = thread->proc(thread->param);
3033

3134
git__free_tls_data();
@@ -95,6 +98,21 @@ int git_thread_join(
9598
return 0;
9699
}
97100

101+
void git_thread_exit(void *value)
102+
{
103+
assert(GIT_GLOBAL->current_thread);
104+
GIT_GLOBAL->current_thread->result = value;
105+
106+
git__free_tls_data();
107+
108+
ExitThread(CLEAN_THREAD_EXIT);
109+
}
110+
111+
size_t git_thread_currentid(void)
112+
{
113+
return GetCurrentThreadId();
114+
}
115+
98116
int git_mutex_init(git_mutex *GIT_RESTRICT mutex)
99117
{
100118
InitializeCriticalSection(mutex);

src/win32/thread.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ int git_thread_create(git_thread *GIT_RESTRICT,
4141
void *(*) (void *),
4242
void *GIT_RESTRICT);
4343
int git_thread_join(git_thread *, void **);
44+
size_t git_thread_currentid(void);
45+
void git_thread_exit(void *);
4446

4547
int git_mutex_init(git_mutex *GIT_RESTRICT mutex);
4648
int git_mutex_free(git_mutex *);

tests/clar_libgit2.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,51 @@
4141
} \
4242
} while(0)
4343

44+
/**
45+
* Thread safe assertions; you cannot use `cl_git_report_failure` from a
46+
* child thread since it will try to `longjmp` to abort and "the effect of
47+
* a call to longjmp() where initialization of the jmp_buf structure was
48+
* not performed in the calling thread is undefined."
49+
*
50+
* Instead, callers can provide a clar thread error context to a thread,
51+
* which will populate and return it on failure. Callers can check the
52+
* status with `cl_git_thread_check`.
53+
*/
54+
typedef struct {
55+
int error;
56+
const char *file;
57+
int line;
58+
const char *expr;
59+
char error_msg[4096];
60+
} cl_git_thread_err;
61+
62+
#ifdef GIT_THREADS
63+
# define cl_git_thread_pass(threaderr, expr) cl_git_thread_pass_(threaderr, (expr), __FILE__, __LINE__)
64+
#else
65+
# define cl_git_thread_pass(threaderr, expr) cl_git_pass(expr)
66+
#endif
67+
68+
#define cl_git_thread_pass_(__threaderr, __expr, __file, __line) do { \
69+
giterr_clear(); \
70+
if ((((cl_git_thread_err *)__threaderr)->error = (__expr)) != 0) { \
71+
const git_error *_last = giterr_last(); \
72+
((cl_git_thread_err *)__threaderr)->file = __file; \
73+
((cl_git_thread_err *)__threaderr)->line = __line; \
74+
((cl_git_thread_err *)__threaderr)->expr = "Function call failed: " #__expr; \
75+
p_snprintf(((cl_git_thread_err *)__threaderr)->error_msg, 4096, "thread 0x%" PRIxZ " - error %d - %s", \
76+
git_thread_currentid(), ((cl_git_thread_err *)__threaderr)->error, \
77+
_last ? _last->message : "<no message>"); \
78+
git_thread_exit(__threaderr); \
79+
} \
80+
} while (0)
81+
82+
static void cl_git_thread_check(void *data)
83+
{
84+
cl_git_thread_err *threaderr = (cl_git_thread_err *)data;
85+
if (threaderr->error != 0)
86+
clar__assert(0, threaderr->file, threaderr->line, threaderr->expr, threaderr->error_msg, 1);
87+
}
88+
4489
void cl_git_report_failure(int, const char *, int, const char *);
4590

4691
#define cl_assert_at_line(expr,file,line) \

tests/core/init.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ void test_core_init__concurrent_init_succeeds(void)
3939
git_thread threads[10];
4040
unsigned i;
4141

42-
cl_assert_equal_i(0, git_libgit2_shutdown());
42+
cl_assert_equal_i(2, git_libgit2_init());
4343

4444
for (i = 0; i < ARRAY_SIZE(threads); i++)
4545
git_thread_create(&threads[i], reinit, NULL);
4646
for (i = 0; i < ARRAY_SIZE(threads); i++)
4747
git_thread_join(&threads[i], NULL);
4848

49-
cl_assert_equal_i(1, git_libgit2_init());
49+
cl_assert_equal_i(1, git_libgit2_shutdown());
5050
cl_sandbox_set_search_path_defaults();
5151
#else
5252
cl_skip();

tests/threads/basic.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,36 @@ void test_threads_basic__set_error(void)
4848
{
4949
run_in_parallel(1, 4, set_error, NULL, NULL);
5050
}
51+
52+
#ifdef GIT_THREADS
53+
static void *return_normally(void *param)
54+
{
55+
return param;
56+
}
57+
58+
static void *exit_abruptly(void *param)
59+
{
60+
git_thread_exit(param);
61+
return NULL;
62+
}
63+
#endif
64+
65+
void test_threads_basic__exit(void)
66+
{
67+
#ifndef GIT_THREADS
68+
clar__skip();
69+
#else
70+
git_thread thread;
71+
void *result;
72+
73+
/* Ensure that the return value of the threadproc is returned. */
74+
cl_git_pass(git_thread_create(&thread, return_normally, (void *)424242));
75+
cl_git_pass(git_thread_join(&thread, &result));
76+
cl_assert_equal_sz(424242, (size_t)result);
77+
78+
/* Ensure that the return value of `git_thread_exit` is returned. */
79+
cl_git_pass(git_thread_create(&thread, return_normally, (void *)232323));
80+
cl_git_pass(git_thread_join(&thread, &result));
81+
cl_assert_equal_sz(232323, (size_t)result);
82+
#endif
83+
}

tests/threads/refdb.c

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ void test_threads_refdb__cleanup(void)
2222
#define NREFS 10
2323

2424
struct th_data {
25+
cl_git_thread_err error;
2526
int id;
2627
const char *path;
2728
};
@@ -34,11 +35,11 @@ static void *iterate_refs(void *arg)
3435
int count = 0, error;
3536
git_repository *repo;
3637

37-
cl_git_pass(git_repository_open(&repo, data->path));
38+
cl_git_thread_pass(data, git_repository_open(&repo, data->path));
3839
do {
3940
error = git_reference_iterator_new(&i, repo);
4041
} while (error == GIT_ELOCKED);
41-
cl_git_pass(error);
42+
cl_git_thread_pass(data, error);
4243

4344
for (count = 0; !git_reference_next(&ref, i); ++count) {
4445
cl_assert(ref != NULL);
@@ -64,26 +65,27 @@ static void *create_refs(void *arg)
6465
git_reference *ref[NREFS];
6566
git_repository *repo;
6667

67-
cl_git_pass(git_repository_open(&repo, data->path));
68+
cl_git_thread_pass(data, git_repository_open(&repo, data->path));
6869

6970
do {
7071
error = git_reference_name_to_id(&head, repo, "HEAD");
7172
} while (error == GIT_ELOCKED);
72-
cl_git_pass(error);
73+
cl_git_thread_pass(data, error);
7374

7475
for (i = 0; i < NREFS; ++i) {
7576
p_snprintf(name, sizeof(name), "refs/heads/thread-%03d-%02d", data->id, i);
7677
do {
7778
error = git_reference_create(&ref[i], repo, name, &head, 0, NULL);
7879
} while (error == GIT_ELOCKED);
79-
cl_git_pass(error);
80+
cl_git_thread_pass(data, error);
8081

8182
if (i == NREFS/2) {
8283
git_refdb *refdb;
83-
cl_git_pass(git_repository_refdb(&refdb, repo));
84+
cl_git_thread_pass(data, git_repository_refdb(&refdb, repo));
8485
do {
8586
error = git_refdb_compress(refdb);
8687
} while (error == GIT_ELOCKED);
88+
cl_git_thread_pass(data, error);
8789
git_refdb_free(refdb);
8890
}
8991
}
@@ -105,7 +107,7 @@ static void *delete_refs(void *arg)
105107
char name[128];
106108
git_repository *repo;
107109

108-
cl_git_pass(git_repository_open(&repo, data->path));
110+
cl_git_thread_pass(data, git_repository_open(&repo, data->path));
109111

110112
for (i = 0; i < NREFS; ++i) {
111113
p_snprintf(
@@ -119,17 +121,17 @@ static void *delete_refs(void *arg)
119121
if (error == GIT_ENOTFOUND)
120122
error = 0;
121123

122-
cl_git_pass(error);
124+
cl_git_thread_pass(data, error);
123125
git_reference_free(ref);
124126
}
125127

126128
if (i == NREFS/2) {
127129
git_refdb *refdb;
128-
cl_git_pass(git_repository_refdb(&refdb, repo));
130+
cl_git_thread_pass(data, git_repository_refdb(&refdb, repo));
129131
do {
130132
error = git_refdb_compress(refdb);
131133
} while (error == GIT_ELOCKED);
132-
cl_git_pass(error);
134+
cl_git_thread_pass(data, error);
133135
git_refdb_free(refdb);
134136
}
135137
}
@@ -194,6 +196,7 @@ void test_threads_refdb__edit_while_iterate(void)
194196
#ifdef GIT_THREADS
195197
for (t = 0; t < THREADS; ++t) {
196198
cl_git_pass(git_thread_join(&th[t], NULL));
199+
cl_git_thread_check(&th_data[t]);
197200
}
198201

199202
memset(th, 0, sizeof(th));
@@ -205,6 +208,7 @@ void test_threads_refdb__edit_while_iterate(void)
205208

206209
for (t = 0; t < THREADS; ++t) {
207210
cl_git_pass(git_thread_join(&th[t], NULL));
211+
cl_git_thread_check(&th_data[t]);
208212
}
209213
#endif
210214
}

0 commit comments

Comments
 (0)