--- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -1020,6 +1020,8 @@ /*! \brief queues.conf per-queue weight option */ static int use_weight = 0; +static int use_auto_weight = 0; +static int auto_weight_factor = 0; /*! \brief queues.conf [general] option */ static int autofill_default = 1; @@ -1132,6 +1134,7 @@ int linpos; /*!< If using linear strategy, what position are we at? */ int linwrapped; /*!< Is the linpos wrapped? */ time_t start; /*!< When we started holding */ + suseconds_t start_us; time_t expire; /*!< When this entry should expire (time out of queue) */ int cancel_answered_elsewhere; /*!< Whether we should force the CAE flag on this call (C) option*/ struct ast_channel *chan; /*!< Our channel */ @@ -2820,6 +2823,9 @@ ast_atomic_fetchadd_int(&use_weight, +1); } } + if (use_auto_weight && !use_weight) { + use_weight=1; + } /* Other cases will end up with the proper value for use_weight */ } else { update_realtime_members(q); @@ -3446,6 +3452,82 @@ return found; } +static int get_longest_waiting_caller( struct call_queue *q ) +{ + struct timeval timevalNow; + struct queue_ent *cur; + int max_wait = 0; + + gettimeofday( &timevalNow, NULL ); + + if (!q->head) //No callers? + return 0; + else + { +//Since callers may have entered the queue with priority, the head of the queue is not nescecarrily the longest waiting +//So we have to traverse the whole queue to find the longest waiting +//If you use weight and auto_weight together, the weight of the queue is multiplied by a factor and added to the wait time , giving them a head start of the other callers. + cur = q->head; + while (cur) { + int qwait = ((long)(timevalNow.tv_sec - cur->start)*1000) + ((long)(timevalNow.tv_usec - cur->start_us)/1000) + (auto_weight_factor * q->weight); + if (qwait > max_wait) { + if (max_wait != 0) { + ast_debug(1, "Found even longer holdtime (%d) in queue '%s'\n", qwait, q->name); + } + max_wait=qwait; + } + + cur = cur->next; + } + } + return max_wait; + } + + +/* traverse all defined queues which have calls waiting and contain this member + return 0 if no other queue has precedence (higher weight) or 1 if found */ +static int compare_holdtime(struct call_queue *rq, struct member *member) +{ + struct call_queue *q; + struct member *mem; + int found = 0; + struct ao2_iterator queue_iter; + int qwait; + int rqwait; + + /* q's lock and rq's lock already set by try_calling() + * to solve deadlock */ + queue_iter = ao2_iterator_init(queues, 0); + while ((q = ao2_t_iterator_next(&queue_iter, "Iterate through queues"))) { + if (q == rq) { /* don't check myself, could deadlock */ + queue_t_unref(q, "Done with iterator"); + continue; + } + ao2_lock(q); + if (q->count && q->members) { + if ((mem = ao2_find(q->members, member, OBJ_POINTER))) { + ast_debug(3, "Found matching member %s in queue '%s'\n", mem->interface, q->name); + qwait = get_longest_waiting_caller(q); + rqwait = get_longest_waiting_caller(rq); + if ( (qwait > (rqwait + 5000)) && (q->count >= num_available_members(q))) { + ast_debug(1, "compare_holdtime: Queue '%s' (holdtime %d, calls %d) is preferred over '%s' (holdtime %d, calls %d)\n", q->name, qwait, q->count, rq->name, rqwait, rq->count); + found = 1; + } + ao2_ref(mem, -1); + } + } + ao2_unlock(q); + queue_t_unref(q, "Done with iterator"); + if (found) { + break; + } + } + ao2_iterator_destroy(&queue_iter); + ast_debug(1, "end of compare_holdtime, found= %d\n", found); + return found; +} + + /*! \brief common hangup actions */ static void do_hang(struct callattempt *o) { @@ -3571,7 +3653,7 @@ return 0; } - if (use_weight && compare_weight(qe->parent, call->member)) { + if (use_weight && !use_auto_weight && compare_weight(qe->parent,call->member)) { ast_debug(1, "Priority queue delaying call to %s:%s\n", qe->parent->name, call->interface); return 0; @@ -3622,6 +3704,8 @@ char *location; const char *macrocontext, *macroexten; + ast_debug(3, "ring_entry: use_weight=%d, use_auto_weight=%d\n", use_weight, use_auto_weight); + /* on entry here, we know that tmp->chan == NULL */ if (!can_ring_entry(qe, tmp)) { if (ast_channel_cdr(qe->chan)) { @@ -3633,6 +3717,16 @@ } ast_assert(tmp->member->ringinuse || tmp->member->call_pending); + if (use_weight && use_auto_weight && compare_holdtime(qe->parent,tmp->member)) { + ast_debug(1, "Priority queue delaying call to %s:%s (autoweight)\n", qe->parent->name, tmp->interface); + if (ast_channel_cdr(qe->chan)) { + ast_cdr_busy(ast_channel_cdr(qe->chan)); + } + tmp->stillgoing = 0; + ++*busies; + return 0; + } + ast_copy_string(tech, tmp->interface, sizeof(tech)); if ((location = strchr(tech, '/'))) { *location++ = '\0'; @@ -4362,7 +4456,7 @@ case AST_CONTROL_ANSWER: /* This is our guy if someone answered. */ if (!peer) { - ast_verb(3, "%s answered %s\n", ochan_name, inchan_name); + ast_verb(3, "%s answered %s for queue (%s)\n", ochan_name, inchan_name, queue); if (!o->block_connected_update) { if (o->pending_connected_update) { if (ast_channel_connected_line_sub(o->chan, in, &o->connected, 0) && @@ -4647,7 +4741,7 @@ ch = qe->parent->head; - ast_debug(1, "There %s %d available %s.\n", avl != 1 ? "are" : "is", avl, avl != 1 ? "members" : "member"); + ast_debug(1, "There %s %d available %s in queue(%s).\n", avl != 1 ? "are" : "is", avl, avl != 1 ? "members" : "member", qe->parent->name ); while ((idx < avl) && (ch) && (ch != qe)) { if (!ch->pending) { @@ -4662,10 +4756,10 @@ * from the front of the queue are valid when autofill is disabled) */ if (ch && idx < avl && (qe->parent->autofill || qe->pos == 1)) { - ast_debug(1, "It's our turn (%s).\n", ast_channel_name(qe->chan)); + ast_debug(1, "It's our turn (%s) in queue (%s).\n", ast_channel_name(qe->chan), qe->parent->name); res = 1; } else { - ast_debug(1, "It's not our turn (%s).\n", ast_channel_name(qe->chan)); + ast_debug(1, "It's not our turn (%s) in queue (%s).\n", ast_channel_name(qe->chan), qe->parent->name); res = 0; } @@ -6923,6 +7017,7 @@ char *parse; int makeannouncement = 0; int position = 0; + struct timeval timevalNow; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(queuename); AST_APP_ARG(options); @@ -6965,7 +7060,9 @@ } /* Setup our queue entry */ + gettimeofday( &timevalNow, NULL ); qe.start = time(NULL); + qe.start_us = timevalNow.tv_usec; /* set the expire time based on the supplied timeout; */ if (!ast_strlen_zero(args.queuetimeoutstr)) { @@ -7798,6 +7895,23 @@ if ((general_val = ast_variable_retrieve(cfg, "general", "log_membername_as_agent"))) { log_membername_as_agent = ast_true(general_val); } + use_auto_weight = 0; + if ((general_val = ast_variable_retrieve(cfg, "general", "autoweight"))) + { + if (ast_true(general_val)) + { + use_auto_weight = 1; + use_weight = 1; + } + } + + auto_weight_factor =0; + if ((general_val = ast_variable_retrieve(cfg, "general", "autoweight_factor"))) + { + auto_weight_factor = atoi( general_val ); + } + ast_log(LOG_WARNING, "queue_set_global_parms: use_weight=%d, use_auto_weight=%d\n", use_weight, use_auto_weight); + } /*! \brief reload information pertaining to a single member @@ -8022,6 +8136,10 @@ } else if (q->weight && !prev_weight) { ast_atomic_fetchadd_int(&use_weight, +1); } + if (use_auto_weight && !use_weight) { + use_weight=1; + } + /* Free remaining members marked as delme */ if (member_reload) {