| #include "clar_libgit2.h" |
| #include "git2/refdb.h" |
| #include "refdb.h" |
| |
| static git_repository *g_repo; |
| static int g_expected = 0; |
| |
| #ifdef GIT_WIN32 |
| static bool concurrent_compress = false; |
| #else |
| static bool concurrent_compress = true; |
| #endif |
| |
| void test_threads_refdb__initialize(void) |
| { |
| g_repo = NULL; |
| } |
| |
| void test_threads_refdb__cleanup(void) |
| { |
| cl_git_sandbox_cleanup(); |
| g_repo = NULL; |
| } |
| |
| #define REPEAT 20 |
| #define THREADS 20 |
| /* Number of references to create or delete in each thread */ |
| #define NREFS 10 |
| |
| struct th_data { |
| cl_git_thread_err error; |
| int id; |
| const char *path; |
| }; |
| |
| static void *iterate_refs(void *arg) |
| { |
| struct th_data *data = (struct th_data *) arg; |
| git_reference_iterator *i; |
| git_reference *ref; |
| int count = 0, error; |
| git_repository *repo; |
| |
| cl_git_thread_pass(data, git_repository_open(&repo, data->path)); |
| do { |
| error = git_reference_iterator_new(&i, repo); |
| } while (error == GIT_ELOCKED); |
| cl_git_thread_pass(data, error); |
| |
| for (count = 0; !git_reference_next(&ref, i); ++count) { |
| cl_assert(ref != NULL); |
| git_reference_free(ref); |
| } |
| |
| if (g_expected > 0) |
| cl_assert_equal_i(g_expected, count); |
| |
| git_reference_iterator_free(i); |
| |
| git_repository_free(repo); |
| giterr_clear(); |
| return arg; |
| } |
| |
| static void *create_refs(void *arg) |
| { |
| int i, error; |
| struct th_data *data = (struct th_data *) arg; |
| git_oid head; |
| char name[128]; |
| git_reference *ref[NREFS]; |
| git_repository *repo; |
| |
| cl_git_thread_pass(data, git_repository_open(&repo, data->path)); |
| |
| do { |
| error = git_reference_name_to_id(&head, repo, "HEAD"); |
| } while (error == GIT_ELOCKED); |
| cl_git_thread_pass(data, error); |
| |
| for (i = 0; i < NREFS; ++i) { |
| p_snprintf(name, sizeof(name), "refs/heads/thread-%03d-%02d", data->id, i); |
| do { |
| error = git_reference_create(&ref[i], repo, name, &head, 0, NULL); |
| } while (error == GIT_ELOCKED); |
| cl_git_thread_pass(data, error); |
| |
| if (concurrent_compress && i == NREFS/2) { |
| git_refdb *refdb; |
| cl_git_thread_pass(data, git_repository_refdb(&refdb, repo)); |
| do { |
| error = git_refdb_compress(refdb); |
| } while (error == GIT_ELOCKED); |
| cl_git_thread_pass(data, error); |
| git_refdb_free(refdb); |
| } |
| } |
| |
| for (i = 0; i < NREFS; ++i) |
| git_reference_free(ref[i]); |
| |
| git_repository_free(repo); |
| |
| giterr_clear(); |
| return arg; |
| } |
| |
| static void *delete_refs(void *arg) |
| { |
| int i, error; |
| struct th_data *data = (struct th_data *) arg; |
| git_reference *ref; |
| char name[128]; |
| git_repository *repo; |
| |
| cl_git_thread_pass(data, git_repository_open(&repo, data->path)); |
| |
| for (i = 0; i < NREFS; ++i) { |
| p_snprintf( |
| name, sizeof(name), "refs/heads/thread-%03d-%02d", (data->id) & ~0x3, i); |
| |
| if (!git_reference_lookup(&ref, repo, name)) { |
| do { |
| error = git_reference_delete(ref); |
| } while (error == GIT_ELOCKED); |
| /* Sometimes we race with other deleter threads */ |
| if (error == GIT_ENOTFOUND) |
| error = 0; |
| |
| cl_git_thread_pass(data, error); |
| git_reference_free(ref); |
| } |
| |
| if (concurrent_compress && i == NREFS/2) { |
| git_refdb *refdb; |
| cl_git_thread_pass(data, git_repository_refdb(&refdb, repo)); |
| do { |
| error = git_refdb_compress(refdb); |
| } while (error == GIT_ELOCKED); |
| cl_git_thread_pass(data, error); |
| git_refdb_free(refdb); |
| } |
| } |
| |
| git_repository_free(repo); |
| giterr_clear(); |
| return arg; |
| } |
| |
| void test_threads_refdb__edit_while_iterate(void) |
| { |
| int r, t; |
| struct th_data th_data[THREADS]; |
| git_oid head; |
| git_reference *ref; |
| char name[128]; |
| git_refdb *refdb; |
| |
| #ifdef GIT_THREADS |
| git_thread th[THREADS]; |
| #endif |
| |
| g_repo = cl_git_sandbox_init("testrepo2"); |
| |
| cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD")); |
| |
| /* make a bunch of references */ |
| |
| for (r = 0; r < 50; ++r) { |
| p_snprintf(name, sizeof(name), "refs/heads/starter-%03d", r); |
| cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0, NULL)); |
| git_reference_free(ref); |
| } |
| |
| cl_git_pass(git_repository_refdb(&refdb, g_repo)); |
| cl_git_pass(git_refdb_compress(refdb)); |
| git_refdb_free(refdb); |
| |
| g_expected = -1; |
| |
| g_repo = cl_git_sandbox_reopen(); /* reopen to flush caches */ |
| |
| for (t = 0; t < THREADS; ++t) { |
| void *(*fn)(void *arg); |
| |
| switch (t & 0x3) { |
| case 0: fn = create_refs; break; |
| case 1: fn = delete_refs; break; |
| default: fn = iterate_refs; break; |
| } |
| |
| th_data[t].id = t; |
| th_data[t].path = git_repository_path(g_repo); |
| |
| #ifdef GIT_THREADS |
| cl_git_pass(git_thread_create(&th[t], fn, &th_data[t])); |
| #else |
| fn(&th_data[t]); |
| #endif |
| } |
| |
| #ifdef GIT_THREADS |
| for (t = 0; t < THREADS; ++t) { |
| cl_git_pass(git_thread_join(&th[t], NULL)); |
| cl_git_thread_check(&th_data[t]); |
| } |
| |
| memset(th, 0, sizeof(th)); |
| |
| for (t = 0; t < THREADS; ++t) { |
| th_data[t].id = t; |
| cl_git_pass(git_thread_create(&th[t], iterate_refs, &th_data[t])); |
| } |
| |
| for (t = 0; t < THREADS; ++t) { |
| cl_git_pass(git_thread_join(&th[t], NULL)); |
| cl_git_thread_check(&th_data[t]); |
| } |
| #endif |
| } |