320 lines
14 KiB
C
320 lines
14 KiB
C
|
/*
|
||
|
* handfact.h
|
||
|
*
|
||
|
* author: John R. Douceur
|
||
|
* date: 26 January 1998
|
||
|
*
|
||
|
* This header file defines structures, function prototypes, and macros for
|
||
|
* the handle factory. The code is object-oriented C, transliterated from a
|
||
|
* C++ implementation.
|
||
|
*
|
||
|
* The handle factory is a component that generates and validates handles. It
|
||
|
* is intended to be used in a software module that provides client software
|
||
|
* modules with means to refer to information structures contained within the
|
||
|
* provider. While such a means could simply be a pointer, this would not
|
||
|
* enable the deletion of the information structures without explicitly
|
||
|
* notifying the clients of such deletion. Unlike pointers, the handles
|
||
|
* generated by the handle factory can be examined (by the handle factory)
|
||
|
* to determine their validity.
|
||
|
*
|
||
|
* Handles can be invalidated in one of two ways. The handle can be released
|
||
|
* by calling the release_HF_handle() function, indicating to the handle
|
||
|
* factory that the handle is no longer necessary and that future requests
|
||
|
* to dereference this handle should be met with a null pointer. Alternately,
|
||
|
* the handle can be revoked by the handle factory; this will happen unter two
|
||
|
* circumstances. If a large number of handles (more than four billion) are
|
||
|
* issued and subsequently released, it becomes necessary to reuse portions of
|
||
|
* the handle space for future assignments; under these circumstances, very
|
||
|
* old handles will be revoked well before this recycling occurs, to give the
|
||
|
* holders of those handles ample opportunity to notice that their handles
|
||
|
* have become invalid and to request new handles. The other situation in
|
||
|
* which revokation can occur is if the amount of available memory becomes
|
||
|
* too small to allocate additional space to expand the handle database; then,
|
||
|
* if the assignment of a new handle is requested, the least-recently-assigned
|
||
|
* handle will be revoked to make room for the new request.
|
||
|
*
|
||
|
* Use of the handle factory in a multi-threaded environment requires a lock.
|
||
|
* This lock must be taken by a single thread for the execution of either
|
||
|
* assign_HF_handle() or release_HF_handle(). Use of dereference_HF_handle()
|
||
|
* does not require taking a lock, since synchronization is handled internally
|
||
|
* through careful sequencing of read and write operations.
|
||
|
*
|
||
|
* Because this code is C, rather than C++, it is not possible to hide as
|
||
|
* much of the implementation from the client code as one might wish.
|
||
|
* Nonetheless, there is an attempt to isolate the client from some of the
|
||
|
* implementation details through the use of macros. Below is described each
|
||
|
* of the functions and macros necessary to use the handle factory.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#ifndef _INC_HANDFACT
|
||
|
|
||
|
#define _INC_HANDFACT
|
||
|
|
||
|
#ifdef __cplusplus
|
||
|
extern "C" {
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* There are two basic structures employed: the HFEntry and the HandleFactory.
|
||
|
* Ideally, these would be completely hidden from the client, but the size of
|
||
|
* the HandleFactory structure structure needs to be known by the client for
|
||
|
* allocation purposes, and this is most easily accomplished by declaring the
|
||
|
* structure itself here in the header file, which in turn requires declaring
|
||
|
* the HFEntry structure. It is strongly urged that the client not directly
|
||
|
* refer to any of the fields of either of these structures. To support the
|
||
|
* documentation of the accompanying rhizome.c file, these structures are
|
||
|
* annotated with internal comments, but these can be ignored by the reader
|
||
|
* who wishes only to understand how to write client code that makes use of
|
||
|
* the handle factory.
|
||
|
*
|
||
|
* The handles generated by the handle factory are of type HFHandle. This is
|
||
|
* typedefed to an unsigned int, but this fact can be ignored by the client,
|
||
|
* since it is an implementation detail.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
//#include <stdlib.h>
|
||
|
//#include <malloc.h>
|
||
|
|
||
|
// HFHandle is the type of the handles generated by the handle factory.
|
||
|
//
|
||
|
typedef unsigned int HFHandle;
|
||
|
|
||
|
struct _HFEntry;
|
||
|
|
||
|
typedef struct _HFEntry HFEntry;
|
||
|
|
||
|
struct _HFEntry
|
||
|
{
|
||
|
// This is the element in which each handle and its associated pointer are
|
||
|
// stored. If handle == next_handle, the entry is not assigned, and it is
|
||
|
// available for assignment to a pointer via the assign_HF_handle()
|
||
|
// function. If handle != next_handle, then the entry is assigned to the
|
||
|
// pointer in the reference field.
|
||
|
//
|
||
|
// Each entry is on one of three lists: the primary free list, the secondary
|
||
|
// free list, or the assigned list. Each of these lists is maintained via
|
||
|
// the next_entry and prev_entry pointers.
|
||
|
|
||
|
HFHandle handle; // value of handle
|
||
|
HFHandle next_handle; // next value given to handle when invalidated
|
||
|
void *reference; // pointer to which handle refers
|
||
|
HFEntry *next_entry; // pointer to next entry in list
|
||
|
HFEntry *prev_entry; // pointer to previous entry in list
|
||
|
};
|
||
|
|
||
|
struct _HandleFactory;
|
||
|
|
||
|
typedef struct _HandleFactory HandleFactory;
|
||
|
|
||
|
struct _HandleFactory
|
||
|
{
|
||
|
// This structure contains private member variables for the handle factory.
|
||
|
// The first four fields are marked volatile to insure that the operations
|
||
|
// performed on them occur in the specified sequence. The handle factory
|
||
|
// can operate in a multi-threaded environment without requiring that a
|
||
|
// lock be taken before calling dereference_HF_handle(), and this is
|
||
|
// accomplished by careful sequencing of the read and write operations on
|
||
|
// these four variables.
|
||
|
//
|
||
|
// The verifier variables are used to provide a simple synchronization
|
||
|
// mechanism. When the variables have the same value, then the table_size
|
||
|
// and entries variables are in a consistent state.
|
||
|
//
|
||
|
// The table that holds the handles can only be contracted (shrunk in half)
|
||
|
// when for each assigned handle in the lower half of the table, there is
|
||
|
// no assigned handle in the corresponding upper half of the table. The
|
||
|
// number of correspondences between the two table halves is given by
|
||
|
// pair_count.
|
||
|
|
||
|
volatile int table_size; // size of table for storing entries
|
||
|
HFEntry *volatile entries; // pointer to tables of entries
|
||
|
volatile int verifier0; // synchronization variable
|
||
|
volatile int verifier1; // synchronization variable
|
||
|
HFHandle handle_base; // rolling point of lowest handle value
|
||
|
int population; // number of handles currently assigned
|
||
|
int pair_count; // contractions can occur when pair_count == 0
|
||
|
int hysteresis_debt; // must be zero before contraction
|
||
|
HFEntry entry_list[3]; // array of all three entry lists
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* The client interface to the handle factory is provided by seven functions
|
||
|
* and one macro. It is expected that the provider will first instantiate a
|
||
|
* handle factory, either in the static data segment, on the stack, or on the
|
||
|
* heap. Then, the provider will assign handles to various pointers by
|
||
|
* calling assign_HF_handle(), which it will distribute to its clients. When
|
||
|
* the provider wishes to release these handles, it will do so by calling
|
||
|
* release_HF_handle(). Each time a client presents a handle to the provider,
|
||
|
* the provider can validate the handle and retrieve the associated pointer
|
||
|
* by calling dereference_HF_handle(). A client can temporarily suspend a
|
||
|
* handle by calling suspend_HF_handle(), after which it can either reinstate
|
||
|
* the handle by calling reinstate_HF_handle() or release the handle by calling
|
||
|
* release_HF_handle().
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
// A handle factory may be allocated in the static data segment or on the stack
|
||
|
// simply by declaring a variable of type HandleFactory. To allocate it on the
|
||
|
// heap, the following macro returns a pointer to a new HandleFactory structure.
|
||
|
// If this macro is used, a corresponding call to free() must be made to
|
||
|
// deallocate the structure from the heap.
|
||
|
//
|
||
|
#define NEW_HandleFactory(_h) GpcAllocMem(&(_h),\
|
||
|
sizeof(HandleFactory),\
|
||
|
HandleFactoryTag)
|
||
|
|
||
|
#define FreeHandleFactory(_h) GpcFreeMem((_h),\
|
||
|
HandleFactoryTag)
|
||
|
|
||
|
// Since this is not C++, the HandleFactory structure is not self-constructing;
|
||
|
// therefore, the following constructor code must be called on the HandleFactory
|
||
|
// structure after it is allocated. If the construction is successful, the
|
||
|
// function returns a value of 0. If the construction fails (due, for example,
|
||
|
// to an inability to allocate memory), the function returns a value of 1.
|
||
|
//
|
||
|
int
|
||
|
constructHandleFactory(
|
||
|
HandleFactory *hfact);
|
||
|
|
||
|
// Since this is not C++, the HandleFactory structure is not self-destructing;
|
||
|
// therefore, the following destructor code must be called on the HandleFactory
|
||
|
// structure before it is deallocated.
|
||
|
//
|
||
|
void
|
||
|
destructHandleFactory(
|
||
|
HandleFactory *hfact);
|
||
|
|
||
|
// This function generates a new handle value, associates the handle value with
|
||
|
// the provided reference pointer, and returns the handle value. Barring
|
||
|
// highly unusual circumstances, this handle will remain valid until it is
|
||
|
// explicitly released by a call to release_HF_handle(). However, there is no
|
||
|
// guarantee that the handle will persist for an arbitrary duration; it may
|
||
|
// become necessary for the handle factory to revoke the handle under some
|
||
|
// circumstances, particularly when the handle becomes very old or when memory
|
||
|
// becomes scarce.
|
||
|
//
|
||
|
// The assign_HF_handle() function will never return a handle value of zero.
|
||
|
// Thus, the client program is free to use a zero handle value as an escape
|
||
|
// indicator, if desired.
|
||
|
//
|
||
|
// In a multi-threaded environment, a single thread must take a lock prior to
|
||
|
// calling this function, and this must be the same lock taken before calling
|
||
|
// release_HF_handle(), suspend_HF_handle(), and reinstate_HF_handle().
|
||
|
//
|
||
|
HFHandle
|
||
|
assign_HF_handle(
|
||
|
HandleFactory *hfact,
|
||
|
void *reference);
|
||
|
|
||
|
// This function releases a handle, indicating that further attempts to
|
||
|
// dereference the handle should result in a null pointer value rather than the
|
||
|
// pointer value that was originally assigned to the handle. The handle factory
|
||
|
// checks the validity of the handle and returns a corresponding status code.
|
||
|
// If the handle is currently assigned, then it is released, and the function
|
||
|
// returns a value of 0. If the handle is not currently assigned, the function
|
||
|
// aborts and returns a value of 1.
|
||
|
//
|
||
|
// In a multi-threaded environment, a single thread must take a lock prior to
|
||
|
// calling this function, and this must be the same lock taken before calling
|
||
|
// assign_HF_handle(), suspend_HF_handle(), and reinstate_HF_handle().
|
||
|
//
|
||
|
int
|
||
|
release_HF_handle(
|
||
|
HandleFactory *hfact,
|
||
|
HFHandle handle);
|
||
|
|
||
|
// This function suspends a handle, indicating that further attempts to
|
||
|
// dereference the handle should result in a null pointer value rather than the
|
||
|
// pointer value that was originally assigned to the handle, unless and until
|
||
|
// reinstate_HF_handle() is called on the handle value. The handle factory
|
||
|
// checks the validity of the handle and returns a corresponding status code.
|
||
|
// If the handle is currently assigned and not suspended, then it is suspended,
|
||
|
// and the function returns a value of 0. If the handle is not currently
|
||
|
// assigned or has already been suspended, the function aborts and returns a
|
||
|
// value of 1.
|
||
|
//
|
||
|
// In a multi-threaded environment, a single thread must take a lock prior to
|
||
|
// calling this function, and this must be the same lock taken before calling
|
||
|
// assign_HF_handle(), release_HF_handle(), and reinstate_HF_handle().
|
||
|
//
|
||
|
int
|
||
|
suspend_HF_handle(
|
||
|
HandleFactory *hfact,
|
||
|
HFHandle handle);
|
||
|
|
||
|
// This function reinstates a suspended handle, indicating that further attempts
|
||
|
// to dereference the handle should result in the pointer value that was
|
||
|
// originally assigned to the handle, rather than the null pointer value to
|
||
|
// which a suspended handle dereferences. The handle factory checks the
|
||
|
// validity of the handle and returns a corresponding status code. If the handle
|
||
|
// is currently assigned and suspended, then it is reinstated, and the function
|
||
|
// returns a value of 0. If the handle is not currently assigned or is not
|
||
|
// suspended, the function aborts and returns a value of 1.
|
||
|
//
|
||
|
// In a multi-threaded environment, a single thread must take a lock prior to
|
||
|
// calling this function, and this must be the same lock taken before calling
|
||
|
// assign_HF_handle(), release_HF_handle(), and suspend_HF_handle().
|
||
|
//
|
||
|
int
|
||
|
reinstate_HF_handle(
|
||
|
HandleFactory *hfact,
|
||
|
HFHandle handle);
|
||
|
|
||
|
// This function validates a handle and returns either the associated pointer
|
||
|
// (if the handle is valid) or a null pointer value (if the handle is invalid).
|
||
|
// If the handle has not been released or suspended but a null value is
|
||
|
// returned, then the handle has been revoked by the handle factory. This is
|
||
|
// expected to be a highly unusual occurrence; however, since it can happen, any
|
||
|
// program that employs the handle factory must have some auxiliary mechanism
|
||
|
// for retrieving the desired pointer information. Once the pointer is
|
||
|
// retrieved through this (presumably expensive) auxiliary means, a new handle
|
||
|
// can be reassigned to the pointer by another call to assign_HF_handle().
|
||
|
//
|
||
|
// Even in a multi-threaded environment, it is not necessary to take a lock
|
||
|
// prior to calling this function. Careful sequencing of read and write
|
||
|
// operations inside the handle factory code obviates the need to explicitly
|
||
|
// lock the data structure for dereferencing handles.
|
||
|
//
|
||
|
void *
|
||
|
dereference_HF_handle(
|
||
|
HandleFactory *hfact,
|
||
|
HFHandle handle);
|
||
|
|
||
|
|
||
|
void *
|
||
|
dereference_HF_handle_with_cb(
|
||
|
HandleFactory *hfact,
|
||
|
HFHandle handle,
|
||
|
ULONG offset);
|
||
|
|
||
|
#ifdef _TEST_HANDFACT
|
||
|
|
||
|
// This is a test routine that simply verifies the internal valididy of the
|
||
|
// handle factory's data structures. By defining the constant _TEST_HANDFACT,
|
||
|
// this routine will be compiled and available to the client code. It can be
|
||
|
// called at any time, unless running in a multi-threaded environment, in which
|
||
|
// case the caller must first take the same lock used for assign_HF_handle(),
|
||
|
// release_HF_handle(), suspend_HF_handle(), and reinstate_HF_handle(). If the
|
||
|
// routine returns any value other than zero, then the internal lists of records
|
||
|
// are in an inconsistent state.
|
||
|
//
|
||
|
int
|
||
|
verify_HF_lists(
|
||
|
HandleFactory *hfact);
|
||
|
|
||
|
#if DBG
|
||
|
#define VERIFY_HF_HFACT(_hfact) ASSERT(verify_HF_lists(_hfact)==0)
|
||
|
#else
|
||
|
#define VERIFY_HF_HFACT(_hfact)
|
||
|
#endif
|
||
|
|
||
|
#endif /* _TEST_HANDFACT */
|
||
|
|
||
|
#ifdef __cplusplus
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#endif /* _INC_HANDFACT */
|