/************************************************************************* * Copyright (C) 2009-2010 Tavian Barnes * * * * This file is part of The Dimension Library. * * * * The Dimension Library is free software; you can redistribute it and/ * * or modify it under the terms of the GNU Lesser General Public License * * as published by the Free Software Foundation; either version 3 of the * * License, or (at your option) any later version. * * * * The Dimension Library is distributed in the hope that it will be * * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this program. If not, see * * . * *************************************************************************/ #include "dimension.h" #include /* For thread synchronization */ static void dmnsn_progress_rdlock(const dmnsn_progress *progress); static void dmnsn_progress_wrlock(dmnsn_progress *progress); static void dmnsn_progress_unlock(const dmnsn_progress *progress); /* Allocate a new dmnsn_progress*, returning NULL on failure */ dmnsn_progress * dmnsn_new_progress() { dmnsn_progress *progress = dmnsn_malloc(sizeof(dmnsn_progress)); progress->elements = dmnsn_new_array(sizeof(dmnsn_progress_element)); dmnsn_progress_element element = { .progress = 0, .total = 1 }; dmnsn_array_push(progress->elements, &element); /* Initialize the rwlock, condition variable, and mutex */ progress->rwlock = dmnsn_malloc(sizeof(pthread_rwlock_t)); if (pthread_rwlock_init(progress->rwlock, NULL) != 0) { dmnsn_error(DMNSN_SEVERITY_HIGH, "Couldn't initialize read-write lock."); } progress->cond = dmnsn_malloc(sizeof(pthread_cond_t)); if (pthread_cond_init(progress->cond, NULL) != 0) { dmnsn_error(DMNSN_SEVERITY_HIGH, "Couldn't initialize condition variable."); } progress->mutex = dmnsn_malloc(sizeof(pthread_mutex_t)); if (pthread_mutex_init(progress->mutex, NULL) != 0) { dmnsn_error(DMNSN_SEVERITY_HIGH, "Couldn't initialize mutex."); } return progress; } /* Delete a dmnsn_progress*, which has not yet been associated with a thread */ void dmnsn_delete_progress(dmnsn_progress *progress) { if (progress) { if (pthread_rwlock_destroy(progress->rwlock) != 0) { dmnsn_error(DMNSN_SEVERITY_LOW, "Leaking rwlock."); } if (pthread_mutex_destroy(progress->mutex) != 0) { dmnsn_error(DMNSN_SEVERITY_LOW, "Leaking mutex."); } if (pthread_cond_destroy(progress->cond) != 0) { dmnsn_error(DMNSN_SEVERITY_LOW, "Leaking condition variable."); } free(progress->rwlock); free(progress->mutex); free(progress->cond); dmnsn_delete_array(progress->elements); free(progress); } } /* Join the worker thread and delete `progress'. */ int dmnsn_finish_progress(dmnsn_progress *progress) { void *ptr; int retval = -1; if (progress) { if (pthread_join(progress->thread, &ptr) != 0) { /* Medium severity because an unjoined thread likely means that the thread is incomplete or invalid */ dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Joining worker thread failed."); } else if (ptr) { retval = *(int *)ptr; free(ptr); /* Wake up all waiters */ dmnsn_done_progress(progress); } dmnsn_delete_progress(progress); } return retval; } /* Get the current progress of the worker thread, in [0.0, 1.0] */ double dmnsn_get_progress(const dmnsn_progress *progress) { dmnsn_progress_element *element; double prog = 0.0; dmnsn_progress_rdlock(progress); size_t size = dmnsn_array_size(progress->elements); for (size_t i = 0; i < size; ++i) { element = dmnsn_array_at(progress->elements, size - i - 1); prog += element->progress; prog /= element->total; } dmnsn_progress_unlock(progress); return prog; } /* Wait until dmnsn_get_progress(progress) >= prog */ void dmnsn_wait_progress(const dmnsn_progress *progress, double prog) { if (pthread_mutex_lock(progress->mutex) != 0) { dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't lock condition mutex."); /* Busy-wait if we can't use the condition variable */ while (dmnsn_get_progress(progress) < prog); } else { while (dmnsn_get_progress(progress) < prog) { if (pthread_cond_wait(progress->cond, progress->mutex) != 0) { dmnsn_error(DMNSN_SEVERITY_LOW, "Couldn't wait on condition variable."); } } if (pthread_mutex_unlock(progress->mutex) != 0) { dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't unlock condition mutex."); } } } /* Start a new level of algorithmic nesting */ void dmnsn_new_progress_element(dmnsn_progress *progress, unsigned int total) { dmnsn_progress_element element = { .progress = 0, .total = total }; dmnsn_progress_wrlock(progress); dmnsn_array_push(progress->elements, &element); dmnsn_progress_unlock(progress); } /* Only the innermost loop needs to call this function - it handles the rest upon loop completion (progress == total) */ void dmnsn_increment_progress(dmnsn_progress *progress) { dmnsn_progress_element *element; size_t size; dmnsn_progress_wrlock(progress); size = dmnsn_array_size(progress->elements); element = dmnsn_array_at(progress->elements, size - 1); ++element->progress; /* Increment the last element */ while (element->progress >= element->total && size > 1) { /* As long as the last element is complete, pop it */ --size; dmnsn_array_resize(progress->elements, size); element = dmnsn_array_at(progress->elements, size - 1); ++element->progress; /* Increment the next element */ } dmnsn_progress_unlock(progress); if (pthread_mutex_lock(progress->mutex) != 0) { dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't lock condition mutex."); } if (pthread_cond_broadcast(progress->cond) != 0) { dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't signal condition variable."); } if (pthread_mutex_unlock(progress->mutex) != 0) { dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't unlock condition mutex."); } } /* Immediately set to 100% completion */ void dmnsn_done_progress(dmnsn_progress *progress) { dmnsn_progress_element *element; dmnsn_progress_wrlock(progress); dmnsn_array_resize(progress->elements, 1); element = dmnsn_array_at(progress->elements, 0); element->progress = element->total; dmnsn_progress_unlock(progress); if (pthread_mutex_lock(progress->mutex) != 0) { dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't lock condition mutex."); } if (pthread_cond_broadcast(progress->cond) != 0) { dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't signal condition variable."); } if (pthread_mutex_unlock(progress->mutex) != 0) { dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't unlock condition mutex."); } } /* Thread synchronization */ static void dmnsn_progress_rdlock(const dmnsn_progress *progress) { if (pthread_rwlock_rdlock(progress->rwlock) != 0) { dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't acquire read-lock."); } } static void dmnsn_progress_wrlock(dmnsn_progress *progress) { if (pthread_rwlock_wrlock(progress->rwlock) != 0) { dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't acquire write-lock."); } } static void dmnsn_progress_unlock(const dmnsn_progress *progress) { if (pthread_rwlock_unlock(progress->rwlock) != 0) { dmnsn_error(DMNSN_SEVERITY_MEDIUM, "Couldn't unlock read-write lock."); } }