File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / libpdel / util / paction.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 23:25:53 2012 UTC (12 years, 4 months ago) by misho
Branches: libpdel, MAIN
CVS tags: v0_5_3, HEAD
libpdel


/*
 * Copyright (c) 2001-2002 Packet Design, LLC.
 * All rights reserved.
 * 
 * Subject to the following obligations and disclaimer of warranty,
 * use and redistribution of this software, in source or object code
 * forms, with or without modifications are expressly permitted by
 * Packet Design; provided, however, that:
 * 
 *    (i)  Any and all reproductions of the source or object code
 *         must include the copyright notice above and the following
 *         disclaimer of warranties; and
 *    (ii) No rights are granted, in any manner or form, to use
 *         Packet Design trademarks, including the mark "PACKET DESIGN"
 *         on advertising, endorsements, or otherwise except as such
 *         appears in the above copyright notice or in the software.
 * 
 * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
 * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 * OR NON-INFRINGEMENT.  PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
 * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
 * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
 * RELIABILITY OR OTHERWISE.  IN NO EVENT SHALL PACKET DESIGN BE
 * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
 * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
 * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
 * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Author: Archie Cobbs <archie@freebsd.org>
 */

#include <sys/types.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>

#include "structs/structs.h"
#include "structs/type/array.h"

#include "util/paction.h"
#include "util/typed_mem.h"

#include "debug/debug.h"

#define	PACTION_MTYPE		"paction"

/* Action structure */
struct paction {
	pthread_t		tid;		/* action thread */
	struct paction		**actionp;	/* user action reference */
	pthread_mutex_t		mutex;		/* action mutex */
	pthread_mutex_t		*umutex;	/* user mutex */
	paction_handler_t	*handler;	/* action handler */
#if PDEL_DEBUG
	int			mutex_count;
	int			umutex_count;
#endif
	paction_finish_t	*finish;	/* action finisher */
	void			*arg;		/* action argument */
	u_char			may_cancel;	/* ok to cancel action thread */
	u_char			canceled;	/* action was canceled */
};

/* Internal functions */
static void	*paction_main(void *arg);
static void	paction_cleanup(void *arg);

/*
 * Start an action.
 */
int
paction_start(struct paction **actionp, pthread_mutex_t *mutex,
	paction_handler_t *handler, paction_finish_t *finish, void *arg)
{
	struct paction *action;

	/* Check if action already in progress */
	if (*actionp != NULL) {
		errno = EBUSY;
		return (-1);
	}

	/* Create new action */
	if ((action = MALLOC(PACTION_MTYPE, sizeof(*action))) == NULL)
		return (-1);
	memset(action, 0, sizeof(*action));
	action->actionp = actionp;
	action->umutex = mutex;
	action->handler = handler;
	action->finish = finish;
	action->arg = arg;

	/* Create mutex */
	if ((errno = pthread_mutex_init(&action->mutex, NULL)) != 0) {
		FREE(PACTION_MTYPE, action);
		return (-1);
	}

	/* Spawn thread */
	if ((errno = pthread_create(&action->tid,
	    NULL, paction_main, action)) != 0) {
		pthread_mutex_destroy(&action->mutex);
		FREE(PACTION_MTYPE, action);
		return (-1);
	}
	pthread_detach(action->tid);

	/* Done */
	*actionp = action;
	return (0);
}

/*
 * Cancel an action.
 */
void
paction_cancel(struct paction **actionp)
{
	struct paction *action = *actionp;

	/* Allow NULL action */
	if (action == NULL)
		return;

	/* Lock action */
	MUTEX_LOCK(&action->mutex, action->mutex_count);

	/* Mark action as canceled; this should only happen once */
	assert(!action->canceled);
	action->canceled = 1;

	/* Invalidate user's reference */
	assert(action->actionp == actionp);
	*action->actionp = NULL;
	action->actionp = NULL;

	/*
	 * Don't cancel the thread before paction_main() starts, because
	 * then paction_cleanup() would never get invoked. Also don't
	 * pthread_cancel() the thread after the handler has completed,
	 * because we might cancel in the middle of the cleanup.
	 */
	if (action->may_cancel)
		pthread_cancel(action->tid);

	/* Unlock action */
	MUTEX_UNLOCK(&action->mutex, action->mutex_count);
}

/*
 * Action thread main entry point.
 */
static void *
paction_main(void *arg)
{
	struct paction *const action = arg;

	/* Cleanup when thread exits */
	pthread_cleanup_push(paction_cleanup, action);

	/* Begin allowing pthread_cancel()'s */
	assert(!action->may_cancel);
	action->may_cancel = 1;

	/* Handle race between paction_cancel() and paction_main() */
	if (action->canceled)			/* race condition ok */
		goto done;

	/* Invoke handler */
	(*action->handler)(action->arg);

done:;
	/* Stop allowing pthread_cancel()'s */
	MUTEX_LOCK(&action->mutex, action->mutex_count);
	action->may_cancel = 0;
	MUTEX_UNLOCK(&action->mutex, action->mutex_count);

	/* Consume any last-minute pthread_cancel() still pending */
	pthread_testcancel();

	/* Done */
	pthread_cleanup_pop(1);
	return (NULL);
}

/*
 * Action thread cleanup
 */
static void
paction_cleanup(void *arg)
{
	struct paction *const action = arg;

	/*
	 * Acquire the action mutex and then the user mutex. We must
	 * do it in this order to avoid referencing the user mutex
	 * after paction_cancel() has been called (because after that
	 * the user mutex may have been destroyed).
	 *
	 * However, because paction_cancel() also acquires the action
	 * mutex and it may be called with the user mutex already held,
	 * there is a possibility for deadlock due to reverse lock ordering.
	 * We avoid this by looping and yielding.
	 */
	while (1) {

		/* Lock the action */
		MUTEX_LOCK(&action->mutex, action->mutex_count);

		/* Check for cancellation */
		if (action->canceled) {
			action->umutex = NULL;
			goto canceled;
		}

		/* Try to lock the user mutex */
		if (action->umutex == NULL)
			break;
		MUTEX_TRYLOCK(action->umutex, action->umutex_count);
		if (errno == 0)
			break;
		assert(errno == EBUSY);

		/* User mutex is busy, so unlock the action and try again */
		MUTEX_UNLOCK(&action->mutex, action->mutex_count);

		/* Let other threads progress */
		usleep(10 * 1000);			/* 10 milliseconds */
	}

	/* Invalidate user reference */
	assert(action->actionp != NULL);
	*action->actionp = NULL;
	action->actionp = NULL;

canceled:
	/* Unlock action */
	MUTEX_UNLOCK(&action->mutex, action->mutex_count);

	/* Invoke finisher */
	(*action->finish)(action->arg, action->canceled);

	/* Release user mutex */
	if (action->umutex != NULL)
		MUTEX_UNLOCK(action->umutex, action->umutex_count);

	/* Destroy action */
	pthread_mutex_destroy(&action->mutex);
	FREE(PACTION_MTYPE, action);
}


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>