* [PATCH] threaded pack-objects: Use condition variables for thread communication. @ 2007-12-15 23:18 Johannes Sixt 2007-12-16 12:05 ` Peter Baumann 0 siblings, 1 reply; 11+ messages in thread From: Johannes Sixt @ 2007-12-15 23:18 UTC (permalink / raw) To: Nicolas Pitre; +Cc: git, Junio C Hamano In the threaded pack-objects code the main thread and the worker threads must mutually signal that they have assigned a new pack of work or have completed their work, respectively. Previously, the code used mutexes that were locked in one thread and unlocked from a different thread, which is bogus (and happens to work on Linux). Here we rectify the implementation by using condition variables: There is one condition variable on which the main thread waits until a thread requests new work; and each worker thread has its own condition variable on which it waits until it is assigned new work or signaled to terminate. As a cleanup, the worker threads are spawned only after the initial work packages have been assigned. Signed-off-by: Johannes Sixt <johannes.sixt@telecom.at> --- builtin-pack-objects.c | 129 +++++++++++++++++++++++++++++------------------ 1 files changed, 79 insertions(+), 50 deletions(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 7dd0d7f..451e48e 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1594,6 +1594,15 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, #ifdef THREADED_DELTA_SEARCH +/* + * The main thread waits on the condition that (at least) one of the workers + * has stopped working (which is indicated in the .working member of + * struct thread_params). + * When a work thread has completed its work, it sets .working to 0 and + * signals the main thread and waits on the condition that .data_ready + * becomes 1. + */ + struct thread_params { pthread_t thread; struct object_entry **list; @@ -1601,37 +1610,50 @@ struct thread_params { unsigned remaining; int window; int depth; + int working; + int data_ready; + pthread_mutex_t mutex; + pthread_cond_t cond; unsigned *processed; }; -static pthread_mutex_t data_request = PTHREAD_MUTEX_INITIALIZER; -static pthread_mutex_t data_ready = PTHREAD_MUTEX_INITIALIZER; -static pthread_mutex_t data_provider = PTHREAD_MUTEX_INITIALIZER; -static struct thread_params *data_requester; +static pthread_cond_t progress_cond = PTHREAD_COND_INITIALIZER; static void *threaded_find_deltas(void *arg) { struct thread_params *me = arg; - for (;;) { - pthread_mutex_lock(&data_request); - data_requester = me; - pthread_mutex_unlock(&data_provider); - pthread_mutex_lock(&data_ready); - pthread_mutex_unlock(&data_request); - - if (!me->remaining) - return NULL; - + while (me->remaining) { find_deltas(me->list, &me->remaining, me->window, me->depth, me->processed); + + progress_lock(); + me->working = 0; + progress_unlock(); + pthread_cond_signal(&progress_cond); + + /* + * We must not set ->data_ready before we wait on the + * condition because the main thread may have set it to 1 + * before we get here. In order to be sure that new + * work is available if we see 1 in ->data_ready, it + * was initialized to 0 before this thread was spawned + * and we reset it to 0 right away. + */ + pthread_mutex_lock(&me->mutex); + while (!me->data_ready) + pthread_cond_wait(&me->cond, &me->mutex); + me->data_ready = 0; + pthread_mutex_unlock(&me->mutex); } + /* leave ->working 1 so that this doesn't get more work assigned */ + return NULL; } static void ll_find_deltas(struct object_entry **list, unsigned list_size, int window, int depth, unsigned *processed) { - struct thread_params *target, p[delta_search_threads]; + struct thread_params p[delta_search_threads]; int i, ret, active_threads = 0; if (delta_search_threads <= 1) { @@ -1639,49 +1661,42 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, return; } - pthread_mutex_lock(&data_provider); - pthread_mutex_lock(&data_ready); - - /* Start work threads. */ + /* Partition the work amongst work threads. */ for (i = 0; i < delta_search_threads; i++) { + unsigned sub_size = list_size / (delta_search_threads - i); + p[i].window = window; p[i].depth = depth; p[i].processed = processed; - p[i].remaining = 0; - ret = pthread_create(&p[i].thread, NULL, - threaded_find_deltas, &p[i]); - if (ret) - die("unable to create thread: %s", strerror(ret)); - active_threads++; - } - - /* Then partition the work amongst them. */ - for (i = 0; i < delta_search_threads; i++) { - unsigned sub_size = list_size / (delta_search_threads - i); - - pthread_mutex_lock(&data_provider); - target = data_requester; - if (!sub_size) { - pthread_mutex_unlock(&data_ready); - pthread_join(target->thread, NULL); - active_threads--; - continue; - } + p[i].working = 1; + p[i].data_ready = 0; + pthread_mutex_init(&p[i].mutex, NULL); + pthread_cond_init(&p[i].cond, NULL); /* try to split chunks on "path" boundaries */ while (sub_size < list_size && list[sub_size]->hash && list[sub_size]->hash == list[sub_size-1]->hash) sub_size++; - target->list = list; - target->list_size = sub_size; - target->remaining = sub_size; - pthread_mutex_unlock(&data_ready); + p[i].list = list; + p[i].list_size = sub_size; + p[i].remaining = sub_size; list += sub_size; list_size -= sub_size; } + /* Start work threads. */ + for (i = 0; i < delta_search_threads; i++) { + if (!p[i].list_size) + continue; + ret = pthread_create(&p[i].thread, NULL, + threaded_find_deltas, &p[i]); + if (ret) + die("unable to create thread: %s", strerror(ret)); + active_threads++; + } + /* * Now let's wait for work completion. Each time a thread is done * with its work, we steal half of the remaining work from the @@ -1690,13 +1705,21 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, * until the remaining object list segments are simply too short * to be worth splitting anymore. */ - do { + while (active_threads) { + struct thread_params *target = NULL; struct thread_params *victim = NULL; unsigned sub_size = 0; - pthread_mutex_lock(&data_provider); - target = data_requester; progress_lock(); + for (;;) { + for (i = 0; !target && i < delta_search_threads; i++) + if (!p[i].working) + target = &p[i]; + if (target) + break; + pthread_cond_wait(&progress_cond, &progress_mutex); + }; + for (i = 0; i < delta_search_threads; i++) if (p[i].remaining > 2*window && (!victim || victim->remaining < p[i].remaining)) @@ -1723,17 +1746,23 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, victim->list_size -= sub_size; victim->remaining -= sub_size; } - progress_unlock(); - target->list_size = sub_size; target->remaining = sub_size; - pthread_mutex_unlock(&data_ready); + target->working = 1; + progress_unlock(); + + pthread_mutex_lock(&target->mutex); + target->data_ready = 1; + pthread_mutex_unlock(&target->mutex); + pthread_cond_signal(&target->cond); if (!sub_size) { pthread_join(target->thread, NULL); + pthread_cond_destroy(&target->cond); + pthread_mutex_destroy(&target->mutex); active_threads--; } - } while (active_threads); + } } #else -- 1.5.3.6.954.g0c63 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH] threaded pack-objects: Use condition variables for thread communication. 2007-12-15 23:18 [PATCH] threaded pack-objects: Use condition variables for thread communication Johannes Sixt @ 2007-12-16 12:05 ` Peter Baumann 2007-12-16 18:41 ` Johannes Sixt 0 siblings, 1 reply; 11+ messages in thread From: Peter Baumann @ 2007-12-16 12:05 UTC (permalink / raw) To: Johannes Sixt; +Cc: Nicolas Pitre, git, Junio C Hamano On Sun, Dec 16, 2007 at 12:18:53AM +0100, Johannes Sixt wrote: > In the threaded pack-objects code the main thread and the worker threads > must mutually signal that they have assigned a new pack of work or have > completed their work, respectively. Previously, the code used mutexes that > were locked in one thread and unlocked from a different thread, which is > bogus (and happens to work on Linux). > > Here we rectify the implementation by using condition variables: There is > one condition variable on which the main thread waits until a thread > requests new work; and each worker thread has its own condition variable > on which it waits until it is assigned new work or signaled to terminate. > > As a cleanup, the worker threads are spawned only after the initial work > packages have been assigned. > > Signed-off-by: Johannes Sixt <johannes.sixt@telecom.at> > --- > builtin-pack-objects.c | 129 +++++++++++++++++++++++++++++------------------ > 1 files changed, 79 insertions(+), 50 deletions(-) > > diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c > index 7dd0d7f..451e48e 100644 > --- a/builtin-pack-objects.c > +++ b/builtin-pack-objects.c > @@ -1594,6 +1594,15 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, > > #ifdef THREADED_DELTA_SEARCH > > +/* > + * The main thread waits on the condition that (at least) one of the workers > + * has stopped working (which is indicated in the .working member of > + * struct thread_params). > + * When a work thread has completed its work, it sets .working to 0 and > + * signals the main thread and waits on the condition that .data_ready > + * becomes 1. > + */ > + > struct thread_params { > pthread_t thread; > struct object_entry **list; > @@ -1601,37 +1610,50 @@ struct thread_params { > unsigned remaining; > int window; > int depth; > + int working; > + int data_ready; > + pthread_mutex_t mutex; > + pthread_cond_t cond; > unsigned *processed; > }; > > -static pthread_mutex_t data_request = PTHREAD_MUTEX_INITIALIZER; > -static pthread_mutex_t data_ready = PTHREAD_MUTEX_INITIALIZER; > -static pthread_mutex_t data_provider = PTHREAD_MUTEX_INITIALIZER; > -static struct thread_params *data_requester; > +static pthread_cond_t progress_cond = PTHREAD_COND_INITIALIZER; > > static void *threaded_find_deltas(void *arg) > { > struct thread_params *me = arg; > > - for (;;) { > - pthread_mutex_lock(&data_request); > - data_requester = me; > - pthread_mutex_unlock(&data_provider); > - pthread_mutex_lock(&data_ready); > - pthread_mutex_unlock(&data_request); > - > - if (!me->remaining) > - return NULL; > - > + while (me->remaining) { > find_deltas(me->list, &me->remaining, > me->window, me->depth, me->processed); > + > + progress_lock(); > + me->working = 0; > + progress_unlock(); > + pthread_cond_signal(&progress_cond); Shouldn't the pthread_cond_signal be inside the lock? e.g. swap progress_unlock() with pthread_cond_signal(&progress_cond) > + > + /* > + * We must not set ->data_ready before we wait on the > + * condition because the main thread may have set it to 1 > + * before we get here. In order to be sure that new > + * work is available if we see 1 in ->data_ready, it > + * was initialized to 0 before this thread was spawned > + * and we reset it to 0 right away. > + */ > + pthread_mutex_lock(&me->mutex); > + while (!me->data_ready) > + pthread_cond_wait(&me->cond, &me->mutex); > + me->data_ready = 0; > + pthread_mutex_unlock(&me->mutex); > } > + /* leave ->working 1 so that this doesn't get more work assigned */ > + return NULL; > } [...] -Peter ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] threaded pack-objects: Use condition variables for thread communication. 2007-12-16 12:05 ` Peter Baumann @ 2007-12-16 18:41 ` Johannes Sixt 2007-12-16 19:00 ` Peter Baumann 2007-12-16 19:18 ` David Brown 0 siblings, 2 replies; 11+ messages in thread From: Johannes Sixt @ 2007-12-16 18:41 UTC (permalink / raw) To: Peter Baumann; +Cc: git, Nicolas Pitre, Junio C Hamano On Sunday 16 December 2007 13:05, Peter Baumann wrote: > On Sun, Dec 16, 2007 at 12:18:53AM +0100, Johannes Sixt wrote: > > + > > + progress_lock(); > > + me->working = 0; > > + progress_unlock(); > > + pthread_cond_signal(&progress_cond); > > Shouldn't the pthread_cond_signal be inside the lock? > e.g. swap progress_unlock() with pthread_cond_signal(&progress_cond) No, that's not necessary. Both ways are correct, but if it's outside the lock there is less contention on the mutex (because the waiting thread must acquire the mutex lock before it can return from pthread_cond_wait). -- Hannes ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] threaded pack-objects: Use condition variables for thread communication. 2007-12-16 18:41 ` Johannes Sixt @ 2007-12-16 19:00 ` Peter Baumann 2007-12-16 19:45 ` [PATCH v2] " Johannes Sixt 2007-12-17 4:26 ` [PATCH] threaded pack-objects: Use condition variables for thread communication Dmitry Potapov 2007-12-16 19:18 ` David Brown 1 sibling, 2 replies; 11+ messages in thread From: Peter Baumann @ 2007-12-16 19:00 UTC (permalink / raw) To: Johannes Sixt; +Cc: git, Nicolas Pitre, Junio C Hamano On Sun, Dec 16, 2007 at 07:41:37PM +0100, Johannes Sixt wrote: > On Sunday 16 December 2007 13:05, Peter Baumann wrote: > > On Sun, Dec 16, 2007 at 12:18:53AM +0100, Johannes Sixt wrote: > > > + > > > + progress_lock(); > > > + me->working = 0; > > > + progress_unlock(); > > > + pthread_cond_signal(&progress_cond); > > > > Shouldn't the pthread_cond_signal be inside the lock? > > e.g. swap progress_unlock() with pthread_cond_signal(&progress_cond) > > No, that's not necessary. Both ways are correct, but if it's outside the lock > there is less contention on the mutex (because the waiting thread must > acquire the mutex lock before it can return from pthread_cond_wait). > At least I was told otherwise and [1] backs my knowledge up. Are you really sure? -Peter http://docs.sun.com/app/docs/doc/806-5257/6je9h032r?a=view#sync-53686 ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v2] threaded pack-objects: Use condition variables for thread communication. 2007-12-16 19:00 ` Peter Baumann @ 2007-12-16 19:45 ` Johannes Sixt 2007-12-16 22:12 ` Junio C Hamano ` (2 more replies) 2007-12-17 4:26 ` [PATCH] threaded pack-objects: Use condition variables for thread communication Dmitry Potapov 1 sibling, 3 replies; 11+ messages in thread From: Johannes Sixt @ 2007-12-16 19:45 UTC (permalink / raw) To: Peter Baumann; +Cc: git, Nicolas Pitre, Junio C Hamano In the threaded pack-objects code the main thread and the worker threads must mutually signal that they have assigned a new pack of work or have completed their work, respectively. Previously, the code used mutexes that were locked in one thread and unlocked from a different thread, which is bogus (and happens to work on Linux). Here we rectify the implementation by using condition variables: There is one condition variable on which the main thread waits until a thread requests new work; and each worker thread has its own condition variable on which it waits until it is assigned new work or signaled to terminate. As a cleanup, the worker threads are spawned only after the initial work packages have been assigned. Signed-off-by: Johannes Sixt <johannes.sixt@telecom.at> --- On Sunday 16 December 2007 20:00, Peter Baumann wrote: > On Sun, Dec 16, 2007 at 07:41:37PM +0100, Johannes Sixt wrote: > > On Sunday 16 December 2007 13:05, Peter Baumann wrote: > > > On Sun, Dec 16, 2007 at 12:18:53AM +0100, Johannes Sixt wrote: > > > > + > > > > + progress_lock(); > > > > + me->working = 0; > > > > + progress_unlock(); > > > > + pthread_cond_signal(&progress_cond); > > > > > > Shouldn't the pthread_cond_signal be inside the lock? > > > e.g. swap progress_unlock() with pthread_cond_signal(&progress_cond) > > > > No, that's not necessary. Both ways are correct, but if it's outside the > > lock there is less contention on the mutex (because the waiting thread > > must acquire the mutex lock before it can return from pthread_cond_wait). > > At least I was told otherwise and [1] backs my knowledge up. Are you > really sure? > > -Peter > > http://docs.sun.com/app/docs/doc/806-5257/6je9h032r?a=view#sync-53686 Uh, yes, that's a very compelling reason. I'm wrong. Here's an updated patch. -- Hannes builtin-pack-objects.c | 129 +++++++++++++++++++++++++++++------------------ 1 files changed, 79 insertions(+), 50 deletions(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 7dd0d7f..4715d5f 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1594,6 +1594,15 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, #ifdef THREADED_DELTA_SEARCH +/* + * The main thread waits on the condition that (at least) one of the workers + * has stopped working (which is indicated in the .working member of + * struct thread_params). + * When a work thread has completed its work, it sets .working to 0 and + * signals the main thread and waits on the condition that .data_ready + * becomes 1. + */ + struct thread_params { pthread_t thread; struct object_entry **list; @@ -1601,37 +1610,50 @@ struct thread_params { unsigned remaining; int window; int depth; + int working; + int data_ready; + pthread_mutex_t mutex; + pthread_cond_t cond; unsigned *processed; }; -static pthread_mutex_t data_request = PTHREAD_MUTEX_INITIALIZER; -static pthread_mutex_t data_ready = PTHREAD_MUTEX_INITIALIZER; -static pthread_mutex_t data_provider = PTHREAD_MUTEX_INITIALIZER; -static struct thread_params *data_requester; +static pthread_cond_t progress_cond = PTHREAD_COND_INITIALIZER; static void *threaded_find_deltas(void *arg) { struct thread_params *me = arg; - for (;;) { - pthread_mutex_lock(&data_request); - data_requester = me; - pthread_mutex_unlock(&data_provider); - pthread_mutex_lock(&data_ready); - pthread_mutex_unlock(&data_request); - - if (!me->remaining) - return NULL; - + while (me->remaining) { find_deltas(me->list, &me->remaining, me->window, me->depth, me->processed); + + progress_lock(); + me->working = 0; + pthread_cond_signal(&progress_cond); + progress_unlock(); + + /* + * We must not set ->data_ready before we wait on the + * condition because the main thread may have set it to 1 + * before we get here. In order to be sure that new + * work is available if we see 1 in ->data_ready, it + * was initialized to 0 before this thread was spawned + * and we reset it to 0 right away. + */ + pthread_mutex_lock(&me->mutex); + while (!me->data_ready) + pthread_cond_wait(&me->cond, &me->mutex); + me->data_ready = 0; + pthread_mutex_unlock(&me->mutex); } + /* leave ->working 1 so that this doesn't get more work assigned */ + return NULL; } static void ll_find_deltas(struct object_entry **list, unsigned list_size, int window, int depth, unsigned *processed) { - struct thread_params *target, p[delta_search_threads]; + struct thread_params p[delta_search_threads]; int i, ret, active_threads = 0; if (delta_search_threads <= 1) { @@ -1639,49 +1661,42 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, return; } - pthread_mutex_lock(&data_provider); - pthread_mutex_lock(&data_ready); - - /* Start work threads. */ + /* Partition the work amongst work threads. */ for (i = 0; i < delta_search_threads; i++) { + unsigned sub_size = list_size / (delta_search_threads - i); + p[i].window = window; p[i].depth = depth; p[i].processed = processed; - p[i].remaining = 0; - ret = pthread_create(&p[i].thread, NULL, - threaded_find_deltas, &p[i]); - if (ret) - die("unable to create thread: %s", strerror(ret)); - active_threads++; - } - - /* Then partition the work amongst them. */ - for (i = 0; i < delta_search_threads; i++) { - unsigned sub_size = list_size / (delta_search_threads - i); - - pthread_mutex_lock(&data_provider); - target = data_requester; - if (!sub_size) { - pthread_mutex_unlock(&data_ready); - pthread_join(target->thread, NULL); - active_threads--; - continue; - } + p[i].working = 1; + p[i].data_ready = 0; + pthread_mutex_init(&p[i].mutex, NULL); + pthread_cond_init(&p[i].cond, NULL); /* try to split chunks on "path" boundaries */ while (sub_size < list_size && list[sub_size]->hash && list[sub_size]->hash == list[sub_size-1]->hash) sub_size++; - target->list = list; - target->list_size = sub_size; - target->remaining = sub_size; - pthread_mutex_unlock(&data_ready); + p[i].list = list; + p[i].list_size = sub_size; + p[i].remaining = sub_size; list += sub_size; list_size -= sub_size; } + /* Start work threads. */ + for (i = 0; i < delta_search_threads; i++) { + if (!p[i].list_size) + continue; + ret = pthread_create(&p[i].thread, NULL, + threaded_find_deltas, &p[i]); + if (ret) + die("unable to create thread: %s", strerror(ret)); + active_threads++; + } + /* * Now let's wait for work completion. Each time a thread is done * with its work, we steal half of the remaining work from the @@ -1690,13 +1705,21 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, * until the remaining object list segments are simply too short * to be worth splitting anymore. */ - do { + while (active_threads) { + struct thread_params *target = NULL; struct thread_params *victim = NULL; unsigned sub_size = 0; - pthread_mutex_lock(&data_provider); - target = data_requester; progress_lock(); + for (;;) { + for (i = 0; !target && i < delta_search_threads; i++) + if (!p[i].working) + target = &p[i]; + if (target) + break; + pthread_cond_wait(&progress_cond, &progress_mutex); + }; + for (i = 0; i < delta_search_threads; i++) if (p[i].remaining > 2*window && (!victim || victim->remaining < p[i].remaining)) @@ -1723,17 +1746,23 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, victim->list_size -= sub_size; victim->remaining -= sub_size; } - progress_unlock(); - target->list_size = sub_size; target->remaining = sub_size; - pthread_mutex_unlock(&data_ready); + target->working = 1; + progress_unlock(); + + pthread_mutex_lock(&target->mutex); + target->data_ready = 1; + pthread_cond_signal(&target->cond); + pthread_mutex_unlock(&target->mutex); if (!sub_size) { pthread_join(target->thread, NULL); + pthread_cond_destroy(&target->cond); + pthread_mutex_destroy(&target->mutex); active_threads--; } - } while (active_threads); + } } #else -- 1.5.3.6.954.g0c63 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v2] threaded pack-objects: Use condition variables for thread communication. 2007-12-16 19:45 ` [PATCH v2] " Johannes Sixt @ 2007-12-16 22:12 ` Junio C Hamano 2007-12-17 3:13 ` Nicolas Pitre 2007-12-17 7:44 ` Johannes Sixt 2 siblings, 0 replies; 11+ messages in thread From: Junio C Hamano @ 2007-12-16 22:12 UTC (permalink / raw) To: Johannes Sixt; +Cc: Peter Baumann, git, Nicolas Pitre Looks nice and I think the clean-up makes the logic easier to follow as well. ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2] threaded pack-objects: Use condition variables for thread communication. 2007-12-16 19:45 ` [PATCH v2] " Johannes Sixt 2007-12-16 22:12 ` Junio C Hamano @ 2007-12-17 3:13 ` Nicolas Pitre 2007-12-17 7:44 ` Johannes Sixt 2 siblings, 0 replies; 11+ messages in thread From: Nicolas Pitre @ 2007-12-17 3:13 UTC (permalink / raw) To: Johannes Sixt; +Cc: Peter Baumann, git, Junio C Hamano On Sun, 16 Dec 2007, Johannes Sixt wrote: > Signed-off-by: Johannes Sixt <johannes.sixt@telecom.at> One micro nit: > + for (;;) { > + for (i = 0; !target && i < delta_search_threads; i++) > + if (!p[i].working) > + target = &p[i]; > + if (target) > + break; > + pthread_cond_wait(&progress_cond, &progress_mutex); > + }; There's a spurious ; here. Otherwise: Acked-by: Nicolas Pitre <nico@cam.org> Nicolas ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v2] threaded pack-objects: Use condition variables for thread communication. 2007-12-16 19:45 ` [PATCH v2] " Johannes Sixt 2007-12-16 22:12 ` Junio C Hamano 2007-12-17 3:13 ` Nicolas Pitre @ 2007-12-17 7:44 ` Johannes Sixt 2007-12-17 19:12 ` [PATCH] Plug a resource leak in threaded pack-objects code Johannes Sixt 2 siblings, 1 reply; 11+ messages in thread From: Johannes Sixt @ 2007-12-17 7:44 UTC (permalink / raw) To: Junio C Hamano; +Cc: Peter Baumann, git, Nicolas Pitre I just discovered a theoretical resource leakage: > + /* Partition the work amongst work threads. */ > for (i = 0; i < delta_search_threads; i++) { ... > + pthread_mutex_init(&p[i].mutex, NULL); > + pthread_cond_init(&p[i].cond, NULL); These two initializations... > } > > + /* Start work threads. */ > + for (i = 0; i < delta_search_threads; i++) { > + if (!p[i].list_size) > + continue; ... should go here because... > + ret = pthread_create(&p[i].thread, NULL, > + threaded_find_deltas, &p[i]); > + if (ret) > + die("unable to create thread: %s", strerror(ret)); > + active_threads++; > + } ... > + while (active_threads) { ... > if (!sub_size) { > pthread_join(target->thread, NULL); > + pthread_cond_destroy(&target->cond); > + pthread_mutex_destroy(&target->mutex); ... we tear down only those for which we actually started a thread. > active_threads--; > } > + } Will send a patch this evening. -- Hannes ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH] Plug a resource leak in threaded pack-objects code. 2007-12-17 7:44 ` Johannes Sixt @ 2007-12-17 19:12 ` Johannes Sixt 0 siblings, 0 replies; 11+ messages in thread From: Johannes Sixt @ 2007-12-17 19:12 UTC (permalink / raw) To: Junio C Hamano; +Cc: git, Peter Baumann, Nicolas Pitre A mutex and a condition variable is allocated for each thread and torn down when the thread terminates. However, for certain workloads it can happen that some threads are actually not started at all. In this case we would leak the mutex and condition variable. Now we allocate them only for those threads that are actually started. Signed-off-by: Johannes Sixt <johannes.sixt@telecom.at> --- > I just discovered a theoretical resource leakage: > > Will send a patch this evening. Here it is. -- Hannes builtin-pack-objects.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 5765d02..e0ce114 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1670,8 +1670,6 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, p[i].processed = processed; p[i].working = 1; p[i].data_ready = 0; - pthread_mutex_init(&p[i].mutex, NULL); - pthread_cond_init(&p[i].cond, NULL); /* try to split chunks on "path" boundaries */ while (sub_size < list_size && list[sub_size]->hash && @@ -1690,6 +1688,8 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, for (i = 0; i < delta_search_threads; i++) { if (!p[i].list_size) continue; + pthread_mutex_init(&p[i].mutex, NULL); + pthread_cond_init(&p[i].cond, NULL); ret = pthread_create(&p[i].thread, NULL, threaded_find_deltas, &p[i]); if (ret) -- 1.5.4.rc0.37.g78e7 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH] threaded pack-objects: Use condition variables for thread communication. 2007-12-16 19:00 ` Peter Baumann 2007-12-16 19:45 ` [PATCH v2] " Johannes Sixt @ 2007-12-17 4:26 ` Dmitry Potapov 1 sibling, 0 replies; 11+ messages in thread From: Dmitry Potapov @ 2007-12-17 4:26 UTC (permalink / raw) To: Peter Baumann; +Cc: Johannes Sixt, git, Nicolas Pitre, Junio C Hamano On Sun, Dec 16, 2007 at 08:00:16PM +0100, Peter Baumann wrote: > On Sun, Dec 16, 2007 at 07:41:37PM +0100, Johannes Sixt wrote: > > On Sunday 16 December 2007 13:05, Peter Baumann wrote: > > > On Sun, Dec 16, 2007 at 12:18:53AM +0100, Johannes Sixt wrote: > > > > + > > > > + progress_lock(); > > > > + me->working = 0; > > > > + progress_unlock(); > > > > + pthread_cond_signal(&progress_cond); > > > > > > Shouldn't the pthread_cond_signal be inside the lock? > > > e.g. swap progress_unlock() with pthread_cond_signal(&progress_cond) > > > > No, that's not necessary. Both ways are correct, but if it's outside the lock > > there is less contention on the mutex (because the waiting thread must > > acquire the mutex lock before it can return from pthread_cond_wait). > > > > At least I was told otherwise and [1] backs my knowledge up. Are you > really sure? > > -Peter > > http://docs.sun.com/app/docs/doc/806-5257/6je9h032r?a=view#sync-53686 The POSIX standard clearly says that usage of pthread_cond_signal ouside of the mutex protection is allowed: === The pthread_cond_signal() or pthread_cond_broadcast() functions may be called by a thread whether or not it currently owns the mutex that threads calling pthread_cond_wait() or pthread_cond_timedwait() have associated with the condition variable during their waits; however, if predictable scheduling behaviour is required, then that mutex is locked by the thread calling pthread_cond_signal() or pthread_cond_broadcast(). === http://www.opengroup.org/onlinepubs/007908775/xsh/pthread_cond_signal.html And the argumentation provided by Sun's manual: > Otherwise, the condition variable could be signaled between the test > of the associated condition and blocking in pthread_cond_wait(), which > can cause an infinite wait. sounds strange to me. Indeed the condition variable could be signaled between the test and blocking, but the condition itself is false, so the thread should be blocked anyway. The only effect of signalling outside of the mutex protection is that the thread blocked on the condition variable can be waken up while the associated condition is still false, but it is not a problem. As to what is more optimal, it is less clear. Because on one side, signaling under the lock increases contention, but, on the other hand, it avoids spurious wakeup. So, I suppose it could be implementation specific. Dmitry ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] threaded pack-objects: Use condition variables for thread communication. 2007-12-16 18:41 ` Johannes Sixt 2007-12-16 19:00 ` Peter Baumann @ 2007-12-16 19:18 ` David Brown 1 sibling, 0 replies; 11+ messages in thread From: David Brown @ 2007-12-16 19:18 UTC (permalink / raw) To: Johannes Sixt; +Cc: Peter Baumann, git, Nicolas Pitre, Junio C Hamano On Sun, Dec 16, 2007 at 07:41:37PM +0100, Johannes Sixt wrote: >On Sunday 16 December 2007 13:05, Peter Baumann wrote: >> On Sun, Dec 16, 2007 at 12:18:53AM +0100, Johannes Sixt wrote: >> > + >> > + progress_lock(); >> > + me->working = 0; >> > + progress_unlock(); >> > + pthread_cond_signal(&progress_cond); >> >> Shouldn't the pthread_cond_signal be inside the lock? >> e.g. swap progress_unlock() with pthread_cond_signal(&progress_cond) > >No, that's not necessary. Both ways are correct, but if it's outside the lock >there is less contention on the mutex (because the waiting thread must >acquire the mutex lock before it can return from pthread_cond_wait). The signal needs to be before the unlock if is more than one signaller. Since normal usage has the signal inside of the lock, most implementations should do this efficiently. Dave ^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2007-12-17 19:13 UTC | newest] Thread overview: 11+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2007-12-15 23:18 [PATCH] threaded pack-objects: Use condition variables for thread communication Johannes Sixt 2007-12-16 12:05 ` Peter Baumann 2007-12-16 18:41 ` Johannes Sixt 2007-12-16 19:00 ` Peter Baumann 2007-12-16 19:45 ` [PATCH v2] " Johannes Sixt 2007-12-16 22:12 ` Junio C Hamano 2007-12-17 3:13 ` Nicolas Pitre 2007-12-17 7:44 ` Johannes Sixt 2007-12-17 19:12 ` [PATCH] Plug a resource leak in threaded pack-objects code Johannes Sixt 2007-12-17 4:26 ` [PATCH] threaded pack-objects: Use condition variables for thread communication Dmitry Potapov 2007-12-16 19:18 ` David Brown
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).