Blob Blame History Raw
/*
 * Copyright (c) 2020 Red Hat, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA. 
 *
 * $Id: //eng/uds-releases/jasper/src/uds/util/eventCount.h#1 $
 */

#ifndef EVENT_COUNT_H
#define EVENT_COUNT_H

#include "timeUtils.h"
#include "typeDefs.h"

/**
 * An EventCount is a lock-free equivalent of a condition variable.
 *
 * Using an EventCount, a lock-free producer/consumer can wait for a state
 * change (adding an item to an empty queue, for example) without spinning or
 * falling back on the use of mutex-based locks. Signalling is cheap when
 * there are no waiters (a memory fence), and preparing to wait is
 * also inexpensive (an atomic add instruction).
 *
 * A lock-free producer should call eventCountBroadcast() after any mutation
 * to the lock-free data structure that a consumer might be waiting on. The
 * consumers should poll for work like this:
 *
 *   for (;;) {
 *     // Fast path--no additional cost to consumer.
 *     if (lockFreeDequeue(&item)) {
 *       return item;
 *     }
 *     // Two-step wait: get current token and poll state, either cancelling
 *     // the wait or waiting for the token to be signalled.
 *     EventToken token = eventCountPrepare(ec);
 *     if (lockFreeDequeue(&item)) {
 *       eventCountCancel(ec, token);
 *       return item;
 *     }
 *     eventCountWait(ec, token, NULL);
 *     // State has changed, but must check condition again, so loop.
 *   }
 *
 * Once eventCountPrepare() is called, the caller should neither dally, sleep,
 * nor perform long-running or blocking actions before passing the token to
 * eventCountCancel() or eventCountWait(). The implementation is optimized for
 * a short polling window, and will not perform well if there are outstanding
 * tokens that have been signalled but not waited upon.
 **/

typedef struct eventCount EventCount;

typedef unsigned int EventToken;

/**
 * Allocate and initialize an EventCount.
 *
 * @param ecPtr  a pointer to hold the new EventCount
 **/
__attribute__((warn_unused_result))
int makeEventCount(EventCount **ecPtr);

/**
 * Free an EventCount. It must no longer be in use.
 *
 * @param ec  the EventCount to free
 **/
void freeEventCount(EventCount *ec);

/**
 * Wake all threads that are waiting for the next event.
 *
 * @param ec  the EventCount to signal
 **/
void eventCountBroadcast(EventCount *ec);

/**
 * Prepare to wait for the EventCount to change by capturing a token of its
 * current state. The caller MUST eventually either call eventCountWait() or
 * eventCountCancel() exactly once for each token obtained.
 *
 * @param ec  the EventCount on which to prepare to wait
 *
 * @return an EventToken to be passed to the next eventCountWait() call
 **/
EventToken eventCountPrepare(EventCount *ec)
  __attribute__((warn_unused_result));

/**
 * Cancel a wait token that has been prepared but not waited upon. This must
 * be called after eventCountPrepare() when eventCountWait() is not going to
 * be invoked on the token.
 *
 * @param ec     the EventCount from which a wait token was obtained
 * @param token  the wait token that will never be passed to eventCountWait()
 **/
void eventCountCancel(EventCount *ec, EventToken token);

/**
 * Check if the current event count state corresponds to the provided token,
 * and if it is, wait for a signal that the state has changed. If an optional
 * timeout is provided, the wait will terminate after the timeout has elapsed.
 * Timing out automatically cancels the wait token, so callers must not
 * attempt to cancel the token on timeout.
 *
 * @param ec       the EventCount on which to wait
 * @param token    the EventToken returned by eventCountPrepare()
 * @param timeout  either NULL or a relative timeout for the wait operation
 *
 * @return true if the state has already changed or if signalled, otherwise
 *         false if a timeout was provided and the wait timed out
 **/
bool eventCountWait(EventCount *ec, EventToken token, const RelTime *timeout);

#endif /* EVENT_COUNT_H */