summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Martín Nieto <cmn@dwim.me>2016-04-05 17:03:06 -0400
committerCarlos Martín Nieto <cmn@dwim.me>2016-11-15 12:29:01 +0100
commitc5f7b5fc60830d9d2ab8db3ac2285f8043ea04a6 (patch)
treed74ff6c42f616048a084df7f1fb3153a90f28e4a
parent032daf4c93e3c2e528ff5396cc28a931803a2144 (diff)
downloadlibgit2-c5f7b5fc60830d9d2ab8db3ac2285f8043ea04a6.tar.gz
moar lock
-rw-r--r--include/git2/cancellable.h6
-rw-r--r--src/cancellable.c40
-rw-r--r--tests/core/cancellable.c55
3 files changed, 85 insertions, 16 deletions
diff --git a/include/git2/cancellable.h b/include/git2/cancellable.h
index d7079596b..555fa66bc 100644
--- a/include/git2/cancellable.h
+++ b/include/git2/cancellable.h
@@ -13,13 +13,13 @@
typedef struct git_cancellable git_cancellable;
typedef struct git_cancellable_source git_cancellable_source;
-typedef void (*git_cancellable_cb)(git_cancellable *cancellable, void *payload);
+typedef int (*git_cancellable_cb)(git_cancellable *cancellable, void *payload);
GIT_EXTERN(int) git_cancellable_source_new(git_cancellable_source **out);
-GIT_EXTERN(int) git_cancellable_source_free(git_cancellable_source *cs);
+GIT_EXTERN(void) git_cancellable_source_free(git_cancellable_source *cs);
GIT_EXTERN(int) git_cancellable_is_cancelled(git_cancellable *c);
GIT_EXTERN(int) git_cancellable_register(git_cancellable *c, git_cancellable_cb cb, void *payload);
GIT_EXTERN(git_cancellable *) git_cancellable_source_token(git_cancellable_source *cs);
-GIT_EXTERN(void) git_cancellable_source_cancel(git_cancellable_source *cs);
+GIT_EXTERN(int) git_cancellable_source_cancel(git_cancellable_source *cs);
#endif
diff --git a/src/cancellable.c b/src/cancellable.c
index 8daaee61b..c3da91ab1 100644
--- a/src/cancellable.c
+++ b/src/cancellable.c
@@ -17,7 +17,8 @@ typedef struct {
struct git_cancellable {
git_array_t(registration) registrations;
- git_atomic cancelled;
+ git_mutex lock;
+ bool cancelled;
};
struct git_cancellable_source {
@@ -32,11 +33,17 @@ int git_cancellable_source_new(git_cancellable_source **out)
cs = git__calloc(1, sizeof(git_cancellable_source));
GITERR_CHECK_ALLOC(cs);
+ if (git_mutex_init(&cs->token.lock) < 0) {
+ git__free(cs);
+ giterr_set(GITERR_OS, "failed to initialize cancellable lock");
+ return -1;
+ }
+
*out= cs;
return 0;
}
-int git_cancellable_source_free(git_cancellable_source *cs)
+void git_cancellable_source_free(git_cancellable_source *cs)
{
git_array_clear(cs->token.registrations);
git__free(cs);
@@ -44,7 +51,8 @@ int git_cancellable_source_free(git_cancellable_source *cs)
int git_cancellable_is_cancelled(git_cancellable *c)
{
- return c->cancelled.val;
+ /* Should we have a memory barrier here? a full lock? */
+ return c->cancelled;
}
int git_cancellable_register(git_cancellable *c, git_cancellable_cb cb, void *payload)
@@ -63,16 +71,36 @@ git_cancellable *git_cancellable_source_token(git_cancellable_source *cs)
return &cs->token;
}
-void git_cancellable_source_cancel(git_cancellable_source *cs)
+int git_cancellable_source_cancel(git_cancellable_source *cs)
{
size_t i;
+ int error = 0;
registration *reg;
- git_atomic_set(&cs->token.cancelled, 1);
+ /* You can only cancel once, short-circuit when already done */
+ if (cs->token.cancelled)
+ return 0;
+
+ if (git_mutex_lock(&cs->token.lock) < 0) {
+ giterr_set(GITERR_OS, "failed to lock cancellable token");
+ return -1;
+ }
+ /* We lost the race for the mutex */
+ if (cs->token.cancelled)
+ goto unlock;
+
+ cs->token.cancelled = true;
+
+ /* Run the registered hooks if it's the first cancellation */
for (i = 0; i < git_array_size(cs->token.registrations); i++) {
reg = git_array_get(cs->token.registrations, i);
assert(reg);
- reg->cb(&cs->token, reg->payload);
+ if ((error = reg->cb(&cs->token, reg->payload)) < 0)
+ goto unlock;
}
+
+ unlock:
+ git_mutex_unlock(&cs->token.lock);
+ return error;
}
diff --git a/tests/core/cancellable.c b/tests/core/cancellable.c
index 38848bb2b..319c9c260 100644
--- a/tests/core/cancellable.c
+++ b/tests/core/cancellable.c
@@ -10,20 +10,22 @@ void test_core_cancellable__can_cancel(void)
token = git_cancellable_source_token(cs);
cl_assert(!git_cancellable_is_cancelled(token));
- git_cancellable_source_cancel(cs);
+ cl_git_pass(git_cancellable_source_cancel(cs));
cl_assert(git_cancellable_is_cancelled(token));
git_cancellable_source_free(cs);
}
-static void cancel_second(git_cancellable *cancellable, void *payload)
+static int cancel_second(git_cancellable *cancellable, void *payload)
{
git_cancellable_source *cs;
GIT_UNUSED(cancellable);
cs = (git_cancellable_source *) payload;
- git_cancellable_source_cancel(cs);
+ cl_git_pass(git_cancellable_source_cancel(cs));
+
+ return 0;
}
void test_core_cancellable__can_register(void)
@@ -42,7 +44,7 @@ void test_core_cancellable__can_register(void)
cl_assert(!git_cancellable_is_cancelled(token1));
cl_assert(!git_cancellable_is_cancelled(token2));
- git_cancellable_source_cancel(cs1);
+ cl_git_pass(git_cancellable_source_cancel(cs1));
cl_assert(git_cancellable_is_cancelled(token1));
cl_assert(git_cancellable_is_cancelled(token2));
@@ -51,13 +53,15 @@ void test_core_cancellable__can_register(void)
git_cancellable_source_free(cs2);
}
-static void cancel_count(git_cancellable *cancellable, void *payload)
+static int cancel_count(git_cancellable *cancellable, void *payload)
{
int *i;
GIT_UNUSED(cancellable);
i = (int *) payload;
*i += 1;
+
+ return 0;
}
void test_core_cancellable__registration_fires_once(void)
@@ -73,10 +77,47 @@ void test_core_cancellable__registration_fires_once(void)
cl_assert(!git_cancellable_is_cancelled(token));
- git_cancellable_source_cancel(cs);
+ cl_git_pass(git_cancellable_source_cancel(cs));
+ cl_assert(git_cancellable_is_cancelled(token));
+
+ cl_git_pass(git_cancellable_source_cancel(cs));
+ cl_assert(git_cancellable_is_cancelled(token));
+
+ cl_assert_equal_i(1, cancelled_times);
+
+ git_cancellable_source_free(cs);
+}
+
+static int cancel_fail(git_cancellable *cancellable, void *payload)
+{
+ int *i;
+ GIT_UNUSED(cancellable);
+
+ i = (int *) payload;
+ *i += 1;
+
+ return GIT_EUSER;
+}
+
+void test_core_cancellable__trigger_failure(void)
+{
+ git_cancellable_source *cs;
+ git_cancellable *token;
+ int cancelled_times = 0;
+
+ cl_git_pass(git_cancellable_source_new(&cs));
+
+ token = git_cancellable_source_token(cs);
+ /* Register twice, but we fail on the first time, so we should only inc once */
+ cl_git_pass(git_cancellable_register(token, cancel_fail, &cancelled_times));
+ cl_git_pass(git_cancellable_register(token, cancel_fail, &cancelled_times));
+
+ cl_assert(!git_cancellable_is_cancelled(token));
+
+ cl_git_fail_with(GIT_EUSER, git_cancellable_source_cancel(cs));
cl_assert(git_cancellable_is_cancelled(token));
- git_cancellable_source_cancel(cs);
+ cl_git_pass(git_cancellable_source_cancel(cs));
cl_assert(git_cancellable_is_cancelled(token));
cl_assert_equal_i(1, cancelled_times);