/************************************************************************* * Copyright (C) 2009-2013 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 * * . * *************************************************************************/ /** * @file * Future objects. */ #include "dimension-internal.h" #include /** * Since C doesn't support anything like C++'s mutable, we fake it by casting * away the constness. This is okay since all valid dmnsn_futures live on the * heap, so cannot be const. */ #define MUTATE(future) ((dmnsn_future *)future) /* Allocate a new dmnsn_future* */ dmnsn_future * dmnsn_new_future(void) { dmnsn_future *future = dmnsn_malloc(sizeof(dmnsn_future)); future->progress = 0; future->total = 1; #if DMNSN_SPINLOCK dmnsn_initialize_spinlock(&future->spinlock, PTHREAD_PROCESS_PRIVATE); #endif dmnsn_initialize_mutex(&future->mutex); dmnsn_initialize_cond(&future->cond); future->min_wait = 1.0; return future; } /* Join the worker thread and delete `future'. */ int dmnsn_future_join(dmnsn_future *future) { void *ptr; int retval = -1; if (future) { /* Get the thread's return value */ dmnsn_join_thread(future->thread, &ptr); if (ptr && ptr != PTHREAD_CANCELED) { retval = *(int *)ptr; dmnsn_free(ptr); } /* Free the future object */ dmnsn_destroy_cond(&future->cond); dmnsn_destroy_mutex(&future->mutex); #if DMNSN_SPINLOCK dmnsn_destroy_spinlock(&future->spinlock); #endif dmnsn_free(future); } return retval; } /* Cancel a background thread */ void dmnsn_future_cancel(dmnsn_future *future) { pthread_cancel(future->thread); } /** Lock the appropriate lock for a future. */ static inline void dmnsn_lock_future(const dmnsn_future *future) { #if DMNSN_SPINLOCK dmnsn_lock_spinlock(&MUTATE(future)->spinlock); #else dmnsn_lock_mutex(&MUTATE(future)->mutex); #endif } /** Unlock the appropriate lock for a future. */ static inline void dmnsn_unlock_future(const dmnsn_future *future) { #if DMNSN_SPINLOCK dmnsn_unlock_spinlock(&MUTATE(future)->spinlock); #else dmnsn_unlock_mutex(&MUTATE(future)->mutex); #endif } /** Lock the future's mutex specifically, if we have the spinlock already. */ static inline void dmnsn_future_exchange_lock(const dmnsn_future *future) { #if DMNSN_SPINLOCK dmnsn_unlock_spinlock(&MUTATE(future)->spinlock); dmnsn_lock_mutex(&MUTATE(future)->mutex); #endif } /** Unlock the future's mutex specifically, if we have the spinlock already. */ static inline void dmnsn_future_exchange_unlock(const dmnsn_future *future) { #if DMNSN_SPINLOCK dmnsn_unlock_mutex(&MUTATE(future)->mutex); dmnsn_lock_spinlock(&MUTATE(future)->spinlock); #endif } /** Get the current progress, without locking anything. */ static inline double dmnsn_future_progress_unlocked(const dmnsn_future *future) { return (double)future->progress/future->total; } /* Get the current progress of the worker thread, in [0.0, 1.0] */ double dmnsn_future_progress(const dmnsn_future *future) { double progress; dmnsn_lock_future(future); progress = dmnsn_future_progress_unlocked(future); dmnsn_unlock_future(future); return progress; } /* Wait until dmnsn_future_progress(future) >= progress */ void dmnsn_future_wait(const dmnsn_future *future, double progress) { dmnsn_future *mfuture = MUTATE(future); dmnsn_lock_future(mfuture); while (dmnsn_future_progress_unlocked(mfuture) < progress) { /* Set the minimum waited-on value */ if (progress < mfuture->min_wait) mfuture->min_wait = progress; dmnsn_future_exchange_lock(mfuture); dmnsn_cond_wait(&mfuture->cond, &mfuture->mutex); dmnsn_future_exchange_unlock(mfuture); } dmnsn_unlock_future(mfuture); } /* Set the total number of loop iterations */ void dmnsn_future_set_total(dmnsn_future *future, size_t total) { dmnsn_lock_future(future); future->total = total; dmnsn_unlock_future(future); } /* Increment the number of completed loop iterations */ void dmnsn_future_increment(dmnsn_future *future) { /* Allow a thread to be canceled whenever it increments a future object -- this is close to PTHREAD_CANCEL_ASYNCHRONOUS but allows consistent state on cancellation */ pthread_testcancel(); dmnsn_lock_future(future); ++future->progress; if (dmnsn_future_progress_unlocked(future) >= future->min_wait) { future->min_wait = 1.0; dmnsn_future_exchange_lock(future); dmnsn_cond_broadcast(&future->cond); dmnsn_future_exchange_unlock(future); } dmnsn_unlock_future(future); } /* Immediately set to 100% completion */ void dmnsn_future_done(dmnsn_future *future) { dmnsn_lock_future(future); future->progress = future->total; dmnsn_future_exchange_lock(future); dmnsn_cond_broadcast(&future->cond); dmnsn_future_exchange_unlock(future); dmnsn_unlock_future(future); }