diff options
author | Shawn Routhier <sar@isc.org> | 2015-05-27 13:17:46 -0700 |
---|---|---|
committer | Shawn Routhier <sar@isc.org> | 2015-05-27 13:17:46 -0700 |
commit | 3933e2aa5183d4602c4fffee4f2ae0d9ca25df99 (patch) | |
tree | 20ec7a7ababacdb01dcf061eef90a7432c70eb04 /server/leasechain.c | |
parent | 4136513e59b6906e585eac0f0fd88706bdefcb5b (diff) | |
download | isc-dhcp-3933e2aa5183d4602c4fffee4f2ae0d9ca25df99.tar.gz |
[master] Add support for manipulating lease queues via a binary search.
Add support for manipluating the queues holding leaes for time
based events (free, backup, active, expired, abandoned and reserved)
via a binary search instead of walking through the linked list.
Diffstat (limited to 'server/leasechain.c')
-rw-r--r-- | server/leasechain.c | 695 |
1 files changed, 695 insertions, 0 deletions
diff --git a/server/leasechain.c b/server/leasechain.c new file mode 100644 index 00000000..d394526d --- /dev/null +++ b/server/leasechain.c @@ -0,0 +1,695 @@ +/* leasechain.c + + Additional support for in-memory database support */ + +/* + * Copyright (c) 2015 by Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + * + */ + +/*! \file server\leasechaing.c + * + * \page leasechain structures overview + * + * A brief description of the leasechain structures + * + * This file provides additional data structures for a leasecain to + * provide faster access to leases on the queues associated with a pool + * than a linear walk. Each pool has a set of queues: active, free, backup, + * expired and abandoned to track leases as they are handed out and returned. + * The original code use a simply linear list for each of those pools but + * this can present performance issues if the pool is large and the lists are + * long. + * This code adds an array on top of the list allowing us to search the list + * in a binary fashion instead of a linear walk. + * + * \verbatim + * leasechain + * +------------+ +-------+-------+-------+-------+ + * | lease list |--> | lease | lease | lease | lease |.... + * | start | | ptr | ptr | ptr | ptr | + * | end | +-------+-------+-------+-------+ + * | max | | | + * +------------+ V V + * +-------+ +-------+ + * | lease | | lease | + * | | | | + * | next |->| next |->NULL + * NULL<- | prev |<-| prev | + * +-------+ +-------+ + * + * The linked list is maintained in an ordered state. Inserting an entry is + * accomplished by doing a binary search on the array to find the proper place + * in the list and then updating the pointers in the linked list to include the + * new entry. The entry is added into the array by copying the remainder of + * the array to provide space for the new entry. + * Removing an entry is the reverse. + * The arrays for the queues will be pre-allocated but not all of them will be + * large enough to hold all of the leases. If additional space is required the + * array will be grown. + */ + +#include "dhcpd.h" + +#if defined (BINARY_LEASES) +/* Default number number of lease pointers to add to the leasechain array + * everytime it grows beyond the current size + */ +#define LC_GROWTH_DELTA 256 + +/*! + * + * \brief Check if leasechain isn't empty + * + * \param lc The leasechain to check + * + * \return 1 if leasechain isn't empty + */ +int +lc_not_empty( struct leasechain *lc ) { +#if defined (DEBUG_BINARY_LEASES) + log_debug("LC empty check %s:%d", MDL); + INSIST(lc != NULL); +#endif + + return (lc->nelem > 0 ? 1 : 0); +} + +/*! + * + * \brief Get the first lease from a leasechain + * + * \param lc The leasechain to check + * + * \return A pointer to the first lease from a lease chain, or NULL if none found + */ +struct lease * +lc_get_first_lease(struct leasechain *lc) { +#if defined (DEBUG_BINARY_LEASES) + log_debug("LC Get first %s:%d", MDL); + INSIST(lc != NULL); + INSIST(lc->total >= lc->nelem); +#endif + + if (lc->nelem > 0) { + return (lc->list)[0]; + } + return (NULL); +} + +/*! + * + * \brief Get the next lease from the chain, based on the lease passed in. + * + * \param lc The leasechain to check + * \param lp The lease to start from + * + * \return The next lease in the ordered list after lp + */ +struct lease * +lc_get_next(struct leasechain *lc, struct lease *lp) { +#if defined (DEBUG_BINARY_LEASES) + log_debug("LC Get next %s:%d", MDL); + INSIST(lc != NULL); + INSIST(lp != NULL); +#endif + + return lp->next; +} + +/*! + * + * \brief Find the best position for inserting a lease + * + * Given a potential range of the array to insert the lease into this routine + * will recursively examine the range to find the proper place in which to + * insert the lease. + * + * \param lc The leasechain to add the lease to + * \param lp The lease to insert + * \param min The minium index of the potential range for insertion + * \param max The maximum index of the potential range for insertion + * + * \return The index of the array entry to insert the lease + */ +size_t +lc_binary_search_insert_point(struct leasechain *lc, + struct lease *lp, + size_t min, size_t max) +{ + size_t mid_index = ((max - min)/2) + min; + + if ((lc->list[mid_index]->sort_time > lp->sort_time) || + ((lc->list[mid_index]->sort_time == lp->sort_time) && + (lc->list[mid_index]->sort_tiebreaker > lp->sort_tiebreaker))) { + if (mid_index == min) { + /* insert in the min position, as sort_time is larger */ + return (min); + } + /* try again with lower half of list */ + return (lc_binary_search_insert_point(lc, lp, + min, mid_index - 1)); + } else if ((lc->list[mid_index]->sort_time < lp->sort_time) || + ((lc->list[mid_index]->sort_time == lp->sort_time) && + (lc->list[mid_index]->sort_tiebreaker < lp->sort_tiebreaker))) { + if (mid_index == max) { + /* insert in mid_index + 1 as sort_time is smaller */ + return (mid_index+1); + } + /* try again with upper half of list */ + return (lc_binary_search_insert_point(lc, lp, + mid_index + 1, max)); + } + + /* sort_time and sort_tiebreaker match, so insert in this position */ + return (mid_index); +} + +/*! + * + * \brief Find an exact match for a lease + * + * Given a potential range of the array to search this routine + * will recursively examine the range to find the proper lease + * + * \param lc The leasechain to check + * \param lp The lease to find + * \param min The minium index of the search range + * \param max The maximum index of the search range + * + * \return The index of the array entry for the lease, SIZE_MAX if the lease + * wasn't found + */ + +size_t +lc_binary_search_lease(struct leasechain *lc, + struct lease *lp, + size_t min, size_t max) +{ + size_t mid_index; + size_t i; + + if (max < min) { + /* lease not found */ + return (SIZE_MAX); + } + + mid_index = ((max - min)/2) + min; + + if ((lc->list[mid_index]->sort_time > lp->sort_time) || + ((lc->list[mid_index]->sort_time == lp->sort_time) && + (lc->list[mid_index]->sort_tiebreaker > lp->sort_tiebreaker))) { + if (mid_index == min) { + /* lease not found */ + return (SIZE_MAX); + } + /* try the lower half of the list */ + return (lc_binary_search_lease(lc, lp, min, mid_index - 1)); + } else if ((lc->list[mid_index]->sort_time < lp->sort_time) || + ((lc->list[mid_index]->sort_time == lp->sort_time) && + (lc->list[mid_index]->sort_tiebreaker < lp->sort_tiebreaker))) { + /* try the upper half of the list */ + return (lc_binary_search_lease(lc, lp, mid_index + 1, max)); + } + + /* + * As sort_time/sort_tiebreaker may not be unique in the list, once we + * find a match, we need to look before and after from this position + * for all matching sort_time/sort_tiebreaker until we find the exact + * lease or until no matching lease is found + */ + if (lp == lc->list[mid_index]) { + return (mid_index); + } + + /* Check out entries below the mid_index */ + if (mid_index > min) { + /* We will break out of the loop if we either go past the + * canddiates or hit the end of the range when i == min. As + * i is unsigned we can't check it in the for loop itself. + */ + for (i = mid_index - 1; ; i--) { + if (lp == lc->list[i]) { + return (i); + } + + /* Are we done with this range? */ + if ((i == min) || + ((lc->list[i]->sort_time != lp->sort_time) || + ((lc->list[i]->sort_time == lp->sort_time) && + (lc->list[i]->sort_tiebreaker != lp->sort_tiebreaker)))) { + break; + } + } + } + + /* Check out entries above the mid_index */ + if (mid_index < max) { + /* We will break out of the loop if we either go past the + * canddiates or hit the end of the range when i == max. + */ + for (i = mid_index + 1; i <= max; i++) { + if (lp == lc->list[i]) { + return (i); + } + + if ((lc->list[i]->sort_time != lp->sort_time) || + ((lc->list[i]->sort_time == lp->sort_time) && + (lc->list[i]->sort_tiebreaker != lp->sort_tiebreaker))) { + break; + } + } + } + + /* Lease not found */ + return (SIZE_MAX); +} + +/*! + * + * \brief Increase the size of the array for the lease chain + * + * \param lc The leasechain to expand + * + * If we are unable to allocate memory we log a fatal error. There's + * not much else to do as we can't figure out where to put the lease. + * + * If we can allocate memory we copy the old lease chain to the new + * lease chain and free the old. + */ +void +lc_grow_chain(struct leasechain *lc) { +#if defined (DEBUG_BINARY_LEASES) + log_debug("LC grow lease chain max was %zu, %s:%d", lc->total, MDL); +#endif + + void *p; + size_t temp_size; + + if (lc->growth == 0) + temp_size = lc->total + LC_GROWTH_DELTA; + else + temp_size = lc->total + lc->growth; + + /* try to allocate the memory */ + p = dmalloc(sizeof(struct lease *) * temp_size, MDL); + if (p == NULL) { + log_fatal("LC grow, unable to allocated memory %s:%d", MDL); + } + + /* Success, copy the lease chain and install the new one */ + if (lc->list != NULL) { + memcpy(p, lc->list, sizeof(struct lease *) * lc->nelem); + dfree(lc->list, MDL); + } + lc->list = (struct lease **) p; + lc->total = temp_size; + + return; +} + + +/*! + * + * \brief Link a lease to a lease chain position + * + * This function may increase the size of the lease chain if necessary and will + * probably need to move entries in the lease chain around. + * + * \param lc The leasechain to update + * \param lp The lease to insert + * \param n The position in which to insert the lease + * + */ +void +lc_link_lcp(struct leasechain *lc, struct lease *lp, size_t n) { +#if defined (DEBUG_BINARY_LEASES) + log_debug("LC link lcp %s:%d", MDL); + INSIST (lc != NULL); + INSIST (lp != NULL); +#endif + + if (lc->nelem == lc->total) { + lc_grow_chain(lc); + } + +#if defined (DEBUG_BINARY_LEASES) + log_debug("LC Link lcp position %zu, elem %zu, %s:%d", + n, lc->nelem, MDL); +#endif + + /* create room for the new pointer */ + if (n < lc->nelem) { +#if defined (DEBUG_BINARY_LEASES) + log_debug("LC link lcp moving position %zu, moving %zu. %s:%d", + n, (lc->nelem-n), MDL); +#endif + memmove(lc->list + n + 1, lc->list + n, + sizeof(struct lease *) * (lc->nelem-n)); + } + + /* clean any stale pointer info from this position before calling + * lease_reference as it won't work if pointer is not NULL + */ + lc->list[n] = NULL; + lease_reference(&(lc->list[n]), lp, MDL); + + lc->nelem++; + + lp->lc = lc; + + return; +} + +/*! + * + * \brief Insert the lease at the specified position in both the lease chain + * and the linked list + * + * This function may increase the size of the lease chain if necessary and will + * probably need to move entries in the lease chain around. + * \param lc The leasechain to update + * \param lp The lease to insert + * \param n The position in which to insert the lease + * + */ +void +lc_add_lease_pos(struct leasechain *lc, struct lease *lp, size_t pos) { +#if defined (DEBUG_BINARY_LEASES) + log_debug("LC Add lease position %zu, %s:%d", pos, MDL); + INSIST (lc != NULL); + INSIST (lp != NULL); +#endif + lc_link_lcp(lc, lp, pos); + +#if 0 + /* this shoudln't be necessary, if we still have pointers on + * the lease being inserted things are broken + */ + if (lp->prev) { + lease_dereference(&lp->prev, MDL); + } + if (lp->next) { + lease_dereference(&lp->next, MDL); + } +#endif + + /* not the first element? */ + if (pos > 0) { + if (lc->list[pos-1]->next) { + lease_dereference(&(lc->list[pos-1]->next), MDL); + } + lease_reference(&(lc->list[pos-1]->next), lp, MDL); + lease_reference(&lp->prev, lc->list[pos-1], MDL ); + } + + /* not the last element? we've already bumped nelem when linking + * into the lease chain so nelem should never be zero here */ + if (pos < (lc->nelem-1)) { + if (lc->list[pos+1]->prev) { + lease_dereference(&(lc->list[pos+1]->prev), MDL); + } + lease_reference(&(lc->list[pos+1]->prev), lp, MDL); + lease_reference(&lp->next, lc->list[pos+1], MDL); + } + + return; +} + +#ifdef POINTER_DEBUG +/*! + * + * \brief Debug only code, check the lease to verify it is sorted + * + * \param lc The leasechain to verify + * + * Calls log_fatal if the leasechain is not properly sorted + */ +void +lc_check_lc_sort_order(struct leasechain *lc) { + size_t i; + TIME t = 0; + long int tiebreak = 0; + + log_debug("LC check sort %s:%d", MDL); + for (i = 0; i < lc->nelem; i++ ) { + if ((lc->list[i]->sort_time < t) || + ((lc->list[i]->sort_time == t) && + (lc->list[i]->tiebreaker < tiebreaker))) { + if (i > 0) { + print_lease(lc->list[i-1]); + } + print_lease(lc->list[i]); + if (i < lc->nelem - 1) { + print_lease(lc->list[i+1]); + } + log_fatal("lc[%p] not sorted properly", lc); + } + + t = lc->list[i]->sort_time; + tiebreak = lc->list[i]->sort_tiebreaker; + } +} +#endif + +/*! + * + * \brief Add a lease into the sorted lease and lease chain + * The sort_time is set by the caller while the sort_tiebreaker is set here + * The value doesn't much matter as long as it prvoides a way to have different + * values in most of the leases. + * + * When choosing a value for tiebreak we choose: + * 0 for the first lease in the queue + * 0 if the lease is going to the end of the queue with a sort_time greater + * than that of the current last lease + * previous tiebreaker + 1 if it is going to the end of the queue with a + * sort_time equal to that of the current last lease + * random if none of the above fit + * + * During startup when we can take advantage of the fact that leases may already + * be sorted and so check the end of the list to see if we can simply add the + * lease to the end. + * + * \param lc The leasechain in which to insert the lease + * \param lp The lease to insert + * + */ +void +lc_add_sorted_lease(struct leasechain *lc, struct lease *lp) { + size_t pos; + +#if defined (DEBUG_BINARY_LEASES) + log_debug("LC add sorted %s:%d", MDL); + INSIST (lc != NULL); + INSIST (lp != NULL); +#endif + if (lc->nelem == 0) { + /* The first lease start with a tiebreak of 0 and add it at + * the first position */ + lp->sort_tiebreaker = 0; + + lc_add_lease_pos(lc, lp, 0); + /* log_debug("LC add sorted done, %s:%d", MDL); */ + + return; + } + + if (lp->sort_time > lc->list[lc->nelem-1]->sort_time) { + /* Adding to end of queue, with a different sort time */ + lp->sort_tiebreaker = 0; + pos = lc->nelem; + } else if (lp->sort_time == lc->list[lc->nelem-1]->sort_time) { + /* Adding to end of queue, with the same sort time */ + if (lc->list[lc->nelem-1]->sort_tiebreaker < LONG_MAX) + lp->sort_tiebreaker = + lc->list[lc->nelem-1]->sort_tiebreaker+1; + else + lp->sort_tiebreaker = LONG_MAX; + pos = lc->nelem; + } else { + /* Adding somewhere in the queue, just pick a random value */ + lp->sort_tiebreaker = random(); + pos = lc_binary_search_insert_point(lc, lp, 0, lc->nelem - 1); + } + + /* Finally add it to the queue */ + lc_add_lease_pos(lc, lp, pos); + +#if defined (DEBUG_BINARY_LEASES) + log_debug("LC add sorted complete position %zu, elements %zu, %s:%d", + pos, lc->nelem, MDL); +#endif + +#ifdef POINTER_DEBUG + lc_check_lc_sort_order(lc); +#endif +} + +/*! + * + * \brief Remove the Nth pointer from a leasechain structure and update counters. + * The pointers in the array will be moved to fill in the hole if necessary. + * + * \param lc The lease chain to update + * \param n the entry to remove from the lease chain + */ +void +lc_unlink_lcp(struct leasechain *lc, size_t n) { +#if defined (DEBUG_BINARY_LEASES) + log_debug("LC unlink lcp %s:%d", MDL); + + /* element index to remove must be less than the number of elements present */ + INSIST(n < lc->nelem); +#endif + + /* Clear the pointer from the lease back to the LC */ + lc->list[n]->lc = NULL; + + /* Clear the pointer from the LC to the lease */ + lease_dereference(&(lc->list[n]), MDL); + + /* memove unless we are removing the last element */ + if ((lc->nelem-1) > n) { + memmove(lc->list + n, lc->list + n + 1, + sizeof(struct lease *) * (lc->nelem-1-n)); + } + lc->nelem--; +} + +/*! + * + * \brief Remove a lease from a specific position. This will first unlink + * the lease from the lease chain and then update the linked list. + * + * \param lc The lease chain to update + * \param pos the entry to remove from the lease chain + */ +void +lc_unlink_lease_pos(struct leasechain *lc, size_t pos) +{ +#if defined (DEBUG_BINARY_LEASES) + INSIST(lc != NULL); +#endif + + struct lease *lp = NULL; + lease_reference(&lp, lc->list[pos], MDL); + + /* unlink from lease chain list */ + lc_unlink_lcp(lc, pos); + + /* unlink from the linked list */ + if (lp->next) { + lease_dereference(&lp->next->prev, MDL); + if (lp->prev) + lease_reference(&lp->next->prev, lp->prev, MDL); + } + if (lp->prev) { + lease_dereference(&lp->prev->next, MDL); + if (lp->next) + lease_reference(&lp->prev->next, lp->next, MDL); + lease_dereference(&lp->prev, MDL); + } + if (lp->next) { + lease_dereference(&lp->next, MDL); + } + lease_dereference(&lp, MDL); +} + +/*! + * + * \brief Find a lease in the lease chain and then remove it + * If we can't find the lease on the given lease chain it's a fatal error. + * + * \param lc The lease chain to update + * \param lp The lease to remove + */ +void +lc_unlink_lease(struct leasechain *lc, struct lease *lp) { +#if defined (DEBUG_BINARY_LEASES) + log_debug("LC unlink lease %s:%d", MDL); + + INSIST(lc != NULL); + INSIST(lc->list != NULL); + INSIST(lp != NULL ); + INSIST(lp->lc != NULL ); + INSIST(lp->lc == lc ); +#endif + + size_t pos = lc_binary_search_lease(lc, lp, 0, lc->nelem-1); + if (pos == SIZE_MAX) { + /* fatal, lease not found in leasechain */ + log_fatal("Lease with binding state %s not on its queue.", + (lp->binding_state < 1 || + lp->binding_state > FTS_LAST) + ? "unknown" + : binding_state_names[lp->binding_state - 1]); + } + + lc_unlink_lease_pos(lc, pos); +} + +#if defined (DEBUG_MEMORY_LEAKAGE) || \ + defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT) +/*! + * + * \brief Unlink all the leases in the lease chain and free the + * lease chain structure. The leases will be freed if and when + * any other references to them are cleared. + * + * \param lc the lease chain to clear + */ +void +lc_delete_all(struct leasechain *lc) { + size_t i; + + if (lc->nelem > 0) { + /* better to delete from the last one, to avoid the memmove */ + for (i = lc->nelem - 1; ; i--) { + lc_unlink_lease_pos(lc, i); + if (i == 0) { + break; + } + } + } + + /* and then get rid of the list itself */ + dfree(lc->list, MDL); + lc->list = NULL; + lc->total = 0; + lc->nelem = 0; +} +#endif + +/*! + * + * \brief Set the growth value. This is the number of elements to + * add to the array whenever it needs to grow. + * + * \param lc the lease chain to set up + * \param growth the growth value to use + */ +void +lc_init_growth(struct leasechain *lc, size_t growth) { + lc->growth = growth; +} + +#endif /* #if defined (BINARY_LEASES) */ |