--- apps/app_queue.c.orig 2007-02-01 00:25:11.000000000 +0300 +++ apps/app_queue.c 2007-02-27 19:01:41.000000000 +0300 @@ -96,6 +96,7 @@ #define QUEUE_STRATEGY_FEWESTCALLS 3 #define QUEUE_STRATEGY_RANDOM 4 #define QUEUE_STRATEGY_RRMEMORY 5 +#define QUEUE_STRATEGY_XRRMEMORY 6 static struct strategy { int strategy; @@ -107,6 +108,7 @@ { QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" }, { QUEUE_STRATEGY_RANDOM, "random" }, { QUEUE_STRATEGY_RRMEMORY, "rrmemory" }, + { QUEUE_STRATEGY_XRRMEMORY, "xrrmemory" }, }; #define DEFAULT_RETRY 5 @@ -223,6 +225,8 @@ static const char *pm_family = "/Queue/PersistentMembers"; /* The maximum lengh of each persistent member queue database entry */ #define PM_MAX_LEN 2048 +/* The maximum penalty in XRRMEMORY strategy */ +#define MAX_QUEUE_PENALTY 9 /*! \brief queues.conf [general] option */ static int queue_persistent_members = 0; @@ -266,10 +270,15 @@ time_t lastcall; struct member *member; struct localuser *next; + int penalty; }; -LOCAL_USER_DECL; +struct call_member { + char interface[80]; + struct call_member *next; +}; +LOCAL_USER_DECL; struct queue_ent { struct call_queue *parent; /*!< What queue is our parent */ @@ -284,10 +293,13 @@ time_t last_pos; /*!< Last time we told the user their position */ int opos; /*!< Where we started in the queue */ int handled; /*!< Whether our call was handled */ + int max_penalty; /*!< Limit the members that can take this call to this penalty or lower */ time_t start; /*!< When we started holding */ 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 { @@ -364,6 +376,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 */ struct member *members; /*!< Head of the list of members */ @@ -810,7 +824,7 @@ if (q->strategy < 0) { ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n", val, q->name); - q->strategy = 0; + q->strategy = QUEUE_STRATEGY_RINGALL; } } else if (!strcasecmp(param, "joinempty")) { if (!strcasecmp(val, "strict")) @@ -1605,37 +1619,121 @@ return 1; } -static int ring_one(struct queue_ent *qe, struct localuser *outgoing, int *busies) +/*! \brief clear called members list for queue entry */ +static void clear_called_members(struct queue_ent *qe) { - struct localuser *cur; - struct localuser *best; - int bestmetric=0; + 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; +} - do { - best = NULL; - cur = outgoing; - while(cur) { +/*! \brief find the entry with the best metric, or NULL */ +static struct localuser *find_best(struct localuser *outgoing) +{ + struct localuser *best = NULL, *cur; + + for (cur = outgoing; cur; cur = cur->next) { if (cur->stillgoing && /* Not already done */ !cur->chan && /* Isn't already going */ - (!best || (cur->metric < bestmetric))) { /* We haven't found one yet, or it's better */ - bestmetric = cur->metric; + (!best || cur->metric < best->metric)) { /* We haven't found one yet, or it's better */ best = cur; } - cur = cur->next; } + + return best; +} + +static struct localuser *find_best_penalty(struct localuser *outgoing, int penalty) +{ + struct localuser *best = NULL, *cur; + + for (cur = outgoing; cur; cur = cur->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 localuser *find_best_not_called(struct localuser *outgoing, struct queue_ent *qe) +{ + struct localuser *best = NULL, *cur; + struct call_member *call; + int callfound; + + for(;;) { + for (cur = outgoing; cur; cur = cur->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 localuser *outgoing, int *busies) +{ + struct localuser *best; + + do { + if(qe->parent->strategy == QUEUE_STRATEGY_XRRMEMORY) + best = find_best_not_called(outgoing, qe); + else + best = find_best(outgoing); + if (best) { if (!qe->parent->strategy) { + struct localuser *cur; /* Ring everyone who shares this best metric (for ringall) */ - cur = outgoing; - while(cur) { - if (cur->stillgoing && !cur->chan && (cur->metric <= bestmetric)) { + for (cur = outgoing; cur; cur = cur->next) { + if (cur->stillgoing && !cur->chan && cur->metric <= best->metric) { if (option_debug) ast_log(LOG_DEBUG, "(Parallel) Trying '%s' with metric %d\n", cur->interface, cur->metric); ring_entry(qe, cur, busies); } - cur = cur->next; } } else { + if (qe->parent->strategy == QUEUE_STRATEGY_XRRMEMORY) { + /* Add calling member to queue entry call list */ + struct call_member *call = malloc(sizeof(*call)); + if (call) { + memset(call, 0, sizeof(*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); @@ -1653,21 +1751,8 @@ static int store_next(struct queue_ent *qe, struct localuser *outgoing) { - struct localuser *cur; - struct localuser *best; - int bestmetric=0; + struct localuser *best = find_best(outgoing); - best = NULL; - cur = outgoing; - while(cur) { - if (cur->stillgoing && /* Not already done */ - !cur->chan && /* Isn't already going */ - (!best || (cur->metric < bestmetric))) { /* We haven't found one yet, or it's better */ - bestmetric = cur->metric; - best = cur; - } - cur = cur->next; - } if (best) { /* Ring just the best channel */ if (option_debug) @@ -1684,6 +1769,39 @@ } } qe->parent->wrapped = 0; + + return 0; +} + +static int store_next_penalty(struct queue_ent *qe, struct localuser *outgoing) +{ + int penalty = qe->last_penalty; + int metric; + struct localuser *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; } @@ -2101,6 +2219,11 @@ static int calc_metric(struct call_queue *q, struct member *mem, int pos, struct queue_ent *qe, struct localuser *tmp) { + 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 */ @@ -2129,6 +2252,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 = rand() % 1000; tmp->metric += mem->penalty * 1000000; @@ -2254,7 +2393,7 @@ } /* Special case: If we ring everyone, go ahead and ring them, otherwise just calculate their metric for the appropriate strategy */ - calc_metric(qe->parent, cur, x++, qe, tmp); + if (!calc_metric(qe->parent, cur, x++, qe, tmp)) { /* Put them in the list of outgoing thingies... We're ready now. XXX If we're forcibly removed, these outgoing calls won't get hung up XXX */ @@ -2263,7 +2402,9 @@ /* If this line is up, don't try anybody else */ if (outgoing->chan && (outgoing->chan->_state == AST_STATE_UP)) break; - + } else { + free(tmp); + } cur = cur->next; } if (qe->expire && (!qe->parent->timeout || (qe->expire - now) <= qe->parent->timeout)) @@ -2278,6 +2419,8 @@ ast_mutex_lock(&qe->parent->lock); 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); if (lpeer) @@ -3017,6 +3160,8 @@ char *url = NULL; char *announceoverride = NULL; char *user_priority; + const char *max_penalty_str; + int max_penalty, is_max_penalty = 0; int prio; char *queuetimeoutstr = NULL; enum queue_result reason = QUEUE_UNKNOWN; @@ -3070,6 +3215,22 @@ prio = 0; } + /* Get the maximum penalty from the variable ${QUEUE_MAX_PENALTY} */ + if ((max_penalty_str = pbx_builtin_getvar_helper(chan, "QUEUE_MAX_PENALTY"))) { + if (sscanf(max_penalty_str, "%d", &max_penalty) == 1) { + 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); + max_penalty = 0; + } + } else { + max_penalty = 0; + } + if (options && (strchr(options, 'r'))) ringing = 1; @@ -3083,6 +3244,12 @@ qe.last_pos = 0; qe.last_periodic_announce_time = time(NULL); if (!join_queue(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(queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", url ? url : "", chan->cid.cid_num ? chan->cid.cid_num : ""); check_turns: @@ -3246,6 +3413,7 @@ set_queue_result(chan, reason); res = 0; } + clear_called_members(&qe); LOCAL_USER_REMOVE(u); return res; }