/* * 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. } }