|
|
/*
* 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 "precomp.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)))
// 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)))
/*
* 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
// This manifest constant is used by the expand and contract routines to request
// access to a set of table_size and entries variables. It is subtracted from
// the appropriate sync variable. If there are ever more than SYNC_SUBTRAHEND
// threads simultaneously invoking dereference_HF_handle{), then the
// synchronization logic will break.
//
#define SYNC_SUBTRAHEND 1000000000
// 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) { int table_size; HFEntry *entries;
// The table size is initially set to 2, and it will never be smaller.
table_size = 2; // Allocate space for the initial table.
entries = NEW_HFEntry_array(table_size); if (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; } // Initially, both sets of the table_size and entries variables are set
// equal. They will match most of the time except during the very brief
// moments when the table size is changed during an expansion or
// contraction.
hfact->table_size[0] = table_size; hfact->entries[0] = entries; hfact->table_size[1] = table_size; hfact->entries[1] = entries; // The sync variables are initialized to zero. These variables are
// incremented by the dereference_HF_handle() routine to request access to
// the corresponding table_size and entries variables. They are massively
// decremented (by a value of SYNC_SUBTRAHEND) by the expansion and
// contraction routines to request permission to change the corresponding
// table_size and entries variables. If a sync variable is positive, then
// at least one thread using the dereference routine has access to the
// corresponding table_size and entries variables. If a sync variable
// equals -SYNC_SUBTRAHEND, then the expand or contract routine has access
// to the corresponding variables. A zero value means no one has requested
// access, and a negative value greater than -SYNC_SUBTRAHEND means that
// the expand or contract routine has requested access but has to wait
// for one or more dereference threads to finish access.
hfact->sync[0] = 0; hfact->sync[1] = 0; // Initially, the default variable set is set to zero. This is arbitrary;
// it could be set to one, instead.
hfact->varset = 0; 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.
entries[0].handle = hfact->handle_base + table_size; entries[0].next_handle = hfact->handle_base + table_size; entries[0].reference = 0; entries[0].next_entry = &hfact->entry_list[LD_SECONDARY]; entries[0].prev_entry = &hfact->entry_list[LD_SECONDARY]; entries[1].handle = hfact->handle_base + 1; entries[1].next_handle = hfact->handle_base + 1; entries[1].reference = 0; entries[1].next_entry = &hfact->entry_list[LD_PRIMARY]; 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 = &entries[1]; hfact->entry_list[LD_PRIMARY].prev_entry = &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 = &entries[0]; hfact->entry_list[LD_SECONDARY].prev_entry = &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.
free(hfact->entries[hfact->varset]); }
// 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 table_size; int list; HFEntry *entry; volatile HFEntry *seq_entry; // volatile to ensure sequencing
HFHandle handle; HFHandle handle_range;
table_size = hfact->table_size[hfact->varset]; if (hfact->population >= 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); // Update the local value of table_size to reflect the new value.
table_size = hfact->table_size[hfact->varset]; if (expansion_failure) { // 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--; } } // 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 + 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 = 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 table_size; HFEntry *entries; 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
table_size = hfact->table_size[hfact->varset]; entries = hfact->entries[hfact->varset]; // 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 & table_size - 1; entry = &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 = &entries[entry_index ^ 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 && 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 table_size; HFEntry *entries; int entry_index; HFEntry *entry;
table_size = hfact->table_size[hfact->varset]; entries = hfact->entries[hfact->varset]; // 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 & table_size - 1; entry = &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 table_size; HFEntry *entries; int entry_index; HFEntry *entry;
table_size = hfact->table_size[hfact->varset]; entries = hfact->entries[hfact->varset]; // 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 & table_size - 1; entry = &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 entry_index; volatile HFEntry *entry; // volatile to ensure sequencing
LONG sync; int varset; int loopcount = 0;
// This loop spins until one of the sync variables passes the interlocked
// increment with a non-negative value, indicating that the corresponding
// data values are valid. There is a very short sequence of instructions
// in the expand and contract routines that modifies the values of the
// entries and table_size variables and also frees memory, and these
// modifications are bracketed by massive interlocked changes to the
// associated sync variables. The loop should rarely be entered, since
// the modification in the other routines is so short. The loop should
// almost never be execute more than once, because this would require two
// invokations of expand or contract during this short function.
//
// Start with the default variable set.
// If we read hfact->varset at the same instant that another thread is
// writing it, we should get either the old value or the new value, either
// of which will work fine in the following code. We should never get a
// garbage value, but just to be safe, we clear all bits other than the
// LSB, to ensure that the value we use is valid.
varset = hfact->varset & 1; // Indicate intention to access table_size and entries.
sync = InterlockedIncrement(&hfact->sync[varset]); loopcount = 0;
while (sync < 0) { // We incremented the sync variable after the expand or contract
// routine massively decremented it, so we can not be sure that we
// have access to the table_size and entries variables. Thus, we
// indicate that we are no longer interested in accessing these
// variables.
InterlockedDecrement(&hfact->sync[varset]); // Since we didn't get access to the table_size and entries variables,
// we try accessing the other set.
varset = 1 - varset; sync = InterlockedIncrement(&hfact->sync[varset]); loopcount++;
}
if (loopcount > 2) { OutputDebugString(TEXT("Loopcount in deref was > 2 - how bizzare!\n")); DEBUGBREAK(); } // We incremented the sync variable before the expand or contract routine
// massively decremented it, so we have access to the table_size and
// entries variables.
//
// 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[varset] - 1; entry = &hfact->entries[varset][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
// Indicate that we're done with table_size and entries
InterlockedDecrement(&hfact->sync[varset]); 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; } }
#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 table_size; HFEntry *entries; int entry_count[3]; int list; HFEntry *entry;
table_size = hfact->table_size[hfact->varset]; entries = hfact->entries[hfact->varset]; 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] != 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 table_size; HFEntry *entries; 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; int varset; DWORD StartTick =0, EndTick = 0;
table_size = hfact->table_size[hfact->varset]; entries = hfact->entries[hfact->varset]; // Expanded table is double the size of the old table.
double_size = table_size * 2; // Allocate space for the expanded table.
new_entries = NEW_HFEntry_array(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 < table_size; index++) { old_entry = &entries[index]; low_entry = &new_entries[index]; high_entry = &new_entries[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 + 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 & 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 & 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 & 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 + 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 + table_size; // Save a pointer to the old entry table so that it can be deallocated.
old_entries = 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. We do this by first updating the
// alternate table_size and entries variables and then updating the standard
// ones. That way, there will always be one set that is correct, so that
// dereferences can proceed relatively unimpeded.
//
// Our local varset is initialized to the non-default set.
varset = 1 - hfact->varset; // Indicate that the non-default table_size and entries are becoming
// inaccessible.
InterlockedExchangeAdd(&hfact->sync[varset], -SYNC_SUBTRAHEND); // Wait until no dereferences to the non-default variables are in progress.
StartTick = GetTickCount();
while (hfact->sync[varset] > -SYNC_SUBTRAHEND) { // no-op or sleep
if ((GetTickCount() - StartTick) > 300000000) { OutputDebugString(TEXT("Expand 1\n")); DEBUGBREAK(); } } // Update non-default table_size and entries to new table.
hfact->entries[varset] = new_entries; hfact->table_size[varset] = double_size; // Indicate that non-default table_size and entries are usable again.
InterlockedExchangeAdd(&hfact->sync[varset], SYNC_SUBTRAHEND); // Make non-default table_size and entries the default set.
hfact->varset = varset; // Update our local varset so it again indicates the non-default set.
varset = 1 - varset; // Indicate that the non-default table_size and entries are becoming
// inaccessible.
InterlockedExchangeAdd(&hfact->sync[varset], -SYNC_SUBTRAHEND); // Wait until no dereferences to the non-default variables are in progress.
while (hfact->sync[varset] > -SYNC_SUBTRAHEND) { if ((GetTickCount() - StartTick) > 300000000) { OutputDebugString(TEXT("Expand 2\n")); DEBUGBREAK(); } // no-op or sleep
} // Update non-default table_size and entries to new table.
hfact->entries[varset] = new_entries; hfact->table_size[varset] = double_size; // Indicate that non-default table_size and entries are usable again.
InterlockedExchangeAdd(&hfact->sync[varset], SYNC_SUBTRAHEND); // Deallocate the old table.
free(old_entries); // 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) { int table_size; HFEntry *entries; 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; int varset; DWORD StartTick = 0;
table_size = hfact->table_size[hfact->varset]; entries = hfact->entries[hfact->varset]; // Contracted table is half the size of the old table.
half_size = table_size / 2; quarter_size = half_size / 2; // Allocate space for the contracted table.
new_entries = NEW_HFEntry_array(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
list = NEW_int_array(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.
free(new_entries); 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 = &entries[half_size + quarter_size + index]; high_entry0 = &entries[half_size + index]; low_entry1 = &entries[quarter_size + index]; low_entry0 = &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 = 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. We do this by first updating the
// alternate table_size and entries variables and then updating the standard
// ones. That way, there will always be one set that is correct, so that
// dereferences can proceed relatively unimpeded.
//
// Our local varset is initialized to the non-default set.
varset = 1 - hfact->varset; // Indicate that the non-default table_size and entries are becoming
// inaccessible.
InterlockedExchangeAdd(&hfact->sync[varset], -SYNC_SUBTRAHEND); // Wait until no dereferences to the non-default variables are in progress.
StartTick = GetTickCount();
while (hfact->sync[varset] > -SYNC_SUBTRAHEND) {
if ((GetTickCount() - StartTick) > 300000000) { OutputDebugString(TEXT("Contract 1\n")); DEBUGBREAK(); }
// no-op or sleep
} // Update non-default table_size and entries to new table.
hfact->table_size[varset] = half_size; hfact->entries[varset] = new_entries; // Indicate that non-default table_size and entries are usable again.
InterlockedExchangeAdd(&hfact->sync[varset], SYNC_SUBTRAHEND); // Make non-default table_size and entries the default set.
hfact->varset = varset; // Update our local varset so it again indicates the non-default set.
varset = 1 - varset; // Indicate that the non-default table_size and entries are becoming
// inaccessible.
InterlockedExchangeAdd(&hfact->sync[varset], -SYNC_SUBTRAHEND); // Wait until no dereferences to the non-default variables are in progress.
while (hfact->sync[varset] > -SYNC_SUBTRAHEND) { // no-op or sleep
if ((GetTickCount() - StartTick) > 300000000) { OutputDebugString(TEXT("Contract 2\n")); DEBUGBREAK(); } } // Update non-default table_size and entries to new table.
hfact->table_size[varset] = half_size; hfact->entries[varset] = new_entries; // Indicate that non-default table_size and entries are usable again.
InterlockedExchangeAdd(&hfact->sync[varset], SYNC_SUBTRAHEND); // Deallocate the old table and the auxiliary list indicator array.
free(old_entries); free(list); // 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) { int table_size; HFEntry *entries; 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
table_size = hfact->table_size[hfact->varset]; entries = hfact->entries[hfact->varset]; // 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 = table_size / 2; for (index = 0; index < half_size; index++) { // We're looking at two entries at once.
high_entry = &entries[half_size + index]; low_entry = &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 = 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 && 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.
} }
|