/* * See the file "COPYING" in the main distribution directory for copyright. */ #include #include #include #include #include #ifdef HAVE_MEMORY_H #include #endif #include #ifdef CQ_DEVELOPMENT #include #include #endif #include "cq.h" /* Priority to virtual bucket (int) */ #define PRI2VBUCKET(hp, p) ((int)((p) / (hp)->bwidth)) /* Priority to bucket (int) */ #define PRI2BUCKET(hp, p) \ ((int)fmod((p) / (hp)->bwidth, (double)((hp)->nbuckets))) /* Priority to bucket top (double) */ #define PRI2BUCKETTOP(hp, p) ((hp)->bwidth * (((p) / (hp)->bwidth) + 1.5)) /* True if bucket is in use */ #define BUCKETINUSE(bp) ((bp)->cookie != NULL) /* Private data */ struct cq_handle { int nbuckets; /* number of buckets */ int qlen; /* number of queued entries */ int max_qlen; /* max. number of queued entries */ int himark; /* high bucket threshold */ int lowmark; /* low bucket threshold */ int nextbucket; /* next bucket to check */ int noresize; /* don't resize while we're resizing */ double lastpri; /* last priority */ double ysize; /* length of a year */ double bwidth; /* width of each bucket */ double buckettop; /* priority of top of current bucket */ struct cq_bucket *buckets; /* array of buckets */ }; struct cq_bucket { double pri; void *cookie; struct cq_bucket *next; }; #ifdef DEBUG #ifdef CQ_DEVELOPMENT extern int debug; #else int debug = 0; #endif #endif static unsigned int memory_allocation = 0; static struct cq_bucket *free_list = 0; /* Forwards */ static int cq_resize(struct cq_handle *, int); static void cq_destroybuckets(struct cq_bucket *, int); #ifdef DEBUG static int cq_debugbucket(struct cq_handle *, struct cq_bucket *); void cq_debug(struct cq_handle *, int); #endif /* Initialize a calendar queue */ struct cq_handle * cq_init(register double ysize, register double placebo) { register struct cq_handle *hp; #ifdef DEBUG if (debug > 1) fprintf(stderr, "cq_init(%f)\n", ysize); #endif /* The year size be positive */ if (ysize <= 0.0) { errno = EINVAL; return (NULL); } /* Allocate handle */ hp = (struct cq_handle *)malloc(sizeof(*hp)); memory_allocation += sizeof(*hp); if (hp == NULL) return (NULL); memset(hp, 0, sizeof(*hp)); /* Initialize handle */ hp->ysize = ysize; hp->max_qlen = 0; /* Allocate initial buckets and finish handle initialization */ if (cq_resize(hp, 0) < 0) { free(hp); memory_allocation -= sizeof(*hp); return (NULL); } return (hp); } /* Returns zero on success, -1 on error (with errno set) */ int cq_enqueue(register struct cq_handle *hp, register double pri, register void *cookie) { register struct cq_bucket *bp, *bp2; #ifdef DEBUG register int q1, q2; register struct cq_bucket *buckethead; #endif #ifdef DEBUG if (debug > 1) fprintf(stderr, "cq_enqueue(%f)\n", pri); #endif /* The priority must be positive and the cookie non-null */ if (pri <= 0.0 || cookie == NULL) { errno = EINVAL; return (-1); } /* We might as well resize now if we're going to need to */ if (hp->qlen + 1 > hp->himark && cq_resize(hp, 1) < 0) return (-1); bp = hp->buckets + PRI2BUCKET(hp, pri); #ifdef DEBUG if (debug) { buckethead = bp; q1 = cq_debugbucket(hp, buckethead); } else { buckethead = NULL; q1 = 0; } #endif if (BUCKETINUSE(bp)) { /* Allocate chained bucket */ if (free_list) { bp2 = free_list; free_list = free_list->next; } else { bp2 = (struct cq_bucket *)malloc(sizeof(*bp2)); memory_allocation += sizeof(*bp2); if (bp2 == NULL) return (-1); } memset(bp2, 0, sizeof(*bp2)); if (pri < bp->pri) { /* Insert new bucket at head of queue */ *bp2 = *bp; bp->next = bp2; } else { /* Insert entry in order (fifo when pri's are equal) */ while (bp->next != NULL && pri >= bp->next->pri) bp = bp->next; bp2->next = bp->next; bp->next = bp2; bp = bp2; } } bp->pri = pri; bp->cookie = cookie; if (++hp->qlen > hp->max_qlen) hp->max_qlen = hp->qlen; #ifdef DEBUG if (debug) { q2 = cq_debugbucket(hp, buckethead); if (q1 + 1 != q2) { fprintf(stderr, "enqueue length wrong\n"); cq_dump(hp); abort(); } } #endif /* If new priority is old, we need to recalculate nextbucket */ if (hp->lastpri == 0.0 || hp->lastpri > pri) { hp->lastpri = pri; hp->nextbucket = PRI2BUCKET(hp, hp->lastpri); hp->buckettop = PRI2BUCKETTOP(hp, hp->lastpri); } #ifdef notdef if (debug) cq_debug(hp, 0); #endif return (0); } void * cq_dequeue(register struct cq_handle *hp, double pri) { register int n; register struct cq_bucket *bp, *bp2, *lowbp; register void *cookie; #ifdef DEBUG if (debug > 1) fprintf(stderr, "cq_dequeue(%f)\n", pri); #endif if (pri < hp->lastpri) /* For sure nothing to do. */ return (NULL); lowbp = NULL; for (n = hp->nbuckets, bp = hp->buckets + hp->nextbucket; n > 0; --n) { /* Check bucket if it contains an entry (in the current year) */ if (BUCKETINUSE(bp)) { if (bp->pri < hp->buckettop) { /* Check first entry in this bucket */ if (pri >= bp->pri) { cookie = bp->cookie; hp->lastpri = bp->pri; /* Shouldn't nextbucket now point here?? */ hp->nextbucket = PRI2BUCKET(hp, hp->lastpri); hp->buckettop = PRI2BUCKETTOP(hp, hp->lastpri); if (bp->next == NULL) { /* Zero out first entry */ bp->pri = 0.0; bp->cookie = NULL; } else { /* Update 1st entry with next */ bp2 = bp->next; *bp = *bp2; bp2->next = free_list; free_list = bp2; /* free(bp2); */ } --hp->qlen; if (hp->qlen < hp->lowmark) (void)cq_resize(hp, 0); #ifdef notdef if (debug) cq_debug(hp, 0); #endif return (cookie); } /* The first entry is in the current year * but comes after pri. This means we're * not going to find *any* subsequent entries * that come before pri. So we're done. */ hp->lastpri = bp->pri; hp->nextbucket = PRI2BUCKET(hp, hp->lastpri); hp->buckettop = PRI2BUCKETTOP(hp, hp->lastpri); return (NULL); #if 0 /* Search linked list */ /* Why is this necessary? Since the list * is sorted, and we already know that the * first entry has too high a priority, * none of the others can be ready to * dequeue, right?? */ for (bp2 = bp; (bp3 = bp2->next) != NULL; bp2 = bp3) { /* Don't look past end of bucket */ if (bp3->pri >= hp->buckettop) break; if (pri >= bp3->pri) { /* Unlink entry */ cookie = bp3->cookie; hp->lastpri = bp->pri; bp2->next = bp3->next; free(bp3); memory_allocation -= sizeof(*bp3); --hp->qlen; if (hp->qlen < hp->lowmark) (void)cq_resize(hp, 0); return (cookie); } } #endif } /* Keep track of lowest priority bucket */ if (lowbp == NULL || lowbp->pri > bp->pri) lowbp = bp; } /* Step to next bucket */ hp->buckettop += hp->bwidth; ++hp->nextbucket; if (hp->nextbucket < hp->nbuckets) ++bp; else { bp = hp->buckets; hp->nextbucket = 0; } } /* * If we got here, we checked all the buckets but came up * empty. This can happen when the queued priorities are * really sparse (e.g. when there is more than a year * between two adjacent entries). * * If there was at least one bucket in use, check to see * if it's the one we're looking for. Also, update nextbucket * (and buckettop) with this bucket. */ if (lowbp != NULL) { cookie = NULL; bp = lowbp; if (pri >= bp->pri) { cookie = bp->cookie; if (bp->next == NULL) { /* Zero out first entry */ bp->pri = 0.0; bp->cookie = NULL; } else { /* Update 1st entry with next */ bp2 = bp->next; *bp = *bp2; bp2->next = free_list; free_list = bp2; /* free(bp2); */ } --hp->qlen; /* If we resize, we don't need to update */ if (hp->qlen < hp->lowmark) { (void)cq_resize(hp, 0); return (cookie); } } hp->lastpri = lowbp->pri; hp->nextbucket = PRI2BUCKET(hp, hp->lastpri); hp->buckettop = PRI2BUCKETTOP(hp, hp->lastpri); if (cookie != NULL) return (cookie); } /* Checked all buckets */ return (NULL); } void * cq_remove(register struct cq_handle *hp, register double pri, register void *cookie) { register struct cq_bucket *bp, *bp2; /* The priority must be positive and the cookie non-null */ if (pri <= 0.0 || cookie == NULL) return (0); bp = hp->buckets + PRI2BUCKET(hp, pri); if (! BUCKETINUSE(bp)) return (0); for ( bp2 = 0; bp && cookie != bp->cookie; bp = bp->next ) { if ( pri < bp->pri ) return (0); bp2 = bp; } if ( ! bp ) return (0); /* Unlink entry */ if ( ! bp2 ) { /* Remove first element */ if ( ! bp->next ) { /* Zero out first entry */ bp->pri = 0.0; bp->cookie = NULL; } else { /* Update 1st entry with next */ bp2 = bp->next; *bp = *bp2; bp2->next = free_list; free_list = bp2; } } else { /* Remove not-first element */ bp2->next = bp->next; bp->next = free_list; free_list = bp; } --hp->qlen; if (hp->qlen < hp->lowmark) (void)cq_resize(hp, 0); /* buckettop etc. don't need to be updated, right? */ return cookie; } int cq_size(struct cq_handle *hp) { return hp->qlen; } int cq_max_size(struct cq_handle *hp) { return hp->max_qlen; } /* Return without doing anything if we fail to allocate a new bucket array */ static int cq_resize(register struct cq_handle *hp, register int grow) { register int n, nbuckets, oldnbuckets; register size_t size; register struct cq_bucket *bp, *bp2, *buckets, *oldbuckets; struct cq_handle savedhandle; if (hp->noresize) return (0); #ifdef DEBUG if (debug) cq_debug(hp, 0); #endif /* Save old bucket array */ oldnbuckets = hp->nbuckets; oldbuckets = hp->buckets; /* If no old buckets, we're initializing */ if (oldbuckets == NULL) nbuckets = 2; else if (grow) nbuckets = oldnbuckets * 2; else nbuckets = oldnbuckets / 2; /* XXX could check that nbuckets is a power of 2 */ size = sizeof(*buckets) * nbuckets; buckets = (struct cq_bucket *)malloc(size); memory_allocation += size; if (buckets == NULL) return (-1); memset(buckets, 0, size); /* Save a copy of the handle in case we have dynamic memory problems */ savedhandle = *hp; /* Install new bucket array */ hp->nbuckets = nbuckets; hp->buckets = buckets; /* Initialize other parameters */ hp->himark = hp->nbuckets * 1.5; hp->lowmark = (hp->nbuckets / 2) - 2; hp->bwidth = hp->ysize / (double)hp->nbuckets; /* Tell cq_enqueue() to update nextbucket and buckettop */ hp->lastpri = 0.0; #ifdef DEBUG if (debug > 1) fprintf(stderr, "buckets 0x%lx, nbuckets %d, bwidth %f, buckettop %f\n", (u_long)hp->buckets, hp->nbuckets, hp->bwidth, hp->buckettop); #endif /* We're done if we were just initializing */ if (oldbuckets == NULL) return (0); /* Insert entries from old bucket array into new one */ ++hp->noresize; hp->qlen = 0; for (bp = oldbuckets, n = oldnbuckets; n > 0; --n, ++bp) if (BUCKETINUSE(bp)) for (bp2 = bp; bp2 != NULL; bp2 = bp2->next) { if (cq_enqueue(hp, bp2->pri, bp2->cookie) < 0) { /* Bummer! */ *hp = savedhandle; /* hp->resize restored */ cq_destroybuckets(buckets, nbuckets); free(buckets); memory_allocation -= size; return (-1); } } --hp->noresize; cq_destroybuckets(oldbuckets, oldnbuckets); free(oldbuckets); memory_allocation -= sizeof(*buckets) * oldnbuckets; #ifdef DEBUG if (debug) cq_debug(hp, 0); #endif return (0); } static void cq_destroybuckets(register struct cq_bucket *buckets, register int n) { register struct cq_bucket *bp, *bp2, *bp3; for (bp = buckets; n > 0; --n, ++bp) { bp2 = bp->next; while (bp2 != NULL) { bp3 = bp2->next; bp2->next = free_list; free_list = bp2; /* free(bp2); */ bp2 = bp3; } } } /* Destroy a calendar queue */ void cq_destroy(register struct cq_handle *hp) { cq_destroybuckets(hp->buckets, hp->nbuckets); while (free_list) { struct cq_bucket *next_free = free_list->next; free(free_list); free_list = next_free; } memory_allocation -= sizeof(struct cq_bucket) * hp->nbuckets; free(hp->buckets); free(hp); memory_allocation -= sizeof(*hp); } unsigned int cq_memory_allocation(void) { return memory_allocation; } #ifdef DEBUG static int cq_debugbucket(register struct cq_handle *hp, register struct cq_bucket *buckets) { register int qlen; register struct cq_bucket *bp, *bp2; double pri; qlen = 0; pri = 0.0; for (bp = buckets; bp != NULL; bp = bp->next) { if (BUCKETINUSE(bp)) { ++qlen; bp2 = hp->buckets + PRI2BUCKET(hp, bp->pri); if (bp2 != buckets) { fprintf(stderr, "%f in wrong bucket! (off by %ld)\n", bp->pri, (long)(bp2 - buckets)); cq_dump(hp); abort(); } if (bp->pri < pri) { fprintf(stderr, "bad pri order %f < %f (qlen %d)\n", bp->pri, pri, qlen); cq_dump(hp); abort(); } pri = bp->pri; } } return (qlen); } void cq_debug(register struct cq_handle *hp, register int print) { register int n, qlen, xnextbucket, nextbucket; register struct cq_bucket *bp, *lowbp; register double xbuckettop, buckettop; qlen = 0; lowbp = NULL; bp = hp->buckets + hp->nextbucket; for (n = hp->nbuckets; n > 0; --n) { if (BUCKETINUSE(bp) && (lowbp == NULL || lowbp->pri > bp->pri)) lowbp = bp; qlen += cq_debugbucket(hp, bp); /* Step to next bucket */ ++bp; if (bp >= hp->buckets + hp->nbuckets) bp = hp->buckets; } if (lowbp != NULL) { /* We expect lastpri gt or eq to the lowest queued priority */ if (lowbp->pri < hp->lastpri) { fprintf(stderr, "lastpri wacked (%f < %f)\n", lowbp->pri, hp->lastpri); cq_dump(hp); abort(); } /* Must search for the next entry just as cq_dequeue() would */ nextbucket = hp->nextbucket; buckettop = hp->buckettop; bp = hp->buckets + nextbucket; for (n = hp->nbuckets; n > 0; --n) { if (BUCKETINUSE(bp) && bp->pri < buckettop) break; /* Step to next bucket */ ++bp; ++nextbucket; buckettop += hp->bwidth; if (bp >= hp->buckets + hp->nbuckets) { bp = hp->buckets; nextbucket = 0; } } xnextbucket = PRI2BUCKET(hp, lowbp->pri); if (xnextbucket != nextbucket) { fprintf(stderr, "nextbucket wacked (%d != %d)\n", xnextbucket, nextbucket); cq_dump(hp); abort(); } xbuckettop = PRI2BUCKETTOP(hp, lowbp->pri); if (xbuckettop != buckettop) { fprintf(stderr, "buckettop wacked (%f != %f)\n", xbuckettop, buckettop); cq_dump(hp); abort(); } } if (qlen != hp->qlen) { fprintf(stderr, "qlen wacked (%d != %d)\n", qlen, hp->qlen); cq_dump(hp); abort(); } } void cq_dump(register struct cq_handle *hp) { // ### FIXME register struct cq_bucket *bp, *bp2; register int n; fprintf(stderr, "\ncq_dump(): nbucket %d, qlen %d, nextbucket %d, lastpri %f\n", hp->nbuckets, hp->qlen, hp->nextbucket, hp->lastpri); fprintf(stderr, "\tysize %f, bwidth %f, buckettop %f\n", hp->ysize, hp->bwidth, hp->buckettop); bp = hp->buckets; for (n = 0, bp = hp->buckets; n < hp->nbuckets; ++n, ++bp) { fprintf(stderr, " %c %2d: %f (0x%lx)\n", (n == hp->nextbucket) ? '+' : ' ', n, bp->pri, (u_long)bp->cookie); for (bp2 = bp->next; bp2 != NULL; bp2 = bp2->next) fprintf(stderr, " %f (0x%lx)\n", bp2->pri, (u_long)bp2->cookie); } } #endif