--- apps/app_queue.c.orig 2007-02-26 05:31:13.000000000 +0300 +++ apps/app_queue.c 2007-02-26 05:31:06.000000000 +0300 @@ -99,7 +99,8 @@ QUEUE_STRATEGY_LEASTRECENT, QUEUE_STRATEGY_FEWESTCALLS, QUEUE_STRATEGY_RANDOM, - QUEUE_STRATEGY_RRMEMORY + QUEUE_STRATEGY_RRMEMORY, + QUEUE_STRATEGY_XRRMEMORY }; static struct strategy { @@ -112,6 +113,7 @@ { QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" }, { QUEUE_STRATEGY_RANDOM, "random" }, { QUEUE_STRATEGY_RRMEMORY, "rrmemory" }, + { QUEUE_STRATEGY_XRRMEMORY, "xrrmemory" }, }; #define DEFAULT_RETRY 5 @@ -238,6 +240,8 @@ static const char *pm_family = "/Queue/PersistentMembers"; /* The maximum length of each persistent member queue database entry */ #define PM_MAX_LEN 8192 +/* The maximum penalty in XRRMEMORY strategy */ +#define MAX_QUEUE_PENALTY 9 /*! \brief queues.conf [general] option */ static int queue_persistent_members = 0; @@ -287,8 +291,13 @@ int oldstatus; time_t lastcall; struct member *member; + int penalty; }; +struct call_member { + char interface[80]; + struct call_member *next; +}; struct queue_ent { struct call_queue *parent; /*!< What queue is our parent */ @@ -309,6 +318,8 @@ time_t expire; /*!< When this entry should expire (time out of queue) */ struct ast_channel *chan; /*!< Our channel */ struct queue_ent *next; /*!< The next queue entry */ + struct call_member *called_members; /*!< The calling members entry */ + int last_penalty; /*!< Last penalty in XRRMEMORY strategy */ }; struct member { @@ -391,6 +402,8 @@ /* Queue strategy things */ int rrpos; /*!< Round Robin - position */ + int xrrpos[MAX_QUEUE_PENALTY+1]; /*!< Extended Round Robin - positions */ + unsigned int xwrapped[MAX_QUEUE_PENALTY+1]; /*!< Extended Round Robin - positions */ int memberdelay; /*!< Seconds to delay connecting member to caller */ int autofill; /*!< Ignore the head call status and ring an available agent */ @@ -1681,6 +1694,21 @@ return 1; } +/*! \brief clear called members list for queue entry */ +static void clear_called_members(struct queue_ent *qe) +{ + struct call_member *call, *next; + for(call = qe->called_members; call; ) + { + next = call->next; + call->next = NULL; + free(call); + call = next; + } + qe->last_penalty = 0; + qe->called_members = NULL; +} + /*! \brief find the entry with the best metric, or NULL */ static struct callattempt *find_best(struct callattempt *outgoing) { @@ -1697,12 +1725,68 @@ return best; } +static struct callattempt *find_best_penalty(struct callattempt *outgoing, int penalty) +{ + struct callattempt *best = NULL, *cur; + + for (cur = outgoing; cur; cur = cur->q_next) { + if (cur->stillgoing && /* Not already done */ + !cur->chan && /* Isn't already going */ + cur->penalty == penalty && + (!best || cur->metric < best->metric)) { /* We haven't found one yet, or it's better */ + best = cur; + } + } + + return best; +} + +static struct callattempt *find_best_not_called(struct callattempt *outgoing, struct queue_ent *qe) +{ + struct callattempt *best = NULL, *cur; + struct call_member *call; + int callfound; + + for(;;) { + for (cur = outgoing; cur; cur = cur->q_next) { + if (cur->stillgoing && /* Not already done */ + !cur->chan && /* Isn't already going */ + (!best || cur->metric < best->metric)) { /* We haven't found one yet, or it's better */ + + /* Scan queue entry called members */ + callfound = 0; + for(call = qe->called_members; call; call = call->next) { + if (!strcasecmp(cur->interface, call->interface)) { + callfound = 1; + break; + } + } + if(callfound) + continue; + best = cur; + } + } + if (!best && qe->called_members) { + clear_called_members(qe); + continue; + } + break; + } + + return best; +} + static int ring_one(struct queue_ent *qe, struct callattempt *outgoing, int *busies) { int ret = 0; while (ret == 0) { - struct callattempt *best = find_best(outgoing); + struct callattempt *best; + if(qe->parent->strategy == QUEUE_STRATEGY_XRRMEMORY) + best = find_best_not_called(outgoing, qe); + else + best = find_best(outgoing); + if (!best) { if (option_debug) ast_log(LOG_DEBUG, "Nobody left to try ringing in queue\n"); @@ -1719,6 +1803,16 @@ } } } else { + if (qe->parent->strategy == QUEUE_STRATEGY_XRRMEMORY) { + /* Add calling member to queue entry call list */ + struct call_member *call = ast_calloc(1, sizeof(*call)); + if (call) { + call->next = qe->called_members; + qe->called_members = call; + ast_copy_string(call->interface, best->interface, sizeof(best->interface)); + qe->last_penalty = best->penalty; + } + } /* Ring just the best channel */ if (option_debug) ast_log(LOG_DEBUG, "Trying '%s' with metric %d\n", best->interface, best->metric); @@ -1755,6 +1849,38 @@ return 0; } +static int store_next_penalty(struct queue_ent *qe, struct callattempt *outgoing) +{ + int penalty = qe->last_penalty; + int metric; + struct callattempt *best = find_best_penalty(outgoing, penalty); + + if (best) { + /* Ring just the best channel */ + metric = best->metric; + penalty = best->penalty; + if (penalty < 0 || penalty > MAX_QUEUE_PENALTY) + penalty = 0; + qe->parent->xrrpos[penalty] = metric % 1000; + if (option_debug) + ast_log(LOG_DEBUG, "Next is '%s' with metric %d and penalty %d\n", best->interface, metric, penalty); + } else { + if (penalty < 0 || penalty > MAX_QUEUE_PENALTY) + penalty = 0; + /* Just increment rrpos */ + if (qe->parent->xwrapped[penalty]) { + /* No more channels, start over */ + qe->parent->xrrpos[penalty] = 0; + } else { + /* Prioritize next entry */ + qe->parent->xrrpos[penalty]++; + } + } + qe->parent->xwrapped[penalty] = 0; + + return 0; +} + static int background_file(struct queue_ent *qe, struct ast_channel *chan, char *filename) { int res; @@ -2229,6 +2355,8 @@ if (qe->max_penalty && (mem->penalty > qe->max_penalty)) return -1; + int rrpos, penalty; + switch (q->strategy) { case QUEUE_STRATEGY_RINGALL: /* Everyone equal, except for penalty */ @@ -2257,6 +2385,22 @@ } tmp->metric += mem->penalty * 1000000; break; + case QUEUE_STRATEGY_XRRMEMORY: + rrpos = q->rrpos; + penalty = mem->penalty; + if(penalty < 0 || penalty > MAX_QUEUE_PENALTY) + penalty = 0; + rrpos = q->xrrpos[penalty]; + if (pos < rrpos) { + tmp->metric = 1000 + pos; + } else { + if (pos > rrpos) + q->xwrapped[penalty] = 1; + tmp->metric = pos; + } + tmp->metric += penalty * 1000000; + tmp->penalty = penalty; + break; case QUEUE_STRATEGY_RANDOM: tmp->metric = ast_random() % 1000; tmp->metric += mem->penalty * 1000000; @@ -2405,6 +2549,9 @@ if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY) { store_next(qe, outgoing); } + else if (qe->parent->strategy == QUEUE_STRATEGY_XRRMEMORY) { + store_next_penalty(qe, outgoing); + } ast_mutex_unlock(&qe->parent->lock); peer = lpeer ? lpeer->chan : NULL; if (!peer) { @@ -3272,7 +3419,7 @@ const char *user_priority; const char *max_penalty_str; int prio; - int max_penalty; + int max_penalty, is_max_penalty = 0; enum queue_result reason = QUEUE_UNKNOWN; /* whether to exit Queue application after the timeout hits */ int go_on = 0; @@ -3332,6 +3479,7 @@ if (option_debug) ast_log(LOG_DEBUG, "%s: Got max penalty %d from ${QUEUE_MAX_PENALTY}.\n", chan->name, max_penalty); + is_max_penalty = 1; } else { ast_log(LOG_WARNING, "${QUEUE_MAX_PENALTY}: Invalid value (%s), channel %s.\n", max_penalty_str, chan->name); @@ -3356,6 +3504,12 @@ qe.last_periodic_announce_time = time(NULL); qe.last_periodic_announce_sound = 0; if (!join_queue(args.queuename, &qe, &reason)) { + if(qe.parent->strategy == QUEUE_STRATEGY_XRRMEMORY) { + if (!is_max_penalty) + qe.max_penalty = MAX_QUEUE_PENALTY; + if (qe.max_penalty < 0 || qe.max_penalty > MAX_QUEUE_PENALTY) + qe.max_penalty = MAX_QUEUE_PENALTY; + } ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", S_OR(args.url, ""), S_OR(chan->cid.cid_num, "")); check_turns: @@ -3524,6 +3678,7 @@ set_queue_result(chan, reason); res = 0; } + clear_called_members(&qe); ast_module_user_remove(lu); return res;