windows-nt/Source/XPSP1/NT/net/qos/pclass/gpc/handfact.c

1560 lines
67 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*
* handfact.c
*
* author: John R. Douceur
* date: 26 January 1998
*
* This source file provides functions that implement assignment, release, and
* dereferencing operations with a 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.
*
* None of the code or comments in this file need to be understood by writers
* of client code; all explanatory information for clients is found in the
* associated header file, handfact.h.
*
*/
#include "gpcpre.h"
/*
* There are a number of aspects to the handle factory that must be understood
* by anyone wishing to modify this code. The description in this comment
* block is intended to provide a progressive overview of the handle factory.
*
* The basic system comprises a table of entries. Each assigned handle
* corresponds to a single, unique entry, as determined by the handle value
* modulo the table size. A handle is validated by comparing the handle value
* to the stored handle value in the entry. The unassigned entries are kept
* on a list; when an entry is released (or revoked), it is put on the tail of
* the list, and when an entry is needed for an assignment, it is taken from
* the head of the list.
*
* If there are no unassigned entries in the table when a new handle is
* requested, a new table of twice the size is allocated, and all assigned
* handles are relocated to the new table. All unassigned handles in the new
* table are placed on the unassigned list.
*
* As handles are released, the space required for handle entries is reduced.
* The table can be contracted into a table of half the size if no two assigned
* handles will yield the same entry address. Two handles which will yield
* the same entry address in a half-size table are called a pair, and the
* number of such pairs is tracked in the variable pair_count, which must be
* zero in order to contract the table. In order to minimize the number of
* pairs in the table, there are actually two lists of unassigned entries.
* Assigning an entry from the primary list will not increase the pair count,
* whereas assigning an entry from the secondary list will increase the pair
* count. Thus, assignments are always made from the primary list, if it is
* not empty.
*
* Assigned handles are also kept on a list, in order of assignment. If it
* becomes necessary to revoke a handle to make room for another, the oldest
* handle will be revoked, and it will be found at the head of this list.
*
*/
// This macro allocates an array of HFEntry structures. The size of the array
// is provided as an argument to the macro.
//
//#define NEW_HFEntry_array(array_size) \
// ((HFEntry *)malloc(array_size * sizeof(HFEntry)))
#define NEW_HFEntry_array(_a,array_size) \
GpcAllocMem(&_a,array_size * sizeof(HFEntry), HandleFactoryTag)
// This macro allocates an array of integers. The size of the array is
// provided as an argument to the macro.
//
//#define NEW_int_array(array_size) \
// ((int *)malloc(array_size * sizeof(int)))
#define NEW_int_array(_a,array_size) \
GpcAllocMem(&_a,array_size * sizeof(int), HandleFactoryTag)
/*
* Following are prototypes for static functions that are used internally by
* the handle factory routines.
*
*/
// This function doubles the size of the table in which the handles and pointers
// are stored. It is called by assign_HF_handle() when there is insufficient
// space in the table to assign the newly requested handle. If the expansion
// is successful, the function returns a value of 0. If the expansion fails
// (due, for example, to an inability to allocate memory), the function returns
// a value of 1.
//
int expand_HF_table(
HandleFactory *hfact);
// This function halves the size of the table in which the handles and pointers
// are stored. In order to reduce the amount of space consumed by the handle
// factory, this function is called called by release_HF_handle() and
// revoke_ancient_HF_handles() when they determine that the table can and should
// be contracted. The table can be contracted when pair_count == 0 and
// table_size > 2. However, the table may not be contracted then, because
// hysteresis is employed both to keep the mean assignment and release times
// constant and to minimize the allocation chatter of rapidly expanding and
// contracting the table. If the contraction is successful, the function
// returns a value of 0. If the contraction fails, the function returns a
// value of 1.
//
int contract_HF_table(
HandleFactory *hfact);
// This function revokes handles that are between handle_base and handle_base
// + 2 * HANDLE_RANGE_STEP - 1, inclusive. It then increments the value of
// handle_base by HANDLE_RANGE_STEP. Suspended handles will be revoked one
// revokation pass later than non-suspended handles.
//
void revoke_ancient_HF_handles(
HandleFactory *hfact);
// Every entry is on one of three lists, and the heads and tails of these lists
// are maintained in the entry_list[] array. The index of this array is given
// by the following three manifest constants.
//
#define LD_PRIMARY 0 // first list from which to select an entry to assign
#define LD_SECONDARY 1 // second list from which to select an entry to assign
#define LD_ASSIGNED 2 // list of assigned entries, in order of assignment age
// When the handle space is recycled, there is a danger of handle collisions.
// In order to substantially reduce the likelihood of these collisions, very
// old handles are revoked well before their recycling begins, to give the
// holders of these handles ample opportunity to notice that their handles
// have become invalid and to request new handles. Thus, handles are revoked
// when they become more than MAX_HANDLE_RANGE less than the currently generated
// handles. To reduce overhead, revokations are performed in batches of size
// determined by HANDLE_RANGE_STEP.
//
// A handle may be suspended by incrementing the handle value by
// HANDLE_RANGE_STEP. This causes the comparison in dereference_HF_handle() to
// fail, so the handle is judged to be invalid. To reinstate the handle, the
// handle value is decremented by HANDLE_RANGE_STEP, returning the handle to its
// original value. A handle that is suspended will be revoked one revokation
// pass later than it would have been if it hadn't been suspended.
//
#define HANDLE_RANGE_STEP ((HFHandle)0x20000000)
#define MAX_HANDLE_RANGE ((HFHandle)0x90000000)
// To keep the mean assignment and release times constant (and, indirectly, to
// minimize the allocation chatter of rapidly expanding and contracting the
// table), the table is not necessarily contracted as soon as possible.
// Hysteresis is employed to postpone the contraction until the computational
// cost of previous expansions and contractions is distributed over a sufficient
// number of assignment or release operations to maintain a constant cost per
// operation ratio. The cost of each expansion is equal to the overhead of
// memory allocation and deallocation plus the cost to split each entry into
// two entries. The cost of each contraction is equal to the overhead of
// memory allocation and deallocation plus the cost to merge each pair of
// entries into one entry. The cost of memory allocation and deallocation is
// equal to ALLOCATION_COST times the mean cost of a single split or merge
// operation. This value was determined by empirical measurement.
//
#define ALLOCATION_COST 12
// 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)
{
// The table size is initially set to 2, and it will never be smaller.
hfact->table_size = 2;
// Allocate space for the initial table.
NEW_HFEntry_array(hfact->entries,hfact->table_size);
if (hfact->entries == 0)
{
// Memory could not be allocated for the array of entries created by
// the constructor. Therefore, we return an indication of failure to
// the client.
return 1;
}
hfact->verifier0 = 0; // the verifiers are initialized with the same value
hfact->verifier1 = 0; // the verifiers are initialized with the same value
hfact->handle_base = 0; // handles will start with 0
hfact->population = 0; // no handles initially assigned
hfact->pair_count = 0; // since no assigned handles, no pairs
hfact->hysteresis_debt = 0;
// Initialize the two entries that are initially allocated. Both are marked
// as unassigned; the larger value (2) is put on the secondary list, and the
// smaller value (1) on the secondary list. Record 0 contains an initial
// handle value of 2 instead of 0 because a handle value of 0 is reserved.
hfact->entries[0].handle = hfact->handle_base + hfact->table_size;
hfact->entries[0].next_handle = hfact->handle_base + hfact->table_size;
hfact->entries[0].reference = 0;
hfact->entries[0].next_entry = &hfact->entry_list[LD_SECONDARY];
hfact->entries[0].prev_entry = &hfact->entry_list[LD_SECONDARY];
hfact->entries[1].handle = hfact->handle_base + 1;
hfact->entries[1].next_handle = hfact->handle_base + 1;
hfact->entries[1].reference = 0;
hfact->entries[1].next_entry = &hfact->entry_list[LD_PRIMARY];
hfact->entries[1].prev_entry = &hfact->entry_list[LD_PRIMARY];
// Initialize the primary list. This list initially contains entry 1.
hfact->entry_list[LD_PRIMARY].handle = 0;
hfact->entry_list[LD_PRIMARY].next_handle = 0;
hfact->entry_list[LD_PRIMARY].reference = 0;
hfact->entry_list[LD_PRIMARY].next_entry = &hfact->entries[1];
hfact->entry_list[LD_PRIMARY].prev_entry = &hfact->entries[1];
// Initialize the secondary list. This list initially contains entry 0.
hfact->entry_list[LD_SECONDARY].handle = 0;
hfact->entry_list[LD_SECONDARY].next_handle = 0;
hfact->entry_list[LD_SECONDARY].reference = 0;
hfact->entry_list[LD_SECONDARY].next_entry = &hfact->entries[0];
hfact->entry_list[LD_SECONDARY].prev_entry = &hfact->entries[0];
// Initialize the assigned list. This list initially is empty.
hfact->entry_list[LD_ASSIGNED].handle = 0;
hfact->entry_list[LD_ASSIGNED].next_handle = 0;
hfact->entry_list[LD_ASSIGNED].reference = 0;
hfact->entry_list[LD_ASSIGNED].next_entry = &hfact->entry_list[LD_ASSIGNED];
hfact->entry_list[LD_ASSIGNED].prev_entry = &hfact->entry_list[LD_ASSIGNED];
// Reduce handle_base by HANDLE_RANGE_STEP so that suspended handles will
// not slip through revokation.
hfact->handle_base -= HANDLE_RANGE_STEP;
// return an indication of success to the client.
return 0;
}
// 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)
{
// Free the space consumed by the table of handles.
GpcFreeMem(hfact->entries, HandleFactoryTag);
}
// 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().
//
HFHandle
assign_HF_handle(
HandleFactory *hfact,
void *reference)
{
int list;
HFEntry *entry;
volatile HFEntry *seq_entry; // volatile to ensure sequencing
HFHandle handle;
HFHandle handle_range;
if (hfact->population >= hfact->table_size)
{
// All entries in the table are assigned, so it is necessary to
// increase the table size.
int expansion_failure = expand_HF_table(hfact);
if (expansion_failure)
{
//
// just fail
//
return 0;
#if 0
// Expanding the table failed, presumably due to inability to
// allocate sufficient memory. So, instead, we revoke the least-
// recently assigned handle. First, remove the entry from the
// assigned list and place it on the secondary list.
entry = hfact->entry_list[LD_ASSIGNED].next_entry;
entry->next_entry->prev_entry = &hfact->entry_list[LD_ASSIGNED];
hfact->entry_list[LD_ASSIGNED].next_entry = entry->next_entry;
entry->next_entry = &hfact->entry_list[LD_SECONDARY];
entry->prev_entry = hfact->entry_list[LD_SECONDARY].prev_entry;
hfact->entry_list[LD_SECONDARY].prev_entry->next_entry = entry;
hfact->entry_list[LD_SECONDARY].prev_entry = entry;
// Then, invalidate the handle. The order of the operations is
// important to correct multi-threaded operation.
seq_entry = entry;
seq_entry->handle = entry->next_handle; // first invalidate handle
seq_entry->reference = 0; // then clear reference
// Decrement the pair count and population, so that when they are
// incremented in the code below, they will have correct values.
hfact->pair_count--;
hfact->population--;
#endif
}
}
// At this point, there is at least one available entry. If there is any
// entry on the primary list, it should be selected.
list = LD_PRIMARY;
if (hfact->entry_list[LD_PRIMARY].next_entry ==
&hfact->entry_list[LD_PRIMARY])
{
// The primary list is empty, so we take from the secondary list. By
// definition, this will increase the pair count.
list = LD_SECONDARY;
hfact->pair_count++;
}
// Remove the entry from the head of the appropriate list and place it on
// the assigned list.
entry = hfact->entry_list[list].next_entry;
handle = entry->handle;
entry->next_entry->prev_entry = entry->prev_entry;
entry->prev_entry->next_entry = entry->next_entry;
entry->next_entry = &hfact->entry_list[LD_ASSIGNED];
entry->prev_entry = hfact->entry_list[LD_ASSIGNED].prev_entry;
hfact->entry_list[LD_ASSIGNED].prev_entry->next_entry = entry;
hfact->entry_list[LD_ASSIGNED].prev_entry = entry;
// Set the reference pointer to that provided as an argument.
entry->reference = reference;
// The next handle for this entry will be greater by the table size. It
// is important to set this value in this routine because unequal values of
// handle and next_handle indicate an assigned entry.
entry->next_handle = handle + hfact->table_size;
if (entry->next_handle == 0)
{
// The handle value has wrapped around back to zero; however, zero is
// a reserved value, so we instead set the next handle to the subsequent
// legal value, which is the table size.
entry->next_handle = hfact->table_size;
}
// The population has increased by one.
hfact->population++;
// We're being tricky with unsigned integer math here. We revoke ancient
// handles if the value of the handle we are currently issuing is greater
// than the handle base by more than MAX_HANDLE_RANGE, modulo the size of
// the handle space. The modulo is implicit.
handle_range = handle - hfact->handle_base;
if (handle_range > MAX_HANDLE_RANGE)
{
revoke_ancient_HF_handles(hfact);
}
// This assignment operation decreases the hysteresis debt.
if (hfact->hysteresis_debt > 0)
{
hfact->hysteresis_debt--;
}
// Return the newly assigned handle.
return handle;
}
// 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().
//
int
release_HF_handle(
HandleFactory *hfact,
HFHandle handle)
{
int entry_index;
HFEntry *entry;
HFEntry *other_entry;
int list;
HFHandle adjusted_next_handle;
HFHandle adjusted_other_next_handle;
volatile HFEntry *seq_entry; // volatile to ensure sequencing
// Compute the index of the entry by taking the handle value modulo the
// table size. Since the table size is a power of two, we can simply
// subtract one to produce a mask and then conjoin the mask with the
// handle value.
entry_index = handle & hfact->table_size - 1;
entry = &hfact->entries[entry_index];
if ((entry->handle != handle && entry->handle != handle + HANDLE_RANGE_STEP)
|| entry->handle == entry->next_handle)
{
// Either the indexed entry does not refer to the provided handle nor to
// the provided handle's suspension value, or the entry is unassigned.
// In any of these cases, abort and return an error code to the client.
return 1;
}
// The "other entry" is the entry that would have to be merged with the
// indexed entry if the table size were to be contracted in half.
other_entry = &hfact->entries[entry_index ^ hfact->table_size / 2];
if (other_entry->handle == other_entry->next_handle)
{
// We're being tricky with unsigned integer math here. Before comparing
// the two next handles, we subtract from each the value of handle_base,
// modulo the size of the handle space (the modulo is implicit). This
// allows the effective comparison of their logical acyclic values
// rather than their actual cyclic values.
adjusted_next_handle = entry->next_handle - hfact->handle_base;
adjusted_other_next_handle =
other_entry->next_handle - hfact->handle_base;
if (adjusted_other_next_handle < adjusted_next_handle)
{
// The other entry is unassigned and has a smaller handle value
// than the indexed entry. Thus, the other entry should be moved
// from the secondary list to the primary list, and the indexed
// entry should be placed on the secondary list.
other_entry->next_entry->prev_entry = other_entry->prev_entry;
other_entry->prev_entry->next_entry = other_entry->next_entry;
other_entry->next_entry = &hfact->entry_list[LD_PRIMARY];
other_entry->prev_entry = hfact->entry_list[LD_PRIMARY].prev_entry;
hfact->entry_list[LD_PRIMARY].prev_entry->next_entry = other_entry;
hfact->entry_list[LD_PRIMARY].prev_entry = other_entry;
list = LD_SECONDARY;
}
else
{
// The other entry is unassigned and has a larger handle value
// than the indexed entry. Thus, the indexed entry should be
// placed on the secondary list.
list = LD_PRIMARY;
}
}
else
{
// The other entry is assigned. Thus, the indexed entry should be
// placed on the secondary list. Also, since the two entries were
// both assigned, they formed a pair. Since we are releasing one of
// them, the pair count drops by one.
list = LD_SECONDARY;
hfact->pair_count--;
}
// Remove the entry from the assigned list and place it on the
// appropriate list.
entry->next_entry->prev_entry = entry->prev_entry;
entry->prev_entry->next_entry = entry->next_entry;
entry->next_entry = &hfact->entry_list[list];
entry->prev_entry = hfact->entry_list[list].prev_entry;
hfact->entry_list[list].prev_entry->next_entry = entry;
hfact->entry_list[list].prev_entry = entry;
// Invalidate the handle. The order of the operations is important to
// correct multi-threaded operation.
seq_entry = entry;
seq_entry->handle = entry->next_handle; // first invalidate handle
seq_entry->reference = 0; // then clear reference
// The population has decreased by one.
hfact->population--;
// This release operation decreases the hysteresis debt.
if (hfact->hysteresis_debt > 0)
{
hfact->hysteresis_debt--;
}
// To contract the table, there must be no pairs, because otherwise two
// assigned handles would yield the same entry index and thereby conflict.
// Furthermore, the table size must be greater than 2, because much of the
// handle factory code assumes that the table is at least of size 2. In
// addition to these strict requirements, hysteresis is employed both to
// keep the mean assignment and release times constant and to minimize the
// allocation chatter of rapidly expanding and contracting the table. Only
// if the hysteresis debt is zero will the table be contracted.
if (hfact->pair_count == 0 && hfact->table_size > 2 &&
hfact->hysteresis_debt == 0)
{
contract_HF_table(hfact);
// Note that we ignore the return code. If the contraction is
// unsuccessful, we just continue as usual. There is no real harm in
// not contracting the table, except that we consume more space than
// necessary.
}
// return an indication of success to the client.
return 0;
}
// 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)
{
int entry_index;
HFEntry *entry;
// Compute the index of the entry by taking the handle value modulo the
// table size. Since the table size is a power of two, we can simply
// subtract one to produce a mask and then conjoin the mask with the
// handle value.
entry_index = handle & hfact->table_size - 1;
entry = &hfact->entries[entry_index];
if (entry->handle != handle || entry->handle == entry->next_handle)
{
// Either the indexed entry does not refer to the provided handle, or
// the entry is unassigned. In either case, abort and return an error
// code to the client.
return 1;
}
// Suspend the handle.
entry->handle += HANDLE_RANGE_STEP;
// This suspension operation decreases the hysteresis debt.
if (hfact->hysteresis_debt > 0)
{
hfact->hysteresis_debt--;
}
// return an indication of success to the client.
return 0;
}
// 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)
{
int entry_index;
HFEntry *entry;
// Compute the index of the entry by taking the handle value modulo the
// table size. Since the table size is a power of two, we can simply
// subtract one to produce a mask and then conjoin the mask with the
// handle value.
entry_index = handle & hfact->table_size - 1;
entry = &hfact->entries[entry_index];
if (entry->handle != handle + HANDLE_RANGE_STEP ||
entry->handle == entry->next_handle)
{
// Either the indexed entry does not refer to the provided handle's
// suspension value, or the entry is unassigned. In either case, abort
// and return an error code to the client.
return 1;
}
// Reinstate the handle.
entry->handle -= HANDLE_RANGE_STEP;
// This reinstatement operation decreases the hysteresis debt.
if (hfact->hysteresis_debt > 0)
{
hfact->hysteresis_debt--;
}
// return an indication of success to the client.
return 0;
}
// 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 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)
{
HFHandle entry_handle;
void *reference;
//int verifier;
int entry_index;
/*volatile*/ HFEntry *entry; // volatile to ensure sequencing
// This loop spins until the verifier variables begin and end a pass of
// the loop with the same value. There is an extremely short sequence
// of instructions in the expand and contract routines that modifies the
// values of the entries and table_size variables, and these modifications
// are bracketed by increments of the verifier variables in the reverse
// order as they are here read. As long as the verifiers have the same
// value, then entries and table_size are in a consistent state. This loop
// should very rarely be executed more than once, since the modification in
// the other routines is so short.
do
{
//verifier = hfact->verifier1;
// Compute the index of the entry by taking the handle value modulo the
// table size. Since the table size is a power of two, we can simply
// subtract one to produce a mask and then conjoin the mask with the
// handle value.
entry_index = handle & hfact->table_size - 1;
entry = &hfact->entries[entry_index];
// Get local copies of the reference pointer and handle value. The
// order of the operations is important to correct multi-threaded
// operation.
reference = entry->reference; // first get reference
entry_handle = entry->handle; // then get handle to check validity
} while (0 /*verifier != hfact->verifier0*/);
if (entry_handle == handle)
{
// The stored handle matches the provided handle, so the latter is
// valid. We thus return the reference pointer.
return reference;
}
else
{
// The stored handle does not match the provided handle, so the latter
// is invalid. We thus return a null pointer.
return 0;
}
}
void *
dereference_HF_handle_with_cb(
HandleFactory *hfact,
HFHandle handle,
ULONG offset)
{
HFHandle entry_handle;
void *reference;
//int verifier;
int entry_index;
/*volatile*/ HFEntry *entry; // volatile to ensure sequencing
// This loop spins until the verifier variables begin and end a pass of
// the loop with the same value. There is an extremely short sequence
// of instructions in the expand and contract routines that modifies the
// values of the entries and table_size variables, and these modifications
// are bracketed by increments of the verifier variables in the reverse
// order as they are here read. As long as the verifiers have the same
// value, then entries and table_size are in a consistent state. This loop
// should very rarely be executed more than once, since the modification in
// the other routines is so short.
do
{
//verifier = hfact->verifier1;
// Compute the index of the entry by taking the handle value modulo the
// table size. Since the table size is a power of two, we can simply
// subtract one to produce a mask and then conjoin the mask with the
// handle value.
entry_index = handle & hfact->table_size - 1;
entry = &hfact->entries[entry_index];
// Get local copies of the reference pointer and handle value. The
// order of the operations is important to correct multi-threaded
// operation.
if ((entry->reference) && (entry->handle == handle)) {
ASSERT(((PCLASSIFICATION_BLOCK)entry->reference)->NumberOfElements > offset);
reference = (void *)((PCLASSIFICATION_BLOCK)entry->reference)->arpBlobBlock[offset];
TRACE(CLASSIFY, entry->reference, reference, "dereference_HF_handle_with_cb");
return reference;
} else {
reference = 0;
return 0;
}
} while (0 /*verifier != hfact->verifier0*/);
}
#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()
// and release_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)
{
int entry_count[3];
int list;
HFEntry *entry;
for (list = 0; list < 3; list++)
{
entry_count[list] = 0;
entry = &hfact->entry_list[list];
do
{
entry_count[list]++;
if (entry->next_entry->prev_entry != entry)
{
return 1;
}
entry = entry->next_entry;
} while (entry != &hfact->entry_list[list]);
entry_count[list]--;
}
if (entry_count[2] != hfact->population)
{
return 2;
}
if (entry_count[0] + entry_count[2] - 2 * hfact->pair_count !=
entry_count[1])
{
return 3;
}
if (entry_count[0] + entry_count[1] + entry_count[2] != hfact->table_size)
{
return 4;
}
return 0;
}
#endif /* _TEST_HANDFACT */
// This function doubles the size of the table in which the handles and pointers
// are stored. It is called by assign_HF_handle() when there is insufficient
// space in the table to assign the newly requested handle. If the expansion
// is successful, the function returns a value of 0. If the expansion fails
// (due, for example, to an inability to allocate memory), the function returns
// a value of 1.
//
int expand_HF_table(
HandleFactory *hfact)
{
int double_size;
HFEntry *new_entries;
HFEntry *old_entries;
HFEntry *old_entry;
HFEntry *low_entry;
HFEntry *high_entry;
HFEntry *assigned_entry;
HFEntry *secondary_entry;
HFEntry *other_entry;
HFHandle handle;
HFHandle next_handle;
HFHandle other_handle;
void *reference;
int other_entry_index;
int index;
// Expanded table is double the size of the old table.
double_size = hfact->table_size * 2;
// Allocate space for the expanded table.
NEW_HFEntry_array(new_entries,double_size);
if (new_entries == 0)
{
// Memory could not be allocated for the new array of entries.
// Therefore, we return an indication of failure.
return 1;
}
// Since we are doubling the table size, we will be treating one more bit
// of each handle as a bit of the entry index. The value of this bit
// determines the index of the entry in the new table. For each entry,
// we have to determine the value of this bit and relocate the entry to
// the indicated location.
for (index = 0; index < hfact->table_size; index++)
{
old_entry = &hfact->entries[index];
low_entry = &new_entries[index];
high_entry = &new_entries[hfact->table_size + index];
handle = old_entry->handle;
next_handle = old_entry->next_handle;
reference = old_entry->reference;
// One of the two entries in the new table that correspond to the
// indexed entry in the old table will have a next handle value equal
// to the next handle value of the entry in the old table, and one will
// have a handle value equal to the indexed entry's next handle plus
// the old table size.
other_handle = next_handle + hfact->table_size;
if (other_handle == 0)
{
// The handle value has wrapped around back to zero; however, zero
// is a reserved value, so we instead set the next handle to the
// subsequent legal value, which is the new table size.
other_handle = double_size;
}
if ((handle & hfact->table_size) == 0)
{
// The handle of the old entry has a zero in its next bit, so the
// old entry will be located in the lower half of the new table.
if ((next_handle & hfact->table_size) == 0)
{
// The next handle of the old entry has a zero in its next bit,
// so this value will be the next handle for the lower entry
// and the other next handle value will be the next handle
// value for the higher entry. The high entry handle is set
// equal to its next handle because it is unassigned.
high_entry->handle = other_handle;
high_entry->next_handle = other_handle;
low_entry->next_handle = next_handle;
}
else
{
// The next handle of the old entry has a zero in its next bit,
// so this value will be the next handle for the higher entry
// and the other next handle value will be the next handle
// value for the lower entry. The high entry handle is set
// equal to its next handle because it is unassigned.
high_entry->handle = next_handle;
high_entry->next_handle = next_handle;
low_entry->next_handle = other_handle;
}
// The high entry is unassigned, so set its reference to null.
// Copy the information from the old entry to the low entry.
// Remove the old entry from the assigned list, and replace it
// with the low entry.
high_entry->reference = 0;
low_entry->handle = handle;
low_entry->reference = reference;
old_entry->next_entry->prev_entry = low_entry;
old_entry->prev_entry->next_entry = low_entry;
low_entry->next_entry = old_entry->next_entry;
low_entry->prev_entry = old_entry->prev_entry;
}
else
{
// The handle of the old entry has a one in its next bit, so the
// old entry will be located in the higher half of the new table.
if ((next_handle & hfact->table_size) == 0)
{
// The next handle of the old entry has a zero in its next bit,
// so this value will be the next handle for the lower entry
// and the other next handle value will be the next handle
// value for the higher entry. The low entry handle is set
// equal to its next handle because it is unassigned.
high_entry->next_handle = other_handle;
low_entry->handle = next_handle;
low_entry->next_handle = next_handle;
}
else
{
// The next handle of the old entry has a zero in its next bit,
// so this value will be the next handle for the higher entry
// and the other next handle value will be the next handle
// value for the lower entry. The low entry handle is set
// equal to its next handle because it is unassigned.
high_entry->next_handle = next_handle;
low_entry->handle = other_handle;
low_entry->next_handle = other_handle;
}
// The low entry is unassigned, so set its reference to null.
// Copy the information from the old entry to the high entry.
// Remove the old entry from the assigned list, and replace it
// with the high entry.
low_entry->reference = 0;
high_entry->handle = handle;
high_entry->reference = reference;
old_entry->next_entry->prev_entry = high_entry;
old_entry->prev_entry->next_entry = high_entry;
high_entry->next_entry = old_entry->next_entry;
high_entry->prev_entry = old_entry->prev_entry;
}
}
// All of the unassigned entries in the new table will be placed on the
// secondary list. We loop through the assigned list and place the
// unassigned entry corresponding each assigned entry onto the secondary
// list. Doing the list assignment in this manner tends to approximately
// sort the secondary list according to handle value, since the assigned
// list is sorted according to assignment order, and this approximately
// correlates to the handle value.
assigned_entry = hfact->entry_list[LD_ASSIGNED].next_entry;
secondary_entry = &hfact->entry_list[LD_SECONDARY];
while (assigned_entry != &hfact->entry_list[LD_ASSIGNED])
{
other_entry_index =
assigned_entry->handle + hfact->table_size & double_size - 1;
other_entry = &new_entries[other_entry_index];
secondary_entry->next_entry = other_entry;
other_entry->prev_entry = secondary_entry;
secondary_entry = other_entry;
assigned_entry = assigned_entry->next_entry;
}
// Wrap up lists by connecting in tails.
secondary_entry->next_entry = &hfact->entry_list[LD_SECONDARY];
hfact->entry_list[LD_SECONDARY].prev_entry = secondary_entry;
// This expansion increases the hysteresis debt by the cost of one set of
// allocation and deallocation operations plus the cost of splitting each
// entry into two entries.
hfact->hysteresis_debt += ALLOCATION_COST + hfact->table_size;
// Save a pointer to the old entry table so that it can be deallocated.
old_entries = hfact->entries;
// Note that we have not modified the handle, next_handle, or reference
// fields of any entries in the old table. Therefore, any calls to the
// dereference_HF_handle() routine that may have been made by other threads
// during the above operations would have been performed successfully.
// We are now about to increase the table size and update the entries
// variable to point to the new table. These operations must be performed
// atomically in order for the dereference routine to perform correctly.
// We thus bracket the operations with increments to the verifier variables.
// When the verifiers have the same value, the table_size and entries
// variables are in a consistent state. This is checked by the dereference
// routine.
hfact->verifier0++; // begin critical section
hfact->entries = new_entries;
hfact->table_size = double_size;
hfact->verifier1 = hfact->verifier0; // end critical section
// Deallocate the old table.
GpcFreeMem(old_entries, handleFactoryTag);
// Since the new table was created by expanding a half-size table, the pair
// count must be zero.
hfact->pair_count = 0;
// return an indication of success.
return 0;
}
// This function halves the size of the table in which the handles and pointers
// are stored. In order to reduce the amount of space consumed by the handle
// factory, this function is called called by release_HF_handle() and
// revoke_ancient_HF_handles() when they determine that the table can and should
// be contracted. The table can be contracted when pair_count == 0 and
// table_size > 2. However, the table may not be contracted then, because
// hysteresis is employed both to keep the mean assignment and release times
// constant and to minimize the allocation chatter of rapidly expanding and
// contracting the table. If the contraction is successful, the function
// returns a value of 0. If the contraction fails, the function returns a
// value of 1.
//
int contract_HF_table(
HandleFactory *hfact)
{
HFEntry *new_entries;
HFEntry *old_entries;
int *list;
int half_size;
int quarter_size;
int index;
HFEntry *high_entry1;
HFEntry *high_entry0;
HFEntry *low_entry1;
HFEntry *low_entry0;
HFEntry *new_entry1;
HFEntry *new_entry0;
HFHandle adjusted_high_next_handle1;
HFHandle adjusted_low_next_handle1;
HFHandle next_handle1;
HFHandle adjusted_high_next_handle0;
HFHandle adjusted_low_next_handle0;
HFHandle next_handle0;
HFHandle adjusted_new_handle0;
HFHandle adjusted_new_handle1;
HFEntry *entry;
HFEntry *primary_entry;
HFEntry *secondary_entry;
// Contracted table is half the size of the old table.
half_size = hfact->table_size / 2;
quarter_size = half_size / 2;
// Allocate space for the contracted table.
NEW_HFEntry_array(new_entries,half_size);
if (new_entries == 0)
{
// Memory could not be allocated for the new array of entries, so we
// are ironically prevented from reducing the amount of memory that
// the handle factory is consuming. Therefore, we return an indication
// of failure.
return 1;
}
// Allocate space for auxiliary array of list indicators
NEW_int_array(list,half_size);
if (list == 0)
{
// Memory could not be allocated for the auxiliary array, so again we
// are ironically prevented from reducing the amount of memory that
// the handle factory is consuming. Therefore, we return an indication
// of failure. First, however, we must free the memory allocated for
// the new array of entries above.
GpcFreeMem(new_entries, handleFactoryTag);
return 1;
}
// Since we are halving the size of the table, it might seem reasonable to
// loop through each index of the new table and merge the two corresponding
// entries from the old table. This is in fact what the following routine
// does; however, it does it by looping through only half of the new indices
// and processing two merges for each index. It does this so that it can
// then examine the two new entries to determine on which list to place each
// of them.
for (index = 0; index < quarter_size; index++)
{
// We're looking at four entries at once. First we merge high_entry1
// and low_entry1, and then we independently merge high_entry0 and
// low_entry0. After the two merges, we examine the results jointly.
high_entry1 = &hfact->entries[half_size + quarter_size + index];
high_entry0 = &hfact->entries[half_size + index];
low_entry1 = &hfact->entries[quarter_size + index];
low_entry0 = &hfact->entries[index];
new_entry1 = &new_entries[quarter_size + index];
new_entry0 = &new_entries[index];
// When merging two entries, the next handle value for the combined
// entry is equal to the larger next handle value of the two, minus
// the new table size. However, the determination of which is larger
// must be made with respect to their logical acyclic values rather
// than their actual cyclic values, so we subtract from each the value
// of handle_base, modulo the size of the handle space. The modulo is
// implicit.
adjusted_high_next_handle1 =
high_entry1->next_handle - hfact->handle_base;
adjusted_low_next_handle1 =
low_entry1->next_handle - hfact->handle_base;
next_handle1 = __max(adjusted_high_next_handle1,
adjusted_low_next_handle1) + hfact->handle_base - half_size;
// Since handle 1 is -- by definition -- in either the second or fourth
// quarter of the table, there is no need to check for the reserved
// value of zero.
if (high_entry1->handle != high_entry1->next_handle)
{
// The high entry is assigned, so we copy its handle value and
// reference pointer. Also, we remove it from the assigned list
// and replace it with the new entry.
new_entry1->handle = high_entry1->handle;
new_entry1->reference = high_entry1->reference;
high_entry1->next_entry->prev_entry = new_entry1;
high_entry1->prev_entry->next_entry = new_entry1;
new_entry1->next_entry = high_entry1->next_entry;
new_entry1->prev_entry = high_entry1->prev_entry;
}
else if (low_entry1->handle != low_entry1->next_handle)
{
// The low entry is assigned, so we copy its handle value and
// reference pointer. Also, we remove it from the assigned list
// and replace it with the new entry.
new_entry1->handle = low_entry1->handle;
new_entry1->reference = low_entry1->reference;
low_entry1->next_entry->prev_entry = new_entry1;
low_entry1->prev_entry->next_entry = new_entry1;
new_entry1->next_entry = low_entry1->next_entry;
new_entry1->prev_entry = low_entry1->prev_entry;
}
else
{
// Neither entry is assigned, so we indicate an unassigned condition
// in the new entry.
new_entry1->handle = next_handle1;
new_entry1->reference = 0;
if (adjusted_high_next_handle1 < adjusted_low_next_handle1)
{
// The high entry next handle has a lesser value than the low
// entry next handle, so the high entry must be on the primary
// list. We remove it from the primary list and replace it
// with the new entry.
high_entry1->next_entry->prev_entry = new_entry1;
high_entry1->prev_entry->next_entry = new_entry1;
new_entry1->next_entry = high_entry1->next_entry;
new_entry1->prev_entry = high_entry1->prev_entry;
}
else
{
// The low entry next handle has a lesser value than the high
// entry next handle, so the low entry must be on the primary
// list. We remove it from the primary list and replace it
// with the new entry.
low_entry1->next_entry->prev_entry = new_entry1;
low_entry1->prev_entry->next_entry = new_entry1;
new_entry1->next_entry = low_entry1->next_entry;
new_entry1->prev_entry = low_entry1->prev_entry;
}
}
// Set the next handle for the new entry.
new_entry1->next_handle = next_handle1;
// When merging two entries, the next handle value for the combined
// entry is equal to the larger next handle value of the two, minus
// the new table size. However, the determination of which is larger
// must be made with respect to their logical acyclic values rather
// than their actual cyclic values, so we subtract from each the value
// of handle_base, modulo the size of the handle space. The modulo is
// implicit.
adjusted_high_next_handle0 =
high_entry0->next_handle - hfact->handle_base;
adjusted_low_next_handle0 =
low_entry0->next_handle - hfact->handle_base;
next_handle0 = __max(adjusted_high_next_handle0,
adjusted_low_next_handle0) + hfact->handle_base - half_size;
if (next_handle0 == 0)
{
// The handle value has wrapped around back to zero; however, zero
// is a reserved value, so we instead set the next handle to the
// subsequent legal value, which is the new table size.
next_handle0 = half_size;
}
if (high_entry0->handle != high_entry0->next_handle)
{
// The high entry is assigned, so we copy its handle value and
// reference pointer. Also, we remove it from the assigned list
// and replace it with the new entry.
new_entry0->handle = high_entry0->handle;
new_entry0->reference = high_entry0->reference;
high_entry0->next_entry->prev_entry = new_entry0;
high_entry0->prev_entry->next_entry = new_entry0;
new_entry0->next_entry = high_entry0->next_entry;
new_entry0->prev_entry = high_entry0->prev_entry;
}
else if (low_entry0->handle != low_entry0->next_handle)
{
// The low entry is assigned, so we copy its handle value and
// reference pointer. Also, we remove it from the assigned list
// and replace it with the new entry.
new_entry0->handle = low_entry0->handle;
new_entry0->reference = low_entry0->reference;
low_entry0->next_entry->prev_entry = new_entry0;
low_entry0->prev_entry->next_entry = new_entry0;
new_entry0->next_entry = low_entry0->next_entry;
new_entry0->prev_entry = low_entry0->prev_entry;
}
else
{
// Neither entry is assigned, so we indicate an unassigned condition
// in the new entry.
new_entry0->handle = next_handle0;
new_entry0->reference = 0;
if (adjusted_high_next_handle0 < adjusted_low_next_handle0)
{
// The high entry next handle has a lesser value than the low
// entry next handle, so the high entry must be on the primary
// list. We remove it from the primary list and replace it
// with the new entry.
high_entry0->next_entry->prev_entry = new_entry0;
high_entry0->prev_entry->next_entry = new_entry0;
new_entry0->next_entry = high_entry0->next_entry;
new_entry0->prev_entry = high_entry0->prev_entry;
}
else
{
// The low entry next handle has a lesser value than the high
// entry next handle, so the low entry must be on the primary
// list. We remove it from the primary list and replace it
// with the new entry.
low_entry0->next_entry->prev_entry = new_entry0;
low_entry0->prev_entry->next_entry = new_entry0;
new_entry0->next_entry = low_entry0->next_entry;
new_entry0->prev_entry = low_entry0->prev_entry;
}
}
// Set the next handle for the new entry.
new_entry0->next_handle = next_handle0;
// Now that we have merged high_entry1 and low_entry1 into new_entry1,
// and independently merged high_entry0 and low_entry0 into new_entry0,
// we examine the two new entries to determine on which list to place
// each of them. Note that we do not actually manipulate the lists in
// this portion of the code; we merely make decisions and record these
// decisions for the future.
if (new_entry0->handle == new_entry0->next_handle &&
new_entry1->handle == new_entry1->next_handle)
{
// Both new_entry0 and new_entry1 are unassigned, so one of them
// belongs on the primary list and the other on the secondary list.
// Which goes on which is determined by a comparison of their handle
// values. We're being tricky with unsigned integer math here.
// Before comparing the two handles, we subtract from each the value
// of handle_base, modulo the size of the handle space (the modulo
// is implicit). This allows the effective comparison of their
// logical acyclic values rather than their actual cyclic values.
adjusted_new_handle0 = new_entry0->handle - hfact->handle_base;
adjusted_new_handle1 = new_entry1->handle - hfact->handle_base;
if (adjusted_new_handle0 < adjusted_new_handle1)
{
// The handle value for new_entry0 is lower, so new_entry0
// belongs on the primary list and new_entry1 on the secondary
// list. We indicate this decision in the list array.
list[index] = LD_PRIMARY;
list[quarter_size + index] = LD_SECONDARY;
}
else
{
// The handle value for new_entry1 is lower, so new_entry1
// belongs on the primary list and new_entry0 on the secondary
// list. We indicate this decision in the list array.
list[index] = LD_SECONDARY;
list[quarter_size + index] = LD_PRIMARY;
}
}
else
{
// Either new_entry0 or new_entry1 (or both) is assigned, and it is
// therefore already on the assigned list. If one of the entries
// is not assigned, it belongs on the secondary list. We indicate
// this decision in both places of the list array, which is safe to
// do since the assigned entry's list indicator will never be
// examined.
list[index] = LD_SECONDARY;
list[quarter_size + index] = LD_SECONDARY;
}
if (new_entry0->handle != new_entry0->next_handle &&
new_entry1->handle != new_entry1->next_handle)
{
// Both new_entry0 and new_entry1 are assigned, so they form a pair.
// We thus increment the pair count. Note that we never set the
// pair count to zero above, but this was not necessary since the
// table could not be contracted unless the pair count was zero.
hfact->pair_count++;
}
}
// At this point, the table has been completely contracted except for the
// reassembly of the unassigned lists. In the code above, any entries that
// had previously been on the secondary list were merged with assigned
// entries, so they are no longer relevant. Only those entries that had
// previously been (and are still) on the primary list will still be
// unassigned. We now loop through the primary list and place each list
// element on the appropriate list, as indicated by the list array. Doing
// the list assignment in these two steps preserves the general order of
// the entries, which has some value since they will tend to be partially
// sorted.
entry = hfact->entry_list[LD_PRIMARY].next_entry;
primary_entry = &hfact->entry_list[LD_PRIMARY];
secondary_entry = &hfact->entry_list[LD_SECONDARY];
while (entry != &hfact->entry_list[LD_PRIMARY])
{
if (list[entry->handle & half_size - 1] == LD_PRIMARY)
{
// The list array indicates the primary list, so place the entry
// onto the primary list.
primary_entry->next_entry = entry;
entry->prev_entry = primary_entry;
primary_entry = entry;
}
else
{
// The list array indicates the secondary list, so place the entry
// onto the secondary list.
secondary_entry->next_entry = entry;
entry->prev_entry = secondary_entry;
secondary_entry = entry;
}
entry = entry->next_entry;
}
// Wrap up lists by connecting in tails.
primary_entry->next_entry = &hfact->entry_list[LD_PRIMARY];
hfact->entry_list[LD_PRIMARY].prev_entry = primary_entry;
secondary_entry->next_entry = &hfact->entry_list[LD_SECONDARY];
hfact->entry_list[LD_SECONDARY].prev_entry = secondary_entry;
// This contraction increases the hysteresis debt by the cost of one set of
// allocation and deallocation operations plus the cost of merging each
// pair of entries into a single entry.
hfact->hysteresis_debt += ALLOCATION_COST + half_size;
// Save a pointer to the old entry table so that it can be deallocated.
old_entries = hfact->entries;
// Note that we have not modified the handle, next_handle, or reference
// fields of any entries in the old table. Therefore, any calls to the
// dereference_HF_handle() routine that may have been made by other threads
// during the above operations would have been performed successfully.
// We are now about to decrease the table size and update the entries
// variable to point to the new table. These operations must be performed
// atomically in order for the dereference routine to perform correctly.
// We thus bracket the operations with increments to the verifier variables.
// When the verifiers have the same value, the table_size and entries
// variables are in a consistent state. This is checked by the dereference
// routine.
hfact->verifier0++; // begin critical section
hfact->table_size = half_size;
hfact->entries = new_entries;
hfact->verifier1 = hfact->verifier0; // end critical section
// Deallocate the old table and the auxiliary list indicator array.
GpcFreeMem(old_entries, handleFactoryTag);
GpcFreeMem(list, handleFactoryTag);
// return an indication of success.
return 0;
}
// This function revokes handles that are between handle_base and handle_base
// + 2 * HANDLE_RANGE_STEP - 1, inclusive. It then increments the value of
// handle_base by HANDLE_RANGE_STEP. Suspended handles will be revoked one
// revokation pass later than non-suspended handles.
//
void revoke_ancient_HF_handles(
HandleFactory *hfact)
{
HFHandle new_handle_base;
int half_size;
int index;
HFEntry *high_entry;
HFEntry *low_entry;
HFHandle adjusted_high_handle;
HFHandle adjusted_low_handle;
HFHandle adjusted_high_next_handle;
HFHandle adjusted_low_next_handle;
HFHandle handle;
volatile HFEntry *seq_entry; // volatile to ensure sequencing
// Compute new handle base.
new_handle_base = hfact->handle_base + HANDLE_RANGE_STEP;
// It might seem reasonable to loop through each index of the table and
// determine whether to revoke the handle of each entry. This is in fact
// what the following routine does; however, it does it by looping through
// only half of the indices and examining two entries for each index. It
// does this so that it can compare the two entries to determine on which
// list to place each of them.
half_size = hfact->table_size / 2;
for (index = 0; index < half_size; index++)
{
// We're looking at two entries at once.
high_entry = &hfact->entries[half_size + index];
low_entry = &hfact->entries[index];
// We're being tricky with unsigned integer math here. Before making
// comparisons on either handle, we subtract from it the value of
// handle_base, modulo the size of the handle space (the modulo is
// implicit). This allows the effective comparison of its logical
// acyclic value rather than its actual cyclic value.
adjusted_high_handle = high_entry->handle - hfact->handle_base;
adjusted_low_handle = low_entry->handle - hfact->handle_base;
if (adjusted_high_handle < 2 * HANDLE_RANGE_STEP ||
adjusted_low_handle < 2 * HANDLE_RANGE_STEP)
{
// At least one of the handles is less than twice HANDLE_RANGE_STEP
// more than the current handle base, so it will need to be updated.
// For the vast majority of cases, this test is expected to fail,
// and so all of the following work can be skipped.
if (high_entry->handle != high_entry->next_handle &&
low_entry->handle != low_entry->next_handle)
{
// Both of the entries are assigned, so, since at least one of
// them will be revoked, we will be losing one pair.
hfact->pair_count--;
}
if (high_entry->handle == high_entry->next_handle ||
adjusted_high_handle < 2 * HANDLE_RANGE_STEP)
{
// Either the high entry is unassigned or in need of revokation
// (after which it will be unassigned), so we remove it from
// whatever list it is on. We do this because all unassigned
// entries will be added to the appropriate list below.
high_entry->next_entry->prev_entry = high_entry->prev_entry;
high_entry->prev_entry->next_entry = high_entry->next_entry;
// Zeroing these pointers is unnecessary, but it will help to
// catch any mistakes made further down.
high_entry->next_entry = 0;
high_entry->prev_entry = 0;
}
if (adjusted_high_handle < 2 * HANDLE_RANGE_STEP)
{
// The high handle needs to be updated.
if (high_entry->handle != high_entry->next_handle)
{
// The high handle is assigned, so this updating will
// revoke the handle. Thus, we decrement the population.
hfact->population--;
}
// Compute the handle value as the maximum of (1) the next
// handle and (2) the new handle base plus the entry index.
// We're being tricky with unsigned integer math here. The
// maximum involves partial decomposition of the sums, from
// which we then subtract the value of handle_base, modulo the
// size of the handle space (the modulo is implicit). Thus,
// the maximum is taken with respect to the logical acyclic
// values rather than the actual cyclic values.
adjusted_high_next_handle =
high_entry->next_handle - hfact->handle_base;
handle = __max(adjusted_high_next_handle,
HANDLE_RANGE_STEP + half_size + index) + hfact->handle_base;
// Since the high handle is -- by definition -- in the upper
// half of the table, there is no need to check for the reserved
// value of zero.
// Update the handle value. Since this updating will invalidate
// the handle if it is currently assigned, the order of the
// operations is important to correct multi-threaded operation.
seq_entry = high_entry;
seq_entry->next_handle = handle;
seq_entry->handle = handle; // first invalidate handle
seq_entry->reference = 0; // then clear reference
}
if (low_entry->handle == low_entry->next_handle ||
adjusted_low_handle < 2 * HANDLE_RANGE_STEP)
{
// Either the low entry is unassigned or in need of revokation
// (after which it will be unassigned), so we remove it from
// whatever list it is on. We do this because all unassigned
// entries will be added to the appropriate list below.
low_entry->next_entry->prev_entry = low_entry->prev_entry;
low_entry->prev_entry->next_entry = low_entry->next_entry;
// Zeroing these pointers is unnecessary, but it will help to
// catch any mistakes made further down.
low_entry->next_entry = 0;
low_entry->prev_entry = 0;
}
if (adjusted_low_handle < 2 * HANDLE_RANGE_STEP)
{
// The low handle needs to be updated.
if (low_entry->handle != low_entry->next_handle)
{
// The low handle is assigned, so this updating will
// revoke the handle. Thus, we decrement the population.
hfact->population--;
}
// Compute the handle value as the maximum of (1) the next
// handle and (2) the new handle base plus the entry index.
// We're being tricky with unsigned integer math here. The
// maximum involves partial decomposition of the sums, from
// which we then subtract the value of handle_base, modulo the
// size of the handle space (the modulo is implicit). Thus,
// the maximum is taken with respect to the logical acyclic
// values rather than the actual cyclic values.
adjusted_low_next_handle =
low_entry->next_handle - hfact->handle_base;
handle = __max(adjusted_low_next_handle,
HANDLE_RANGE_STEP + index) + hfact->handle_base;
if (handle == 0)
{
// The handle value has wrapped around back to zero;
// however, zero is a reserved value, so we instead set the
// handle to the subsequent legal value, which is the table
// size.
handle = hfact->table_size;
}
// Update the handle value. Since this updating will invalidate
// the handle if it is currently assigned, the order of the
// operations is important to correct multi-threaded operation.
seq_entry = low_entry;
seq_entry->next_handle = handle;
seq_entry->handle = handle; // first invalidate handle
seq_entry->reference = 0; // then clear reference
}
if (high_entry->handle != high_entry->next_handle)
{
// The high entry is still assigned, so the low entry belongs
// on the secondary list.
low_entry->next_entry = &hfact->entry_list[LD_SECONDARY];
low_entry->prev_entry =
hfact->entry_list[LD_SECONDARY].prev_entry;
hfact->entry_list[LD_SECONDARY].prev_entry->next_entry =
low_entry;
hfact->entry_list[LD_SECONDARY].prev_entry = low_entry;
}
else if (low_entry->handle != low_entry->next_handle)
{
// The low entry is still assigned, so the high entry belongs
// on the secondary list.
high_entry->next_entry = &hfact->entry_list[LD_SECONDARY];
high_entry->prev_entry =
hfact->entry_list[LD_SECONDARY].prev_entry;
hfact->entry_list[LD_SECONDARY].prev_entry->next_entry =
high_entry;
hfact->entry_list[LD_SECONDARY].prev_entry = high_entry;
}
else
{
// Neither entry is still assigned, so one entry belongs on the
// primary list and one on the secondary list. Which goes on
// which is determined by a comparison of their handle values.
// We're being tricky with unsigned integer math here. Before
// comparing the two handles, we subtract from each the value
// of handle_base, modulo the size of the handle space (the
// modulo is implicit). This allows the effective comparison
// of their logical acyclic values rather than their actual
// cyclic values.
adjusted_high_next_handle =
high_entry->next_handle - new_handle_base;
adjusted_low_next_handle =
low_entry->next_handle - new_handle_base;
if (adjusted_low_next_handle < adjusted_high_next_handle)
{
// The handle value for the low entry is smaller, so it
// belongs on the primary list and the high entry on the
// secondary list.
high_entry->next_entry = &hfact->entry_list[LD_SECONDARY];
high_entry->prev_entry =
hfact->entry_list[LD_SECONDARY].prev_entry;
hfact->entry_list[LD_SECONDARY].prev_entry->next_entry =
high_entry;
hfact->entry_list[LD_SECONDARY].prev_entry = high_entry;
low_entry->next_entry = &hfact->entry_list[LD_PRIMARY];
low_entry->prev_entry =
hfact->entry_list[LD_PRIMARY].prev_entry;
hfact->entry_list[LD_PRIMARY].prev_entry->next_entry =
low_entry;
hfact->entry_list[LD_PRIMARY].prev_entry = low_entry;
}
else
{
// The handle value for the high entry is smaller, so it
// belongs on the primary list and the low entry on the
// secondary list.
high_entry->next_entry = &hfact->entry_list[LD_PRIMARY];
high_entry->prev_entry =
hfact->entry_list[LD_PRIMARY].prev_entry;
hfact->entry_list[LD_PRIMARY].prev_entry->next_entry =
high_entry;
hfact->entry_list[LD_PRIMARY].prev_entry = high_entry;
low_entry->next_entry = &hfact->entry_list[LD_SECONDARY];
low_entry->prev_entry =
hfact->entry_list[LD_SECONDARY].prev_entry;
hfact->entry_list[LD_SECONDARY].prev_entry->next_entry =
low_entry;
hfact->entry_list[LD_SECONDARY].prev_entry = low_entry;
}
}
}
}
// Update the handle base with the new handle base.
hfact->handle_base = new_handle_base;
// To contract the table, there must be no pairs, because otherwise two
// assigned handles would yield the same entry index and thereby conflict.
// Furthermore, the table size must be greater than 2, because much of the
// handle factory code assumes that the table is at least of size 2. In
// addition to these strict requirements, hysteresis is employed both to
// keep the mean assignment and release times constant and to minimize the
// allocation chatter of rapidly expanding and contracting the table. Only
// if the hysteresis debt is zero will the table be contracted.
if (hfact->pair_count == 0 && hfact->table_size > 2 &&
hfact->hysteresis_debt == 0)
{
contract_HF_table(hfact);
// Note that we ignore the return code. If the contraction is
// unsuccessful, we just continue as usual. There is no real harm in
// not contracting the table, except that we consume more space than
// necessary.
}
}